Event based UI. Abstract instructions. Colored mem

This commit is contained in:
Alexander Bass 2024-02-16 00:17:23 -05:00
parent 51751c568b
commit ea7ea322f7
15 changed files with 461 additions and 311 deletions

View file

@ -41,7 +41,6 @@
"no-multi-assign":"warn",
"no-else-return":"warn",
"spaced-comment":"warn",
"prefer-destructuring":"warn",
"no-restricted-globals":"warn",
"prefer-template":"warn",
"class-methods-use-this":"warn",
@ -54,7 +53,6 @@
"no-param-reassign":"warn",
"prefer-arrow-callback":"warn",
"no-array-constructor": "warn",
"object-shorthand": "warn",
"no-empty": "off",
"no-self-compare": "warn",
"eqeqeq": "warn",

4
.gitignore vendored
View file

@ -1,3 +1,3 @@
node_modules
out/
dist/
dist/
makefile

View file

@ -4,6 +4,6 @@
"semi": true,
"endOfLine": "lf",
"singleQuote": false,
"printWidth": 100
"printWidth": 120
}

View file

@ -9,7 +9,9 @@ INSTRUCTIONS
0x28: Copy Register -> Register - 2 Parameter - Copies byte from register (P1) to register (P2)
0x2F: Assign value to register - 2 Parameter - Assigns register (P1) to value (P2)
--- Operations ---
0x40: Add registers - 2 Parameter - Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256)
0x30: increment register - 1 Parameter - Increments register (P1) by 1.
0x31: decrement register - 1 Parameter - Decrements register (P1) by 1.
0x40: Add registers - 2 Parameter - Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256)
0x41: Reserved
--- Bit Operations ---
0x48: Bitwise and - 2 Parameter - Ands & each bit of register (P1) and register (P2) and stores result to register (P1)

11
TODO
View file

@ -1,5 +1,6 @@
Highlight memory cells based on what they have been used for
add screen (VRAM?)
live memory and register editing
draw lines between registers and memory when used
add description for the current instruction
Add screen (VRAM?)
Live memory and register editing (Probably should pause autostep when it reaches the cell you're modifying)
Draw lines between registers and memory when used
Explain mode to explain what instructions are doing
Window showing ISA
Save binary button

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<script src="./dist/main.js"></script>
<script src="main.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="style.css" />
@ -26,13 +26,8 @@
<button type="button" id="pause_play_button">Start</button>
<button type="button" id="step_button">Step</button>
<label for="binary_upload" class="button">Load Binary</label>
<input
id="binary_upload"
name="binary_upload"
id="binary_upload"
style="visibility: hidden"
type="file"
/>
<input id="binary_upload" name="binary_upload" id="binary_upload" style="visibility: hidden" type="file" />
</div>
<pre id="ISA"></pre>
</body>
</html>

View file

@ -1,58 +1,12 @@
import { u8, $ } from "./etc";
// Set R1 to 255, then print R1, then go back to beginning
export enum Instr {
NoOp,
Goto,
GotoIfLowBit,
LoadToRegister,
WriteToMem,
CopyRegReg,
AssignRegister,
Add,
And,
Or,
Not,
LeftBitShift,
RightBitShift,
Equals,
LessThan,
GreaterThan,
Print,
PrintASCII,
HaltCatchFire,
}
const InstParamCount = new Map();
InstParamCount.set(Instr.NoOp, 0);
InstParamCount.set(Instr.Goto, 1);
InstParamCount.set(Instr.GotoIfLowBit, 2);
InstParamCount.set(Instr.LoadToRegister, 2);
InstParamCount.set(Instr.WriteToMem, 2);
InstParamCount.set(Instr.CopyRegReg, 2);
InstParamCount.set(Instr.AssignRegister, 2);
InstParamCount.set(Instr.Add, 2);
InstParamCount.set(Instr.And, 2);
InstParamCount.set(Instr.Or, 2);
InstParamCount.set(Instr.Not, 1);
InstParamCount.set(Instr.LeftBitShift, 2);
InstParamCount.set(Instr.RightBitShift, 2);
InstParamCount.set(Instr.Equals, 3);
InstParamCount.set(Instr.LessThan, 3);
InstParamCount.set(Instr.GreaterThan, 3);
InstParamCount.set(Instr.Print, 1);
InstParamCount.set(Instr.PrintASCII, 1);
InstParamCount.set(Instr.HaltCatchFire, 0);
import { CpuEvent, MemoryCellType } from "./events";
import { u8 } from "./etc";
import { EventHandler } from "./eventHandler";
import { ConstParam, Instruction, ISA, MemorParam, RegisParam } from "./instructionSet";
export type TempInstrState = {
pos: u8;
params_found: number;
instr: Instr;
instr: Instruction;
params: Uint8Array;
};
export type ComputerState = {
@ -67,235 +21,132 @@ export class Computer {
private program_counter: u8;
private registers: Uint8Array;
private current_instr: TempInstrState | null;
events: EventHandler<CpuEvent>;
private state_change_callback: (c: ComputerState) => void;
constructor(callback: (c: ComputerState) => void) {
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>();
this.state_change_callback = callback;
// 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.memory[this.program_counter];
if (this.current_instr === null) {
const parsed_instruction = Computer.parse_instruction(current_byte);
const parsed_instruction = ISA.getInstruction(current_byte);
if (parsed_instruction === null) {
// console.log("invalid instruction");
this.events.dispatch(CpuEvent.MemoryByteParsed, {
type: MemoryCellType.InvalidInstruction,
pos: this.program_counter,
});
console.log(`Invalid instruction: ${current_byte.toString(16).toUpperCase().padStart(2, "0")}`);
this.step_forward();
return;
}
const instr_param_count = InstParamCount.get(parsed_instruction);
this.current_instr = {
pos: this.program_counter,
instr: parsed_instruction,
params_found: 0,
params: new Uint8Array(instr_param_count),
params: new Uint8Array(parsed_instruction.params.length),
};
this.events.dispatch(CpuEvent.MemoryByteParsed, {
type: MemoryCellType.Instruction,
pos: this.program_counter,
instr: parsed_instruction,
});
}
if (this.current_instr.pos === this.program_counter) {
if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) {
this.step_forward();
return;
}
if (this.current_instr.params.length !== this.current_instr.params_found) {
let a;
const b = this.current_instr.instr.params[this.current_instr.params_found];
if (b instanceof ConstParam) {
a = MemoryCellType.Constant;
} else if (b instanceof RegisParam) {
a = MemoryCellType.Register;
} else if (b instanceof MemorParam) {
a = MemoryCellType.Memory;
}
this.events.dispatch(CpuEvent.MemoryByteParsed, { type: a, pos: this.program_counter, param: b });
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();
return;
}
}
const execution_post_action_state = {
should_step: true,
noStep: function (): void {
this.should_step = false;
},
event: this.events.dispatch.bind(this.events),
};
this.current_instr.instr.execute(this, this.current_instr.params, execution_post_action_state);
if (this.current_instr.params.length !== this.current_instr.params_found) {
this.step_forward();
return;
}
const should_step = this.execute_instruction(this.current_instr);
this.current_instr = null;
if (should_step) {
if (execution_post_action_state.should_step) {
this.step_forward();
} else {
this.state_change_callback(this.get_state());
}
}
private execute_instruction(inst: TempInstrState): boolean {
const instr_param_count = InstParamCount.get(inst.instr);
const current_pram_count = inst.params.length;
if (instr_param_count !== current_pram_count) {
throw new Error(
`Tried executing instruction #${inst.instr} without proper parameters. (has ${current_pram_count}, needs ${instr_param_count})`
);
}
switch (inst.instr) {
case Instr.Goto: {
const [parameter] = inst.params;
// console.log(`Goto ${parameter}`);
this.program_counter = parameter;
return false;
}
case Instr.GotoIfLowBit: {
const [mem_address, register_no] = inst.params;
if (this.registers[register_no] % 2 === 1) {
this.program_counter = mem_address;
return false;
}
return true;
}
case Instr.AssignRegister: {
const [register_no, new_value] = inst.params;
if (register_no >= this.registers.length) {
throw new Error(`Got register number ${register_no} in assign register`);
}
// console.log(`Set register ${register_no} to ${new_value}`);
this.registers[register_no] = new_value;
break;
}
case Instr.LoadToRegister: {
const [register_no, mem_address] = inst.params;
this.registers[register_no] = this.memory[this.registers[mem_address]];
break;
}
case Instr.WriteToMem: {
const [register_no, mem_address] = inst.params;
this.memory[mem_address] = this.memory[this.registers[mem_address]];
break;
}
case Instr.Add: {
const [register_1, register_2] = inst.params;
this.registers[register_1] += this.registers[register_2];
break;
}
case Instr.And: {
const [register_no_1, register_no_2] = inst.params;
this.registers[register_no_1] &= this.registers[register_no_2];
break;
}
case Instr.Or: {
const [register_no_1, register_no_2] = inst.params;
this.registers[register_no_1] |= this.registers[register_no_2];
break;
}
case Instr.Not: {
const [register_no_1] = inst.params;
this.registers[register_no_1] = ~this.registers[register_no_1];
break;
}
case Instr.LeftBitShift: {
const [register_no_1, register_no_2] = inst.params;
this.registers[register_no_1] <<= this.registers[register_no_2];
break;
}
case Instr.RightBitShift: {
const [register_no_1, register_no_2] = inst.params;
this.registers[register_no_1] >>= this.registers[register_no_2];
break;
}
case Instr.Equals: {
const [register_out, register_no_1, register_no_2] = inst.params;
if (this.registers[register_no_1] === this.registers[register_no_2]) {
this.registers[register_out] = 0x01;
} else {
this.registers[register_out] = 0x00;
}
break;
}
case Instr.LessThan: {
const [register_out, register_no_1, register_no_2] = inst.params;
if (this.registers[register_no_1] < this.registers[register_no_2]) {
this.registers[register_out] = 0x01;
}
break;
}
case Instr.GreaterThan: {
const [register_out, register_no_1, register_no_2] = inst.params;
if (this.registers[register_no_1] > this.registers[register_no_2]) {
this.registers[register_out] = 0x01;
}
break;
}
case Instr.Print: {
const [register_no] = inst.params;
const value = this.registers[register_no];
$("printout").textContent += this.registers[register_no].toString(10);
break;
}
case Instr.PrintASCII: {
const [register_num] = inst.params;
const ASCIIbyte = this.registers[register_num];
const char = String.fromCharCode(ASCIIbyte);
// console.log(char);
$("printout").textContent += char;
break;
}
case Instr.HaltCatchFire: {
throw new Error("FIRE FIRE FIRE FIRE");
}
case Instr.CopyRegReg: {
const [register_no_to, register_no_from] = inst.params;
this.registers[register_no_to] = this.registers[register_no_from];
break;
}
default:
break;
}
return true;
getMemory(address: u8): u8 {
return this.memory[address];
}
setMemory(address: u8, value: u8): void {
this.events.dispatch(CpuEvent.MemoryChanged, { address, value });
this.memory[address] = value;
}
load_program(program: Array<u8>): void {
getRegister(register_no: u8): u8 {
return this.registers[register_no];
}
setRegister(register_no: u8, 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;
}
reset(): void {
this.memory = new Uint8Array(256);
this.registers = new Uint8Array(8);
this.current_instr = null;
this.program_counter = 0;
this.events.dispatch(CpuEvent.Reset, null);
}
load_memory(program: Array<u8>): void {
const max_loop = Math.min(this.memory.length, program.length);
for (let i = 0; i < max_loop; i++) {
this.memory[i] = program[i];
this.events.dispatch(CpuEvent.MemoryChanged, { address: i, value: program[i] });
}
this.program_counter = 0;
this.state_change_callback(this.get_state());
}
private step_forward(): void {
this.program_counter = (this.program_counter + 1) % 256;
this.state_change_callback(this.get_state());
}
get_state(): ComputerState {
return {
memory: this.memory,
program_counter: this.program_counter,
registers: this.registers,
current_instruction: this.current_instr,
};
}
static parse_instruction(byte: u8): null | Instr {
if (byte === 0x00) return Instr.NoOp;
if (byte === 0x10) return Instr.Goto;
if (byte === 0x11) return Instr.GotoIfLowBit;
if (byte === 0x20) return Instr.LoadToRegister;
if (byte === 0x21) return Instr.WriteToMem;
if (byte === 0x2f) return Instr.AssignRegister;
if (byte === 0x28) return Instr.CopyRegReg;
if (byte === 0x40) return Instr.Add;
if (byte === 0x48) return Instr.And;
if (byte === 0x49) return Instr.Or;
if (byte === 0x4a) return Instr.Not;
if (byte === 0x4b) return Instr.LeftBitShift;
if (byte === 0x4c) return Instr.RightBitShift;
if (byte === 0x50) return Instr.Equals;
if (byte === 0x51) return Instr.LessThan;
if (byte === 0x51) return Instr.GreaterThan;
if (byte === 0xff) return Instr.Print;
if (byte === 0xfe) return Instr.PrintASCII;
if (byte === 0x66) return Instr.HaltCatchFire;
return null;
this.events.dispatch(CpuEvent.ProgramCounterChanged, { counter: this.program_counter });
}
}

View file

@ -1,7 +1,9 @@
// The u8 type represents an unsigned 8bit integer: byte. It does not add any safety, other than as a hint to the programmer.
export type u8 = number;
// Jquery lite
export const $ = (s: string): HTMLElement => document.getElementById(s) as HTMLElement;
export function el(type: string, id?: string): HTMLElement {
const element = document.createElement(type);
if (id === undefined) {

49
src/eventHandler.ts Normal file
View file

@ -0,0 +1,49 @@
export class Event<T> {
identifier: T;
callbacks: Array<(event_data: unknown) => void>;
constructor(identifier: T) {
this.identifier = identifier;
this.callbacks = [];
}
}
export class EventHandler<T> {
events: Array<Event<T>>;
private sealed: boolean;
constructor() {
this.sealed = false;
this.events = [];
}
seal(): void {
if (this.sealed) {
throw new Error("Already Sealed");
}
this.sealed = true;
}
register_event(identifier: T): void {
if (this.sealed) {
throw new Error("Can't add event to sealed event handler");
}
const event = new Event<T>(identifier);
this.events.push(event);
}
dispatch(identifier: T, event_data: unknown): void {
const event = this.events.find((e) => e.identifier === identifier);
if (event === undefined) {
throw new Error("Event not found");
}
for (const callback of event.callbacks) {
callback(event_data);
}
}
add_listener(identifier: T, callback: (event_data: unknown) => void): void {
if (!this.sealed) throw new Error("Event handler must be sealed before adding listener");
const event = this.events.find((e) => e.identifier === identifier);
if (event === undefined) {
throw new Error("No event found given identifier");
}
event.callbacks.push(callback);
}
}

16
src/events.ts Normal file
View file

@ -0,0 +1,16 @@
export enum CpuEvent {
MemoryChanged,
RegisterChanged,
MemoryByteParsed,
ProgramCounterChanged,
Print,
Reset,
}
export enum MemoryCellType {
Instruction,
InvalidInstruction,
Register,
Memory,
Constant,
}

View file

@ -1,25 +1,27 @@
import { Computer } from "./computer";
import { $ } from "./etc";
import { ISA } from "./instructionSet";
import { generate_isa } from "./isaGenerator";
import { UI } from "./ui";
function main(): void {
// const program = [0x2f, 0x01, 0x01, 0x40, 0x00, 0x01, 0x21, 0x00, 0x02, 0x10, 0x00];
const program = [0x2f, 0x00, 0x49, 0xfe, 0x00, 0x10, 0x03];
const program = [0x2f, 0x00, 0x41, 0xfe, 0x00, 0x30, 0x00, 0x10, 0x03];
const container = document.getElementById("container");
const container = $("container");
if (container === null) {
throw new Error("no");
}
const computer = new Computer();
const ui = new UI(container);
const computer = new Computer(ui.state_update_event.bind(ui));
computer.load_program(program);
const ui = new UI(container, computer.events);
computer.load_memory(program);
ui.set_step_func(computer.cycle.bind(computer));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>window).comp = computer;
$("ISA").textContent = generate_isa(ISA);
// eslint-disable-next-line prefer-arrow-callback
$("binary_upload").addEventListener("change", function (e) {
if (e.target === null) {
@ -37,7 +39,8 @@ function main(): void {
const view = new Uint8Array(data);
const array = [...view];
ui.stop_auto();
computer.load_program(array);
computer.reset();
computer.load_memory(array);
} else {
console.log("not array");
}

173
src/instructionSet.ts Normal file
View file

@ -0,0 +1,173 @@
import { CpuEvent } from "./events";
import { u8 } from "./etc";
class ParameterType {
readonly description: string;
constructor(description: string) {
this.description = description;
}
}
export class ConstParam extends ParameterType {}
export class RegisParam extends ParameterType {}
export class MemorParam extends ParameterType {}
interface GenericComputer {
getMemory: (address: u8) => u8;
setMemory: (address: u8, value: u8) => void;
setProgramCounter: (address: u8) => void;
getProgramCounter: () => u8;
getRegister: (number: u8) => u8;
setRegister: (number: u8, value: u8) => void;
}
interface AfterExecutionComputerAction {
// Does not step forward the program counter
noStep: () => void;
event: (e: CpuEvent, data: unknown) => void;
}
export interface Instruction {
readonly name: string;
readonly desc: string;
readonly params: Array<ParameterType>;
execute: (computer_reference: GenericComputer, parameters: Uint8Array, a: AfterExecutionComputerAction) => void;
}
export class InstructionSet {
instructions: Map<u8, Instruction>;
constructor() {
this.instructions = new Map();
}
insertInstruction(hexCode: u8, instruction: Instruction): void {
if (this.instructions.has(hexCode)) {
throw new Error(`Instruction "${hexCode.toString(16)}" already exists`);
}
this.instructions.set(hexCode, instruction);
}
getInstruction(hexCode: u8): Instruction | null {
return this.instructions.get(hexCode) ?? null;
}
}
export const ISA = new InstructionSet();
ISA.insertInstruction(0x00, {
name: "NoOp",
desc: "No operation; do nothing",
params: [],
execute: () => {},
});
ISA.insertInstruction(0x10, {
name: "Goto",
desc: "Moves the CPU instruction counter to the value in (P1)",
params: [new ConstParam("new instruction counter location")],
execute: (c, p, a) => {
const new_address = p[0];
c.setProgramCounter(new_address);
a.noStep();
},
});
ISA.insertInstruction(0x20, {
name: "LoadToRegister",
desc: "Sets the byte in register (P1) to be the contents of memory cell (P2)",
params: [new RegisParam(""), new MemorParam("")],
execute(c, p) {
const [register_no, mem_address] = p;
c.setRegister(register_no, c.getMemory(mem_address));
},
});
ISA.insertInstruction(0x21, {
name: "SaveToMemory",
desc: "Writes the byte in register (P1) to the processing memory location (P2)",
params: [new RegisParam(""), new MemorParam("")],
execute(c, p) {
const [register_no, mem_address] = p;
c.setMemory(mem_address, c.getRegister(register_no));
},
});
ISA.insertInstruction(0x2f, {
name: "AssignRegister",
desc: "Assigns constant value (P2) to register (P1)",
params: [new RegisParam(""), new ConstParam("")],
execute(c, p) {
const [register_no, value] = p;
c.setRegister(register_no, value);
},
});
ISA.insertInstruction(0x11, {
name: "GotoIfLowBit",
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")],
execute(c, p, a) {
const [new_address, check_register_no] = p;
if (c.getRegister(check_register_no) % 2 === 1) {
c.setProgramCounter(new_address);
a.noStep();
}
},
});
ISA.insertInstruction(0x30, {
name: "Increment",
desc: "Increments the value within register (P1) by 1",
params: [new RegisParam("register to be incremented")],
execute(c, p) {
const register_no = p[0];
const current_value = c.getRegister(register_no);
c.setRegister(register_no, current_value + 1);
},
});
ISA.insertInstruction(0x31, {
name: "Decrement",
desc: "Decrements the value within register (P1) by 1",
params: [new RegisParam("register to be decremented")],
execute(c, p) {
const register_no = p[0];
const current_value = c.getRegister(register_no);
c.setRegister(register_no, current_value - 1);
},
});
ISA.insertInstruction(0x40, {
name: "Add",
desc: "Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256)",
params: [new RegisParam(""), new RegisParam("")],
execute(c, p) {
const [register_no_1, register_no_2] = p;
const new_value = (c.getRegister(register_no_1) + c.getRegister(register_no_2)) % 256;
c.setRegister(register_no_1, new_value);
},
});
ISA.insertInstruction(0x50, {
name: "Equals",
desc: "If byte in register (P1) equals byte in register (P2), set byte in register (P3) to 0x01",
params: [new RegisParam(""), new RegisParam(""), new RegisParam("")],
execute(c, 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;
c.setRegister(register_no_3, truth);
},
});
ISA.insertInstruction(0xfe, {
name: "PrintASCII",
desc: "Prints the ASCII byte in register (P1) to console",
params: [new RegisParam("")],
execute(c, p, a) {
const register_no = p[0];
const asciiByte = c.getRegister(register_no);
const char = String.fromCharCode(asciiByte);
a.event(CpuEvent.Print, { data: char });
},
});

23
src/isaGenerator.ts Normal file
View file

@ -0,0 +1,23 @@
import { u8 } from "./etc";
import { Instruction, InstructionSet } from "./instructionSet";
export function generate_isa(iset: InstructionSet): string {
const instructions: Array<[u8, Instruction]> = [];
for (const kv of iset.instructions.entries()) {
instructions.push(kv);
}
let output_string = "INSTRUCTIONS\n";
let max_instr_name_len = 0;
for (const instruction of instructions) {
const short_description = instruction[1].name;
max_instr_name_len = Math.max(max_instr_name_len, short_description.length);
}
for (const instruction of instructions) {
const hex_code = instruction[0].toString(16).toUpperCase().padStart(2, "0");
const short_description = instruction[1].name.padEnd(max_instr_name_len, " ");
const parameter_count = instruction[1].params.length;
const description = instruction[1].desc;
output_string += `0x${hex_code}: ${short_description} - ${parameter_count} Parameter - ${description}\n`;
}
return output_string;
}

114
src/ui.ts
View file

@ -1,5 +1,7 @@
import { ComputerState } from "./computer";
import { $, el } from "./etc";
import { CpuEvent, MemoryCellType } from "./events";
import { $, el, u8 } from "./etc";
import { EventHandler } from "./eventHandler";
export class UI {
container: HTMLElement;
@ -8,15 +10,18 @@ export class UI {
registers: HTMLElement;
register_cells: Array<HTMLElement>;
step_func: null | (() => void);
auto_running: boolean;
constructor(parent: HTMLElement) {
this.container = parent;
program_counter: u8;
auto_running: boolean;
constructor(parent: HTMLElement, cpu_events: EventHandler<CpuEvent>) {
this.container = parent;
this.program_counter = 0;
const program_mem = $("memory");
this.program_memory_cells = [];
for (let i = 0; i < 256; i++) {
const mem_cell = el("div", `p_${i}`);
mem_cell.textContent = "0x00";
mem_cell.textContent = "00";
program_mem.appendChild(mem_cell);
this.program_memory_cells.push(mem_cell);
}
@ -28,43 +33,12 @@ export class UI {
for (let i = 0; i < 8; i++) {
const reg_cell = el("div", `R_${i}`);
reg_cell.textContent = "00";
// reg_cell.setAttribute("contenteditable", "true");
// reg_cell.setAttribute("spellcheck", "false");
registers.appendChild(reg_cell);
this.register_cells.push(reg_cell);
}
// // eslint-disable-next-line prefer-arrow-callback
// registers.addEventListener("input", function (e) {
// const allowed_chars = "0123456789ABCDEFG";
// const r = e.target as HTMLElement;
// let data = (r.textContent as string).toUpperCase();
// for (let i = 0; i < data.length; i++) {
// if (!allowed_chars.includes(data[i])) {
// data = "00";
// break;
// }
// }
// e.preventDefault();
// return false;
// });
// registers.addEventListener("keydown", (e) => {
// if (e.key === "Enter") {
// e.preventDefault();
// (e.target as HTMLElement).blur();
// }
// });
// registers.addEventListener("blur", (e) => {
// const allowed_chars = "0123456789ABCDEFG";
// const r = e.target as HTMLElement;
// const data = (r.textContent as string).toUpperCase();
// });
this.registers = registers;
// this.container.append(registers, program_mem);
this.step_func = null;
this.auto_running = false;
const pp_button = $("pause_play_button");
@ -89,6 +63,60 @@ export class UI {
}
this.step_func();
});
cpu_events.add_listener(CpuEvent.MemoryChanged, (e) => {
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");
});
cpu_events.add_listener(CpuEvent.ProgramCounterChanged, (e) => {
const { counter } = e as { counter: u8 };
this.program_memory_cells[this.program_counter].classList.remove("program_counter");
this.program_memory_cells[counter].classList.add("program_counter");
this.program_counter = counter;
});
cpu_events.add_listener(CpuEvent.Print, (e) => {
const { data } = e as { data: u8 };
const printout = $("printout");
if (printout === null) {
throw new Error("Couldn't get printout");
}
printout.textContent = (printout.textContent ?? "") + data;
});
cpu_events.add_listener(CpuEvent.Reset, () => {
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;
});
const map: Map<MemoryCellType, string> = new Map();
map.set(MemoryCellType.Constant, "constant");
map.set(MemoryCellType.Instruction, "instruction");
map.set(MemoryCellType.InvalidInstruction, "invalid_instruction");
map.set(MemoryCellType.Memory, "memory");
map.set(MemoryCellType.Register, "register");
cpu_events.add_listener(CpuEvent.MemoryByteParsed, (e) => {
const { type, pos } = e as { type: MemoryCellType; pos: u8 };
const css_class = map.get(type);
if (css_class === undefined) {
throw new Error("Something went wrong");
}
for (const other_class of map.values()) {
if (other_class === css_class) continue;
this.program_memory_cells[pos].classList.remove(other_class);
}
this.program_memory_cells[pos].classList.add(css_class);
});
}
start_auto(speed: number = 200): void {
@ -109,6 +137,7 @@ export class UI {
}
this.step_func();
setTimeout(loop, speed);
// requestAnimationFrame(loop);
};
loop();
}
@ -122,12 +151,6 @@ export class UI {
}
state_update_event(state: ComputerState): void {
for (let i = 0; i < 256; i++) {
const current = this.program_memory_cells[i];
current.className = "";
current.textContent = state.memory[i].toString(16).toUpperCase().padStart(2, "0");
}
this.program_memory_cells[state.program_counter].classList.add("program_counter");
const current_instr = state.current_instruction;
if (current_instr !== null) {
this.program_memory_cells[current_instr.pos].classList.add("current_instruction");
@ -136,12 +159,5 @@ export class UI {
this.program_memory_cells[offset].classList.add("instruction_argument");
}
}
for (let i = 0; i < state.registers.length; i++) {
const new_text = state.registers[i].toString(16).toUpperCase().padStart(2, "0");
const old = this.register_cells[i].textContent;
if (new_text !== old) {
this.register_cells[i].textContent = new_text;
}
}
}
}

View file

@ -8,7 +8,27 @@
grid-gap: 5px;
padding: 10px;
border: 5px solid yellow;
color: lightgray;
/* color: lightgray; */
}
pre {
font-size: 0.5em;
}
#memory .instruction {
color: greenyellow;
}
#memory .constant {
color: purple;
}
#memory .register {
color: orange;
}
#memory .memory {
color: pink;
}
#memory .invalid_instruction {
color: maroon;
}
#memory div {
@ -50,9 +70,10 @@ body {
}
#title {
writing-mode: sideways-lr;
writing-mode: vertical-lr;
text-align: left;
user-select: none;
text-orientation: mixed;
transform: scale(-1, -1);
}
#printout {