function formatHex(value,places) { let hexval = parseInt(value,10).toString(16).toUpperCase(); if (hexval.length < places) { let placecount = (places - hexval.length); for (let a = 0; a < placecount; a++) { hexval = "0" + hexval; } } return hexval; } function GetMnemonic(mcarray,instruction) { for (let a = 0; a < mcarray.length; a++) { if (mcarray[a] === instruction || mcarray[a].Bytecode === instruction) { return mcarray[a].Mnemonic; } } } function generateClocks(clk_cnt) { for (let a = 0; a < clk_cnt; a++) { cpu.CLOCK(true); updateHTML(); cpu.CLOCK(false); updateHTML(); } } function printRAM(ram) { let sp_ram = document.getElementById("RAM"); let ramtext = "ADDR  :   0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
"; for (let a = 0; a < 16; a++) { ramtext += "0x" + formatHex(a*16,4) + ": "; for (let b = 0; b < 16; b++) { if (cpu.ADDRBUS === (a*16)+b) ramtext += ''; ramtext += formatHex(ram[(a*16)+b],4) + " "; if (cpu.ADDRBUS === (a*16)+b) ramtext += ''; } ramtext += "
"; } sp_ram.innerHTML = ramtext; } function updateHTML() { let sp_addr = document.getElementById("Address_BUS"); sp_addr.innerText = "0x" + cpu.ADDRBUS.toString(16).toUpperCase(); let sp_data = document.getElementById("Data_BUS"); sp_data.innerText = "0x" + cpu.DATABUS.toString(16).toUpperCase(); let sp_pc = document.getElementById("PC_Register"); sp_pc.innerText = "0x" + cpu.PC.toString(16).toUpperCase(); let sp_mcc = document.getElementById("MCC_Register"); sp_mcc.innerText = "0x" + cpu.MCC.toString(16).toUpperCase(); let sp_co = document.getElementById("CO_Register"); sp_co.innerText = "0b" + cpu.MC_Controls.toString(2); let sp_sp = document.getElementById("SP_Register"); sp_sp.innerText = "0x" + cpu.SP.toString(16).toUpperCase(); let sp_sr = document.getElementById("SR_Register"); sp_sr.innerText = "0b" + cpu.SR.toString(2); let sp_ir = document.getElementById("IR_Register"); sp_ir.innerText = "0b" + cpu.IR.toString(2) + " (" + GetMnemonic(Instructions,cpu.IR) + ")"; let sp_gpa = document.getElementById("GPA_Register"); sp_gpa.innerText = "0x" + cpu.GPA.toString(16).toUpperCase(); let sp_gpb = document.getElementById("GPB_Register"); sp_gpb.innerText = "0x" + cpu.GPB.toString(16).toUpperCase(); let sp_gpc = document.getElementById("GPC_Register"); sp_gpc.innerText = "0x" + cpu.GPC.toString(16).toUpperCase(); let sp_gpd = document.getElementById("GPD_Register"); sp_gpd.innerText = "0x" + cpu.GPD.toString(16).toUpperCase(); printRAM(cpu.RAM); } // Setup a few bitmasks we can use in the code that are handy to clean inputs to registers const BITMASK_8 = 0x00000000000000ff; const BITMASK_16 = 0x000000000000ffff; const BITMASK_24 = 0x0000000000ffffff; // These are the control lines that are controlled by the microcode RAM output const CONTROL_PCC = 0b00000000000000000000000000000001; // PC CLK UP const CONTROL_PCI = 0b00000000000000000000000000000010; // PC Input Enable const CONTROL_SPD = 0b00000000000000000000000000000100; // SP Count Down const CONTROL_SPC = 0b00000000000000000000000000001000; // SP CLK (UP / DOWN) const CONTROL_SPI = 0b00000000000000000000000000010000; // SP Input Enable const CONTROL_RAIL = 0b00000000000000000000000000100000; // GPA LOW Input Enable const CONTROL_RAIH = 0b00000000000000000000000001000000; // GPA HIGH Input Enable const CONTROL_RBIL = 0b00000000000000000000000010000000; // GPB LOW Input Enable const CONTROL_RBIH = 0b00000000000000000000000100000000; // GPB HIGH Input Enable const CONTROL_RCIL = 0b00000000000000000000001000000000; // GPC LOW Input Enable const CONTROL_RCIH = 0b00000000000000000000010000000000; // GPC HIGH Input Enable const CONTROL_RDIL = 0b00000000000000000000100000000000; // GPD LOW Input Enable const CONTROL_RDIH = 0b00000000000000000001000000000000; // GPD HIGH Input Enable const CONTROL_RRI = 0b00000000000000000010000000000000; // LOW Ram Register Input Enable const CONTROL_RHI = 0b00000000000000000100000000000000; // HIGH Ram Register Input Enable const CONTROL_ALUM0 = 0b00000000000000001000000000000000; // ALU MUX 0 const CONTROL_ALUM1 = 0b00000000000000010000000000000000; // ALU MUX 1 const CONTROL_ALUI = 0b00000000000000100000000000000000; // ALU Invert (high side) const CONTROL_ALUC = 0b00000000000001000000000000000000; // ALU Carry Input const CONTROL_ALUSL = 0b00000000000010000000000000000000; // ALU Shift Left const CONTROL_ALUSR = 0b00000000000100000000000000000000; // ALU Shift Right const CONTROL_OEM0 = 0b00000000001000000000000000000000; // Output Enable MUX 0 const CONTROL_OEM1 = 0b00000000010000000000000000000000; // Output Enable MUX 1 const CONTROL_OEM2 = 0b00000000100000000000000000000000; // Output Enable MUX 2 const CONTROL_OEM3 = 0b00000001000000000000000000000000; // Output Enable MUX 3 const CONTROL_OEM4 = 0b00000010000000000000000000000000; // Output Enable MUX 4 const CONTROL_OEME = 0b00000100000000000000000000000000; // Output Enable MUX Enable const CONTROL_RI = 0b00010000000000000000000000000000; // RAM Input Enable const CONTROL_IRI = 0b00100000000000000000000000000000; // Instruction Register Input Enable const CONTROL_MCL0 = 0b01000000000000000000000000000000; // Microcode Counter to 0 const CONTROL_MCL8 = 0b10000000000000000000000000000000; // Microcode Counter to 8 // These are the output enable control lines, they are muxed since none of them can ever be on together const OECONTROL_PC = 0b00000 // PC Register const OECONTROL_SP = 0b00001 // SP Register const OECONTROL_AL = 0b00010 // GPA to LOW const OECONTROL_AH = 0b00011 // GPA to HIGH const OECONTROL_BL = 0b00100 // GPB to LOW const OECONTROL_BH = 0b00101 // GPB to HIGH const OECONTROL_CL = 0b00110 // GPC to LOW const OECONTROL_CH = 0b00111 // GPC to HIGH const OECONTROL_DL = 0b01000 // GPD to LOW const OECONTROL_DH = 0b01001 // GPD to HIGH const OECONTROL_AB = 0b01010 // GPB to HIGH, GPA to LOW const OECONTROL_AC = 0b01011 // GPC to HIGH, GPA to LOW const OECONTROL_AD = 0b01100 // GPD to HIGH, GPA to LOW const OECONTROL_BA = 0b01101 // GPA to HIGH, GPB to LOW const OECONTROL_BC = 0b01110 // GPC to HIGH, GPB to LOW const OECONTROL_BD = 0b01111 // GPD to HIGH, GPB to LOW const OECONTROL_CA = 0b10000 // GPA to HIGH, GPC to LOW const OECONTROL_CB = 0b10001 // GPB to HIGH, GPC to LOW const OECONTROL_CD = 0b10010 // GPD to HIGH, GPC to LOW const OECONTROL_DA = 0b10011 // GPA to HIGH, GPD to LOW const OECONTROL_DB = 0b10100 // GPB to HIGH, GPD to LOW const OECONTROL_DC = 0b10101 // GPC to HIGH, GPD to LOW const OECONTROL_AE = 0b11100 // ALU Enable const OECONTROL_AO = 0b11101 // ALU Output to LOW const OECONTROL_SR = 0b11110 // Status Register to LOW const OECONTROL_RO = 0b11111 // RAM to DATABUS Enable class CPU_8SA1 { constructor() { this.DATABUS = 0; this.ADDRBUS = 0; this.PC = 0; // Set PC register to 0 this.SP = BITMASK_16; // Set SP register to 0xFFFF this.SR = 0; // Set the STATUS register this.RR = 0; // Set lower RAM register to 0 this.RH = 0; // Set higher RAM register to 0 this.GPA = 0; // Set the 4 GP registers this.GPB = 0; this.GPC = 0; this.GPD = 0; this.MCC = 0xF; // Set the microcode counter this.IR = 0; // Set the initial IR this.MC_Controls = 0; // Set the control output lines this.ALUSUM = 0; // ALU Register to hold SUM this.MCRAM = new Array(2 ** 16); // Initialize the microcode RAM this.RAM = new Array(2 ** 24); // Initialize the main RAM array let RAMSIZE = 2**24; while(RAMSIZE--) this.RAM[RAMSIZE] = 0; this._CLK = false; } CLOCK(clk_val) { if (clk_val && !this._CLK) { // Clock going HIGH this._CLK = true; this._CLOCK_HIGH(); } else { if (!clk_val && this._CLK) { // Clock going LOW this._CLK = false; this._CLOCK_LOW(); } } } _CLOCK_HIGH() { // Call anything that needs to be called on positive clock edge if (this.MC_Controls & CONTROL_OEME) { let OUTMUX = (this.MC_Controls & CONTROL_OEM4) ? 0b10000 : 0; OUTMUX |= (this.MC_Controls & CONTROL_OEM3) ? 0b01000 : 0; OUTMUX |= (this.MC_Controls & CONTROL_OEM2) ? 0b00100 : 0; OUTMUX |= (this.MC_Controls & CONTROL_OEM1) ? 0b00010 : 0; OUTMUX |= (this.MC_Controls & CONTROL_OEM0) ? 0b00001 : 0; if (OUTMUX === OECONTROL_RO) { console.log("DATABUS = RAM"); this.DATABUS = this.RAM[this.ADDRBUS]; if (this.DATABUS === undefined) this.DATABUS = 0; } } if (this.MC_Controls & CONTROL_PCC ) this.PC_CLK(); if (this.MC_Controls & CONTROL_PCI ) this.PC_In_CLK(); if (this.MC_Controls & CONTROL_SPC ) this.SP_CLK(); if (this.MC_Controls & CONTROL_SPI ) this.SP_In_CLK(); if (this.MC_Controls & CONTROL_RI ) this.RAM_In_CLK(); if (this.MC_Controls & CONTROL_IRI ) this.IR_In_CLK(); if (this.MC_Controls & CONTROL_RRI ) this.RR_In_CLK(); if (this.MC_Controls & CONTROL_RHI ) this.RH_In_CLK(); if (this.MC_Controls & CONTROL_RAIL) this.GPA_In_LOW_CLK(); if (this.MC_Controls & CONTROL_RAIH) this.GPA_In_HIGH_CLK(); if (this.MC_Controls & CONTROL_RBIL) this.GPB_In_LOW_CLK(); if (this.MC_Controls & CONTROL_RBIH) this.GPB_In_HIGH_CLK(); if (this.MC_Controls & CONTROL_RCIL) this.GPC_In_LOW_CLK(); if (this.MC_Controls & CONTROL_RCIH) this.GPC_In_HIGH_CLK(); if (this.MC_Controls & CONTROL_RDIL) this.GPD_In_LOW_CLK(); if (this.MC_Controls & CONTROL_RDIH) this.GPD_In_HIGH_CLK(); } _CLOCK_LOW() { // Call anything that needs to be called on low going clock edge this._FetchDecode_CLK_neg(); } _FetchDecode_CLK_neg() { // We actually setup all of our fetch and decode logic during the clocks low phase this.DATABUS = 0; this.MCC += 1; if (this.MCC > 15) this.MCC = 0; let mcra = ((this.IR & 0b0000001111111111) << 4) | this.MCC | ((this.SR & 0b00000011) << 15); let MC_Controls = this.MCRAM[mcra]; if (MC_Controls & CONTROL_MCL0) this.MCC = 0; if (MC_Controls & CONTROL_MCL8) this.MCC = 8; if (MC_Controls & CONTROL_MCL0) console.log(`Reset microcode counter to 0`); if (MC_Controls & CONTROL_MCL8) console.log(`Reset microcode counter to 8`); mcra = ((this.IR & 0b0000001111111111) << 4) | this.MCC | ((this.SR & 0b00000011) << 15); MC_Controls = this.MCRAM[mcra]; console.log(`[${mcra.toString(2)}] Control Lines: ${MC_Controls.toString(2)}`); if (MC_Controls !== this.MC_Controls) { if (MC_Controls & CONTROL_OEME) { // Set the DATABUS based on whatever output we are controlling let OUTMUX = (MC_Controls & CONTROL_OEM4) ? 0b10000 : 0; OUTMUX |= (MC_Controls & CONTROL_OEM3) ? 0b01000 : 0; OUTMUX |= (MC_Controls & CONTROL_OEM2) ? 0b00100 : 0; OUTMUX |= (MC_Controls & CONTROL_OEM1) ? 0b00010 : 0; OUTMUX |= (MC_Controls & CONTROL_OEM0) ? 0b00001 : 0; console.log(`OUTPUT MUX: ${OUTMUX.toString(2)}`); switch (OUTMUX) { case OECONTROL_PC: { this.DATABUS = this.PC; console.log("DATABUS = PC"); break; } case OECONTROL_SP: { this.DATABUS = this.SP; console.log("DATABUS = SP"); break; } case OECONTROL_AL: { this.DATABUS = ((this.DATABUS & BITMASK_8) << 8) | (this.GPA & BITMASK_8); console.log("DATABUS LOW = A"); break; } case OECONTROL_AH: { this.DATABUS = ((this.GPA & BITMASK_8) << 8) | (this.DATABUS & BITMASK_8); console.log("DATABUS HIGH = A"); break; } case OECONTROL_BL: { this.DATABUS = ((this.DATABUS & BITMASK_8) << 8) | (this.GPB & BITMASK_8); console.log("DATABUS LOW = B"); break; } case OECONTROL_BH: { this.DATABUS = ((this.GPB & BITMASK_8) << 8) | (this.DATABUS & BITMASK_8); console.log("DATABUS HIGH = B"); break; } case OECONTROL_CL: { this.DATABUS = ((this.DATABUS & BITMASK_8) << 8) | (this.GPC & BITMASK_8); console.log("DATABUS LOW = C"); break; } case OECONTROL_CH: { this.DATABUS = ((this.GPC & BITMASK_8) << 8) | (this.DATABUS & BITMASK_8); console.log("DATABUS HIGH = C"); break; } case OECONTROL_DL: { this.DATABUS = ((this.DATABUS & BITMASK_8) << 8) | (this.GPD & BITMASK_8); break; } case OECONTROL_DH: { this.DATABUS = ((this.GPD & BITMASK_8) << 8) | (this.DATABUS & BITMASK_8); break; } case OECONTROL_AB: { this.DATABUS = ((this.GPB & BITMASK_8) << 8) | (this.GPA & BITMASK_8); console.log("DATABUS = AB"); break; } case OECONTROL_AC: { this.DATABUS = ((this.GPC & BITMASK_8) << 8) | (this.GPA & BITMASK_8); break; } case OECONTROL_AD: { this.DATABUS = ((this.GPD & BITMASK_8) << 8) | (this.GPA & BITMASK_8); break; } case OECONTROL_BA: { this.DATABUS = ((this.GPA & BITMASK_8) << 8) | (this.GPB & BITMASK_8); break; } case OECONTROL_BC: { this.DATABUS = ((this.GPC & BITMASK_8) << 8) | (this.GPB & BITMASK_8); break; } case OECONTROL_BD: { this.DATABUS = ((this.GPD & BITMASK_8) << 8) | (this.GPB & BITMASK_8); break; } case OECONTROL_CA: { this.DATABUS = ((this.GPA & BITMASK_8) << 8) | (this.GPC & BITMASK_8); break; } case OECONTROL_CB: { this.DATABUS = ((this.GPB & BITMASK_8) << 8) | (this.GPC & BITMASK_8); break; } case OECONTROL_CD: { this.DATABUS = ((this.GPD & BITMASK_8) << 8) | (this.GPC & BITMASK_8); break; } case OECONTROL_DA: { this.DATABUS = ((this.GPA & BITMASK_8) << 8) | (this.GPD & BITMASK_8); break; } case OECONTROL_DB: { this.DATABUS = ((this.GPB & BITMASK_8) << 8) | (this.GPD & BITMASK_8); break; } case OECONTROL_DC: { this.DATABUS = ((this.GPC & BITMASK_8) << 8) | (this.GPD & BITMASK_8); break; } case OECONTROL_AE: { console.log("ALU Enable"); this.DATABUS = (this.GPB << 8) | this.GPA; let ALU_A = this.DATABUS & BITMASK_8; let ALU_B = (this.DATABUS & (BITMASK_8 << 8)) >> 8; let ALU_Result = 0; let ALUMUX = (MC_Controls & CONTROL_ALUM1) ? 0b10 : 0; ALUMUX |= (MC_Controls & CONTROL_ALUM0) ? 0b01 : 0; console.log(`ALU: A=${ALU_A}, B=${ALU_B}, MUX: ${ALUMUX}`); if (MC_Controls & CONTROL_ALUI) console.log("ALU Inverting B"); let SHIFTING = false; if (MC_Controls & CONTROL_ALUSL) SHIFTING = true; if (MC_Controls & CONTROL_ALUSR) SHIFTING = true; switch (ALUMUX) { case 0: { // ADD if (MC_Controls & CONTROL_ALUI) ALU_B = ~ALU_B; ALU_Result = ALU_A + ALU_B; if (MC_Controls & CONTROL_ALUC && !SHIFTING) ALU_Result += 1; break; } case 1: { // AND ALU_Result = ALU_A & ALU_B; if (MC_Controls & CONTROL_ALUI) ALU_Result = ~ALU_Result; if (MC_Controls & CONTROL_ALUC && !SHIFTING) ALU_Result += 1; break; } case 2: { // OR ALU_Result = ALU_A | ALU_B; if (MC_Controls & CONTROL_ALUI) ALU_Result = ~ALU_Result; if (MC_Controls & CONTROL_ALUC && !SHIFTING) ALU_Result += 1; break; } case 3: { // XOR ALU_Result = ALU_A ^ ALU_B; if (MC_Controls & CONTROL_ALUI) ALU_Result = ~ALU_Result; if (MC_Controls & CONTROL_ALUC && !SHIFTING) ALU_Result += 1; break; } } if (MC_Controls & CONTROL_ALUSL) ALU_Result = ALU_Result << 1; if ((MC_Controls & CONTROL_ALUSL) && (MC_Controls & CONTROL_ALUC)) ALU_Result = ALU_Result | 0b00000001; if (MC_Controls & CONTROL_ALUSR) ALU_Result = ALU_Result >> 1; if ((MC_Controls & CONTROL_ALUSR) && (MC_Controls & CONTROL_ALUC)) ALU_Result = ALU_Result | 0b10000000; if (ALU_Result & 0b100000000) { // We have a carry this.SR = this.SR | 0b00000001; } else { this.SR = this.SR & 0b11111110; } if (ALU_Result === 0) { // We have a ZERO this.SR = this.SR | 0b00000010; } else { this.SR = this.SR & 0b11111101; } this.ALUSUM = ALU_Result & BITMASK_8; break; } case OECONTROL_AO: { this.DATABUS = (this.DATABUS & (BITMASK_8 << 8)) | this.ALUSUM; console.log("DATABUS = SUM"); break; } case OECONTROL_SR: { this.DATABUS = ((this.DATABUS & BITMASK_8) << 8) | (this.SR & BITMASK_8); console.log("DATABUS = STATUS REGISTER"); break; } case OECONTROL_RO: { // We dont actually want to do anything here since ram outputs on CLK console.log("DATABUS = RAM (on CLK)"); break; } } } } this.MC_Controls = MC_Controls; } _FetchDecode_CLK_pos() { } PC_CLK() { this.PC += 1; if (this.PC > BITMASK_16) this.PC = 0; } PC_In_CLK() { this.PC = this.DATABUS; } SP_CLK() { if (this.MC_Controls & CONTROL_SPD) { this.SP -= 1; if (this.SP < 0) this.SP = BITMASK_16; } else { this.SP += 1; if (this.SP > BITMASK_16) this.SP = 0; } } SP_In_CLK() { this.SP = this.DATABUS; } RAM_In_CLK() { this.RAM[this.ADDRBUS] = this.DATABUS; } IR_In_CLK() { this.IR = this.DATABUS; } RR_In_CLK() { this.RR = this.DATABUS; this.ADDRBUS = this.RR | this.RH << 16; } RH_In_CLK() { this.RH = this.DATABUS; this.ADDRBUS = this.RR | this.RH << 16; } GPA_In_LOW_CLK() { this.GPA = this.DATABUS & 0x00ff; console.log(`GPA = DATABUS LOW`); } GPA_In_HIGH_CLK() { this.GPA = (this.DATABUS & 0xff00) >> 8; console.log(`GPA = DATABUS HIGH`); } GPB_In_LOW_CLK() { this.GPB = this.DATABUS & 0x00ff; console.log(`GPB = DATABUS LOW`); } GPB_In_HIGH_CLK() { this.GPB = (this.DATABUS & 0xff00) >> 8; console.log(`GPB = DATABUS HIGH`); } GPC_In_LOW_CLK() { this.GPC = this.DATABUS & 0x00ff; console.log(`GPC = DATABUS LOW`); } GPC_In_HIGH_CLK() { this.GPC = (this.DATABUS & 0xff00) >> 8; console.log(`GPC = DATABUS HIGH`); } GPD_In_LOW_CLK() { this.GPD = this.DATABUS & 0x00ff; } GPD_In_HIGH_CLK() { this.GPD = (this.DATABUS & 0xff00) >> 8; } PrintDebug() { // We want to print out all the registers in a way that makes sense } }