mkweb

PDP-8 Emulator written in Javascipt with HTML5 Canvas based frontend.



const LED_PC = 0;
const LED_MA = 1;
const LED_MB = 2;
const LED_AC = 3;
const LED_MQ = 4;
const LED_DF = 5;
const LED_IF = 6;
const LED_L  = 7;

class FrontPanel extends Phaser.Scene
{
    LEDs = [[],[],[],[],[],[],[],null];
    
    LEDInstruction = [];
    LEDPhase = [];

    constructor () {
        super('frontpanel');
    }

    preload () {
        // Google webfonts
        this.load.script('webfont', 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js');
    }

    create () {
        const basePanel = this.add.graphics();

        // Background color
        basePanel.fillStyle(0xffffff);
        basePanel.fillRect(0,0,1200,600);

        // Top of computer
        basePanel.fillStyle(0);
        basePanel.fillRect(5,0, 1190, 10)

        // White faceplate base
        basePanel.fillRect(25,110,1150,2);   // Black accent line
        basePanel.fillRect(25,10,1150,2); // Horizontal line on top of white faceplate


        // Orange logo backdrop
        basePanel.fillStyle(0xb34602);
        basePanel.fillRect(900,10,300,100);
        // Black accent lines
        basePanel.fillStyle(0);
        basePanel.fillRect(898, 10, 2, 100); // Vertical left outline
        basePanel.fillRect(900, 10, 300, 2); // Horizontal top outline
        basePanel.fillRect(900, 40, 300, 4); // Horizontal sep

        // Black console
        basePanel.fillRoundedRect(100,165,1000,355,5);
        basePanel.fillRect(25,515,1150,75);

        // End caps
        basePanel.fillStyle(0x616161);
        basePanel.fillRoundedRect(0,0,25,600,5);
        basePanel.fillRoundedRect(1175,0,25,600,5);

        // Create the 5 LED registers
        basePanel.fillStyle(0xffffff);   // We draw some white accents between registers etc.
        let rx = 372;
        let ry = 211;
        let labelY = [];
        for (let r = 0; r<5; r++) { // Loop through the 5 rows of LEDs
            if (r === 0) {
                // Create data and inst field LEDs
                let fx = rx - (35 * 6);
                for (let i = 0; i < 3; i++) { // Data field
                    this.LEDs[LED_DF][i] = this.add.circle(fx, ry, 10, 0x600000);
                    fx += 35
                }
                
                // White vertical separator that goes all the way down to switches
                basePanel.fillRect(fx-19, ry-15, 3, 308);

                for (let i = 0; i < 3; i++) { // Inst field
                    this.LEDs[LED_IF][i] = this.add.circle(fx, ry, 10, 0x600000);
                    fx += 35
                }
            }
            if (r === 3) {
                // Create the Link bit LED
                this.LEDs[LED_L] = this.add.circle(rx-35, ry,10,0x600000);
            }

            for (let i = 0; i<12; i++) {
                if (i % 3 === 0) {
                    // Create a white veritcal separator
                    basePanel.fillRect(rx-19,ry-15, 3, 30);
                }
                this.LEDs[r][i] = this.add.circle(rx, ry, 10, 0x600000);   
                rx += 35;
            }

            // White separator going all the way down to switches
            if (r === 0) {
                basePanel.fillRect(rx-19, ry-15, 3, 308);
            }

            // Save y for ez label math
            labelY[r] = ry-10;

            rx = 372;
            ry += 56;
        }



        // Create the 26 switches for the switch bank
        let fillStyleOn = function () {
            this.setFillStyle(0xe0d843); // Yellow
        };
        let fillStyleOff = function() {
            this.setFillStyle(0xe08043); // Orange
        }
        const icfg = {cursor: 'pointer'};

        let x = 147+15;
        let y = ry+28;
        labelY[5] = y-15;
        for (let i=0; i<26; i++) {
            if (i<3) { // First 3 switches are data field
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive();
            } else if (i<6) { // Next 3 are instruction field
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive();
            } else if (i<18) { // 12-bit switch register
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive(icfg);
                ellipse.on("pointerdown", function () {
                    COMPUTER.SR ^= 1<<17-i; // toggle bit
                    if (COMPUTER.SR & 1<<17-i) { // bit is set
                        this.setFillStyle(0xe0d843);// Set to nice yellow
                    } else {    // bit is not set
                        this.setFillStyle(0xe08043); // set to nice orange
                    }
                });
            } else if (i==18) { // CLEAR
                // Unclear what this actually does.
                // Right now just clear the Link bit.
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive(icfg);
                ellipse.on("pointerdown", function () {
                    this.setFillStyle(0xe0d843);
                    COMPUTER.L = false;
                });
                ellipse.on("pointerup", fillStyleOff);
                ellipse.on("pointerout", fillStyleOff);
                // Horizontal White line across top of all switches
                basePanel.fillRect(x-17, y-15-40, 245+17+18, 2);
                
            } else if (i==19) { // LOAD ADDR - SR -> PC
                // Load address loads the SR in the PC
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive(icfg);
                ellipse.on("pointerdown", function () {
                    this.setFillStyle(0xe0d843);
                    COMPUTER.PC = COMPUTER.SR;
                });
                ellipse.on("pointerup", fillStyleOff);
                ellipse.on("pointerout", fillStyleOff);
            } else if (i==20) { // DEP - Deposit (DEFAULT ON)
                // Deposit SR -> MB
                // Move PC -> MA
                // Increment PC
                // Write Memory MB -> MEM[MA]
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe0d843).setInteractive(icfg);
                ellipse.on("pointerdown", function () {
                    this.setFillStyle(0xe08043);
                    COMPUTER.MB = COMPUTER.SR;
                    COMPUTER.MA = COMPUTER.PC;
                    COMPUTER.PC = COMPUTER.PC + 1;
                    COMPUTER.write();
                });
                ellipse.on("pointerup", fillStyleOn);
                ellipse.on("pointerout", fillStyleOn);
            } else if (i==21) { // EXAM -Examine
                // This allows you to view programs.
                // Move PC -> MA then increment PC and performs a 'read'
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive(icfg);
                ellipse.on("pointerdown", function () {
                    this.setFillStyle(0xe0d843);
                    COMPUTER.MA = COMPUTER.PC;
                    COMPUTER.PC = (COMPUTER.PC + 1) % 4096;
                    COMPUTER.read();
                });
                ellipse.on("pointerup", fillStyleOff);
                ellipse.on("pointerout", fillStyleOff);
            } else if (i==22) { // CONT
                // Starts the computer
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive(icfg);
                ellipse.on("pointerdown", function () {
                    this.setFillStyle(0xe0d843);
                    COMPUTER.start();
                });
                ellipse.on("pointerup", fillStyleOff);
                ellipse.on("pointerout", fillStyleOff);
            } else if (i==23) { // STOP
                // Stops the computer
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive(icfg);
                ellipse.on("pointerdown", function () {
                    this.setFillStyle(0xe0d843);
                    COMPUTER.halt();
                });
                ellipse.on("pointerup", fillStyleOff);
                ellipse.on("pointerout", fillStyleOff);
            } else if (i==24) { // SING INST
                // Execute a single instruction cycle (fetch AND execute)
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive(icfg);
                ellipse.on("pointerdown", function () {
                    this.setFillStyle(0xe0d843);
                    COMPUTER.cycle();
                });
                ellipse.on("pointerup", fillStyleOff);
                ellipse.on("pointerout", fillStyleOff);
            } else if (i==25) { // SING STEP
                // Executes a single processor step (fetch OR execute)
                let ellipse = this.add.ellipse(x, y+15, 30, 60, 0xe08043).setInteractive(icfg);
                ellipse.on("pointerdown", function () {
                    this.setFillStyle(0xe0d843);
                    COMPUTER.step();
                });
                ellipse.on("pointerup", fillStyleOff);
                ellipse.on("pointerout", fillStyleOff);
            }

            // Draw white separators for switch register
            if ((6 <= i) && (i <= 15) && (i % 3 == 0)) {
                basePanel.fillRect(x-19,y-15-15,3,15);
            }

            if ((18 <= i) && (i <= 25)) {
                // Verticle line on right of switch
                basePanel.fillRect(x+17, y-15-40, 2, 40);
            }

            x += 35;
        }


        // Create the 8 instruction indicators
        let ix = 897;
        let iy = 211;
        for (let i = 0; i < 8; i++) {
            labelY[i+6] = iy;
            this.LEDInstruction[i] = this.add.ellipse(ix, iy, 20, 10, 0x600000);
            if (i < 3) {
                this.LEDPhase[i] = this.add.ellipse(ix+140,iy, 20, 10, 0x600000);
            }
            iy += 32;
        }


        // Text labels using google webfonts
        const add = this.add;
        const fontRegisterLabel = {fontFamily: 'Roboto', fontStyle:'400', fontSize: 15, color: '#FFFFFF'};
        const fontInstructionLabel = {fontFamily: 'Roboto', fontStyle:'400', fontSize: 12, color: '#FFFFFF'};
        const fontSwitchLabel = {fontFamily: 'Roboto', fontStyle:'400', fontSize: 10, color: '#FFFFFF', align:'center'};
        WebFont.load({
        google: {
            families: [ 'Montserrat:800', 'Roboto', 'Roboto Serif' ]
        },
        active: function ()
        {
            // PDP-8 Logo
            add.text(935, 48, 'PDP - 8', { fontFamily: 'Montserrat', fontStyle:'800', fontSize: 50, color: '#b34602' , strokeThickness: 2});
            // Data Processor
            add.text(936, 12, 'DATA PROCESSOR', { fontFamily: 'Roboto Serif', fontStyle:'600', fontSize: 20, color: '#0'});


            // Digital Equipment Corporation
            add.text(75, 17, 'DIGITAL  EQUIPMENT  CORPORATION', { fontFamily: 'Roboto Serif', fontStyle:'400', fontSize: 30, color: '#0'});


            // Program counter label
            add.text(490, labelY[0]-15-11, 'PROGRAM  COUNTER', fontRegisterLabel);

            // Memory Address label
            add.text(497, labelY[1]-15-11, 'MEMORY  ADDRESS', fontRegisterLabel);

            // Memory Buffer label
            add.text(499, labelY[2]-15-11, 'MEMORY  BUFFER', fontRegisterLabel);

            // Accumulator label
            add.text(510, labelY[3]-15-11, 'ACCUMULATOR', fontRegisterLabel);
            // Link label
            add.text(317, labelY[3]-15-11, 'LINK', fontRegisterLabel);

            // Multiplier Quotient label
            add.text(480, labelY[4]-15-11, 'MULTIPLIER  QUOTIENT', fontRegisterLabel);

            // Data field labels
            add.text(155, labelY[0]-15-11, 'DATA FIELD', fontRegisterLabel);
            add.text(155, labelY[5]-15-20, 'DATA FIELD', fontRegisterLabel);

            // Inst field labels
            add.text(265, labelY[0]-15-11, 'INST FIELD', fontRegisterLabel);
            add.text(265, labelY[5]-15-20, 'INST FIELD', fontRegisterLabel);

            // Switch Register label
            add.text(500, labelY[5]-15-20, 'SWITCH  REGISTER', fontRegisterLabel);


            // AND instruction label
            add.text(850, labelY[6]-9, 'AND', fontInstructionLabel);

            // TAD instruction label
            add.text(850, labelY[7]-9, 'TAD', fontInstructionLabel);

            // ISZ instruction label
            add.text(850, labelY[8]-9, 'ISZ', fontInstructionLabel);

            // DCA instruction label
            add.text(850, labelY[9]-9, 'DCA', fontInstructionLabel);

            // JMS instruction label
            add.text(850, labelY[10]-9, 'JMS', fontInstructionLabel);

            // JMP instruction label
            add.text(850, labelY[11]-9, 'JMP', fontInstructionLabel);

            // IOT instruction label
            add.text(850, labelY[12]-9, 'IOT', fontInstructionLabel);

            // OPR instruction label
            add.text(850, labelY[13]-9, 'OPR', fontInstructionLabel);

            // HALT phase label
            add.text(987, labelY[6]-9, 'HALT', fontInstructionLabel);

            // FETCH phase label
            add.text(980, labelY[7]-9, 'FETCH', fontInstructionLabel);

            // EXEC phase label
            add.text(987, labelY[8]-9, 'EXEC', fontInstructionLabel);

            
            // Clear switch label
            add.text(783, labelY[5]-15-9, 'CLR', fontSwitchLabel);

            // Load Adr switch label
            add.text(815, labelY[5]-15-15, 'LOAD\nADR', fontSwitchLabel);

            // Deposit switch label
            add.text(853, labelY[5]-15-9, 'DEP', fontSwitchLabel);

            // Examine switch label
            add.text(884, labelY[5]-15-9, 'EXAM', fontSwitchLabel);
            
            // Start switch label
            add.text(918, labelY[5]-15-9, 'START', fontSwitchLabel);

            // Stop switch label
            add.text(955, labelY[5]-15-9, 'STOP', fontSwitchLabel);

            // Single Instruction label
            add.text(992, labelY[5]-15-15, 'SING\nINST', fontSwitchLabel);

            // Single Step label
            add.text(1026, labelY[5]-15-15, 'SING\nSTEP', fontSwitchLabel);
        }
        });


    }

    update () {
        // 5 12-bit registers
        let regVals = [COMPUTER.PC, COMPUTER.MA, COMPUTER.MB, COMPUTER.AC, COMPUTER.MQ];
        for (let r = 0; r<5; r++) {
            for (let i = 0; i<12; i++) {
                if (regVals[r] & 1<<i) { // Bit is set
                    this.LEDs[r][11-i].setFillStyle(0xff0000);
                } else { // Bit is not set
                    this.LEDs[r][11-i].setFillStyle(0x600000);
                }
            }
        }

        // Link bit
        if (COMPUTER.L) { // bit is set
            this.LEDs[LED_L].setFillStyle(0xff0000);
        } else { // bit not set
            this.LEDs[LED_L].setFillStyle(0x600000);
        }

        // Instruction indicators
        let inst = COMPUTER.IR >> 9;
        for (let i = 0; i < 8; i++) {
            if (i == inst) { // Turn LED on if instruction matches
                this.LEDInstruction[i].setFillStyle(0xff0000);
            } else { // Turn LED off otherwise
                this.LEDInstruction[i].setFillStyle(0x600000);
            }
        }

        // Phase indicators
        if (COMPUTER.STATE.HALT) { // Turn LED on when halted
            this.LEDPhase[0].setFillStyle(0xff0000);
        } else { // Turn LED off
            this.LEDPhase[0].setFillStyle(0x600000);
        }

        if (COMPUTER.STATE.FETCH) {
            this.LEDPhase[1].setFillStyle(0xff0000); // Turn Fetch LED on
            this.LEDPhase[2].setFillStyle(0x600000); // Turn Execute LED off
        } else {
            this.LEDPhase[2].setFillStyle(0xff0000); // Turn Execute LED on
            this.LEDPhase[1].setFillStyle(0x600000); // Turn Fetch LED off
        }
    }
}