Add UiEvent's. Make events follow types
This commit is contained in:
parent
caf86de9b9
commit
e2d3bae806
BIN
HelloWorld.bin
BIN
HelloWorld.bin
Binary file not shown.
|
@ -20,6 +20,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="memory"></div>
|
<div id="memory"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="instruction_explainer"></div>
|
||||||
<div id="printout"></div>
|
<div id="printout"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="controls_bar">
|
<div id="controls_bar">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { CpuEvent, MemoryCellType } from "./events";
|
import { CpuEvent, CpuEventHandler, MemoryCellType, UiEvent, UiEventHandler } from "./events";
|
||||||
import { u8 } from "./etc";
|
import { byte_array_to_js_source, format_hex, u8 } from "./etc";
|
||||||
import { EventHandler } from "./eventHandler";
|
import { EventHandler } from "./eventHandler";
|
||||||
import { ConstParam, Instruction, ISA, MemorParam, RegisParam } from "./instructionSet";
|
import { ConstParam, Instruction, ISA, MemorParam, RegisParam } from "./instructionSet";
|
||||||
|
|
||||||
|
@ -17,21 +17,13 @@ export type ComputerState = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Computer {
|
export class Computer {
|
||||||
private memory: Uint8Array;
|
private memory = new Uint8Array(256);
|
||||||
private program_counter: u8;
|
private registers = new Uint8Array(8);
|
||||||
private registers: Uint8Array;
|
private program_counter: u8 = 0;
|
||||||
private current_instr: TempInstrState | null;
|
private current_instr: TempInstrState | null = null;
|
||||||
events: EventHandler<CpuEvent>;
|
events: CpuEventHandler = new EventHandler<CpuEvent>() as CpuEventHandler;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// 256 bytes for both program and general purpose memory.
|
|
||||||
this.memory = new Uint8Array(256);
|
|
||||||
// 8 registers
|
|
||||||
this.registers = new Uint8Array(8);
|
|
||||||
this.program_counter = 0;
|
|
||||||
this.current_instr = null;
|
|
||||||
this.events = new EventHandler<CpuEvent>();
|
|
||||||
|
|
||||||
// Add events
|
// Add events
|
||||||
for (const [, e_type] of Object.entries(CpuEvent)) {
|
for (const [, e_type] of Object.entries(CpuEvent)) {
|
||||||
this.events.register_event(e_type as CpuEvent);
|
this.events.register_event(e_type as CpuEvent);
|
||||||
|
@ -47,8 +39,9 @@ export class Computer {
|
||||||
this.events.dispatch(CpuEvent.MemoryByteParsed, {
|
this.events.dispatch(CpuEvent.MemoryByteParsed, {
|
||||||
type: MemoryCellType.InvalidInstruction,
|
type: MemoryCellType.InvalidInstruction,
|
||||||
pos: this.program_counter,
|
pos: this.program_counter,
|
||||||
|
code: current_byte,
|
||||||
});
|
});
|
||||||
console.log(`Invalid instruction: ${current_byte.toString(16).toUpperCase().padStart(2, "0")}`);
|
console.log(`Invalid instruction: ${format_hex(current_byte)}`);
|
||||||
this.step_forward();
|
this.step_forward();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -62,6 +55,7 @@ export class Computer {
|
||||||
type: MemoryCellType.Instruction,
|
type: MemoryCellType.Instruction,
|
||||||
pos: this.program_counter,
|
pos: this.program_counter,
|
||||||
instr: parsed_instruction,
|
instr: parsed_instruction,
|
||||||
|
code: current_byte,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +74,15 @@ export class Computer {
|
||||||
} else if (b instanceof MemorParam) {
|
} else if (b instanceof MemorParam) {
|
||||||
a = MemoryCellType.Memory;
|
a = MemoryCellType.Memory;
|
||||||
}
|
}
|
||||||
this.events.dispatch(CpuEvent.MemoryByteParsed, { type: a, pos: this.program_counter, param: b });
|
if (a === undefined) {
|
||||||
|
throw new Error("Shouldn't");
|
||||||
|
}
|
||||||
|
this.events.dispatch(CpuEvent.MemoryByteParsed, {
|
||||||
|
type: a,
|
||||||
|
pos: this.program_counter,
|
||||||
|
code: current_byte,
|
||||||
|
param: b,
|
||||||
|
});
|
||||||
this.current_instr.params[this.current_instr.params_found] = current_byte;
|
this.current_instr.params[this.current_instr.params_found] = current_byte;
|
||||||
this.current_instr.params_found += 1;
|
this.current_instr.params_found += 1;
|
||||||
if (this.current_instr.params.length !== this.current_instr.params_found) {
|
if (this.current_instr.params.length !== this.current_instr.params_found) {
|
||||||
|
@ -93,10 +95,10 @@ export class Computer {
|
||||||
noStep: function (): void {
|
noStep: function (): void {
|
||||||
this.should_step = false;
|
this.should_step = false;
|
||||||
},
|
},
|
||||||
event: this.events.dispatch.bind(this.events),
|
dispatch: this.events.dispatch.bind(this.events),
|
||||||
};
|
};
|
||||||
this.current_instr.instr.execute(this, this.current_instr.params, execution_post_action_state);
|
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;
|
this.current_instr = null;
|
||||||
|
|
||||||
if (execution_post_action_state.should_step) {
|
if (execution_post_action_state.should_step) {
|
||||||
|
@ -136,9 +138,20 @@ export class Computer {
|
||||||
this.events.dispatch(CpuEvent.Reset, null);
|
this.events.dispatch(CpuEvent.Reset, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_events(ui: UiEventHandler): void {
|
||||||
|
ui.listen(UiEvent.RequestCpuCycle, () => {
|
||||||
|
this.cycle();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
load_memory(program: Array<u8>): void {
|
load_memory(program: Array<u8>): void {
|
||||||
|
console.log(byte_array_to_js_source(program));
|
||||||
const max_loop = Math.min(this.memory.length, program.length);
|
const max_loop = Math.min(this.memory.length, program.length);
|
||||||
for (let i = 0; i < max_loop; i++) {
|
for (let i = 0; i < max_loop; i++) {
|
||||||
|
// Don't fire event if no change is made
|
||||||
|
if (this.memory[i] === program[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
this.memory[i] = program[i];
|
this.memory[i] = program[i];
|
||||||
this.events.dispatch(CpuEvent.MemoryChanged, { address: i, value: program[i] });
|
this.events.dispatch(CpuEvent.MemoryChanged, { address: i, value: program[i] });
|
||||||
}
|
}
|
||||||
|
|
11
src/etc.ts
11
src/etc.ts
|
@ -4,6 +4,17 @@ export type u8 = number;
|
||||||
// Jquery lite
|
// Jquery lite
|
||||||
export const $ = (s: string): HTMLElement => document.getElementById(s) as HTMLElement;
|
export const $ = (s: string): HTMLElement => document.getElementById(s) as HTMLElement;
|
||||||
|
|
||||||
|
export const format_hex = (n: number): string => n.toString(16).toUpperCase().padStart(2, "0");
|
||||||
|
|
||||||
|
export const byte_array_to_js_source = (a: Array<u8>): string => {
|
||||||
|
let str = "[";
|
||||||
|
for (const b of a) {
|
||||||
|
str += `0x${format_hex(b)},`;
|
||||||
|
}
|
||||||
|
str += "]";
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
export function el(type: string, id?: string): HTMLElement {
|
export function el(type: string, id?: string): HTMLElement {
|
||||||
const element = document.createElement(type);
|
const element = document.createElement(type);
|
||||||
if (id === undefined) {
|
if (id === undefined) {
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
export class Event<T> {
|
export class Event<T> {
|
||||||
identifier: T;
|
identifier: T;
|
||||||
callbacks: Array<(event_data: unknown) => void>;
|
callbacks: Array<(event_data: unknown) => void> = [];
|
||||||
constructor(identifier: T) {
|
constructor(identifier: T) {
|
||||||
this.identifier = identifier;
|
this.identifier = identifier;
|
||||||
this.callbacks = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventHandler<T> {
|
export class EventHandler<T> {
|
||||||
events: Array<Event<T>>;
|
events: Array<Event<T>> = [];
|
||||||
private sealed: boolean;
|
private sealed: boolean;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.sealed = false;
|
this.sealed = false;
|
||||||
this.events = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
seal(): void {
|
seal(): void {
|
||||||
|
@ -38,7 +36,7 @@ export class EventHandler<T> {
|
||||||
callback(event_data);
|
callback(event_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_listener(identifier: T, callback: (event_data: unknown) => void): void {
|
listen(identifier: T, callback: (event_data: unknown) => void): void {
|
||||||
if (!this.sealed) throw new Error("Event handler must be sealed before adding listener");
|
if (!this.sealed) throw new Error("Event handler must be sealed before adding listener");
|
||||||
const event = this.events.find((e) => e.identifier === identifier);
|
const event = this.events.find((e) => e.identifier === identifier);
|
||||||
if (event === undefined) {
|
if (event === undefined) {
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
|
import { u8 } from "./etc";
|
||||||
|
import { EventHandler } from "./eventHandler";
|
||||||
|
import { Instruction, ParameterType } from "./instructionSet";
|
||||||
|
|
||||||
export enum CpuEvent {
|
export enum CpuEvent {
|
||||||
MemoryChanged,
|
MemoryChanged,
|
||||||
RegisterChanged,
|
RegisterChanged,
|
||||||
MemoryByteParsed,
|
|
||||||
ProgramCounterChanged,
|
ProgramCounterChanged,
|
||||||
|
MemoryByteParsed,
|
||||||
|
InstructionExecuted,
|
||||||
Print,
|
Print,
|
||||||
Reset,
|
Reset,
|
||||||
}
|
}
|
||||||
|
@ -14,3 +19,32 @@ export enum MemoryCellType {
|
||||||
Memory,
|
Memory,
|
||||||
Constant,
|
Constant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handily explained in https://www.cgjennings.ca/articles/typescript-events/
|
||||||
|
interface CpuEventMap {
|
||||||
|
[CpuEvent.MemoryChanged]: { address: u8; value: u8 };
|
||||||
|
[CpuEvent.RegisterChanged]: { register_no: u8; value: u8 };
|
||||||
|
[CpuEvent.ProgramCounterChanged]: { counter: u8 };
|
||||||
|
[CpuEvent.Reset]: null;
|
||||||
|
[CpuEvent.MemoryByteParsed]: { type: MemoryCellType; pos: u8; code: u8; param?: ParameterType; instr?: Instruction };
|
||||||
|
[CpuEvent.InstructionExecuted]: { instr: Instruction };
|
||||||
|
[CpuEvent.Print]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CpuEventHandler extends EventHandler<CpuEvent> {
|
||||||
|
listen<E extends keyof CpuEventMap>(type: E, listener: (ev: CpuEventMap[E]) => void): void;
|
||||||
|
dispatch<E extends keyof CpuEventMap>(type: E, data: CpuEventMap[E]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UiEvent {
|
||||||
|
RequestCpuCycle,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UiEventMap {
|
||||||
|
[UiEvent.RequestCpuCycle]: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UiEventHandler extends EventHandler<UiEvent> {
|
||||||
|
listen<E extends keyof UiEventMap>(type: E, listener: (ev: UiEventMap[E]) => void): void;
|
||||||
|
dispatch<E extends keyof UiEventMap>(type: E, data: UiEventMap[E]): void;
|
||||||
|
}
|
||||||
|
|
23
src/index.ts
23
src/index.ts
|
@ -5,8 +5,22 @@ import { generate_isa } from "./isaGenerator";
|
||||||
import { UI } from "./ui";
|
import { UI } from "./ui";
|
||||||
|
|
||||||
function main(): void {
|
function main(): void {
|
||||||
// const program = [0x2f, 0x01, 0x01, 0x40, 0x00, 0x01, 0x21, 0x00, 0x02, 0x10, 0x00];
|
const program = [
|
||||||
const program = [0x2f, 0x00, 0x41, 0xfe, 0x00, 0x30, 0x00, 0x10, 0x03];
|
0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
|
||||||
|
0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
const container = $("container");
|
const container = $("container");
|
||||||
if (container === null) {
|
if (container === null) {
|
||||||
|
@ -14,9 +28,10 @@ function main(): void {
|
||||||
}
|
}
|
||||||
const computer = new Computer();
|
const computer = new Computer();
|
||||||
|
|
||||||
const ui = new UI(container, computer.events);
|
const ui = new UI(container);
|
||||||
|
ui.init_events(computer.events);
|
||||||
computer.load_memory(program);
|
computer.load_memory(program);
|
||||||
ui.set_step_func(computer.cycle.bind(computer));
|
computer.init_events(ui.events);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(<any>window).comp = computer;
|
(<any>window).comp = computer;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { CpuEvent } from "./events";
|
import { CpuEvent, CpuEventHandler } from "./events";
|
||||||
import { u8 } from "./etc";
|
import { format_hex, u8 } from "./etc";
|
||||||
|
|
||||||
class ParameterType {
|
export class ParameterType {
|
||||||
readonly description: string;
|
readonly desc: string;
|
||||||
constructor(description: string) {
|
constructor(description: string) {
|
||||||
this.description = description;
|
this.desc = description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ interface GenericComputer {
|
||||||
interface AfterExecutionComputerAction {
|
interface AfterExecutionComputerAction {
|
||||||
// Does not step forward the program counter
|
// Does not step forward the program counter
|
||||||
noStep: () => void;
|
noStep: () => void;
|
||||||
event: (e: CpuEvent, data: unknown) => void;
|
dispatch: CpuEventHandler["dispatch"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Instruction {
|
export interface Instruction {
|
||||||
|
@ -43,7 +43,7 @@ export class InstructionSet {
|
||||||
|
|
||||||
insertInstruction(hexCode: u8, instruction: Instruction): void {
|
insertInstruction(hexCode: u8, instruction: Instruction): void {
|
||||||
if (this.instructions.has(hexCode)) {
|
if (this.instructions.has(hexCode)) {
|
||||||
throw new Error(`Instruction "${hexCode.toString(16)}" already exists`);
|
throw new Error(`Instruction "${format_hex(hexCode)}" already exists`);
|
||||||
}
|
}
|
||||||
this.instructions.set(hexCode, instruction);
|
this.instructions.set(hexCode, instruction);
|
||||||
}
|
}
|
||||||
|
@ -75,17 +75,17 @@ ISA.insertInstruction(0x10, {
|
||||||
|
|
||||||
ISA.insertInstruction(0x20, {
|
ISA.insertInstruction(0x20, {
|
||||||
name: "LoadToRegister",
|
name: "LoadToRegister",
|
||||||
desc: "Sets the byte in register (P1) to be the contents of memory cell (P2)",
|
desc: "Sets the byte in register (P1) to be the contents of memory cell at address in register (P2)",
|
||||||
params: [new RegisParam(""), new MemorParam("")],
|
params: [new RegisParam("Set this register to"), new MemorParam("the byte held in this memory address")],
|
||||||
execute(c, p) {
|
execute(c, p) {
|
||||||
const [register_no, mem_address] = p;
|
const [register_no, mem_address] = p;
|
||||||
c.setRegister(register_no, c.getMemory(mem_address));
|
c.setRegister(register_no, c.getMemory(c.getRegister(mem_address)));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
ISA.insertInstruction(0x21, {
|
ISA.insertInstruction(0x21, {
|
||||||
name: "SaveToMemory",
|
name: "SaveToMemory",
|
||||||
desc: "Writes the byte in register (P1) to the processing memory location (P2)",
|
desc: "Writes the byte in register (P1) to the memory cell (P2)",
|
||||||
params: [new RegisParam(""), new MemorParam("")],
|
params: [new RegisParam(""), new MemorParam("")],
|
||||||
execute(c, p) {
|
execute(c, p) {
|
||||||
const [register_no, mem_address] = p;
|
const [register_no, mem_address] = p;
|
||||||
|
@ -96,7 +96,7 @@ ISA.insertInstruction(0x21, {
|
||||||
ISA.insertInstruction(0x2f, {
|
ISA.insertInstruction(0x2f, {
|
||||||
name: "AssignRegister",
|
name: "AssignRegister",
|
||||||
desc: "Assigns constant value (P2) to register (P1)",
|
desc: "Assigns constant value (P2) to register (P1)",
|
||||||
params: [new RegisParam(""), new ConstParam("")],
|
params: [new RegisParam("Set this register"), new ConstParam("to this constant")],
|
||||||
execute(c, p) {
|
execute(c, p) {
|
||||||
const [register_no, value] = p;
|
const [register_no, value] = p;
|
||||||
c.setRegister(register_no, value);
|
c.setRegister(register_no, value);
|
||||||
|
@ -104,9 +104,9 @@ ISA.insertInstruction(0x2f, {
|
||||||
});
|
});
|
||||||
|
|
||||||
ISA.insertInstruction(0x11, {
|
ISA.insertInstruction(0x11, {
|
||||||
name: "GotoIfLowBit",
|
name: "GotoIfLowBitHigh",
|
||||||
desc: "Moves the CPU instruction counter to the value in (P1) if the value in register (P2) has the lowest bit true",
|
desc: "Moves the CPU instruction counter to the value in (P1) if the value in register (P2) has the lowest bit true",
|
||||||
params: [new ConstParam("new instruction counter location"), new RegisParam("Register to check")],
|
params: [new ConstParam("Set program counter to this constant"), new RegisParam("if this register's 1 bit is set")],
|
||||||
execute(c, p, a) {
|
execute(c, p, a) {
|
||||||
const [new_address, check_register_no] = p;
|
const [new_address, check_register_no] = p;
|
||||||
if (c.getRegister(check_register_no) % 2 === 1) {
|
if (c.getRegister(check_register_no) % 2 === 1) {
|
||||||
|
@ -150,24 +150,28 @@ ISA.insertInstruction(0x40, {
|
||||||
|
|
||||||
ISA.insertInstruction(0x50, {
|
ISA.insertInstruction(0x50, {
|
||||||
name: "Equals",
|
name: "Equals",
|
||||||
desc: "If byte in register (P1) equals byte in register (P2), set byte in register (P3) to 0x01",
|
desc: "If byte in register (P2) equals byte in register (P3), set byte in register (P1) to 0x01",
|
||||||
params: [new RegisParam(""), new RegisParam(""), new RegisParam("")],
|
params: [
|
||||||
|
new RegisParam("Set this register to be 0x01"),
|
||||||
|
new RegisParam("if this register and"),
|
||||||
|
new RegisParam("this register are equal (else 0x00)"),
|
||||||
|
],
|
||||||
execute(c, p) {
|
execute(c, p) {
|
||||||
const [register_no_1, register_no_2, register_no_3] = p;
|
const [register_no_1, register_no_2, register_no_3] = p;
|
||||||
const truth = c.getRegister(register_no_1) === c.getRegister(register_no_2) ? 0x01 : 0x00;
|
const truth = c.getRegister(register_no_2) === c.getRegister(register_no_3) ? 0x01 : 0x00;
|
||||||
c.setRegister(register_no_3, truth);
|
c.setRegister(register_no_1, truth);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
ISA.insertInstruction(0xfe, {
|
ISA.insertInstruction(0xfe, {
|
||||||
name: "PrintASCII",
|
name: "PrintASCII",
|
||||||
desc: "Prints the ASCII byte in register (P1) to console",
|
desc: "Prints the ASCII byte in register (P1) to console",
|
||||||
params: [new RegisParam("")],
|
params: [new RegisParam("Register to print from")],
|
||||||
execute(c, p, a) {
|
execute(c, p, a) {
|
||||||
const register_no = p[0];
|
const register_no = p[0];
|
||||||
const asciiByte = c.getRegister(register_no);
|
const asciiByte = c.getRegister(register_no);
|
||||||
|
|
||||||
const char = String.fromCharCode(asciiByte);
|
const char = String.fromCharCode(asciiByte);
|
||||||
a.event(CpuEvent.Print, { data: char });
|
a.dispatch(CpuEvent.Print, char);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { u8 } from "./etc";
|
import { format_hex, u8 } from "./etc";
|
||||||
import { Instruction, InstructionSet } from "./instructionSet";
|
import { Instruction, InstructionSet } from "./instructionSet";
|
||||||
|
|
||||||
export function generate_isa(iset: InstructionSet): string {
|
export function generate_isa(iset: InstructionSet): string {
|
||||||
|
@ -13,7 +13,7 @@ export function generate_isa(iset: InstructionSet): string {
|
||||||
max_instr_name_len = Math.max(max_instr_name_len, short_description.length);
|
max_instr_name_len = Math.max(max_instr_name_len, short_description.length);
|
||||||
}
|
}
|
||||||
for (const instruction of instructions) {
|
for (const instruction of instructions) {
|
||||||
const hex_code = instruction[0].toString(16).toUpperCase().padStart(2, "0");
|
const hex_code = format_hex(instruction[0]);
|
||||||
const short_description = instruction[1].name.padEnd(max_instr_name_len, " ");
|
const short_description = instruction[1].name.padEnd(max_instr_name_len, " ");
|
||||||
const parameter_count = instruction[1].params.length;
|
const parameter_count = instruction[1].params.length;
|
||||||
const description = instruction[1].desc;
|
const description = instruction[1].desc;
|
||||||
|
|
150
src/ui.ts
150
src/ui.ts
|
@ -1,24 +1,33 @@
|
||||||
import { ComputerState } from "./computer";
|
import { ComputerState } from "./computer";
|
||||||
import { CpuEvent, MemoryCellType } from "./events";
|
import { CpuEvent, CpuEventHandler, MemoryCellType, UiEvent, UiEventHandler } from "./events";
|
||||||
import { $, el, u8 } from "./etc";
|
import { $, el, format_hex, u8 } from "./etc";
|
||||||
|
import { Instruction, ParameterType } from "./instructionSet";
|
||||||
import { EventHandler } from "./eventHandler";
|
import { EventHandler } from "./eventHandler";
|
||||||
|
|
||||||
export class UI {
|
export class UI {
|
||||||
container: HTMLElement;
|
container: HTMLElement;
|
||||||
program_memory: HTMLElement;
|
program_memory: HTMLElement;
|
||||||
program_memory_cells: Array<HTMLElement>;
|
program_memory_cells: Array<HTMLElement> = [];
|
||||||
registers: HTMLElement;
|
registers: HTMLElement;
|
||||||
register_cells: Array<HTMLElement>;
|
printout: HTMLElement;
|
||||||
step_func: null | (() => void);
|
instruction_explainer: HTMLElement;
|
||||||
|
register_cells: Array<HTMLElement> = [];
|
||||||
program_counter: u8;
|
instruction_parsing_addresses: Array<u8> = [];
|
||||||
|
program_counter: u8 = 0;
|
||||||
|
|
||||||
auto_running: boolean;
|
auto_running: boolean;
|
||||||
constructor(parent: HTMLElement, cpu_events: EventHandler<CpuEvent>) {
|
|
||||||
|
events: UiEventHandler = new EventHandler<UiEvent>() as UiEventHandler;
|
||||||
|
|
||||||
|
constructor(parent: HTMLElement) {
|
||||||
|
for (const [, e_type] of Object.entries(UiEvent)) {
|
||||||
|
this.events.register_event(e_type as UiEvent);
|
||||||
|
}
|
||||||
|
this.events.seal();
|
||||||
this.container = parent;
|
this.container = parent;
|
||||||
this.program_counter = 0;
|
this.printout = $("printout");
|
||||||
|
this.instruction_explainer = $("instruction_explainer");
|
||||||
const program_mem = $("memory");
|
const program_mem = $("memory");
|
||||||
this.program_memory_cells = [];
|
|
||||||
for (let i = 0; i < 256; i++) {
|
for (let i = 0; i < 256; i++) {
|
||||||
const mem_cell = el("div", `p_${i}`);
|
const mem_cell = el("div", `p_${i}`);
|
||||||
mem_cell.textContent = "00";
|
mem_cell.textContent = "00";
|
||||||
|
@ -28,10 +37,9 @@ export class UI {
|
||||||
this.program_memory_cells[0].classList.add("div", "program_counter");
|
this.program_memory_cells[0].classList.add("div", "program_counter");
|
||||||
this.program_memory = program_mem;
|
this.program_memory = program_mem;
|
||||||
|
|
||||||
this.register_cells = [];
|
|
||||||
const registers = $("registers");
|
const registers = $("registers");
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
const reg_cell = el("div", `R_${i}`);
|
const reg_cell = el("div", `r_${i}`);
|
||||||
reg_cell.textContent = "00";
|
reg_cell.textContent = "00";
|
||||||
registers.appendChild(reg_cell);
|
registers.appendChild(reg_cell);
|
||||||
this.register_cells.push(reg_cell);
|
this.register_cells.push(reg_cell);
|
||||||
|
@ -39,7 +47,6 @@ export class UI {
|
||||||
|
|
||||||
this.registers = registers;
|
this.registers = registers;
|
||||||
|
|
||||||
this.step_func = null;
|
|
||||||
this.auto_running = false;
|
this.auto_running = false;
|
||||||
const pp_button = $("pause_play_button");
|
const pp_button = $("pause_play_button");
|
||||||
if (pp_button === null) {
|
if (pp_button === null) {
|
||||||
|
@ -54,48 +61,32 @@ export class UI {
|
||||||
pp_button.textContent = "Storp";
|
pp_button.textContent = "Storp";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.getElementById("step_button")?.addEventListener("click", () => {
|
$("step_button")?.addEventListener("click", () => {
|
||||||
if (this.auto_running) {
|
if (this.auto_running) {
|
||||||
this.stop_auto();
|
this.stop_auto();
|
||||||
}
|
}
|
||||||
if (this.step_func === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.step_func();
|
|
||||||
});
|
|
||||||
|
|
||||||
cpu_events.add_listener(CpuEvent.MemoryChanged, (e) => {
|
this.events.dispatch(UiEvent.RequestCpuCycle, null);
|
||||||
const { address, value } = e as { address: u8; value: u8 };
|
|
||||||
this.program_memory_cells[address].textContent = value.toString(16).toUpperCase().padStart(2, "0");
|
|
||||||
});
|
});
|
||||||
cpu_events.add_listener(CpuEvent.RegisterChanged, (e) => {
|
}
|
||||||
const { register_no, value } = e as { register_no: u8; value: u8 };
|
|
||||||
this.register_cells[register_no].textContent = value.toString(16).toUpperCase().padStart(2, "0");
|
init_events(cpu_events: CpuEventHandler): void {
|
||||||
|
cpu_events.listen(CpuEvent.MemoryChanged, ({ address, value }) => {
|
||||||
|
this.program_memory_cells[address].textContent = format_hex(value);
|
||||||
});
|
});
|
||||||
cpu_events.add_listener(CpuEvent.ProgramCounterChanged, (e) => {
|
cpu_events.listen(CpuEvent.RegisterChanged, ({ register_no, value }) => {
|
||||||
const { counter } = e as { counter: u8 };
|
this.register_cells[register_no].textContent = format_hex(value);
|
||||||
|
});
|
||||||
|
cpu_events.listen(CpuEvent.ProgramCounterChanged, ({ counter }) => {
|
||||||
this.program_memory_cells[this.program_counter].classList.remove("program_counter");
|
this.program_memory_cells[this.program_counter].classList.remove("program_counter");
|
||||||
this.program_memory_cells[counter].classList.add("program_counter");
|
this.program_memory_cells[counter].classList.add("program_counter");
|
||||||
this.program_counter = counter;
|
this.program_counter = counter;
|
||||||
});
|
});
|
||||||
cpu_events.add_listener(CpuEvent.Print, (e) => {
|
cpu_events.listen(CpuEvent.Print, (char) => {
|
||||||
const { data } = e as { data: u8 };
|
this.printout.textContent = (this.printout.textContent ?? "") + char;
|
||||||
const printout = $("printout");
|
|
||||||
if (printout === null) {
|
|
||||||
throw new Error("Couldn't get printout");
|
|
||||||
}
|
|
||||||
printout.textContent = (printout.textContent ?? "") + data;
|
|
||||||
});
|
});
|
||||||
cpu_events.add_listener(CpuEvent.Reset, () => {
|
cpu_events.listen(CpuEvent.Reset, () => {
|
||||||
this.stop_auto();
|
this.reset();
|
||||||
this.program_memory_cells.forEach((c) => {
|
|
||||||
c.className = "";
|
|
||||||
c.textContent = "00";
|
|
||||||
});
|
|
||||||
this.register_cells.forEach((r) => {
|
|
||||||
r.textContent = "00";
|
|
||||||
});
|
|
||||||
this.program_counter = 0;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const map: Map<MemoryCellType, string> = new Map();
|
const map: Map<MemoryCellType, string> = new Map();
|
||||||
|
@ -105,8 +96,8 @@ export class UI {
|
||||||
map.set(MemoryCellType.InvalidInstruction, "invalid_instruction");
|
map.set(MemoryCellType.InvalidInstruction, "invalid_instruction");
|
||||||
map.set(MemoryCellType.Memory, "memory");
|
map.set(MemoryCellType.Memory, "memory");
|
||||||
map.set(MemoryCellType.Register, "register");
|
map.set(MemoryCellType.Register, "register");
|
||||||
cpu_events.add_listener(CpuEvent.MemoryByteParsed, (e) => {
|
cpu_events.listen(CpuEvent.MemoryByteParsed, (e) => {
|
||||||
const { type, pos } = e as { type: MemoryCellType; pos: u8 };
|
const { type, pos, code } = e;
|
||||||
const css_class = map.get(type);
|
const css_class = map.get(type);
|
||||||
if (css_class === undefined) {
|
if (css_class === undefined) {
|
||||||
throw new Error("Something went wrong");
|
throw new Error("Something went wrong");
|
||||||
|
@ -115,27 +106,74 @@ export class UI {
|
||||||
if (other_class === css_class) continue;
|
if (other_class === css_class) continue;
|
||||||
this.program_memory_cells[pos].classList.remove(other_class);
|
this.program_memory_cells[pos].classList.remove(other_class);
|
||||||
}
|
}
|
||||||
|
if (type === MemoryCellType.Instruction) {
|
||||||
|
while (this.instruction_parsing_addresses.length > 0) {
|
||||||
|
const num = this.instruction_parsing_addresses.pop();
|
||||||
|
if (num === undefined) {
|
||||||
|
throw new Error("Shouldn't happen");
|
||||||
|
}
|
||||||
|
this.program_memory_cells[num].classList.remove("instruction_argument");
|
||||||
|
this.program_memory_cells[num].classList.remove("current_instruction");
|
||||||
|
}
|
||||||
|
this.instruction_explainer.innerHTML = "";
|
||||||
|
const { instr } = e as { instr: Instruction };
|
||||||
|
this.program_memory_cells[pos].classList.add("current_instruction");
|
||||||
|
this.instruction_parsing_addresses.push(pos);
|
||||||
|
const instr_box = el("div", "expl_box");
|
||||||
|
const instr_icon = el("span", "expl_icon");
|
||||||
|
instr_icon.classList.add(css_class);
|
||||||
|
instr_icon.setAttribute("title", css_class.toUpperCase());
|
||||||
|
instr_icon.textContent = format_hex(code);
|
||||||
|
const instr_box_text = el("span", "expl_text");
|
||||||
|
instr_box_text.textContent = `${instr.name}`;
|
||||||
|
instr_box.appendChild(instr_icon);
|
||||||
|
instr_box.appendChild(instr_box_text);
|
||||||
|
this.instruction_explainer.appendChild(instr_box);
|
||||||
|
} else if (type !== MemoryCellType.InvalidInstruction) {
|
||||||
|
const { param } = e as { param: ParameterType };
|
||||||
|
this.program_memory_cells[pos].classList.add("instruction_argument");
|
||||||
|
this.instruction_parsing_addresses.push(pos);
|
||||||
|
const instr_box = el("div", "expl_box");
|
||||||
|
const instr_icon = el("span", "expl_icon");
|
||||||
|
instr_icon.classList.add(css_class);
|
||||||
|
instr_icon.setAttribute("title", css_class.toUpperCase());
|
||||||
|
instr_icon.textContent = format_hex(code);
|
||||||
|
const instr_box_text = el("span", "expl_text");
|
||||||
|
instr_box_text.textContent = `${param.desc}`;
|
||||||
|
instr_box.appendChild(instr_icon);
|
||||||
|
instr_box.appendChild(instr_box_text);
|
||||||
|
this.instruction_explainer.appendChild(instr_box);
|
||||||
|
}
|
||||||
this.program_memory_cells[pos].classList.add(css_class);
|
this.program_memory_cells[pos].classList.add(css_class);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cpu_events.listen(CpuEvent.InstructionExecuted, ({ instr }) => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.stop_auto();
|
||||||
|
this.program_memory_cells.forEach((c) => {
|
||||||
|
c.className = "";
|
||||||
|
c.textContent = "00";
|
||||||
|
});
|
||||||
|
this.register_cells.forEach((r) => {
|
||||||
|
r.textContent = "00";
|
||||||
|
});
|
||||||
|
this.program_counter = 0;
|
||||||
|
this.program_memory_cells[0].classList.add("program_counter");
|
||||||
|
this.printout.textContent = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
start_auto(speed: number = 200): void {
|
start_auto(speed: number = 200): void {
|
||||||
if (this.step_func === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.auto_running) {
|
if (this.auto_running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.auto_running = true;
|
this.auto_running = true;
|
||||||
const loop = (): void => {
|
const loop = (): void => {
|
||||||
if (this.step_func === null) {
|
|
||||||
this.auto_running = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.auto_running === false) {
|
if (this.auto_running === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.step_func();
|
this.events.dispatch(UiEvent.RequestCpuCycle, null);
|
||||||
setTimeout(loop, speed);
|
setTimeout(loop, speed);
|
||||||
// requestAnimationFrame(loop);
|
// requestAnimationFrame(loop);
|
||||||
};
|
};
|
||||||
|
@ -146,10 +184,6 @@ export class UI {
|
||||||
this.auto_running = false;
|
this.auto_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_step_func(f: () => void): void {
|
|
||||||
this.step_func = f;
|
|
||||||
}
|
|
||||||
|
|
||||||
state_update_event(state: ComputerState): void {
|
state_update_event(state: ComputerState): void {
|
||||||
const current_instr = state.current_instruction;
|
const current_instr = state.current_instruction;
|
||||||
if (current_instr !== null) {
|
if (current_instr !== null) {
|
||||||
|
|
Loading…
Reference in a new issue