various assorted tweaks, and type system tweaks

This commit is contained in:
Alexander Bass 2024-02-22 00:29:29 -05:00
parent 0764614b66
commit 6bf0c9917a
16 changed files with 268 additions and 353 deletions

View file

@ -15,6 +15,7 @@
}, },
"scripts": { "scripts": {
"build": "webpack --mode=production", "build": "webpack --mode=production",
"build-dev": "webpack --mode=development",
"watch": "webpack --mode=development --watch" "watch": "webpack --mode=development --watch"
} }
} }

View file

@ -1,8 +1,7 @@
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events"; import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { byte_array_to_js_source, format_hex } from "./etc"; import { byte_array_to_js_source, format_hex } from "./etc";
import { EventHandler } from "./eventHandler";
import { Instruction, ISA } from "./instructionSet"; import { Instruction, ISA } from "./instructionSet";
import { m256, u8 } from "./num"; import { m256, u1, u3, u8 } from "./num";
export type TempInstrState = { export type TempInstrState = {
pos: u8; pos: u8;
@ -11,14 +10,25 @@ export type TempInstrState = {
params: Array<u8>; params: Array<u8>;
}; };
// It would be a shame not to use the `Uint8Array` type JS provides to store memory and other byte arrays.
// Unfortunately Typescript defines indexing a `Uint8Array` to return a generic `number`, not the constrained `u8`.
// This redefines it.
declare global {
interface Uint8Array {
[key: number]: u8;
}
}
const bank_count = 1; // 1 additional bank: video memory
export class Computer { export class Computer {
private memory = new Array<u8>(256); private memory: Uint8Array = new Uint8Array(256 + 256 * bank_count);
private registers = new Array<u8>(256); private registers: Uint8Array = new Uint8Array(8);
private call_stack: Array<u8> = []; private call_stack: Array<u8> = [];
private program_counter: u8 = 0; private program_counter: u8 = 0;
private bank: u8 = 0; private bank: u1 = 0;
private current_instr: TempInstrState | null = null; private current_instr: TempInstrState | null = null;
events: CpuEventHandler = new EventHandler<CpuEvent>() as CpuEventHandler; events: CpuEventHandler = new CpuEventHandler();
constructor() { constructor() {
// Add events // Add events
@ -29,7 +39,8 @@ export class Computer {
} }
cycle(): void { cycle(): void {
const current_byte = this.memory[this.program_counter]; const current_byte = this.getMemory(this.program_counter, 0);
if (this.current_instr === null) { if (this.current_instr === null) {
const parsed_instruction = ISA.getInstruction(current_byte); const parsed_instruction = ISA.getInstruction(current_byte);
if (parsed_instruction === null) { if (parsed_instruction === null) {
@ -39,7 +50,7 @@ export class Computer {
}); });
console.log(`Invalid instruction: ${format_hex(current_byte)}`); console.log(`Invalid instruction: ${format_hex(current_byte)}`);
this.step_forward(); this.step_forward();
this.events.dispatch(CpuEvent.ClockCycle, null); this.events.dispatch(CpuEvent.Cycle);
return; return;
} }
this.current_instr = { this.current_instr = {
@ -57,7 +68,7 @@ export class Computer {
if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) { if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) {
this.step_forward(); this.step_forward();
this.events.dispatch(CpuEvent.ClockCycle, null); this.events.dispatch(CpuEvent.Cycle);
return; return;
} }
@ -73,7 +84,7 @@ export class Computer {
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) {
this.step_forward(); this.step_forward();
this.events.dispatch(CpuEvent.ClockCycle, null); this.events.dispatch(CpuEvent.Cycle);
return; return;
} }
} }
@ -91,21 +102,28 @@ export class Computer {
if (execution_post_action_state.should_step) { if (execution_post_action_state.should_step) {
this.step_forward(); this.step_forward();
} }
this.events.dispatch(CpuEvent.ClockCycle, null); this.events.dispatch(CpuEvent.Cycle);
} }
getMemory(address: u8): u8 { getMemory(address: u8, bank_override?: u1): u8 {
return this.memory[address]; if (bank_override !== undefined) {
const value = this.memory[address + 256 * bank_override] as u8;
return value;
}
const value = this.memory[address + 256 * this.bank] as u8;
return value;
} }
setMemory(address: u8, value: u8): void { setMemory(address: u8, value: u8): void {
this.events.dispatch(CpuEvent.MemoryChanged, { address, value }); this.events.dispatch(CpuEvent.MemoryChanged, { address, value });
this.memory[address] = value; this.memory[address + 256 * bank_count] = value;
} }
getRegister(register_no: u8): u8 { getRegister(register_no: u3): u8 {
return this.registers[register_no]; return this.registers[register_no] as u8;
} }
setRegister(register_no: u8, value: u8): void {
setRegister(register_no: u3, value: u8): void {
this.events.dispatch(CpuEvent.RegisterChanged, { register_no, value }); this.events.dispatch(CpuEvent.RegisterChanged, { register_no, value });
this.registers[register_no] = value; this.registers[register_no] = value;
} }
@ -113,6 +131,7 @@ export class Computer {
getProgramCounter(): u8 { getProgramCounter(): u8 {
return this.program_counter; return this.program_counter;
} }
setProgramCounter(new_value: u8): void { setProgramCounter(new_value: u8): void {
this.events.dispatch(CpuEvent.ProgramCounterChanged, { counter: new_value }); this.events.dispatch(CpuEvent.ProgramCounterChanged, { counter: new_value });
this.program_counter = new_value; this.program_counter = new_value;
@ -128,22 +147,22 @@ export class Computer {
return this.call_stack.pop() ?? null; return this.call_stack.pop() ?? null;
} }
setBank(bank_no: u8): void { setBank(bank_no: u1): void {
this.bank = bank_no; this.bank = bank_no;
} }
reset(): void { reset(): void {
this.events.dispatch(CpuEvent.Reset, null); this.events.dispatch(CpuEvent.Reset);
this.memory = new Array<u8>(256); this.memory = new Uint8Array(256);
this.registers = new Array<u8>(8); this.registers = new Uint8Array(8);
this.call_stack = []; this.call_stack = [];
this.current_instr = null; this.current_instr = null;
this.program_counter = 0; this.program_counter = 0;
} }
init_events(ui: UiEventHandler): void { init_events(ui: UiEventHandler): void {
ui.listen(UiEvent.RequestCpuCycle, (n) => { ui.listen(UiEvent.RequestCpuCycle, (cycle_count) => {
for (let i = 0; i < n; i++) this.cycle(); for (let i = 0; i < cycle_count; i++) this.cycle();
}); });
ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value)); ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
} }
@ -161,7 +180,7 @@ export class Computer {
this.program_counter = 0; this.program_counter = 0;
} }
dump_memory(): Array<u8> { dump_memory(): Uint8Array {
return this.memory; return this.memory;
} }

View file

@ -1,24 +1,38 @@
/**
* @file Assorted small functions to be used throughout this program.
* @copyright Alexander Bass 2024
* @license GPL-3.0
*/
import { u8 } from "./num"; import { u8 } from "./num";
// The u8 type represents an unsigned 8bit integer: byte. It does not add any safety, other than as a hint to the programmer. /**
* Alias to `document.getElementById(id)`. Jquery lite.
// export type u8 = number; * @param id id of element to be located in DOM
*/
// Jquery lite export const $ = (id: string): HTMLElement => document.getElementById(id) as HTMLElement;
export const $ = (s: string): HTMLElement => document.getElementById(s) as HTMLElement;
export const format_hex = (n: u8): string => n.toString(16).toUpperCase().padStart(2, "0"); export const format_hex = (n: u8): string => n.toString(16).toUpperCase().padStart(2, "0");
export const byte_array_to_js_source = (a: Array<u8>): string => { /**
* Converts array of bytes to a JavaScript syntax array of hexadecimal literals
* @param bytes
*/
export const byte_array_to_js_source = (bytes: Array<u8>): string => {
let str = "["; let str = "[";
for (const b of a) { for (const b of bytes) {
str += `0x${format_hex(b)},`; str += `0x${format_hex(b)},`;
} }
str += "]"; str += "]";
return str; return str;
}; };
export function el(type: string, id?: string): HTMLElement { /**
* Create an html element
* @param type
* @param id id attribute to set
*/
export function el<E extends keyof HTMLElementTagNameMap>(type: E, id?: string): HTMLElementTagNameMap[E];
export function el(type: string, id?: string): HTMLElement | undefined {
const element = document.createElement(type); const element = document.createElement(type);
if (id === undefined) { if (id === undefined) {
return element; return element;

View file

@ -1,3 +1,9 @@
/**
* @file Generic Event handler similar to the DOM event handlers
* @copyright Alexander Bass 2024
* @license GPL-3.0
*/
export class Event<T> { export class Event<T> {
identifier: T; identifier: T;
callbacks: Array<(event_data: unknown) => void> = []; callbacks: Array<(event_data: unknown) => void> = [];

View file

@ -1,7 +1,15 @@
/**
* @file Specific definitions of the event handlers (CPU & UI) used within this program
* @copyright Alexander Bass 2024
* @license GPL-3.0
*/
import { EventHandler } from "./eventHandler"; import { EventHandler } from "./eventHandler";
import { Instruction, ParameterType } from "./instructionSet"; import { Instruction, ParameterType } from "./instructionSet";
import { u8 } from "./num.js"; import { u3, u8 } from "./num";
//
// CPU Event Handler Definition
//
export enum CpuEvent { export enum CpuEvent {
MemoryChanged, MemoryChanged,
RegisterChanged, RegisterChanged,
@ -10,20 +18,25 @@ export enum CpuEvent {
ParameterParsed, ParameterParsed,
InvalidParsed, InvalidParsed,
InstructionExecuted, InstructionExecuted,
ClockCycle, Cycle,
Print, Print,
Reset, Reset,
Halt, Halt,
ClockStarted,
ClockStopped,
} }
// Handily explained in https://www.cgjennings.ca/articles/typescript-events/ type VoidDataCpuEventList =
| CpuEvent.Halt
| CpuEvent.Reset
| CpuEvent.Cycle
| CpuEvent.ClockStarted
| CpuEvent.ClockStopped;
interface CpuEventMap { interface CpuEventMap {
[CpuEvent.MemoryChanged]: { address: u8; value: u8 }; [CpuEvent.MemoryChanged]: { address: u8; value: u8 };
[CpuEvent.RegisterChanged]: { register_no: u8; value: u8 }; [CpuEvent.RegisterChanged]: { register_no: u3; value: u8 };
[CpuEvent.ProgramCounterChanged]: { counter: u8 }; [CpuEvent.ProgramCounterChanged]: { counter: u8 };
[CpuEvent.Halt]: null;
[CpuEvent.Reset]: null;
[CpuEvent.ClockCycle]: null;
[CpuEvent.InstructionParsed]: { pos: u8; code: u8; instr: Instruction }; [CpuEvent.InstructionParsed]: { pos: u8; code: u8; instr: Instruction };
[CpuEvent.ParameterParsed]: { pos: u8; code: u8; param: ParameterType }; [CpuEvent.ParameterParsed]: { pos: u8; code: u8; param: ParameterType };
[CpuEvent.InvalidParsed]: { pos: u8; code: u8 }; [CpuEvent.InvalidParsed]: { pos: u8; code: u8 };
@ -32,10 +45,22 @@ interface CpuEventMap {
} }
export interface CpuEventHandler extends EventHandler<CpuEvent> { export interface CpuEventHandler extends EventHandler<CpuEvent> {
listen<E extends VoidDataCpuEventList>(type: E, listener: () => void): void;
dispatch<E extends VoidDataCpuEventList>(type: E): void;
listen<E extends keyof CpuEventMap>(type: E, listener: (ev: CpuEventMap[E]) => void): void; listen<E extends keyof CpuEventMap>(type: E, listener: (ev: CpuEventMap[E]) => void): void;
dispatch<E extends keyof CpuEventMap>(type: E, data: CpuEventMap[E]): void; dispatch<E extends keyof CpuEventMap>(type: E, data: CpuEventMap[E]): void;
} }
interface CpuEventHandlerConstructor {
new (): CpuEventHandler;
}
export const CpuEventHandler = EventHandler<CpuEvent> as CpuEventHandlerConstructor;
//
// Ui Event Handler Definition
//
export enum UiEvent { export enum UiEvent {
RequestCpuCycle, RequestCpuCycle,
RequestMemoryChange, RequestMemoryChange,
@ -50,3 +75,9 @@ export interface UiEventHandler extends EventHandler<UiEvent> {
listen<E extends keyof UiEventMap>(type: E, listener: (ev: UiEventMap[E]) => void): void; listen<E extends keyof UiEventMap>(type: E, listener: (ev: UiEventMap[E]) => void): void;
dispatch<E extends keyof UiEventMap>(type: E, data: UiEventMap[E]): void; dispatch<E extends keyof UiEventMap>(type: E, data: UiEventMap[E]): void;
} }
interface UiEventHandlerConstructor {
new (): UiEventHandler;
}
export const UiEventHandler = EventHandler<UiEvent> as UiEventHandlerConstructor;

View file

@ -1,3 +1,8 @@
/**
* @file Virtual 8-Bit Computer
* @copyright Alexander Bass 2024
* @license GPL-3.0
*/
import { Computer } from "./computer"; import { Computer } from "./computer";
import { $ } from "./etc"; import { $ } from "./etc";
import { ISA } from "./instructionSet"; import { ISA } from "./instructionSet";
@ -7,6 +12,13 @@ import { u8 } from "./num";
import "./style.scss"; import "./style.scss";
declare global {
interface Window {
comp: Computer;
ui: UI;
}
}
function main(): void { function main(): void {
const program: Array<u8> = [ const program: Array<u8> = [
0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03, 0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03,
@ -48,20 +60,23 @@ function main(): void {
ui.init_events(computer.events); ui.init_events(computer.events);
computer.load_memory(program); computer.load_memory(program);
computer.init_events(ui.events); computer.init_events(ui.events);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>window).comp = computer; window.comp = computer;
// eslint-disable-next-line @typescript-eslint/no-explicit-any window.ui = ui;
(<any>window).ui = ui;
$("ISA").textContent = generate_isa(ISA); $("ISA").textContent = generate_isa(ISA);
$("binary_upload").addEventListener("change", (e) => { $("binary_upload").addEventListener("change", (e) => {
if (e.target === null) { const t = e.target;
if (t === null) {
return; return;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any const file: File | undefined = (t as HTMLInputElement).files?.[0];
const file: File = (<any>e.target).files[0]; if (file === undefined) {
console.log("No files attribute on file input");
return;
}
const reader = new FileReader(); const reader = new FileReader();
console.log(file); console.log(file);
reader.addEventListener("load", (e) => { reader.addEventListener("load", (e) => {
@ -70,7 +85,6 @@ function main(): void {
if (data instanceof ArrayBuffer) { if (data instanceof ArrayBuffer) {
const view = new Uint8Array(data); const view = new Uint8Array(data);
const array = [...view] as Array<u8>; const array = [...view] as Array<u8>;
ui.stop_auto();
computer.reset(); computer.reset();
computer.load_memory(array); computer.load_memory(array);
} else { } else {
@ -83,8 +97,7 @@ function main(): void {
$("save_button").addEventListener("click", () => { $("save_button").addEventListener("click", () => {
const memory = computer.dump_memory(); const memory = computer.dump_memory();
const buffer = new Uint8Array(memory); const blob = new Blob([memory], { type: "application/octet-stream" });
const blob = new Blob([buffer], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const link = document.createElement("a"); const link = document.createElement("a");

View file

@ -1,3 +1,8 @@
/**
* @file CPU instruction definitions & type definitions for parameters and instructions
* @copyright Alexander Bass 2024
* @license GPL-3.0
*/
import { CpuEvent, CpuEventHandler } from "./events"; import { CpuEvent, CpuEventHandler } from "./events";
import { format_hex } from "./etc"; import { format_hex } from "./etc";
import { isU3, m256, u1, u3, u8 } from "./num"; import { isU3, m256, u1, u3, u8 } from "./num";
@ -77,6 +82,7 @@ export class InstructionSet {
} }
getInstruction(hexCode: u8): Instruction | null { getInstruction(hexCode: u8): Instruction | null {
// console.log(format_hex(hexCode));
return this.instructions.get(hexCode) ?? null; return this.instructions.get(hexCode) ?? null;
} }
} }
@ -111,8 +117,9 @@ ISA.insertInstruction(0x20, {
const [register_no, register_2] = p; const [register_no, register_2] = p;
if (!isU3(register_no)) throw new Error("TODO"); if (!isU3(register_no)) throw new Error("TODO");
if (!isU3(register_2)) throw new Error("TODO"); if (!isU3(register_2)) throw new Error("TODO");
const mem_value = c.getMemory(c.getRegister(register_2));
c.setRegister(register_no, c.getMemory(c.getRegister(register_2))); c.setRegister(register_no, mem_value);
}, },
}); });
@ -273,7 +280,7 @@ ISA.insertInstruction(0x66, {
desc: "Stops program execu..... Fire! FIRE EVERYWHERE!", desc: "Stops program execu..... Fire! FIRE EVERYWHERE!",
params: [], params: [],
execute(c, p, a) { execute(c, p, a) {
a.dispatch(CpuEvent.Halt, null); a.dispatch(CpuEvent.Halt);
}, },
}); });

View file

@ -1,3 +1,8 @@
/**
* @file Automatic generation of instruction set description
* @copyright Alexander Bass 2024
* @license GPL-3.0
*/
import { format_hex } from "./etc"; import { format_hex } from "./etc";
import { Instruction, InstructionSet } from "./instructionSet"; import { Instruction, InstructionSet } from "./instructionSet";
import { u8 } from "./num.js"; import { u8 } from "./num.js";

View file

@ -1,260 +1,27 @@
/**
* @file Constrained integer types and validation functions
* @copyright Alexander Bass 2024
* @license GPL-3.0
*/
// prettier-ignore
export type u8 = export type u8 =
| 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
| 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31
| 2 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47
| 3 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63
| 4 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79
| 5 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95
| 6 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111
| 7 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127
| 8 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143
| 9 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159
| 10 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175
| 11 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191
| 12 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207
| 13 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223
| 14 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239
| 15 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255;
| 16
| 17
| 18
| 19
| 20
| 21
| 22
| 23
| 24
| 25
| 26
| 27
| 28
| 29
| 30
| 31
| 32
| 33
| 34
| 35
| 36
| 37
| 38
| 39
| 40
| 41
| 42
| 43
| 44
| 45
| 46
| 47
| 48
| 49
| 50
| 51
| 52
| 53
| 54
| 55
| 56
| 57
| 58
| 59
| 60
| 61
| 62
| 63
| 64
| 65
| 66
| 67
| 68
| 69
| 70
| 71
| 72
| 73
| 74
| 75
| 76
| 77
| 78
| 79
| 80
| 81
| 82
| 83
| 84
| 85
| 86
| 87
| 88
| 89
| 90
| 91
| 92
| 93
| 94
| 95
| 96
| 97
| 98
| 99
| 100
| 101
| 102
| 103
| 104
| 105
| 106
| 107
| 108
| 109
| 110
| 111
| 112
| 113
| 114
| 115
| 116
| 117
| 118
| 119
| 120
| 121
| 122
| 123
| 124
| 125
| 126
| 127
| 128
| 129
| 130
| 131
| 132
| 133
| 134
| 135
| 136
| 137
| 138
| 139
| 140
| 141
| 142
| 143
| 144
| 145
| 146
| 147
| 148
| 149
| 150
| 151
| 152
| 153
| 154
| 155
| 156
| 157
| 158
| 159
| 160
| 161
| 162
| 163
| 164
| 165
| 166
| 167
| 168
| 169
| 170
| 171
| 172
| 173
| 174
| 175
| 176
| 177
| 178
| 179
| 180
| 181
| 182
| 183
| 184
| 185
| 186
| 187
| 188
| 189
| 190
| 191
| 192
| 193
| 194
| 195
| 196
| 197
| 198
| 199
| 200
| 201
| 202
| 203
| 204
| 205
| 206
| 207
| 208
| 209
| 210
| 211
| 212
| 213
| 214
| 215
| 216
| 217
| 218
| 219
| 220
| 221
| 222
| 223
| 224
| 225
| 226
| 227
| 228
| 229
| 230
| 231
| 232
| 233
| 234
| 235
| 236
| 237
| 238
| 239
| 240
| 241
| 242
| 243
| 244
| 245
| 246
| 247
| 248
| 249
| 250
| 251
| 252
| 253
| 254
| 255;
export type u1 = 0 | 1; export type u1 = 0 | 1;
export type u2 = 0 | 1 | 2 | 3; export type u2 = 0 | 1 | 2 | 3;

View file

@ -1,26 +1,25 @@
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events"; import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { $, el, format_hex } from "./etc"; import { $, el, format_hex } from "./etc";
import { EventHandler } from "./eventHandler";
import { InstructionExplainer } from "./ui/instructionExplainer"; import { InstructionExplainer } from "./ui/instructionExplainer";
import { MemoryView } from "./ui/memoryView"; import { MemoryView } from "./ui/memoryView";
import { ParamType } from "./instructionSet";
import { frequencyIndicator } from "./ui/frequencyIndicator"; import { frequencyIndicator } from "./ui/frequencyIndicator";
import { RegisterView } from "./ui/registerView";
// Certainly the messiest portion of this program // Certainly the messiest portion of this program
// Needs to be broken into components // Needs to be broken into components
// Breaking up into components has started but has yet to conclude
let delay = 100; let delay = 100;
export class UI { export class UI {
registers: HTMLElement;
printout: HTMLElement; printout: HTMLElement;
register_cells: Array<HTMLElement> = [];
auto_running: boolean; auto_running: boolean;
events: UiEventHandler = new EventHandler<UiEvent>() as UiEventHandler; events: UiEventHandler = new UiEventHandler();
frequencyIndicator: frequencyIndicator; frequencyIndicator: frequencyIndicator;
memory: MemoryView; memory: MemoryView;
registers: RegisterView;
instruction_explainer: InstructionExplainer; instruction_explainer: InstructionExplainer;
constructor() { constructor() {
@ -29,23 +28,13 @@ export class UI {
} }
this.events.seal(); this.events.seal();
this.memory = new MemoryView($("memory")); this.memory = new MemoryView($("memory"), this.events);
this.frequencyIndicator = new frequencyIndicator($("cycles")); this.frequencyIndicator = new frequencyIndicator($("cycles"), this.events);
this.instruction_explainer = new InstructionExplainer($("instruction_explainer"), this.events);
this.instruction_explainer = new InstructionExplainer($("instruction_explainer")); this.registers = new RegisterView($("registers"), this.events);
this.printout = $("printout"); this.printout = $("printout");
const registers = $("registers");
for (let i = 0; i < 8; i++) {
const reg_cell = el("div", `r_${i}`);
reg_cell.textContent = "00";
registers.appendChild(reg_cell);
this.register_cells.push(reg_cell);
}
this.registers = registers;
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) {
@ -75,13 +64,14 @@ export class UI {
} }
init_events(cpu_events: CpuEventHandler): void { init_events(cpu_events: CpuEventHandler): void {
cpu_events.listen(CpuEvent.RegisterChanged, ({ register_no, value }) => {
this.register_cells[register_no].textContent = format_hex(value);
});
cpu_events.listen(CpuEvent.Print, (char) => { cpu_events.listen(CpuEvent.Print, (char) => {
this.printout.textContent = (this.printout.textContent ?? "") + char; this.printout.textContent = (this.printout.textContent ?? "") + char;
}); });
cpu_events.listen(CpuEvent.Reset, () => {
this.reset();
});
this.registers.init_cpu_events(cpu_events);
this.frequencyIndicator.init_cpu_events(cpu_events); this.frequencyIndicator.init_cpu_events(cpu_events);
this.memory.init_cpu_events(cpu_events); this.memory.init_cpu_events(cpu_events);
this.instruction_explainer.init_cpu_events(cpu_events); this.instruction_explainer.init_cpu_events(cpu_events);
@ -89,9 +79,7 @@ export class UI {
reset(): void { reset(): void {
this.stop_auto(); this.stop_auto();
this.register_cells.forEach((r) => { this.registers.reset();
r.textContent = "00";
});
this.frequencyIndicator.reset(); this.frequencyIndicator.reset();
this.instruction_explainer.reset(); this.instruction_explainer.reset();
this.memory.reset(); this.memory.reset();

View file

@ -7,8 +7,10 @@ export class frequencyIndicator implements UiComponent {
private count: number = 0; private count: number = 0;
private last_value: number = 0; private last_value: number = 0;
private last_time: number = 0; private last_time: number = 0;
constructor(element: HTMLElement) { events: UiEventHandler;
constructor(element: HTMLElement, e: UiEventHandler) {
this.element = element; this.element = element;
this.events = e;
this.start(); this.start();
} }
@ -39,9 +41,6 @@ export class frequencyIndicator implements UiComponent {
this.count = 0; this.count = 0;
} }
init_events(eh: UiEventHandler): void {
this;
}
clock_cycle(): void { clock_cycle(): void {
this.count += 1; this.count += 1;
} }
@ -51,7 +50,7 @@ export class frequencyIndicator implements UiComponent {
this.last_value = 0; this.last_value = 0;
} }
init_cpu_events(c: CpuEventHandler): void { init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.ClockCycle, () => { c.listen(CpuEvent.Cycle, () => {
this.count += 1; this.count += 1;
}); });
} }

View file

@ -6,8 +6,10 @@ import { UiComponent } from "./uiComponent";
export class InstructionExplainer implements UiComponent { export class InstructionExplainer implements UiComponent {
element: HTMLElement; element: HTMLElement;
constructor(element: HTMLElement) { events: UiEventHandler;
constructor(element: HTMLElement, e: UiEventHandler) {
this.element = element; this.element = element;
this.events = e;
} }
add_instruction(instr: Instruction, pos: u8, byte: u8): void { add_instruction(instr: Instruction, pos: u8, byte: u8): void {
this.reset(); this.reset();
@ -47,7 +49,6 @@ export class InstructionExplainer implements UiComponent {
this.add_box(format_hex(byte), "Invalid Instruction", "invalid"); this.add_box(format_hex(byte), "Invalid Instruction", "invalid");
} }
init_events(eh: UiEventHandler): void {}
init_cpu_events(c: CpuEventHandler): void { init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.ParameterParsed, ({ param, code, pos }) => { c.listen(CpuEvent.ParameterParsed, ({ param, code, pos }) => {
this.add_param(param, pos, code); this.add_param(param, pos, code);

View file

@ -5,15 +5,17 @@ import { u8 } from "../num.js";
import { UiComponent } from "./uiComponent"; import { UiComponent } from "./uiComponent";
type MemoryCell = { type MemoryCell = {
el: HTMLElement; el: HTMLDivElement;
}; };
export class MemoryView implements UiComponent { export class MemoryView implements UiComponent {
element: HTMLElement; element: HTMLElement;
cells: Array<MemoryCell> = []; cells: Array<MemoryCell> = [];
program_counter: number = 0; program_counter: number = 0;
constructor(element: HTMLElement) { events: UiEventHandler;
constructor(element: HTMLElement, e: UiEventHandler) {
this.element = element; this.element = element;
this.events = e;
for (let i = 0; i < 256; i++) { for (let i = 0; i < 256; i++) {
const mem_cell_el = el("div"); const mem_cell_el = el("div");
mem_cell_el.textContent = "00"; mem_cell_el.textContent = "00";
@ -58,20 +60,13 @@ export class MemoryView implements UiComponent {
} }
reset(): void { reset(): void {
this.element.innerHTML = "";
for (let i = 0; i < 256; i++) { for (let i = 0; i < 256; i++) {
const mem_cell_el = el("div"); this.cells[i].el.textContent = "00";
mem_cell_el.textContent = "00"; this.cells[i].el.className = "";
this.element.appendChild(mem_cell_el);
const mem_cell = { el: mem_cell_el };
this.cells.push(mem_cell);
} }
this.set_program_counter(0); this.set_program_counter(0);
} }
init_events(eh: UiEventHandler): void {
this;
}
init_cpu_events(c: CpuEventHandler): void { init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.MemoryChanged, ({ address, value }) => { c.listen(CpuEvent.MemoryChanged, ({ address, value }) => {
this.set_cell_value(address, value); this.set_cell_value(address, value);

68
src/ui/registerView.ts Normal file
View file

@ -0,0 +1,68 @@
import { el, format_hex } from "../etc";
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
import { u8 } from "../num.js";
import { UiComponent } from "./uiComponent";
type MemoryCell = {
el: HTMLDivElement;
};
const REGISTER_COUNT = 8;
export class RegisterView implements UiComponent {
element: HTMLElement;
cells: Array<MemoryCell> = [];
program_counter: number = 0;
events: UiEventHandler;
constructor(element: HTMLElement, e: UiEventHandler) {
this.element = element;
this.events = e;
for (let i = 0; i < REGISTER_COUNT; i++) {
const mem_cell_el = el("div");
mem_cell_el.textContent = "00";
element.appendChild(mem_cell_el);
const mem_cell = { el: mem_cell_el, tags: [] };
this.cells.push(mem_cell);
}
}
add_cell_class(address: u8, ...css_class: string[]): void {
for (const str of css_class) {
this.cells[address].el.classList.add(str);
}
}
remove_cell_class(address: u8, ...css_class: string[]): void {
for (const str of css_class) {
this.cells[address].el.classList.remove(str);
}
}
remove_all_cell_class(css_class: string): void {
for (const cell of this.cells) {
cell.el.classList.remove(css_class);
}
}
add_cell_class_exclusive(address: u8, css_class: string): void {
this.remove_all_cell_class(css_class);
this.add_cell_class(address, css_class);
}
set_cell_value(address: u8, value: u8): void {
this.cells[address].el.textContent = format_hex(value);
}
reset(): void {
for (let i = 0; i < REGISTER_COUNT; i++) {
this.cells[i].el.textContent = "00";
this.cells[i].el.className = "";
}
}
init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.RegisterChanged, ({ register_no, value }) => {
this.set_cell_value(register_no, value);
});
}
}

View file

@ -2,7 +2,8 @@ import { CpuEventHandler, UiEventHandler } from "../events";
export interface UiComponent { export interface UiComponent {
element: HTMLElement; element: HTMLElement;
events: UiEventHandler;
reset: () => void; reset: () => void;
init_events: (ui: UiEventHandler) => void; // init_events: (ui: UiEventHandler) => void;
init_cpu_events: (c: CpuEventHandler) => void; init_cpu_events: (c: CpuEventHandler) => void;
} }

View file

@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"lib": [ "lib": [
"esnext", "es2021",
"dom", "dom",
"DOM.Iterable" "DOM.Iterable"
], ],