207 lines
6 KiB
TypeScript
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 });
|
|
}
|
|
}
|