diff --git a/.eslintrc.json b/.eslintrc.json index 80e2dcb..ddd250f 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -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", diff --git a/.gitignore b/.gitignore index b81d599..bc930a8 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules -out/ -dist/ \ No newline at end of file +dist/ +makefile \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json index ae3f68c..fd23213 100755 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -4,6 +4,6 @@ "semi": true, "endOfLine": "lf", "singleQuote": false, - "printWidth": 100 + "printWidth": 120 } \ No newline at end of file diff --git a/ISA.txt b/ISA.txt index 110c5e6..b4669a7 100644 --- a/ISA.txt +++ b/ISA.txt @@ -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) diff --git a/TODO b/TODO index a3a2fce..7ee5542 100644 --- a/TODO +++ b/TODO @@ -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 \ No newline at end of file diff --git a/index.html b/index.html index e0ae901..a51a87e 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + Document @@ -26,13 +26,8 @@ - + +

 	
 
diff --git a/src/computer.ts b/src/computer.ts
index b914509..a71f1de 100644
--- a/src/computer.ts
+++ b/src/computer.ts
@@ -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;
 
-	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();
 
-		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): 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): 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 });
 	}
 }
diff --git a/src/etc.ts b/src/etc.ts
index bf98aae..04243c1 100644
--- a/src/etc.ts
+++ b/src/etc.ts
@@ -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) {
diff --git a/src/eventHandler.ts b/src/eventHandler.ts
new file mode 100644
index 0000000..6ddead8
--- /dev/null
+++ b/src/eventHandler.ts
@@ -0,0 +1,49 @@
+export class Event {
+	identifier: T;
+	callbacks: Array<(event_data: unknown) => void>;
+	constructor(identifier: T) {
+		this.identifier = identifier;
+		this.callbacks = [];
+	}
+}
+
+export class EventHandler {
+	events: Array>;
+	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(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);
+	}
+}
diff --git a/src/events.ts b/src/events.ts
new file mode 100644
index 0000000..1df9206
--- /dev/null
+++ b/src/events.ts
@@ -0,0 +1,16 @@
+export enum CpuEvent {
+	MemoryChanged,
+	RegisterChanged,
+	MemoryByteParsed,
+	ProgramCounterChanged,
+	Print,
+	Reset,
+}
+
+export enum MemoryCellType {
+	Instruction,
+	InvalidInstruction,
+	Register,
+	Memory,
+	Constant,
+}
diff --git a/src/index.ts b/src/index.ts
index e176d35..bde19a1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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
 	(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");
 				}
diff --git a/src/instructionSet.ts b/src/instructionSet.ts
new file mode 100644
index 0000000..4c44927
--- /dev/null
+++ b/src/instructionSet.ts
@@ -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;
+	execute: (computer_reference: GenericComputer, parameters: Uint8Array, a: AfterExecutionComputerAction) => void;
+}
+
+export class InstructionSet {
+	instructions: Map;
+
+	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 });
+	},
+});
diff --git a/src/isaGenerator.ts b/src/isaGenerator.ts
new file mode 100644
index 0000000..0538ac6
--- /dev/null
+++ b/src/isaGenerator.ts
@@ -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;
+}
diff --git a/src/ui.ts b/src/ui.ts
index 29c174e..4d6b844 100644
--- a/src/ui.ts
+++ b/src/ui.ts
@@ -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;
 	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) {
+		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 = 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;
-			}
-		}
 	}
 }
diff --git a/style.css b/style.css
index e737286..7d12fd3 100644
--- a/style.css
+++ b/style.css
@@ -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 {