diff --git a/sketches/simulator-sketch-v3.js b/sketches/simulator-sketch-v3.js new file mode 100644 index 0000000..c5771d0 --- /dev/null +++ b/sketches/simulator-sketch-v3.js @@ -0,0 +1,213 @@ +// NOTES: +// +// - instructions are two bytes long: +// one byte for the opcode, one for the argument + +// STATE + +const CPU = { + running: false, + IP: 0, + 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"); + 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("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(); + console.log("————————————————————————————————————————"); + let time = new Date(); + console.log( `Running at ${time.toLocaleTimeString('en-US')}` ); + console.log("————————————————————————————————————————"); + logCPUState(); + + CPU.running = true; + for (let i = 0; i < 255; i++) { // FIXME: temporary limit as a lazy way to halt infinite loops + if (!CPU.running) break; + if (CPU.IP >= CPU.memory.length) break; + stepCPU(); + }; +} + + +// FUNCTIONS THAT PULL INFO FROM STATE TO DISPLAY + +function logCPUState() { + console.log(); + console.group('CPU state'); + console.log( `Acc: ${CPU.Acc} IP: ${CPU.IP} CF: ${CPU.CF}  ${CPU.running ? "running" : "halted" }` ); + console.log(); + console.groupEnd('CPU state'); +}; + + +// 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); +}; \ No newline at end of file