215 lines
5.3 KiB
JavaScript
215 lines
5.3 KiB
JavaScript
const { INITIAL_IP_ADDRESS, CYCLE_LIMIT } = require('./machine.config');
|
||
const { num2hex } = require('./logging.js');
|
||
const display = require('./display.js');
|
||
|
||
// STATE
|
||
const CPU = {
|
||
running: false,
|
||
IP: INITIAL_IP_ADDRESS,
|
||
CF: 0,
|
||
Acc: 0,
|
||
memory: null,
|
||
loadMemory: (data) => { // data: Uint8Array
|
||
if (data.length > 256) { throw new Error("Out of memory error (program too long)"); }
|
||
CPU.memory = data;
|
||
},
|
||
currentInstruction: {
|
||
opcode: null,
|
||
argument: null,
|
||
mnemonic: null,
|
||
}
|
||
}
|
||
|
||
|
||
// FUNCTIONS THAT MODIFY STATE
|
||
|
||
const Instructions = {
|
||
end: () => {
|
||
CPU.currentInstruction.mnemonic = 'END';
|
||
CPU.running = false;
|
||
},
|
||
|
||
store_lit: (lit) => {
|
||
CPU.currentInstruction.mnemonic = 'STO lit';
|
||
CPU.memory[lit] = CPU.Acc;
|
||
CPU.IP = CPU.IP += 2;
|
||
},
|
||
|
||
store_addr: (addr) => {
|
||
CPU.currentInstruction.mnemonic = 'STO addr';
|
||
CPU.memory[CPU.memory[addr]] = CPU.Acc;
|
||
CPU.IP = CPU.IP += 2;
|
||
},
|
||
|
||
load_lit: (lit) => {
|
||
CPU.currentInstruction.mnemonic = 'LDA lit';
|
||
CPU.Acc = lit;
|
||
CPU.IP = CPU.IP += 2;
|
||
},
|
||
|
||
load_addr: (addr) => {
|
||
CPU.currentInstruction.mnemonic = `LDA addr \n mem at addr: ${num2hex(CPU.memory[addr])}`;
|
||
CPU.Acc = CPU.memory[addr];
|
||
CPU.IP = CPU.IP += 2;
|
||
},
|
||
|
||
add_lit: (lit) => {
|
||
CPU.currentInstruction.mnemonic = 'ADD lit';
|
||
let sum = CPU.Acc + lit;
|
||
if (sum > 255) {
|
||
CPU.CF = 1;
|
||
CPU.Acc = (sum % 255) - 1;
|
||
} else {
|
||
CPU.CF = 0;
|
||
CPU.Acc = sum;
|
||
}
|
||
CPU.IP = CPU.IP += 2;
|
||
},
|
||
|
||
add_addr: (addr) => {
|
||
CPU.currentInstruction.mnemonic = 'ADD addr';
|
||
let sum = CPU.Acc + CPU.memory[addr];
|
||
if (sum > 255) {
|
||
CPU.CF = 1;
|
||
CPU.Acc = (sum % 255) - 1;
|
||
} else {
|
||
CPU.CF = 0;
|
||
CPU.Acc = sum;
|
||
}
|
||
CPU.IP = CPU.IP += 2;
|
||
},
|
||
|
||
sub_lit: (lit) => {
|
||
CPU.currentInstruction.mnemonic = 'SUB lit';
|
||
let sum = CPU.Acc - lit;
|
||
if (sum < 0) {
|
||
CPU.CF = 1;
|
||
CPU.Acc = 255 + sum + 1;
|
||
} else {
|
||
CPU.CF = 0;
|
||
CPU.Acc = sum;
|
||
}
|
||
CPU.IP = CPU.IP += 2;
|
||
},
|
||
|
||
sub_addr: (addr) => {
|
||
CPU.currentInstruction.mnemonic = 'SUB addr';
|
||
let sum = CPU.Acc - CPU.memory[addr];
|
||
if (sum < 0) {
|
||
CPU.CF = 1;
|
||
CPU.Acc = 255 + sum + 1;
|
||
} else {
|
||
CPU.CF = 0;
|
||
CPU.Acc = sum;
|
||
}
|
||
CPU.IP = CPU.IP += 2;
|
||
},
|
||
|
||
hop_lit: (lit) => {
|
||
CPU.currentInstruction.mnemonic = `HOP lit \n ↳ 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) => {
|
||
CPU.currentInstruction.mnemonic = 'HOP addr';
|
||
if (CPU.Acc === CPU.memory[addr]) {
|
||
CPU.IP += 4;
|
||
} else {
|
||
CPU.IP += 2;
|
||
}
|
||
},
|
||
|
||
jump_lit: (lit) => {
|
||
CPU.currentInstruction.mnemonic = 'JMP lit';
|
||
CPU.IP = lit;
|
||
},
|
||
|
||
jump_addr: (addr) => {
|
||
CPU.currentInstruction.mnemonic = 'JMP addr';
|
||
CPU.IP = CPU.memory[addr];
|
||
},
|
||
|
||
carry_clear: () => {
|
||
CPU.currentInstruction.mnemonic = 'CFC';
|
||
CPU.CF = 0;
|
||
CPU.IP += 2;
|
||
},
|
||
|
||
carry_hop: () => {
|
||
CPU.currentInstruction.mnemonic = `CHP \n ↳ Memory at IP+2 and +3: ${CPU.memory[CPU.IP+2]}, ${CPU.memory[CPU.IP+3]}`;
|
||
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 startCPU(code) {
|
||
CPU.loadMemory(code);
|
||
CPU.running = true;
|
||
}
|
||
|
||
function stepCPU() {
|
||
CPU.currentInstruction.opcode = CPU.memory[CPU.IP];
|
||
CPU.currentInstruction.argument = CPU.memory[CPU.IP+1];
|
||
let executeInstruction = opcodes2mnemonics[CPU.currentInstruction.opcode];
|
||
executeInstruction(CPU.currentInstruction.argument);
|
||
}
|
||
|
||
exports.runProgram = async (code, debug = false) => {
|
||
startCPU(code);
|
||
let step = 0;
|
||
while (true) {
|
||
step = step + 1;
|
||
if (CYCLE_LIMIT && (step > CYCLE_LIMIT)) { break; } // Temporary limit as a lazy way to halt infinite loops:
|
||
if (!CPU.running) break;
|
||
if (CPU.IP >= CPU.memory.length) break;
|
||
stepCPU();
|
||
await logCPUState(debug);
|
||
};
|
||
}
|
||
|
||
|
||
// FUNCTIONS THAT PULL INFO FROM STATE TO DISPLAY
|
||
|
||
async function logCPUState(debug = false) {
|
||
console.group(`Step`);
|
||
if (!debug) console.clear();
|
||
if (debug) {
|
||
display.printDisplay(CPU.memory);
|
||
} else {
|
||
display.prettyPrintDisplay(CPU.memory);
|
||
}
|
||
console.log();
|
||
console.log('Mnemonic:', CPU.currentInstruction.mnemonic);
|
||
console.log(`Machine: $${num2hex(CPU.currentInstruction.opcode)} $${num2hex(CPU.currentInstruction.argument)}`);
|
||
console.log();
|
||
console.log(`IP: $${num2hex(CPU.IP)} Acc: $${num2hex(CPU.Acc)} CF: ${CPU.CF} ${CPU.running ? "running" : "halted" }`);
|
||
console.log();
|
||
// Pause to show animated display:
|
||
if (!debug) await new Promise(resolve => setTimeout(resolve, 75));
|
||
console.groupEnd('Step');
|
||
}; |