diff --git a/src/cpu.lib.js b/src/cpu.lib.js index 5ccbc22..22649de 100644 --- a/src/cpu.lib.js +++ b/src/cpu.lib.js @@ -1,352 +1,352 @@ -const readline = require('readline'); -const readlineSync = require('readline-sync'); +const { num2hex } = require('./logging.js'); -const { - INITIAL_IP_ADDRESS, - DEFAULT_CYCLE_LIMIT: CYCLE_LIMIT, - KEYPAD_ADDR, - KEY_MAP, -} = require('./machine.config'); +module.exports = class CPU { -const { - num2hex, - bool2bit, -} = require('./logging.js'); -const display = require('./display.js'); + /** + * @arg {number} initialIP + **/ + constructor(initialIP, cycleLimit) { + this.running = false; + this.IP = initialIP; + this.acc = 0; + this.flags = {'C': false, 'Z': false, 'N': false, 'O': false}; + this.flagNums = {0: 'C', 1: 'Z', 2: 'N', 3: 'O'}; + this.instruction = { opcode: null, operand: null }; + this.memory = null; -// STATE -const CPU = { + this._cycleLimit = cycleLimit; - /** Core state **/ - state: { - running: false, - IP: INITIAL_IP_ADDRESS, - flags: {'C': false, 'Z': false, 'N': false, 'O': false}, - FLAGNUMS2NAMES: {0: 'C', 1: 'Z', 2: 'N', 3: 'O'}, - acc: 0, - memory: null, - }, + this.dbg = { + sourceInfo: null, + currentMnemonic: null, + previousIP: initialIP, + cycleCounter: 0, + } + } - /** Debug info **/ - debug: { - sourceInfo: null, - previousIP: 0, - currentInstruction: { - opcode: null, - operand: null, - mnemonic: null, - }, - cycleCounter: 0, - }, - /** Functions that update state **/ + /** Public interface **/ - /** @param {Uint8Array} data **/ - loadMemory(data) { - this.state.memory = new Uint8Array(256); - this.state.memory.set(data, 0); - }, + /** + * @param {Uint8Array} machineCode + **/ + loadMemory(machineCode) { + this.memory = new Uint8Array(256); + this.memory.set(machineCode, 0); + } + + peek() { return; } // TODO + + poke() { return; } // TODO /** @param {Array} info **/ // TODO type info loadSourceInfo(info) { - this.debug.sourceInfo = info; - }, + this.dbg.sourceInfo = info; + } - incrementIP(offset) { - this.state.previousIP = this.state.IP; - this.state.IP = this.state.IP + offset; - }, - - setIP(address) { - this.state.previousIP = this.state.IP; - this.state.IP = address; - }, - - updateFlagZero() { this.state.flags.Z = this.state.acc === 0; }, - updateFlagNegative() { this.state.acc & 128 ? this.state.flags.N = true : this.state.flags.N = false }, - - /** - * Load code into memory and set CPU state to "state.running" - * @param {Uint8Array} code - Machine code to load - **/ - startCPU(code) { - CPU.loadMemory(code); - CPU.state.running = true; - }, + /** Set CPU state to "state.running" **/ + start() { + this.running = true; + } /** Execute the next instruction in memory **/ - stepCPU() { - CPU.cycleStartCallbacks.forEach((fn) => fn()); + step() { + this._cycleStartCallbacks.forEach((fn) => fn()); - if (CPU.state.IP >= CPU.state.memory.length) { + if (this.IP >= this.memory.length) { console.error('HALTING - IP greater than memory size'); // TODO -- should this throw an error instead? - CPU.state.running = false; + this.running = false; } else { - CPU.debug.currentInstruction.opcode = CPU.state.memory[CPU.state.IP]; - CPU.debug.currentInstruction.operand = CPU.state.memory[CPU.state.IP+1]; - let executeInstruction = opcodes2mnemonics[CPU.debug.currentInstruction.opcode]; - - if (typeof executeInstruction === 'undefined') { failInvalidOpcode(); } - - executeInstruction(CPU.debug.currentInstruction.operand); - CPU.debug.cycleCounter += 1; + this.instruction.opcode = this.memory[this.IP]; + this.instruction.operand = this.memory[this.IP+1]; + let mnem = this._nums2mnems[this.instruction.opcode]; + let op = this._ops[mnem]; + if (typeof op === 'undefined') { this._failInvalidOpcode(); } + op(this.instruction.operand); + this.dbg.cycleCounter += 1; } // Temporary limit as a lazy way to halt infinite loops - if ((CYCLE_LIMIT > 0) && CPU.debug.cycleCounter >= CYCLE_LIMIT) { + if ((this._cycleLimit > 0) && this.dbg.cycleCounter >= this._cycleLimit) { console.warn(' HALTING - reached cycle limit'); // TODO -- throw error? - CPU.state.running = false; + this.running = false; } - if (!CPU.state.running) process.exit(); - CPU.cycleEndCallbacks.forEach((fn) => fn()); - }, + this._cycleEndCallbacks.forEach((fn) => fn()); + if (!this.running) process.exit(); + } + + + /** Private methods **/ + + _incrementIP(offset) { + this.dbg.previousIP = this.IP; + this.IP = this.IP + offset; + } + + _setIP(address) { + this.dbg.previousIP = this.IP; + this.IP = address; + } + + _updateFlagZero() { + this.flags.Z = this.acc === 0; + } + + _updateFlagNegative() { + if (this.acc & 128) + { this.flags.N = true; } + else + { this.flags.N = false; } + } /** Hooks **/ - cycleStartCallbacks: [], - cycleEndCallbacks: [], + /** @type Array **/ + _cycleStartCallbacks = []; + + /** @type Array **/ + _cycleEndCallbacks = []; /** @param {function} fn **/ - onCycleStart(fn) { this.cycleStartCallbacks.push(fn) }, - onCycleEnd(fn) { this.cycleEndCallbacks.push(fn) }, + onCycleStart(fn) { this._cycleStartCallbacks.push(fn) }; + + /** @param {function} fn **/ + onCycleEnd(fn) { this._cycleEndCallbacks.push(fn) }; + + _ops = { + end: () => { + this.dbg.currentMnemonic = 'END'; + this.running = false; + this._incrementIP(2); + }, + + store_lit: (lit) => { + this.dbg.currentMnemonic = 'STO lit'; + this.memory[lit] = this.acc; + this._incrementIP(2); + }, + + store_addr: (addr) => { + this.dbg.currentMnemonic = `STO addr; @addr: ${num2hex(this.memory[addr])}`; + this.memory[this.memory[addr]] = this.acc; + this._incrementIP(2); + }, + + load_lit: (lit) => { + this.dbg.currentMnemonic = 'LDA lit'; + this.acc = lit; + this._updateFlagNegative(); + this._updateFlagZero(); + this._incrementIP(2); + }, + + load_addr: (addr) => { + this.dbg.currentMnemonic = `LDA addr; @ addr: ${num2hex(this.memory[addr])}`; + this.acc = this.memory[addr]; + this._updateFlagNegative(); + this._updateFlagZero(); + this._incrementIP(2); + }, + + add_lit: (lit) => { + this.dbg.currentMnemonic = 'ADD lit'; + // Calculate sum + let sum = this.acc + lit; + if (sum > 255) { + this.flags.C = true; + sum = (sum % 255) - 1; + } else { + this.flags.C = false; + } + // Calculate overflow flag status + let bitSixCarry = 0; + if ((this.acc & 64) && (lit & 64)) { bitSixCarry = 1; } + // let overflow = bitSixCarry ^ (this.flags & 8); + // FIXME FIXME FIXME + // I'm on a plane and can't remember how this works + let overflow = 0; + if (overflow) { + this.flags.O = true; + } else { + this.flags.O = false; + } + this.acc = sum; + this._updateFlagNegative(); + this._updateFlagZero(); + this._incrementIP(2); + }, + + add_addr: (addr) => { + this.dbg.currentMnemonic = 'ADD addr'; + // Calculate sum + let sum = this.acc + this.memory[addr]; + if (sum > 255) { + this.flags.C = true; + sum = (sum % 255) - 1; + } else { + this.flags.C = false; + } + // Calculate overflow flag status + let bitSixCarry = 0; + if ((this.acc & 64) && (addr & 64)) { bitSixCarry = 1; } + // let overflow = bitSixCarry ^ (this.flags & 8); + // FIXME FIXME FIXME + // I'm on a plane and can't remember how this works + let overflow = 0; + if (overflow) { + this.flags.O = true; + } else { + this.flags.O = false; + } + this.acc = sum; + this._updateFlagNegative(); + this._updateFlagZero(); + this._incrementIP(2); + }, + + sub_lit: (lit) => { + this.dbg.currentMnemonic = 'SUB lit'; + // Calculate sum + let sum = this.acc - lit; + if (sum < 0) { + this.flags.C = true; + sum = sum + 256; + } else { + this.flags.C = false; + } + // Calculate overflow flag status + let bitSixCarry = 0; + if ((this.acc & 64) && (lit & 64)) { bitSixCarry = 1; } + // let overflow = bitSixCarry ^ (this.flags & 8); + // FIXME FIXME FIXME + // I'm on a plane and can't remember how this works + let overflow = 0; + if (overflow) { + this.flags.O = true; + } else { + this.flags.O = false; + } + this.acc = sum; + this._updateFlagNegative(); + this._updateFlagZero(); + this._incrementIP(2); + }, + + sub_addr: (addr) => { + this.dbg.currentMnemonic = 'SUB addr'; + // Calculate sum + let sum = this.acc - this.memory[addr]; + if (sum < 0) { + this.flags.C = true; + sum = sum + 256; + } else { + this.flags.C = false; + } + // Calculate overflow flag status + let bitSixCarry = 0; + if ((this.acc & 64) && (addr & 64)) { bitSixCarry = 1; } + // let overflow = bitSixCarry ^ (this.flags & 8); + // FIXME FIXME FIXME + // I'm on a plane and can't remember how this works + let overflow = 0; + if (overflow) { + this.flags.O = true; + } else { + this.flags.O = false; + } + this.acc = sum; + this._updateFlagNegative(); + this._updateFlagZero(); + this._incrementIP(2); + }, + + hop_lit: (lit) => { + this.dbg.currentMnemonic = `HOP lit; IP+2: ${this.memory[this.IP+2]}, IP+3: ${this.memory[this.IP+3]}`; + if (this.acc === lit) { + this._incrementIP(4); + } else { + this._incrementIP(2); + } + }, + + hop_addr: (addr) => { + this.dbg.currentMnemonic = 'HOP addr'; + if (this.acc === this.memory[addr]) { + this._incrementIP(4); + } else { + this._incrementIP(2); + } + }, + + jump_lit: (lit) => { + this.dbg.currentMnemonic = 'JMP lit'; + this._setIP(lit); + }, + + jump_addr: (addr) => { + this.dbg.currentMnemonic = 'JMP addr'; + this._setIP(this.memory[addr]); + }, + + flag_toggle: (flagNum) => { + if (flagNum === null) { + console.error('Invalid flag number'); + process.exit(); // TODO review + } + const flagName = this.flagNums[flagNum]; + this.dbg.currentMnemonic = `FTG ${flagName}`; + this.flags[flagName] = !this.flags[flagName]; + this._incrementIP(2); + }, + + flag_hop: (flagNum) => { + if (flagNum === null) { + console.error('Invalid flag number'); + process.exit(); + } + const flagName = this.flagNums[flagNum]; + this.dbg.currentMnemonic = + `FHP ${flagName}; IP+2: ${this.memory[this.IP+2]}, IP+3: ${this.memory[this.IP+3]}`; + if (this.flags[this.flagNums[flagNum]]) { + this._incrementIP(4); + } else { + this._incrementIP(2); + } + }, + + no_op: () => { + this.dbg.currentMnemonic = `NOP`; + this._incrementIP(2); + }, + } + + _nums2mnems = { + 0: "end", + 1: "store_lit", + 2: "store_addr", + 3: "load_lit", + 4: "load_addr", + 5: "add_lit", + 6: "add_addr", + 7: "sub_lit", + 8: "sub_addr", + 9: "hop_lit", + 10: "hop_addr", + 11: "jump_lit", + 12: "jump_addr", + 13: "flag_toggle", + 14: "flag_hop", + 15: "no_op", + } + + _failInvalidOpcode() { + let info = this.dbg.sourceInfo[this.dbg.previousIP]; + console.error(); + console.error(`Error: Invalid opcode`); + console.error(` Executing $${num2hex(info.machine[0])} $${num2hex(info.machine[1])}`); + console.error(` from line ${info.lineNumber}: ${info.source}`); + process.exit(); + } } - - -// FUNCTIONS THAT MODIFY STATE - -const Instructions = { - end: () => { - CPU.debug.currentInstruction.mnemonic = 'END'; - CPU.state.running = false; - CPU.incrementIP(2); - }, - - store_lit: (lit) => { - CPU.debug.currentInstruction.mnemonic = 'STO lit'; - CPU.state.memory[lit] = CPU.state.acc; - CPU.incrementIP(2); - }, - - store_addr: (addr) => { - CPU.debug.currentInstruction.mnemonic = `STO addr; @addr: ${num2hex(CPU.state.memory[addr])}`; - CPU.state.memory[CPU.state.memory[addr]] = CPU.state.acc; - CPU.incrementIP(2); - }, - - load_lit: (lit) => { - CPU.debug.currentInstruction.mnemonic = 'LDA lit'; - CPU.state.acc = lit; - CPU.updateFlagNegative(); - CPU.updateFlagZero(); - CPU.incrementIP(2); - }, - - load_addr: (addr) => { - CPU.debug.currentInstruction.mnemonic = `LDA addr; @ addr: ${num2hex(CPU.state.memory[addr])}`; - CPU.state.acc = CPU.state.memory[addr]; - CPU.updateFlagNegative(); - CPU.updateFlagZero(); - CPU.incrementIP(2); - }, - - add_lit: (lit) => { - CPU.debug.currentInstruction.mnemonic = 'ADD lit'; - // Calculate sum - let sum = CPU.state.acc + lit; - if (sum > 255) { - CPU.state.flags.C = true; - sum = (sum % 255) - 1; - } else { - CPU.state.flags.C = false; - } - // Calculate overflow flag status - let bitSixCarry = 0; - if ((CPU.state.acc & 64) && (lit & 64)) { bitSixCarry = 1; } - // let overflow = bitSixCarry ^ (CPU.state.flags & 8); - // FIXME FIXME FIXME - // I'm on a plane and can't remember how this works - let overflow = 0; - if (overflow) { - CPU.state.flags.O = true; - } else { - CPU.state.flags.O = false; - } - CPU.state.acc = sum; - CPU.updateFlagNegative(); - CPU.updateFlagZero(); - CPU.incrementIP(2); - }, - - add_addr: (addr) => { - CPU.debug.currentInstruction.mnemonic = 'ADD addr'; - // Calculate sum - let sum = CPU.state.acc + CPU.state.memory[addr]; - if (sum > 255) { - CPU.state.flags.C = true; - sum = (sum % 255) - 1; - } else { - CPU.state.flags.C = false; - } - // Calculate overflow flag status - let bitSixCarry = 0; - if ((CPU.state.acc & 64) && (addr & 64)) { bitSixCarry = 1; } - // let overflow = bitSixCarry ^ (CPU.state.flags & 8); - // FIXME FIXME FIXME - // I'm on a plane and can't remember how this works - let overflow = 0; - if (overflow) { - CPU.state.flags.O = true; - } else { - CPU.state.flags.O = false; - } - CPU.state.acc = sum; - CPU.updateFlagNegative(); - CPU.updateFlagZero(); - CPU.incrementIP(2); - }, - - sub_lit: (lit) => { - CPU.debug.currentInstruction.mnemonic = 'SUB lit'; - // Calculate sum - let sum = CPU.state.acc - lit; - if (sum < 0) { - CPU.state.flags.C = true; - sum = sum + 256; - } else { - CPU.state.flags.C = false; - } - // Calculate overflow flag status - let bitSixCarry = 0; - if ((CPU.state.acc & 64) && (lit & 64)) { bitSixCarry = 1; } - // let overflow = bitSixCarry ^ (CPU.state.flags & 8); - // FIXME FIXME FIXME - // I'm on a plane and can't remember how this works - let overflow = 0; - if (overflow) { - CPU.state.flags.O = true; - } else { - CPU.state.flags.O = false; - } - CPU.state.acc = sum; - CPU.updateFlagNegative(); - CPU.updateFlagZero(); - CPU.incrementIP(2); - }, - - sub_addr: (addr) => { - CPU.debug.currentInstruction.mnemonic = 'SUB addr'; - // Calculate sum - let sum = CPU.state.acc - CPU.state.memory[addr]; - if (sum < 0) { - CPU.state.flags.C = true; - sum = sum + 256; - } else { - CPU.state.flags.C = false; - } - // Calculate overflow flag status - let bitSixCarry = 0; - if ((CPU.state.acc & 64) && (addr & 64)) { bitSixCarry = 1; } - // let overflow = bitSixCarry ^ (CPU.state.flags & 8); - // FIXME FIXME FIXME - // I'm on a plane and can't remember how this works - let overflow = 0; - if (overflow) { - CPU.state.flags.O = true; - } else { - CPU.state.flags.O = false; - } - CPU.state.acc = sum; - CPU.updateFlagNegative(); - CPU.updateFlagZero(); - CPU.incrementIP(2); - }, - - hop_lit: (lit) => { - CPU.debug.currentInstruction.mnemonic = `HOP lit; IP+2: ${CPU.state.memory[CPU.state.IP+2]}, IP+3: ${CPU.state.memory[CPU.state.IP+3]}`; - if (CPU.state.acc === lit) { - CPU.incrementIP(4); - } else { - CPU.incrementIP(2); - } - }, - - hop_addr: (addr) => { - CPU.debug.currentInstruction.mnemonic = 'HOP addr'; - if (CPU.state.acc === CPU.state.memory[addr]) { - CPU.incrementIP(4); - } else { - CPU.incrementIP(2); - } - }, - - jump_lit: (lit) => { - CPU.debug.currentInstruction.mnemonic = 'JMP lit'; - CPU.setIP(lit); - }, - - jump_addr: (addr) => { - CPU.debug.currentInstruction.mnemonic = 'JMP addr'; - CPU.setIP(CPU.state.memory[addr]); - }, - - flag_toggle: (flagNum) => { - if (flagNum === null) { - console.error('Invalid flag number'); - process.exit(); - } - const flagName = CPU.FLAGNUMS2NAMES[flagNum]; - CPU.debug.currentInstruction.mnemonic = `FTG ${flagName}`; - CPU.state.flags[flagName] = !CPU.state.flags[flagName]; - CPU.incrementIP(2); - }, - - flag_hop: (flagNum) => { - if (flagNum === null) { - console.error('Invalid flag number'); - process.exit(); - } - const flagName = CPU.FLAGNUMS2NAMES[flagNum]; - CPU.debug.currentInstruction.mnemonic = `FHP ${flagName}; IP+2: ${CPU.state.memory[CPU.state.IP+2]}, IP+3: ${CPU.state.memory[CPU.state.IP+3]}`; - if (CPU.state.flags[CPU.FLAGNUMS2NAMES[flagNum]]) { - CPU.incrementIP(4); - } else { - CPU.incrementIP(2); - } - }, - - no_op: () => { - CPU.debug.currentInstruction.mnemonic = `NOP`; - CPU.incrementIP(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(), -}; - - -function failInvalidOpcode() { - let info = CPU.debug.sourceInfo[CPU.previousIP]; - console.error(); - console.error(`Error: Invalid opcode`); - console.error(` Executing $${num2hex(info.machine[0])} $${num2hex(info.machine[1])}`); - console.error(` from line ${info.lineNumber}: ${info.source}`); - process.exit(); -} \ No newline at end of file