300 lines
7.5 KiB
JavaScript
300 lines
7.5 KiB
JavaScript
// This is a sketch of a simulator for my paper computer,
|
||
// made for two purposes:
|
||
//
|
||
// (1) figuring out the basic structure for a simple simulator
|
||
// (2) testing some simple programs (hopefully)
|
||
//
|
||
// NOTA BENE: this simple version makes naive use of
|
||
// Javascript numbers for opcodes, and a Javascript array
|
||
// for the computer's "memory." This may cause some problems.
|
||
|
||
function CPU(mem) {
|
||
this.memory = mem;
|
||
this.running = false;
|
||
this.instructionPointer = 0;
|
||
this.carryFlag = 0;
|
||
this.acc = 0;
|
||
|
||
this.instructions = {
|
||
end: () => {
|
||
console.log('END');
|
||
this.running = false
|
||
},
|
||
|
||
store_lit: (lit) => {
|
||
console.log('STO lit#');
|
||
this.memory[lit] = this.acc;
|
||
log_table_with_title(this.memory, 'Current memory');
|
||
this.instructionPointer = this.instructionPointer += 1;
|
||
},
|
||
|
||
store_addr: (addr) => {
|
||
console.log('STO addr');
|
||
this.memory[this.memory[addr]] = this.acc;
|
||
log_table_with_title(this.memory, 'Memory');
|
||
this.instructionPointer = this.instructionPointer += 1;
|
||
},
|
||
|
||
load_lit: (lit) => {
|
||
console.log('LDA lit#');
|
||
this.acc = lit;
|
||
this.instructionPointer = this.instructionPointer += 1;
|
||
},
|
||
|
||
load_addr: (addr) => {
|
||
console.log('LDA addr');
|
||
console.log('mem at addr: ', this.memory[addr]);
|
||
this.acc = this.memory[addr];
|
||
this.instructionPointer = this.instructionPointer += 1;
|
||
},
|
||
|
||
add_lit: (lit) => {
|
||
console.log("ADD lit");
|
||
if ( (this.acc + lit) > 15 ) { this.carryFlag = 1; }
|
||
this.acc = ((this.acc + lit) % 15);
|
||
this.instructionPointer = this.instructionPointer += 1;
|
||
},
|
||
|
||
add_addr: (addr) => {
|
||
console.log("ADD addr");
|
||
if ( (this.acc + this.memory[addr]) > 15 ) { this.carryFlag = 1; }
|
||
this.acc = ((this.acc + this.memory[addr]) % 15);
|
||
this.instructionPointer = this.instructionPointer += 1;
|
||
},
|
||
|
||
sub_lit: (lit) => { // TODO: carry flag
|
||
console.log("SUB lit");
|
||
this.acc = this.acc - lit;
|
||
this.instructionPointer = this.instructionPointer += 1;
|
||
},
|
||
|
||
sub_addr: (addr) => { // TODO: carry flag
|
||
console.log("SUB addr");
|
||
this.acc = this.acc - this.memory[addr];
|
||
this.instructionPointer = this.instructionPointer += 1;
|
||
},
|
||
|
||
hop_lit: (lit) => {
|
||
console.log("HOP lit");
|
||
console.log(" ↳ Memory at IP+1:", this.memory[this.instructionPointer+1]);
|
||
if (this.acc === lit) {
|
||
this.instructionPointer += 2;
|
||
} else {
|
||
this.instructionPointer += 1;
|
||
}
|
||
},
|
||
|
||
hop_addr: (addr) => {
|
||
console.log("HOP addr");
|
||
if (this.acc === this.memory[addr]) {
|
||
this.instructionPointer += 2;
|
||
} else {
|
||
this.instructionPointer += 1;
|
||
}
|
||
},
|
||
|
||
jump_lit: (lit) => {
|
||
console.log("JMP lit");
|
||
this.instructionPointer = lit;
|
||
},
|
||
|
||
jump_addr: (addr) => {
|
||
console.log("JMP addr");
|
||
this.instructionPointer = this.memory[addr];
|
||
},
|
||
|
||
carry_clear: () => {
|
||
console.log("CFC");
|
||
this.carryFlag = 0;
|
||
this.instructionPointer += 1;
|
||
},
|
||
|
||
carry_hop: () => {
|
||
console.log("CHP");
|
||
if (this.carryFlag != 0) {
|
||
this.instructionPointer += 2;
|
||
} else {
|
||
this.instructionPointer += 1;
|
||
}
|
||
},
|
||
};
|
||
|
||
this.perform_operation = (opcode, arg) => {
|
||
switch (opcode) {
|
||
case 0:
|
||
this.instructions.end(arg);
|
||
break;
|
||
|
||
case 1:
|
||
this.instructions.store_lit(arg);
|
||
break;
|
||
|
||
case 2:
|
||
this.instructions.store_addr(arg);
|
||
break;
|
||
|
||
case 3:
|
||
this.instructions.load_lit(arg);
|
||
break;
|
||
|
||
case 4:
|
||
this.instructions.load_addr(arg);
|
||
break;
|
||
|
||
case 5:
|
||
this.instructions.add_lit(arg);
|
||
break;
|
||
|
||
case 6:
|
||
this.instructions.add_addr(arg);
|
||
break;
|
||
|
||
case 7:
|
||
this.instructions.sub_lit(arg);
|
||
break;
|
||
|
||
case 8:
|
||
this.instructions.sub_addr(arg);
|
||
break;
|
||
|
||
case 9:
|
||
this.instructions.hop_lit(arg);
|
||
break;
|
||
|
||
case 10:
|
||
this.instructions.hop_addr(arg);
|
||
break;
|
||
|
||
case 11:
|
||
this.instructions.jump_lit(arg);
|
||
break;
|
||
|
||
case 12:
|
||
this.instructions.jump_addr(arg);
|
||
break;
|
||
|
||
case 13:
|
||
this.instructions.carry_clear(arg);
|
||
break;
|
||
|
||
case 14:
|
||
this.instructions.carry_hop(arg);
|
||
break;
|
||
|
||
default:
|
||
console.error( `Invalid opcode: ${opcode} with argument ${arg}` );
|
||
}
|
||
}
|
||
|
||
this.run_program = () => {
|
||
const initialMemory = JSON.parse(JSON.stringify(this.memory)); // Hack to make a copy-by-value -- https://stackoverflow.com/questions/18829099/copy-a-variables-value-into-another
|
||
console.log();
|
||
console.log("————————————————————————————————————————");
|
||
let time = new Date();
|
||
console.log( `Running at ${time.toLocaleTimeString('en-US')}` );
|
||
console.log("————————————————————————————————————————");
|
||
log_debug_state();
|
||
|
||
this.running = true;
|
||
|
||
for (let i = 1; i < 16; i++) {
|
||
if ( this.running &&
|
||
(this.instructionPointer < this.memory.length) ) {
|
||
let op_arg_tuple = this.memory[this.instructionPointer];
|
||
console.group("Proccessing instruction");
|
||
console.log( op_arg_tuple );
|
||
// console.log( `processing opcode ${op_arg_tuple[0]} with arg ${op_arg_tuple[1]}` );
|
||
this.perform_operation(op_arg_tuple[0], op_arg_tuple[1]);
|
||
log_debug_state();
|
||
console.groupEnd("Processing instruction");
|
||
}
|
||
}
|
||
|
||
return {
|
||
memoryAtStart: initialMemory,
|
||
memoryAtEnd: this.memory
|
||
}
|
||
}
|
||
|
||
log_debug_state = () => {
|
||
console.log();
|
||
console.group('CPU state');
|
||
console.log( `Acc: ${this.acc} IP: ${this.instructionPointer} CF: ${this.carryFlag} ${this.running ? "running" : "halted" }` );
|
||
console.log();
|
||
console.groupEnd('CPU state');
|
||
};
|
||
};
|
||
|
||
log_table_with_title = (memory, tableTitle) => {
|
||
console.log();
|
||
console.group(tableTitle);
|
||
console.table(memory);
|
||
console.groupEnd(tableTitle);
|
||
};
|
||
|
||
|
||
// TESTS
|
||
|
||
let halt_and_catch_fire = [
|
||
[0, 0],
|
||
[1, 0],
|
||
];
|
||
|
||
let test_lda_sto = [
|
||
[3, 8], // LDA lit
|
||
[1, 5], // STO lit
|
||
[4, 5], // LDA addr
|
||
[2, 6], // STO addr
|
||
[0, 0], // END
|
||
];
|
||
|
||
let test_add_sub_nocarry = [
|
||
[5, 6], // ADD lit ... acc = 6
|
||
[7, 1], // SUB lit ... acc = 5
|
||
[1, 8], // STO lit ... mem[8] = 5
|
||
[6, 8], // ADD addr ... acc = 10
|
||
[8, 8], // SUB addr ... acc = 5
|
||
[0, 0], // END
|
||
]
|
||
let test_add_sub = [
|
||
[5, 26], // ADD lit
|
||
[0, 0], // END
|
||
]
|
||
|
||
let test_hop = [
|
||
[5, 8], // ADD lit ... acc = 8
|
||
[9, 8], // HOP lit ... hop over next op if acc = 8
|
||
[0, 0], // END ... (hopped over)
|
||
[7, 8], // SUB lit ... acc = 0
|
||
[0, 0]
|
||
]
|
||
|
||
let test_jmp = [
|
||
[11, 4], // 0
|
||
[0, 0], // 1 ... END ... JMP'd over
|
||
[0, 0], // 2
|
||
[0, 0], // 3
|
||
[5, 8], // 4 ... ADD lit ... acc = 8
|
||
[0, 0], // 5 ... END
|
||
]
|
||
|
||
let test_chp = [
|
||
[5, 8], // ADD lit ... acc = 8
|
||
[14, 0], // CHP ... shouldn't hop (CF = 0)
|
||
[5, 1], // ADD lit ... acc = 9
|
||
[5, 8], // ADD lit ... acc = 1 and CF = 1
|
||
[14, 0], // CHP ... hop! (CF = 1)
|
||
[0, 0], // END
|
||
[7, 1], // SUB lit ... acc = 0
|
||
[13, 0], // CFC ... CF = 0
|
||
[0, 0], // END
|
||
]
|
||
|
||
//let comp = new CPU(test_chp);
|
||
|
||
let comp = new CPU(test_lda_sto);
|
||
let memory_snapshots = comp.run_program();
|
||
log_table_with_title(memory_snapshots.memoryAtEnd, 'Memory after running');
|
||
log_table_with_title(memory_snapshots.memoryAtStart, 'Memory before running');
|
||
|
||
// TODO: TEST HOP_addr
|