Compare commits

...

2 commits

Author SHA1 Message Date
Alexander Bass f290b836cf add screen and memory banking 2024-02-23 01:37:12 -05:00
Alexander Bass 6bf0c9917a various assorted tweaks, and type system tweaks 2024-02-22 00:29:29 -05:00
22 changed files with 471 additions and 425 deletions

15
TODO
View file

@ -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
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

View file

@ -27,6 +27,7 @@
</div>
<span id="cycles"></span>
</div>
<canvas id="screen"></canvas>
<pre id="ISA"></pre>
</body>
</html>

View file

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

View file

@ -1,8 +1,7 @@
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { byte_array_to_js_source, format_hex } from "./etc";
import { EventHandler } from "./eventHandler";
import { Instruction, ISA } from "./instructionSet";
import { m256, u8 } from "./num";
import { m256, u1, u3, u8 } from "./num";
export type TempInstrState = {
pos: u8;
@ -12,13 +11,14 @@ export type TempInstrState = {
};
export class Computer {
private memory = new Array<u8>(256);
private registers = new Array<u8>(256);
private memory: Uint8Array = new Uint8Array(256);
private vram: Uint8Array = new Uint8Array(256);
private registers: Uint8Array = new Uint8Array(8);
private call_stack: Array<u8> = [];
private program_counter: u8 = 0;
private bank: u8 = 0;
private bank: u1 = 0;
private current_instr: TempInstrState | null = null;
events: CpuEventHandler = new EventHandler<CpuEvent>() as CpuEventHandler;
events: CpuEventHandler = new CpuEventHandler();
constructor() {
// Add events
@ -29,7 +29,8 @@ export class Computer {
}
cycle(): void {
const current_byte = this.memory[this.program_counter];
const current_byte = this.getMemorySilent(this.program_counter, 0);
if (this.current_instr === null) {
const parsed_instruction = ISA.getInstruction(current_byte);
if (parsed_instruction === null) {
@ -39,7 +40,7 @@ export class Computer {
});
console.log(`Invalid instruction: ${format_hex(current_byte)}`);
this.step_forward();
this.events.dispatch(CpuEvent.ClockCycle, null);
this.events.dispatch(CpuEvent.Cycle);
return;
}
this.current_instr = {
@ -57,7 +58,7 @@ export class Computer {
if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) {
this.step_forward();
this.events.dispatch(CpuEvent.ClockCycle, null);
this.events.dispatch(CpuEvent.Cycle);
return;
}
@ -73,7 +74,7 @@ export class Computer {
this.current_instr.params_found += 1;
if (this.current_instr.params.length !== this.current_instr.params_found) {
this.step_forward();
this.events.dispatch(CpuEvent.ClockCycle, null);
this.events.dispatch(CpuEvent.Cycle);
return;
}
}
@ -91,21 +92,44 @@ export class Computer {
if (execution_post_action_state.should_step) {
this.step_forward();
}
this.events.dispatch(CpuEvent.ClockCycle, null);
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): u8 {
return this.memory[address];
getMemory(address: u8, bank_override?: u1): 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] = 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: u8): u8 {
return this.registers[register_no];
getRegister(register_no: u3): u8 {
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.registers[register_no] = value;
}
@ -113,6 +137,7 @@ export class Computer {
getProgramCounter(): u8 {
return this.program_counter;
}
setProgramCounter(new_value: u8): void {
this.events.dispatch(CpuEvent.ProgramCounterChanged, { counter: new_value });
this.program_counter = new_value;
@ -128,22 +153,23 @@ export class Computer {
return this.call_stack.pop() ?? null;
}
setBank(bank_no: u8): void {
setBank(bank_no: u1): void {
this.events.dispatch(CpuEvent.SwitchBank, { bank: bank_no });
this.bank = bank_no;
}
reset(): void {
this.events.dispatch(CpuEvent.Reset, null);
this.memory = new Array<u8>(256);
this.registers = new Array<u8>(8);
this.events.dispatch(CpuEvent.Reset);
this.memory = new Uint8Array(256);
this.registers = new Uint8Array(8);
this.call_stack = [];
this.current_instr = null;
this.program_counter = 0;
}
init_events(ui: UiEventHandler): void {
ui.listen(UiEvent.RequestCpuCycle, (n) => {
for (let i = 0; i < n; i++) this.cycle();
ui.listen(UiEvent.RequestCpuCycle, (cycle_count) => {
for (let i = 0; i < cycle_count; i++) this.cycle();
});
ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
}
@ -156,12 +182,12 @@ 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;
}
dump_memory(): Array<u8> {
dump_memory(): Uint8Array {
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";
// 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;
/**
* Alias to `document.getElementById(id)`. Jquery lite.
* @param id id of element to be located in DOM
*/
export const $ = (id: string): HTMLElement => document.getElementById(id) as HTMLElement;
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 = "[";
for (const b of a) {
for (const b of bytes) {
str += `0x${format_hex(b)},`;
}
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);
if (id === undefined) {
return element;
@ -26,3 +40,5 @@ export function el(type: string, id?: string): HTMLElement {
element.id = id;
return element;
}
export type NonEmptyArray<T> = T[] & { 0: T };

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> {
identifier: T;
callbacks: Array<(event_data: unknown) => void> = [];
@ -36,6 +42,17 @@ export class EventHandler<T> {
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);

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 { Instruction, ParameterType } from "./instructionSet";
import { u8 } from "./num.js";
import { u1, u3, u8 } from "./num";
//
// CPU Event Handler Definition
//
export enum CpuEvent {
MemoryChanged,
RegisterChanged,
@ -10,32 +18,50 @@ export enum CpuEvent {
ParameterParsed,
InvalidParsed,
InstructionExecuted,
ClockCycle,
Cycle,
Print,
Reset,
Halt,
// ClockStarted,
// ClockStopped,
MemoryAccessed,
SwitchBank,
}
// Handily explained in https://www.cgjennings.ca/articles/typescript-events/
type VoidDataCpuEventList = CpuEvent.Halt | CpuEvent.Reset | CpuEvent.Cycle;
// | CpuEvent.ClockStarted
// | CpuEvent.ClockStopped;
interface CpuEventMap {
[CpuEvent.MemoryChanged]: { address: u8; value: u8 };
[CpuEvent.RegisterChanged]: { register_no: 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.Halt]: null;
[CpuEvent.Reset]: null;
[CpuEvent.ClockCycle]: null;
[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;
}
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;
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 {
RequestCpuCycle,
RequestMemoryChange,
@ -50,3 +76,9 @@ export interface UiEventHandler extends EventHandler<UiEvent> {
listen<E extends keyof UiEventMap>(type: E, listener: (ev: UiEventMap[E]) => void): void;
dispatch<E extends keyof UiEventMap>(type: E, data: UiEventMap[E]): void;
}
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 { $ } from "./etc";
import { ISA } from "./instructionSet";
@ -6,11 +11,36 @@ import { UI } from "./ui";
import { u8 } from "./num";
import "./style.scss";
import { CpuEvent } from "./events";
declare global {
interface Window {
comp: Computer;
ui: UI;
}
}
function main(): void {
// const program: Array<u8> = [
// 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<u8> = [
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,
@ -48,20 +78,23 @@ function main(): void {
ui.init_events(computer.events);
computer.load_memory(program);
computer.init_events(ui.events);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>window).comp = computer;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>window).ui = ui;
window.comp = computer;
window.ui = ui;
$("ISA").textContent = generate_isa(ISA);
$("binary_upload").addEventListener("change", (e) => {
if (e.target === null) {
const t = e.target;
if (t === null) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const file: File = (<any>e.target).files[0];
const file: File | undefined = (t as HTMLInputElement).files?.[0];
if (file === undefined) {
console.log("No files attribute on file input");
return;
}
const reader = new FileReader();
console.log(file);
reader.addEventListener("load", (e) => {
@ -70,7 +103,6 @@ function main(): void {
if (data instanceof ArrayBuffer) {
const view = new Uint8Array(data);
const array = [...view] as Array<u8>;
ui.stop_auto();
computer.reset();
computer.load_memory(array);
} else {
@ -81,10 +113,13 @@ 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 buffer = new Uint8Array(memory);
const blob = new Blob([buffer], { type: "application/octet-stream" });
const blob = new Blob([memory], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
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 { format_hex } from "./etc";
import { isU3, m256, u1, u3, u8 } from "./num";
@ -77,6 +82,7 @@ export class InstructionSet {
}
getInstruction(hexCode: u8): Instruction | null {
// console.log(format_hex(hexCode));
return this.instructions.get(hexCode) ?? null;
}
}
@ -111,8 +117,9 @@ ISA.insertInstruction(0x20, {
const [register_no, register_2] = p;
if (!isU3(register_no)) 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,13 +280,13 @@ ISA.insertInstruction(0x66, {
desc: "Stops program execu..... Fire! FIRE EVERYWHERE!",
params: [],
execute(c, p, a) {
a.dispatch(CpuEvent.Halt, null);
a.dispatch(CpuEvent.Halt);
},
});
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();
@ -295,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();
@ -312,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);
},
});

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 { Instruction, InstructionSet } from "./instructionSet";
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 =
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 13
| 14
| 15
| 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;
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
| 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 u2 = 0 | 1 | 2 | 3;

View file

@ -142,6 +142,10 @@ body {
.current_instruction {
outline: 3px dashed var(--color);
}
div.last_access {
color: orange;
}
.invalid {
&::after {
user-select: none;

View file

@ -1,27 +1,25 @@
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { $, el, format_hex } from "./etc";
import { EventHandler } from "./eventHandler";
import { InstructionExplainer } from "./ui/instructionExplainer";
import { MemoryView } from "./ui/memoryView";
import { ParamType } from "./instructionSet";
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
let delay = 100;
export class UI {
registers: HTMLElement;
printout: HTMLElement;
register_cells: Array<HTMLElement> = [];
auto_running: boolean;
events: UiEventHandler = new EventHandler<UiEvent>() as UiEventHandler;
events: UiEventHandler = new UiEventHandler();
frequencyIndicator: frequencyIndicator;
memory: MemoryView;
instruction_explainer: InstructionExplainer;
private components: Array<UiComponent>;
constructor() {
for (const [, e_type] of Object.entries(UiEvent)) {
@ -29,28 +27,18 @@ export class UI {
}
this.events.seal();
this.memory = new MemoryView($("memory"));
this.frequencyIndicator = new frequencyIndicator($("cycles"));
this.instruction_explainer = new InstructionExplainer($("instruction_explainer"));
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");
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;
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();
@ -60,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();
}
@ -73,28 +61,33 @@ 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.RegisterChanged, ({ register_no, value }) => {
this.register_cells[register_no].textContent = format_hex(value);
});
cpu_events.listen(CpuEvent.Print, (char) => {
this.printout.textContent = (this.printout.textContent ?? "") + char;
});
cpu_events.listen(CpuEvent.Reset, () => {
this.reset();
});
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.register_cells.forEach((r) => {
r.textContent = "00";
});
this.frequencyIndicator.reset();
this.instruction_explainer.reset();
this.memory.reset();
for (const c of this.components) {
c.reset();
}
this.printout.textContent = "";
}

64
src/ui/celledViewer.ts Normal file
View file

@ -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<GenericCell> = [];
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<string>): void {
for (const str of css_class) {
this.cells[address].el.classList.add(str);
}
}
remove_cell_class(address: u8, ...css_class: NonEmptyArray<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);
}
}

View file

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

View file

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

View file

@ -1,79 +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: HTMLElement;
el: HTMLDivElement;
};
export class MemoryView implements UiComponent {
element: HTMLElement;
cells: Array<MemoryCell> = [];
program_counter: number = 0;
constructor(element: HTMLElement) {
this.element = element;
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);
export class MemoryView extends CelledViewer implements UiComponent {
program_counter: u8 = 0;
last_accessed_cell: u8 | null = null;
events: UiEventHandler;
constructor(element: HTMLElement, e: UiEventHandler) {
super(16, 16, element);
this.program_counter = 0;
this.events = e;
}
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 {
this.element.innerHTML = "";
for (let i = 0; i < 256; 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);
}
super.reset();
this.last_accessed_cell = null;
this.set_program_counter(0);
}
init_events(eh: UiEventHandler): void {
this;
}
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 }) => {
@ -98,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 }) => {

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

@ -0,0 +1,17 @@
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
import { CelledViewer } from "./celledViewer";
import { UiComponent } from "./uiComponent";
export class RegisterView extends CelledViewer implements UiComponent {
events: UiEventHandler;
constructor(element: HTMLElement, e: UiEventHandler) {
super(8, 1, element);
this.events = e;
}
init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.RegisterChanged, ({ register_no, value }) => {
this.set_cell_value(register_no, value);
});
}
}

65
src/ui/screen.ts Normal file
View file

@ -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);
}
}

View file

@ -1,8 +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;
}

BIN
test.bin Normal file

Binary file not shown.

View file

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