// NOTES: // // - instructions are two bytes long: // one byte for the opcode, one for the argument const INITIAL_IP_ADDRESS = 32; const CYCLE_LIMIT = 128; // max number of times to step the CPU, to stop endless loops const display = require('./display.js'); // STATE const CPU = { running: false, IP: INITIAL_IP_ADDRESS, CF: 0, Acc: 0, memory: null, loadMemory: (data) => { // data: Uint8Array // TODO: check length of data CPU.memory = data; }, } // FUNCTIONS THAT MODIFY STATE const Instructions = { end: () => { console.log('END'); CPU.running = false; }, store_lit: (lit) => { console.log('STO lit#'); CPU.memory[lit] = CPU.Acc; // logTableTitled(CPU.memory, 'Current memory'); CPU.IP = CPU.IP += 2; }, store_addr: (addr) => { console.log('STO addr'); CPU.memory[CPU.memory[addr]] = CPU.Acc; //logTableTitled(CPU.memory, 'Memory'); CPU.IP = CPU.IP += 2; }, load_lit: (lit) => { console.log('LDA lit#'); CPU.Acc = lit; CPU.IP = CPU.IP += 2; }, load_addr: (addr) => { console.log('LDA addr'); console.log('mem at addr: ', CPU.memory[addr]); CPU.Acc = CPU.memory[addr]; CPU.IP = CPU.IP += 2; }, add_lit: (lit) => { console.log("ADD lit"); let sum = CPU.Acc + lit; if (sum > 15) { CPU.CF = 1; CPU.Acc = (sum % 15) - 1; } else { CPU.CF = 0; CPU.Acc = sum; } CPU.IP = CPU.IP += 2; }, add_addr: (addr) => { console.log("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) => { // TODO: carry flag console.log("SUB lit"); CPU.Acc = CPU.Acc - lit; 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) => { console.log("HOP lit"); console.log(` ↳ 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) => { console.log("HOP addr"); if (CPU.Acc === CPU.memory[addr]) { CPU.IP += 4; } else { CPU.IP += 2; } }, jump_lit: (lit) => { console.log("JMP lit"); CPU.IP = lit; }, jump_addr: (addr) => { console.log("JMP addr"); CPU.IP = CPU.memory[addr]; }, carry_clear: () => { console.log("CFC"); CPU.CF = 0; CPU.IP += 2; }, carry_hop: () => { console.log("CHP"); console.log(` ↳ Memory at IP+2 and +3: ${CPU.memory[CPU.IP+2]}, ${CPU.memory[CPU.IP+3]}`); // console.table(CPU.memory); 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() { // console.group("Step CPU"); console.group(`IP: ${CPU.IP}`); let opcode = CPU.memory[CPU.IP]; let argument = CPU.memory[CPU.IP+1]; console.log(`OP: ${opcode} ARG: ${argument}`); let instruction = opcodes2mnemonics[opcode]; instruction(argument); logCPUState(); console.groupEnd(`IP:`); // console.groupEnd("Step CPU"); } exports.runProgram = (code) => { CPU.loadMemory(code); const initialMemory = JSON.parse(JSON.stringify(CPU.memory)); // Hack to make a copy-by-value -- https://stackoverflow.com/questions/18829099/copy-a-variables-value-into-another console.log(); let time = new Date(); console.log( `┌─────────────────────┐`); // Running at 11:48:15 AM console.log( `│ Running at ${time.toLocaleTimeString('en-GB')} │` ); console.log( `└─────────────────────┘`); logCPUState(); CPU.running = true; for (let i = 0; i < CYCLE_LIMIT; i++) { // FIXME: temporary limit as a lazy way to halt infinite loops if (!CPU.running) break; if (CPU.IP >= CPU.memory.length) break; stepCPU(); display.printDisplay(CPU.memory); }; } // FUNCTIONS THAT PULL INFO FROM STATE TO DISPLAY function logCPUState() { console.log(); console.log( `Acc: ${CPU.Acc} IP: ${CPU.IP} CF: ${CPU.CF}  ${CPU.running ? "running" : "halted" }` ); console.log(); }; // FUNCTIONS FOR DISPLAYING DATA function num2hex(num) { return num.toString(16) }; function hex2num(hex) { return parseInt(hex, 16) }; logTableTitled = (memory, tableTitle) => { console.log(); console.group(tableTitle); console.table(memory); console.groupEnd(tableTitle); };