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,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<Function> **/
_cycleStartCallbacks = [];
/** @type Array<Function> **/
_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();
}