Computer/src/computer.ts
2024-03-06 01:46:29 -05:00

207 lines
6 KiB
TypeScript

import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { byte_array_to_js_source, format_hex } from "./etc";
import { Instruction, ISA } from "./instructionSet";
import { m256, u2, u3, u8 } from "./num";
export type TempInstrState = {
pos: u8;
params_found: number;
instr: Instruction;
params: Array<u8>;
};
function init_banks(): [Uint8Array, Uint8Array, Uint8Array, Uint8Array] {
const banks = [];
for (let i = 0; i < 4; i++) {
banks.push(new Uint8Array(256));
}
return banks as [Uint8Array, Uint8Array, Uint8Array, Uint8Array];
}
export class Computer {
private banks: [Uint8Array, Uint8Array, Uint8Array, Uint8Array] = init_banks();
private registers: Uint8Array = new Uint8Array(8);
private call_stack: Array<u8> = [];
private carry_flag: boolean = false;
private program_counter: u8 = 0;
private bank: u2 = 0;
private current_instr: TempInstrState | null = null;
events: CpuEventHandler = new CpuEventHandler();
constructor() {
// Add events
for (const [, e_type] of Object.entries(CpuEvent)) {
this.events.register_event(e_type as CpuEvent);
}
this.events.seal();
}
cycle(): void {
const current_byte = this.getMemorySilent(this.program_counter, 0);
if (this.current_instr === null) {
const parsed_instruction = ISA.getInstruction(current_byte);
if (parsed_instruction === null) {
this.events.dispatch(CpuEvent.InvalidParsed, {
pos: this.program_counter,
code: current_byte,
});
console.log(`Invalid instruction: ${format_hex(current_byte)}`);
this.step_forward();
this.events.dispatch(CpuEvent.Cycle);
return;
}
this.current_instr = {
pos: this.program_counter,
instr: parsed_instruction,
params_found: 0,
params: new Array<u8>(parsed_instruction.params.length),
};
this.events.dispatch(CpuEvent.InstructionParsed, {
pos: this.program_counter,
instr: parsed_instruction,
code: current_byte,
});
}
if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) {
this.step_forward();
this.events.dispatch(CpuEvent.Cycle);
return;
}
if (this.current_instr.params.length !== this.current_instr.params_found) {
const b = this.current_instr.instr.params[this.current_instr.params_found];
this.events.dispatch(CpuEvent.ParameterParsed, {
param: b,
pos: this.program_counter,
code: current_byte,
});
this.current_instr.params[this.current_instr.params_found] = current_byte;
this.current_instr.params_found += 1;
if (this.current_instr.params.length !== this.current_instr.params_found) {
this.step_forward();
this.events.dispatch(CpuEvent.Cycle);
return;
}
}
const execution_post_action_state = {
should_step: true,
noStep: function (): void {
this.should_step = false;
},
dispatch: this.events.dispatch.bind(this.events),
};
this.current_instr.instr.execute(this, this.current_instr.params, execution_post_action_state);
this.events.dispatch(CpuEvent.InstructionExecuted, { instr: this.current_instr.instr });
this.current_instr = null;
if (execution_post_action_state.should_step) {
this.step_forward();
}
this.events.dispatch(CpuEvent.Cycle);
}
private getMemorySilent(address: u8, bank_override?: u2): u8 {
const bank = this.banks[bank_override ?? this.bank];
const value = bank[address] as u8;
return value;
}
getMemory(address: u8, bank_override?: u2): u8 {
const value = this.getMemorySilent(address, bank_override);
this.events.dispatch(CpuEvent.MemoryAccessed, { address, bank: this.bank, value });
return value;
}
setMemory(address: u8, value: u8): void {
this.banks[this.bank][address] = value;
this.events.dispatch(CpuEvent.MemoryChanged, { address, bank: this.bank, value });
}
getRegister(register_no: u3): u8 {
return this.registers[register_no] as u8;
}
setRegister(register_no: u3, value: u8): void {
this.events.dispatch(CpuEvent.RegisterChanged, { register_no, value });
this.registers[register_no] = value;
}
getProgramCounter(): u8 {
return this.program_counter;
}
setProgramCounter(new_value: u8): void {
this.events.dispatch(CpuEvent.ProgramCounterChanged, { counter: new_value });
this.program_counter = new_value;
}
pushCallStack(address: u8): boolean {
if (this.call_stack.length >= 8) return false;
this.call_stack.push(address);
return true;
}
popCallStack(): u8 | null {
return this.call_stack.pop() ?? null;
}
setBank(bank_no: u2): void {
this.events.dispatch(CpuEvent.SwitchBank, { bank: bank_no });
this.bank = bank_no;
}
setCarry(state: boolean): void {
this.carry_flag = state;
this.events.dispatch(CpuEvent.SetFlagCarry, true);
}
getCarry(): boolean {
return this.carry_flag;
}
reset(): void {
this.events.dispatch(CpuEvent.Reset);
this.banks = init_banks();
this.registers = new Uint8Array(8);
this.call_stack = [];
this.current_instr = null;
this.program_counter = 0;
this.carry_flag = false;
}
init_events(ui: UiEventHandler): void {
ui.listen(UiEvent.RequestCpuCycle, (cycle_count) => {
for (let i = 0; i < cycle_count; i++) this.cycle();
});
ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
ui.listen(UiEvent.RequestRegisterChange, ({ register_no, value }) => this.setRegister(register_no, value));
}
load_memory(program: Array<u8>): void {
// TODO allow loading into other banks
console.log(byte_array_to_js_source(program));
const max_loop: u8 = Math.min(255, program.length) as u8;
for (let i: u8 = 0; i < 256; i++) {
// Don't fire event if no change is made
if (this.banks[0][i] === program[i]) continue;
this.banks[0][i] = program[i];
this.events.dispatch(CpuEvent.MemoryChanged, { address: i as u8, bank: 0, value: program[i] });
}
this.program_counter = 0;
}
dump_memory(): Uint8Array {
return this.banks[0];
}
private step_forward(): void {
this.program_counter = m256(this.program_counter + 1);
this.events.dispatch(CpuEvent.ProgramCounterChanged, { counter: this.program_counter });
}
}