commit f116826575fe338fc4fab71307fb63327f4e96b6 Author: MatCat Date: Sat Apr 3 19:03:47 2021 -0700 First Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d56b3d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# MatCat 8SA1 CPU Simulator + +This is a simple JS based simulator to simulate the function of the 8SA1 CPU. \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..53d80a6 --- /dev/null +++ b/index.html @@ -0,0 +1,8 @@ + + + MatCat's 8SA1 CPU Simulator + + + + + \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..f272e8f --- /dev/null +++ b/js/main.js @@ -0,0 +1,404 @@ +// 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_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.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: { + this.DATABUS = this.RAM[this.ADDRBUS]; + 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; + } + + RH_In_CLK() { + this.RH = this.DATABUS; + } + + 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 + } +} \ No newline at end of file