Compare commits
2 commits
0764614b66
...
f290b836cf
Author | SHA1 | Date | |
---|---|---|---|
|
f290b836cf | ||
|
6bf0c9917a |
15
TODO
15
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
|
||||
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
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
</div>
|
||||
<span id="cycles"></span>
|
||||
</div>
|
||||
<canvas id="screen"></canvas>
|
||||
<pre id="ISA"></pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"build": "webpack --mode=production",
|
||||
"build-dev": "webpack --mode=development",
|
||||
"watch": "webpack --mode=development --watch"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
34
src/etc.ts
34
src/etc.ts
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
59
src/index.ts
59
src/index.ts
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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";
|
||||
|
|
279
src/num.ts
279
src/num.ts
|
@ -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;
|
||||
|
|
|
@ -142,6 +142,10 @@ body {
|
|||
.current_instruction {
|
||||
outline: 3px dashed var(--color);
|
||||
}
|
||||
|
||||
div.last_access {
|
||||
color: orange;
|
||||
}
|
||||
.invalid {
|
||||
&::after {
|
||||
user-select: none;
|
||||
|
|
69
src/ui.ts
69
src/ui.ts
|
@ -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
64
src/ui/celledViewer.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
17
src/ui/registerView.ts
Normal 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
65
src/ui/screen.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"esnext",
|
||||
"es2021",
|
||||
"dom",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue