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
}
}
}