const { INITIAL_IP_ADDRESS, CYCLE_LIMIT } = require('./machine.config'); const { logRunningHeader, num2hex } = require('./logging.js'); const display = require('./display.js'); // STATE const CPU = { running: false, IP: INITIAL_IP_ADDRESS, CF: 0, Acc: 0, memory: null, loadMemory: (data) => { // data: Uint8Array if (data.length > 256) { throw new Error("Out of memory error (program too long)"); } CPU.memory = data; }, currentInstruction: { opcode: null, argument: null, mnemonic: null, } } // 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.IP = CPU.IP += 2; }, load_addr: (addr) => { CPU.currentInstruction.mnemonic = 'LDA addr'; console.log('mem at addr: ', CPU.memory[addr]); CPU.Acc = CPU.memory[addr]; CPU.IP = CPU.IP += 2; }, add_lit: (lit) => { CPU.currentInstruction.mnemonic = 'ADD lit'; let sum = CPU.Acc + lit; if (sum > 255) { CPU.CF = 1; CPU.Acc = (sum % 255) - 1; } else { CPU.CF = 0; CPU.Acc = sum; } CPU.IP = CPU.IP += 2; }, add_addr: (addr) => { CPU.currentInstruction.mnemonic = 'ADD addr'; let sum = CPU.Acc + CPU.memory[addr]; if (sum > 15) { CPU.CF = 1; CPU.Acc = (sum % 15) - 1; } else { CPU.CF = 0; CPU.Acc = sum; } CPU.IP = CPU.IP += 2; }, sub_lit: (lit) => { CPU.currentInstruction.mnemonic = 'SUB lit'; let sum = CPU.Acc - lit; if (sum < 0) { CPU.CF = 1; CPU.Acc = (sum % 15) + 1; // FIXME ??? } else { CPU.CF = 0; CPU.Acc = sum; } CPU.IP = CPU.IP += 2; }, sub_addr: (addr) => { // TODO: carry flag console.log("SUB addr"); CPU.Acc = CPU.Acc - CPU.memory[addr]; CPU.IP = CPU.IP += 2; }, hop_lit: (lit) => { CPU.currentInstruction.mnemonic = `HOP lit \n ↳ Memory at IP+2 and +3: ${CPU.memory[CPU.IP+2]}, ${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]; }, carry_clear: () => { CPU.currentInstruction.mnemonic = 'CFC'; CPU.CF = 0; CPU.IP += 2; }, carry_hop: () => { CPU.currentInstruction.mnemonic = `CHP \n ↳ Memory at IP+2 and +3: ${CPU.memory[CPU.IP+2]}, ${CPU.memory[CPU.IP+3]}`; if (CPU.CF != 0) { CPU.IP += 4; } else { CPU.IP += 2; } }, } const opcodes2mnemonics = { 0: (arg) => Instructions.end(arg), 1: (arg) => Instructions.store_lit(arg), 2: (arg) => Instructions.store_addr(arg), 3: (arg) => Instructions.load_lit(arg), 4: (arg) => Instructions.load_addr(arg), 5: (arg) => Instructions.add_lit(arg), 6: (arg) => Instructions.add_addr(arg), 7: (arg) => Instructions.sub_lit(arg), 8: (arg) => Instructions.sub_addr(arg), 9: (arg) => Instructions.hop_lit(arg), 10: (arg) => Instructions.hop_addr(arg), 11: (arg) => Instructions.jump_lit(arg), 12: (arg) => Instructions.jump_addr(arg), 13: (arg) => Instructions.carry_clear(arg), 14: (arg) => Instructions.carry_hop(arg), }; function stepCPU() { CPU.currentInstruction.opcode = CPU.memory[CPU.IP]; CPU.currentInstruction.argument = CPU.memory[CPU.IP+1]; let executeInstruction = opcodes2mnemonics[CPU.currentInstruction.opcode]; executeInstruction(CPU.currentInstruction.argument); } exports.runProgram = async (code, debug = false) => { if (debug) logRunningHeader(); CPU.loadMemory(code); CPU.running = true; let step = 0; while (true) { step = step + 1; if (CYCLE_LIMIT && (step > CYCLE_LIMIT)) { break; } // Temporary limit as a lazy way to halt infinite loops: if (!CPU.running) break; if (CPU.IP >= CPU.memory.length) break; stepCPU(); console.group(`Step`); if (!debug) console.clear(); display.printDisplay(CPU.memory); logCPUState(); if (!debug) await new Promise(resolve => setTimeout(resolve, 75)); console.groupEnd('Step'); }; } // FUNCTIONS THAT PULL INFO FROM STATE TO DISPLAY function logCPUState() { console.log(); console.log('Mnemonic:', CPU.currentInstruction.mnemonic); console.log(`Machine: $${num2hex(CPU.currentInstruction.opcode)} $${num2hex(CPU.currentInstruction.argument)}`); console.log(); console.log( `IP: $${num2hex(CPU.IP)} Acc: $${num2hex(CPU.Acc)} CF: ${CPU.CF}  ${CPU.running ? "running" : "halted" }` ); console.log(); };