// 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 = 0b1000000000000000000000000000000; // 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 = 0; // 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 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) this.DATABUS = this.RAM[this.ADDRBUS]; } 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; let MC_Controls = this.MCRAM[((this.IR & 0b0000001111111111) << 4) | this.MCC | ((this.SR & 0b00000011) << 14)]; if (MC_Controls & CONTROL_MCL0) this.MCC = 0; if (MC_Controls & CONTROL_MCL8) this.MCC = 8; if (MC_Controls !== this.MC_Controls) { if (this.MC_Controls & CONTROL_OEME) { // Set the DATABUS based on whatever output we are controlling 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; switch (OUTMUX) { case OECONTROL_PC: { this.DATABUS = this.PC; break; } case OECONTROL_SP: { this.DATABUS = this.SP; break; } case OECONTROL_AL: { this.DATABUS = ((this.DATABUS & BITMASK_8) << 8) | (this.GPA & BITMASK_8); break; } case OECONTROL_AH: { this.DATABUS = ((this.GPA & BITMASK_8) << 8) | (this.DATABUS & BITMASK_8); break; } case OECONTROL_BL: { this.DATABUS = ((this.DATABUS & BITMASK_8) << 8) | (this.GPB & BITMASK_8); break; } case OECONTROL_BH: { this.DATABUS = ((this.GPB & BITMASK_8) << 8) | (this.DATABUS & BITMASK_8); break; } case OECONTROL_CL: { this.DATABUS = ((this.DATABUS & BITMASK_8) << 8) | (this.GPC & BITMASK_8); break; } case OECONTROL_CH: { this.DATABUS = ((this.GPC & BITMASK_8) << 8) | (this.DATABUS & BITMASK_8); 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); 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: { 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; let SHIFTING = false; if (MC_Controls & CONTROL_ALUSL) SHIFTING = true; if (MC_Controls & CONTROL_ALUSR) SHIFTING = true; switch (ALUMUX) { case 0: { // ADD if (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 (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 (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 (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; break; } case OECONTROL_SR: { this.DATABUS = ((this.DATABUS & BITMASK_8) << 8) | (this.SR & BITMASK_8); break; } case OECONTROL_RO: { // We dont actually want to do anything here since ram outputs 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; } GPA_In_HIGH_CLK() { this.GPA = (this.DATABUS & 0xff00) >> 8; } GPB_In_LOW_CLK() { this.GPB = this.DATABUS & 0x00ff; } GPB_In_HIGH_CLK() { this.GPB = (this.DATABUS & 0xff00) >> 8; } GPC_In_LOW_CLK() { this.GPC = this.DATABUS & 0x00ff; } GPC_In_HIGH_CLK() { this.GPC = (this.DATABUS & 0xff00) >> 8; } 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 } }