cardiograph-computer/src/cpu.js

353 lines
9.0 KiB
JavaScript

const { num2hex } = require('./logging.js');
module.exports = class CPU {
/**
* @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;
this._cycleLimit = cycleLimit;
this.dbg = {
sourceInfo: null,
currentMnemonic: null,
previousIP: initialIP,
cycleCounter: 0,
}
}
/** Public interface **/
/**
* @param {Uint8Array} machineCode
**/
loadMemory(machineCode) {
this.memory = new Uint8Array(256);
this.memory.set(machineCode, 0);
}
peek() { return; } // TODO - implement Peek
poke() { return; } // TODO - implement Poke
/** @param {Array} info **/ // TODO - document type for 'sourceInfo'
loadSourceInfo(info) {
this.dbg.sourceInfo = info;
}
/** Set CPU state to "state.running" **/
start() {
this.running = true;
}
/** Execute the next instruction in memory **/
step() {
this._cycleStartCallbacks.forEach((fn) => fn());
if (this.IP >= this.memory.length) {
console.error('HALTING - IP greater than memory size'); // FIXME - halting the CPU should throw an error instead
this.running = false;
} else {
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 ((this._cycleLimit > 0) && this.dbg.cycleCounter >= this._cycleLimit) {
console.warn(' HALTING - reached cycle limit'); // FIXME - throw error instead
this.running = false;
}
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 **/
/** @type Array<Function> **/
_cycleStartCallbacks = [];
/** @type Array<Function> **/
_cycleEndCallbacks = [];
/** @param {function} 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 - re-implement overflow
// 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 - re-implement overflow
// 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 - re-implement overflow
// 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 - re-implement overflow
// 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(); // FIXME -- throw error instead
}
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();
}
}