const { INITIAL_IP_ADDRESS, CYCLE_LIMIT } = require('./machine.config'); const { num2hex, num2bin_4bit } = require('./logging.js'); const display = require('./display.js'); // STATE const CPU = { // Core state running: false, IP: INITIAL_IP_ADDRESS, FLAGS: 0, // A bit field! 0000 = NZOC Acc: 0, memory: null, // Functions that update core state /** @param {Uint8Array} data */ loadMemory: (data) => { if (data.length > 256) { throw new Error("Out of memory error (program too long)"); } CPU.memory = data; }, setFlagNegative: () => { CPU.FLAGS |= 8 }, setFlagZero: () => { CPU.FLAGS |= 4 }, setFlagOverflow: () => { CPU.FLAGS |= 2 }, setFlagCarry: () => { CPU.FLAGS |= 1 }, unsetFlagNegative: () => { CPU.FLAGS &= ~8 }, unsetFlagZero: () => { CPU.FLAGS &= ~4 }, unsetFlagOverflow: () => { CPU.FLAGS &= ~2 }, unsetFlagCarry: () => { CPU.FLAGS &= ~1 }, updateFlagZero: () => { if (CPU.Acc === 0) { CPU.setFlagZero(); } else { CPU.unsetFlagZero(); } }, updateFlagNegative: () => { CPU.Acc & 128 ? CPU.setFlagNegative : CPU.unsetFlagNegative }, // Debug info currentInstruction: { opcode: null, operand: null, mnemonic: null, }, cycleCounter: 0, } // FUNCTIONS THAT MODIFY STATE const Instructions = { end: () => { CPU.currentInstruction.mnemonic = 'END'; CPU.running = false; }, store_lit: (lit) => { CPU.currentInstruction.mnemonic = 'STO lit'; CPU.memory[lit] = CPU.Acc; CPU.IP = CPU.IP += 2; }, store_addr: (addr) => { CPU.currentInstruction.mnemonic = 'STO addr'; CPU.memory[CPU.memory[addr]] = CPU.Acc; CPU.IP = CPU.IP += 2; }, load_lit: (lit) => { CPU.currentInstruction.mnemonic = 'LDA lit'; CPU.Acc = lit; CPU.updateFlagNegative(); CPU.updateFlagZero(); CPU.IP = CPU.IP += 2; }, load_addr: (addr) => { CPU.currentInstruction.mnemonic = `LDA addr; @ addr: ${num2hex(CPU.memory[addr])}`; CPU.Acc = CPU.memory[addr]; CPU.updateFlagNegative(); CPU.updateFlagZero(); CPU.IP = CPU.IP += 2; }, add_lit: (lit) => { CPU.currentInstruction.mnemonic = 'ADD lit'; // Calculate sum let sum = CPU.Acc + lit; if (sum > 255) { CPU.setFlagCarry(); sum = (sum % 255) - 1; } else { CPU.unsetFlagCarry(); } // Calculate overflow flag status let bitSixCarry = 0; if ((CPU.Acc & 64) && (lit & 64)) { bitSixCarry = 1; } let overflow = bitSixCarry ^ (CPU.FLAGS & 1) if (overflow) { CPU.setFlagOverflow(); } else { CPU.unsetFlagOverflow(); } CPU.Acc = sum; CPU.updateFlagNegative(); CPU.updateFlagZero(); CPU.IP = CPU.IP += 2; }, add_addr: (addr) => { CPU.currentInstruction.mnemonic = 'ADD addr'; // Calculate sum let sum = CPU.Acc + CPU.memory[addr]; if (sum > 255) { CPU.setFlagCarry(); sum = (sum % 255) - 1; } else { CPU.unsetFlagCarry(); } // Calculate overflow flag status let bitSixCarry = 0; if ((CPU.Acc & 64) && (addr & 64)) { bitSixCarry = 1; } let overflow = bitSixCarry ^ (CPU.FLAGS & 1) if (overflow) { CPU.setFlagOverflow(); } else { CPU.unsetFlagOverflow(); } CPU.Acc = sum; CPU.updateFlagNegative(); CPU.updateFlagZero(); CPU.IP = CPU.IP += 2; }, sub_lit: (lit) => { CPU.currentInstruction.mnemonic = 'SUB lit'; // Calculate sum let sum = CPU.Acc - lit; if (sum < 0) { CPU.setFlagCarry(); sum = sum + 256; } else { CPU.unsetFlagCarry(); } // Calculate overflow flag status let bitSixCarry = 0; if ((CPU.Acc & 64) && (lit & 64)) { bitSixCarry = 1; } let overflow = bitSixCarry ^ (CPU.FLAGS & 1) if (overflow) { CPU.setFlagOverflow(); } else { CPU.unsetFlagOverflow(); } CPU.Acc = sum; CPU.updateFlagNegative(); CPU.updateFlagZero(); CPU.IP = CPU.IP += 2; }, sub_addr: (addr) => { CPU.currentInstruction.mnemonic = 'SUB addr'; // Calculate sum let sum = CPU.Acc - CPU.memory[addr]; if (sum < 0) { CPU.setFlagCarry(); sum = sum + 256; } else { CPU.unsetFlagCarry(); } // Calculate overflow flag status let bitSixCarry = 0; if ((CPU.Acc & 64) && (addr & 64)) { bitSixCarry = 1; } let overflow = bitSixCarry ^ (CPU.FLAGS & 1) if (overflow) { CPU.setFlagOverflow(); } else { CPU.unsetFlagOverflow(); } CPU.Acc = sum; CPU.updateFlagNegative(); CPU.updateFlagZero(); CPU.IP = CPU.IP += 2; }, hop_lit: (lit) => { CPU.currentInstruction.mnemonic = `HOP lit; IP+2: ${CPU.memory[CPU.IP+2]}, IP+3: ${CPU.memory[CPU.IP+3]}`; if (CPU.Acc === lit) { CPU.IP += 4; } else { CPU.IP += 2; } }, hop_addr: (addr) => { CPU.currentInstruction.mnemonic = 'HOP addr'; if (CPU.Acc === CPU.memory[addr]) { CPU.IP += 4; } else { CPU.IP += 2; } }, jump_lit: (lit) => { CPU.currentInstruction.mnemonic = 'JMP lit'; CPU.IP = lit; }, jump_addr: (addr) => { CPU.currentInstruction.mnemonic = 'JMP addr'; CPU.IP = CPU.memory[addr]; }, flag_toggle: (flagNum) => { CPU.currentInstruction.mnemonic = 'FTG'; let mask = null; if (flagNum === 0) { mask = 1; } if (flagNum === 1) { mask = 2; } if (flagNum === 2) { mask = 4; } if (flagNum === 3) { mask = 8; } if (mask === null) { throw new Error('Invalid flag number'); } CPU.FLAGS = CPU.FLAGS ^= mask; CPU.IP += 2; }, flag_hop: (flagNum) => { CPU.currentInstruction.mnemonic = `FHP; IP+2: ${CPU.memory[CPU.IP+2]}, IP+3: ${CPU.memory[CPU.IP+3]}`; let mask = null; if (flagNum === 0) { mask = 1; } if (flagNum === 1) { mask = 2; } if (flagNum === 2) { mask = 4; } if (flagNum === 3) { mask = 8; } if (mask === null) { throw new Error('Invalid flag number'); } if (CPU.FLAGS & mask) { CPU.IP += 4; } else { CPU.IP += 2; } }, no_op: () => { CPU.currentInstruction.mnemonic = `NOP`; CPU.IP += 2; }, } const opcodes2mnemonics = { 0: (operand) => Instructions.end(), 1: (operand) => Instructions.store_lit(operand), 2: (operand) => Instructions.store_addr(operand), 3: (operand) => Instructions.load_lit(operand), 4: (operand) => Instructions.load_addr(operand), 5: (operand) => Instructions.add_lit(operand), 6: (operand) => Instructions.add_addr(operand), 7: (operand) => Instructions.sub_lit(operand), 8: (operand) => Instructions.sub_addr(operand), 9: (operand) => Instructions.hop_lit(operand), 10: (operand) => Instructions.hop_addr(operand), 11: (operand) => Instructions.jump_lit(operand), 12: (operand) => Instructions.jump_addr(operand), 13: (operand) => Instructions.flag_toggle(operand), 14: (operand) => Instructions.flag_hop(operand), 15: (operand) => Instructions.no_op(), }; /** * Load code into memory and set CPU state to "running" * @param {Uint8Array} code - Machine code to load **/ function startCPU(code) { CPU.loadMemory(code); CPU.cycleCounter = 0; CPU.running = true; } /** * Execute just the next instruction in memory **/ async function stepCPU(debug = false) { if (CYCLE_LIMIT) { // Temporary limit as a lazy way to halt infinite loops if (CPU.cycleCounter > CYCLE_LIMIT) { console.warn('HALTING - reached cycle limit'); CPU.running = false; } } if (CPU.running) { if (CPU.IP >= CPU.memory.length) { console.error('HALTING - IP greater than memory size'); CPU.running = false; } else { CPU.currentInstruction.opcode = CPU.memory[CPU.IP]; CPU.currentInstruction.operand = CPU.memory[CPU.IP+1]; let executeInstruction = opcodes2mnemonics[CPU.currentInstruction.opcode]; executeInstruction(CPU.currentInstruction.operand); CPU.cycleCounter += 1; } } logCPUState(debug); } /** * @param {Uint8Array} code - Machine code to run * @param {Boolean} [debug] - Enable/disable debugging printouts * @param {Number} [clockSpeed] - CPU clock speed in milliseconds **/ exports.runProgram = (code, debug = false, clockSpeed = 10) => { startCPU(code); // Animate the output by pausing between steps const loop = setInterval(async () => { stepCPU(debug); if (!CPU.running) clearInterval(loop); }, clockSpeed); } // FUNCTIONS THAT PULL INFO FROM STATE TO DISPLAY /** * @param {Boolean} [debug] - Enable/disable debugging printouts **/ function logCPUState(debug = false) { console.group(`Step`); if (!debug) console.clear(); if (debug) { display.printDisplay(CPU.memory); } else { display.prettyPrintDisplay(CPU.memory); } console.log(); console.log('Mnemonic:', CPU.currentInstruction.mnemonic); console.log(`Machine: $${num2hex(CPU.currentInstruction.opcode)} $${num2hex(CPU.currentInstruction.operand)}`); console.log(); console.log(`IP: $${num2hex(CPU.IP)} Acc: $${num2hex(CPU.Acc)} NZOC: ${num2bin_4bit(CPU.FLAGS)}  ${CPU.running ? "running" : "halted" }`); console.log(); console.groupEnd(); };