From f290b836cf6631a8b782dc9d157ec8e6fe1616ec Mon Sep 17 00:00:00 2001 From: Alexander Bass Date: Fri, 23 Feb 2024 01:37:12 -0500 Subject: [PATCH] add screen and memory banking --- TODO | 15 +++++++-- index.html | 1 + src/computer.ts | 49 +++++++++++++++------------ src/etc.ts | 2 ++ src/eventHandler.ts | 11 ++++++ src/events.ts | 21 ++++++------ src/index.ts | 26 ++++++++++++-- src/instructionSet.ts | 12 +++++-- src/style.scss | 4 +++ src/ui.ts | 45 ++++++++++++++----------- src/ui/celledViewer.ts | 64 +++++++++++++++++++++++++++++++++++ src/ui/memoryView.ts | 75 ++++++++++++++--------------------------- src/ui/registerView.ts | 57 ++----------------------------- src/ui/screen.ts | 65 +++++++++++++++++++++++++++++++++++ src/ui/uiComponent.ts | 12 ++++++- test.bin | Bin 0 -> 256 bytes 16 files changed, 295 insertions(+), 164 deletions(-) create mode 100644 src/ui/celledViewer.ts create mode 100644 src/ui/screen.ts create mode 100644 test.bin diff --git a/TODO b/TODO index a58b191..e021ef9 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,13 @@ -Add screen (VRAM?) -- Bank switching Live memory and register editing (Probably should pause autostep when it reaches the cell you're modifying) -HCF flames \ No newline at end of file +HCF flames +add hcf +Move start/stop/auto logic into computer +Speed control slider behavior +Speed control slider styling + +Determine how to implement 16 bit integers in code (new instructions?) + +UI for screen (toggling (click an icon?)) +UI for togging other UI elements + +Instruction assign memory based on register diff --git a/index.html b/index.html index 3ce56be..b7e2086 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,7 @@ +

 	
 
diff --git a/src/computer.ts b/src/computer.ts
index d011734..34e8826 100644
--- a/src/computer.ts
+++ b/src/computer.ts
@@ -10,19 +10,9 @@ export type TempInstrState = {
 	params: Array;
 };
 
-// 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 {
-	private memory: Uint8Array = new Uint8Array(256 + 256 * bank_count);
+	private memory: Uint8Array = new Uint8Array(256);
+	private vram: Uint8Array = new Uint8Array(256);
 	private registers: Uint8Array = new Uint8Array(8);
 	private call_stack: Array = [];
 	private program_counter: u8 = 0;
@@ -39,7 +29,7 @@ export class Computer {
 	}
 
 	cycle(): void {
-		const current_byte = this.getMemory(this.program_counter, 0);
+		const current_byte = this.getMemorySilent(this.program_counter, 0);
 
 		if (this.current_instr === null) {
 			const parsed_instruction = ISA.getInstruction(current_byte);
@@ -104,19 +94,35 @@ export class Computer {
 		}
 		this.events.dispatch(CpuEvent.Cycle);
 	}
+	private getMemorySilent(address: u8, bank_override?: u1): u8 {
+		const banks = [this.memory, this.vram];
+
+		const bank = banks[bank_override ?? this.bank];
+		const value = bank[address] as u8;
+
+		return value;
+	}
 
 	getMemory(address: u8, bank_override?: u1): u8 {
-		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;
+		const value = this.getMemorySilent(address, bank_override);
+		this.events.dispatch(CpuEvent.MemoryAccessed, { address, bank: this.bank, value });
 		return value;
 	}
 
 	setMemory(address: u8, value: u8): void {
-		this.events.dispatch(CpuEvent.MemoryChanged, { address, value });
-		this.memory[address + 256 * bank_count] = value;
+		let bank: Uint8Array | undefined;
+		if (this.bank === 0) {
+			bank = this.memory;
+		} else if (this.bank === 1) {
+			bank = this.vram;
+		} else {
+			const _: never = this.bank;
+		}
+		if (bank === undefined) {
+			throw new Error("unreachable");
+		}
+		bank[address] = value;
+		this.events.dispatch(CpuEvent.MemoryChanged, { address, bank: this.bank, value });
 	}
 
 	getRegister(register_no: u3): u8 {
@@ -148,6 +154,7 @@ export class Computer {
 	}
 
 	setBank(bank_no: u1): void {
+		this.events.dispatch(CpuEvent.SwitchBank, { bank: bank_no });
 		this.bank = bank_no;
 	}
 
@@ -175,7 +182,7 @@ export class Computer {
 			if (this.memory[i] === program[i]) continue;
 
 			this.memory[i] = program[i];
-			this.events.dispatch(CpuEvent.MemoryChanged, { address: i as u8, value: program[i] });
+			this.events.dispatch(CpuEvent.MemoryChanged, { address: i as u8, bank: 0, value: program[i] });
 		}
 		this.program_counter = 0;
 	}
diff --git a/src/etc.ts b/src/etc.ts
index d3bc452..4e27ac5 100644
--- a/src/etc.ts
+++ b/src/etc.ts
@@ -40,3 +40,5 @@ export function el(type: string, id?: string): HTMLElement | undefined {
 	element.id = id;
 	return element;
 }
+
+export type NonEmptyArray = T[] & { 0: T };
diff --git a/src/eventHandler.ts b/src/eventHandler.ts
index 2876f48..4db7dbf 100644
--- a/src/eventHandler.ts
+++ b/src/eventHandler.ts
@@ -42,6 +42,17 @@ export class EventHandler {
 			callback(event_data);
 		}
 	}
+
+	/**
+	 * Listens to all events with one listener. Ideally used for debugging
+	 * @param callback called for event called on this event handler
+	 */
+	firehose(callback: (identifier: T, data: unknown) => void): void {
+		this.events.forEach((e) => {
+			const identifier = e.identifier;
+			e.callbacks.push(callback.bind(undefined, identifier));
+		});
+	}
 	listen(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);
diff --git a/src/events.ts b/src/events.ts
index 7b1f737..4858a90 100644
--- a/src/events.ts
+++ b/src/events.ts
@@ -5,7 +5,7 @@
  */
 import { EventHandler } from "./eventHandler";
 import { Instruction, ParameterType } from "./instructionSet";
-import { u3, u8 } from "./num";
+import { u1, u3, u8 } from "./num";
 
 //
 // CPU Event Handler Definition
@@ -22,25 +22,26 @@ export enum CpuEvent {
 	Print,
 	Reset,
 	Halt,
-	ClockStarted,
-	ClockStopped,
+	// ClockStarted,
+	// ClockStopped,
+	MemoryAccessed,
+	SwitchBank,
 }
 
-type VoidDataCpuEventList =
-	| CpuEvent.Halt
-	| CpuEvent.Reset
-	| CpuEvent.Cycle
-	| CpuEvent.ClockStarted
-	| CpuEvent.ClockStopped;
+type VoidDataCpuEventList = CpuEvent.Halt | CpuEvent.Reset | CpuEvent.Cycle;
+// | CpuEvent.ClockStarted
+// | CpuEvent.ClockStopped;
 
 interface CpuEventMap {
-	[CpuEvent.MemoryChanged]: { address: u8; value: u8 };
+	[CpuEvent.MemoryChanged]: { address: u8; bank: u1; value: u8 };
+	[CpuEvent.MemoryAccessed]: { address: u8; bank: u1; value: u8 };
 	[CpuEvent.RegisterChanged]: { register_no: u3; value: u8 };
 	[CpuEvent.ProgramCounterChanged]: { counter: u8 };
 	[CpuEvent.InstructionParsed]: { pos: u8; code: u8; instr: Instruction };
 	[CpuEvent.ParameterParsed]: { pos: u8; code: u8; param: ParameterType };
 	[CpuEvent.InvalidParsed]: { pos: u8; code: u8 };
 	[CpuEvent.InstructionExecuted]: { instr: Instruction };
+	[CpuEvent.SwitchBank]: { bank: u1 };
 	[CpuEvent.Print]: string;
 }
 
diff --git a/src/index.ts b/src/index.ts
index 34fc5c3..4b4ce28 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,6 +11,7 @@ import { UI } from "./ui";
 import { u8 } from "./num";
 
 import "./style.scss";
+import { CpuEvent } from "./events";
 
 declare global {
 	interface Window {
@@ -20,9 +21,26 @@ declare global {
 }
 
 function main(): void {
+	// const program: Array = [
+	// 	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 program: Array = [
-		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,
+		0x2f, 0x00, 0x00, 0x2f, 0x01, 0xff, 0x21, 0x01, 0x0d, 0xb1, 0x01, 0x21, 0x01, 0x00, 0x31, 0x01, 0xb1, 0x00, 0x10,
+		0x06, 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,
@@ -95,6 +113,10 @@ function main(): void {
 		reader.readAsArrayBuffer(file);
 	});
 
+	// computer.events.firehose((ident, data) => {
+	// 	console.log(`New Event: ${CpuEvent[ident]}. data: `, data);
+	// });
+
 	$("save_button").addEventListener("click", () => {
 		const memory = computer.dump_memory();
 		const blob = new Blob([memory], { type: "application/octet-stream" });
diff --git a/src/instructionSet.ts b/src/instructionSet.ts
index 9f926f4..c95e6be 100644
--- a/src/instructionSet.ts
+++ b/src/instructionSet.ts
@@ -286,7 +286,7 @@ ISA.insertInstruction(0x66, {
 
 ISA.insertInstruction(0xa0, {
 	name: "Call",
-	desc: "",
+	desc: "Calls a subroute",
 	params: [new ConstParam("the subroutine at this memory address")],
 	execute(c, p, a) {
 		const current_address = c.getProgramCounter();
@@ -302,7 +302,7 @@ ISA.insertInstruction(0xa0, {
 
 ISA.insertInstruction(0xa1, {
 	name: "Return",
-	desc: "",
+	desc: "returns from a subroutine",
 	params: [],
 	execute(c, p, a) {
 		const new_address = c.popCallStack();
@@ -319,5 +319,11 @@ ISA.insertInstruction(0xb1, {
 	name: "Set bank",
 	desc: "Selects which bank of memory to write and read to",
 	params: [new ConstParam("Bank number")],
-	execute(c, p, a) {},
+	execute(c, p) {
+		const bank_no = p[0];
+		if (!(bank_no === 1 || bank_no === 0)) {
+			throw new Error("TODO");
+		}
+		c.setBank(bank_no);
+	},
 });
diff --git a/src/style.scss b/src/style.scss
index 08758bb..d4301c3 100644
--- a/src/style.scss
+++ b/src/style.scss
@@ -142,6 +142,10 @@ body {
 	.current_instruction {
 		outline: 3px dashed var(--color);
 	}
+
+	div.last_access {
+		color: orange;
+	}
 	.invalid {
 		&::after {
 			user-select: none;
diff --git a/src/ui.ts b/src/ui.ts
index 47ae139..fd80428 100644
--- a/src/ui.ts
+++ b/src/ui.ts
@@ -4,6 +4,8 @@ import { InstructionExplainer } from "./ui/instructionExplainer";
 import { MemoryView } from "./ui/memoryView";
 import { frequencyIndicator } from "./ui/frequencyIndicator";
 import { RegisterView } from "./ui/registerView";
+import { Screen } from "./ui/screen";
+import { UiComponent, UiComponentConstructor } from "./ui/uiComponent.js";
 // Certainly the messiest portion of this program
 // Needs to be broken into components
 // Breaking up into components has started but has yet to conclude
@@ -17,10 +19,7 @@ export class UI {
 
 	events: UiEventHandler = new UiEventHandler();
 
-	frequencyIndicator: frequencyIndicator;
-	memory: MemoryView;
-	registers: RegisterView;
-	instruction_explainer: InstructionExplainer;
+	private components: Array;
 
 	constructor() {
 		for (const [, e_type] of Object.entries(UiEvent)) {
@@ -28,18 +27,18 @@ export class UI {
 		}
 		this.events.seal();
 
-		this.memory = new MemoryView($("memory"), this.events);
-		this.frequencyIndicator = new frequencyIndicator($("cycles"), this.events);
-		this.instruction_explainer = new InstructionExplainer($("instruction_explainer"), this.events);
-		this.registers = new RegisterView($("registers"), this.events);
+		this.components = [];
 
+		this.register_component(MemoryView, $("memory"));
+		this.register_component(frequencyIndicator, $("cycles"));
+		this.register_component(InstructionExplainer, $("instruction_explainer"));
+		this.register_component(RegisterView, $("registers"));
+		this.register_component(Screen, $("screen") as HTMLCanvasElement);
 		this.printout = $("printout");
 
 		this.auto_running = false;
 		const pp_button = $("pause_play_button");
-		if (pp_button === null) {
-			throw new Error("Cant find pause_play button");
-		}
+
 		pp_button.addEventListener("click", () => {
 			if (this.auto_running) {
 				this.stop_auto();
@@ -49,7 +48,7 @@ export class UI {
 				pp_button.textContent = "Storp";
 			}
 		});
-		$("step_button")?.addEventListener("click", () => {
+		$("step_button").addEventListener("click", () => {
 			if (this.auto_running) {
 				this.stop_auto();
 			}
@@ -62,6 +61,14 @@ export class UI {
 			// console.log(delay);
 		});
 	}
+	private register_component(c: UiComponentConstructor, e: HTMLElement): void {
+		if (e === undefined) {
+			console.log(c);
+			throw new Error("Could not find HTML element while registering UI component");
+		}
+		const component = new c(e, this.events);
+		this.components.push(component);
+	}
 
 	init_events(cpu_events: CpuEventHandler): void {
 		cpu_events.listen(CpuEvent.Print, (char) => {
@@ -71,18 +78,16 @@ export class UI {
 			this.reset();
 		});
 
-		this.registers.init_cpu_events(cpu_events);
-		this.frequencyIndicator.init_cpu_events(cpu_events);
-		this.memory.init_cpu_events(cpu_events);
-		this.instruction_explainer.init_cpu_events(cpu_events);
+		for (const c of this.components) {
+			c.init_cpu_events(cpu_events);
+		}
 	}
 
 	reset(): void {
 		this.stop_auto();
-		this.registers.reset();
-		this.frequencyIndicator.reset();
-		this.instruction_explainer.reset();
-		this.memory.reset();
+		for (const c of this.components) {
+			c.reset();
+		}
 		this.printout.textContent = "";
 	}
 
diff --git a/src/ui/celledViewer.ts b/src/ui/celledViewer.ts
new file mode 100644
index 0000000..672ddd4
--- /dev/null
+++ b/src/ui/celledViewer.ts
@@ -0,0 +1,64 @@
+/**
+ * @file Abstract implementation of a grid of (up to) 256 items or less
+ * @copyright Alexander Bass 2024
+ * @license GPL-3.0
+ */
+import { NonEmptyArray, el, format_hex } from "../etc";
+import { u8 } from "../num";
+
+// TODO, make generic
+interface GenericCell {
+	el: HTMLElement;
+}
+
+export abstract class CelledViewer {
+	cells: Array = [];
+	width: number;
+	height: number;
+	element: HTMLElement;
+	constructor(width: number, height: number, element: HTMLElement) {
+		this.element = element;
+		this.width = width;
+		this.height = height;
+		for (let i = 0; i < this.width * this.height; i++) {
+			const mem_cell_el = el("div");
+			mem_cell_el.textContent = "00";
+			this.element.appendChild(mem_cell_el);
+			const mem_cell = { el: mem_cell_el };
+			this.cells.push(mem_cell);
+		}
+	}
+
+	reset(): void {
+		for (let i = 0; i < this.height * this.width; i++) {
+			this.cells[i].el.textContent = "00";
+			this.cells[i].el.className = "";
+		}
+	}
+	add_cell_class(address: u8, ...css_class: NonEmptyArray): void {
+		for (const str of css_class) {
+			this.cells[address].el.classList.add(str);
+		}
+	}
+
+	remove_cell_class(address: u8, ...css_class: NonEmptyArray): 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);
+	}
+}
diff --git a/src/ui/memoryView.ts b/src/ui/memoryView.ts
index 2612eae..0ed57dc 100644
--- a/src/ui/memoryView.ts
+++ b/src/ui/memoryView.ts
@@ -1,74 +1,49 @@
-import { el, format_hex } from "../etc";
 import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
 import { ParamType } from "../instructionSet";
 import { u8 } from "../num.js";
 import { UiComponent } from "./uiComponent";
+import { CelledViewer } from "./celledViewer";
 
 type MemoryCell = {
 	el: HTMLDivElement;
 };
 
-export class MemoryView implements UiComponent {
-	element: HTMLElement;
-	cells: Array = [];
-	program_counter: number = 0;
+export class MemoryView extends CelledViewer implements UiComponent {
+	program_counter: u8 = 0;
+	last_accessed_cell: u8 | null = null;
 	events: UiEventHandler;
 	constructor(element: HTMLElement, e: UiEventHandler) {
-		this.element = element;
+		super(16, 16, element);
+		this.program_counter = 0;
 		this.events = e;
-		for (let i = 0; i < 256; 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);
-		}
-		this.set_program_counter(0);
 	}
 
-	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);
-	}
-
-	set_program_counter(position: number): void {
-		this.cells[this.program_counter].el.classList.remove("program_counter");
-		this.cells[position].el.classList.add("program_counter");
+	set_program_counter(position: u8): void {
+		this.remove_cell_class(this.program_counter, "program_counter");
+		this.add_cell_class(position, "program_counter");
 		this.program_counter = position;
 	}
 
 	reset(): void {
-		for (let i = 0; i < 256; i++) {
-			this.cells[i].el.textContent = "00";
-			this.cells[i].el.className = "";
-		}
+		super.reset();
+		this.last_accessed_cell = null;
 		this.set_program_counter(0);
 	}
 
 	init_cpu_events(c: CpuEventHandler): void {
-		c.listen(CpuEvent.MemoryChanged, ({ address, value }) => {
+		c.listen(CpuEvent.MemoryAccessed, ({ address, value }) => {
+			if (this.last_accessed_cell !== address) {
+				if (this.last_accessed_cell !== null) {
+					this.remove_cell_class(this.last_accessed_cell, "last_access");
+				}
+				this.add_cell_class(address, "last_access");
+				this.last_accessed_cell = address;
+			}
+		});
+		c.listen(CpuEvent.MemoryChanged, ({ address, bank, value }) => {
+			if (bank !== 0) {
+				return;
+			}
 			this.set_cell_value(address, value);
 		});
 		c.listen(CpuEvent.ProgramCounterChanged, ({ counter }) => {
@@ -93,8 +68,8 @@ export class MemoryView implements UiComponent {
 		c.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => {
 			this.remove_all_cell_class("instruction_argument");
 			this.remove_all_cell_class("current_instruction");
-			this.add_cell_class(pos, "current_instruction");
 			this.remove_cell_class(pos, "constant", "register", "memory", "invalid");
+			this.add_cell_class(pos, "current_instruction");
 			this.add_cell_class(pos, "instruction");
 		});
 		c.listen(CpuEvent.InvalidParsed, ({ code, pos }) => {
diff --git a/src/ui/registerView.ts b/src/ui/registerView.ts
index 92a1016..8bd5fa3 100644
--- a/src/ui/registerView.ts
+++ b/src/ui/registerView.ts
@@ -1,63 +1,12 @@
-import { el, format_hex } from "../etc";
 import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
-import { u8 } from "../num.js";
+import { CelledViewer } from "./celledViewer";
 import { UiComponent } from "./uiComponent";
 
-type MemoryCell = {
-	el: HTMLDivElement;
-};
-
-const REGISTER_COUNT = 8;
-
-export class RegisterView implements UiComponent {
-	element: HTMLElement;
-	cells: Array = [];
-	program_counter: number = 0;
+export class RegisterView extends CelledViewer implements UiComponent {
 	events: UiEventHandler;
 	constructor(element: HTMLElement, e: UiEventHandler) {
-		this.element = element;
+		super(8, 1, 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 {
diff --git a/src/ui/screen.ts b/src/ui/screen.ts
new file mode 100644
index 0000000..7e3e8d7
--- /dev/null
+++ b/src/ui/screen.ts
@@ -0,0 +1,65 @@
+import { UiEventHandler, CpuEventHandler, CpuEvent } from "../events";
+import { u4, u8 } from "../num";
+import { UiComponent } from "./uiComponent";
+export class Screen implements UiComponent {
+	element: HTMLCanvasElement;
+	events: UiEventHandler;
+	ctx: CanvasRenderingContext2D;
+	scale: [number, number];
+	constructor(element: HTMLElement, event: UiEventHandler) {
+		this.element = element as HTMLCanvasElement;
+		this.events = event;
+		const canvas_size = [512, 512];
+		const data_size = [16, 16];
+		this.scale = [canvas_size[0] / data_size[0], canvas_size[1] / data_size[1]];
+		[this.element.width, this.element.height] = canvas_size;
+		const ctx = this.element.getContext("2d");
+		if (ctx === null) {
+			throw new Error("todo");
+		}
+		this.ctx = ctx;
+		// for (let x = 0; x < 16; x++) {
+		// 	for (let y = 0; y < 16; y++) {
+		// 		this.setPixel(x as u4, y as u4, (x + 16 * y) as u8);
+		// 	}
+		// }
+	}
+
+	reset(): void {
+		const ctx = this.element.getContext("2d");
+		if (ctx === null) {
+			throw new Error("todo");
+		}
+	}
+
+	init_cpu_events(c: CpuEventHandler): void {
+		c.listen(CpuEvent.MemoryChanged, ({ address, bank, value }) => {
+			if (bank !== 1) return;
+			const x = (address % 16) as u4;
+			const y = Math.floor(address / 16) as u4;
+			this.setPixel(x, y, value);
+		});
+	}
+
+	setPixel(x: u4, y: u4, value: u8): void {
+		const point: [number, number] = [x * this.scale[0], y * this.scale[1]];
+		// const RED_SCALE = 255 / 2 ** 2;
+		// const GREEN_SCALE = 255 / 2 ** 2;
+		// const BLUE_SCALE = 255 / 2 ** 2;
+		// const red = ((value >> 4) & 0b11) * RED_SCALE;
+		// const green = ((value >> 2) & 0b11) * GREEN_SCALE;
+		// const blue = (value & 0b11) * BLUE_SCALE;
+
+		const RED_SCALE = 255 / 2 ** 3;
+		const GREEN_SCALE = 255 / 2 ** 3;
+		const BLUE_SCALE = 255 / 2 ** 2;
+		const red = ((value >> 5) & 0b111) * RED_SCALE;
+		const green = ((value >> 2) & 0b111) * GREEN_SCALE;
+		const blue = (value & 0b11) * BLUE_SCALE;
+
+		const color = `rgb(${red},${green},${blue})`;
+		console.log(x, y, value, color);
+		this.ctx.fillStyle = color;
+		this.ctx.fillRect(...point, ...this.scale);
+	}
+}
diff --git a/src/ui/uiComponent.ts b/src/ui/uiComponent.ts
index 1a3ef80..841bb91 100644
--- a/src/ui/uiComponent.ts
+++ b/src/ui/uiComponent.ts
@@ -1,9 +1,19 @@
+/**
+ * @file Definition of what a UI component is in the context of this program
+ * @copyright Alexander Bass 2024
+ * @license GPL-3.0
+ */
 import { CpuEventHandler, UiEventHandler } from "../events";
 
 export interface UiComponent {
 	element: HTMLElement;
+	/** Allows listening and emitting UiEvent's*/
 	events: UiEventHandler;
 	reset: () => void;
-	// init_events: (ui: UiEventHandler) => void;
+	/**  Allows listening CPUEvent's*/
 	init_cpu_events: (c: CpuEventHandler) => void;
 }
+
+export interface UiComponentConstructor {
+	new (el: HTMLElement, ue: UiEventHandler): UiComponent;
+}
diff --git a/test.bin b/test.bin
new file mode 100644
index 0000000000000000000000000000000000000000..42736ce093f32a615822ee3450bbcd8ad71841a5
GIT binary patch
literal 256
ucmdOAV9;m$ugJ)|kx`M6!H{tyg8&=DFa{o}IXU?X;rT^5DT-V`e*yq%tOue1

literal 0
HcmV?d00001