cpu - WIP - Finish refactoring into a library

This commit is contained in:
n loewen 2023-08-28 23:09:12 -04:00
parent 81617dfe42
commit 18d77086df
1 changed files with 323 additions and 323 deletions

View File

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