add screen and memory banking

This commit is contained in:
Alexander Bass 2024-02-23 01:37:12 -05:00
parent 6bf0c9917a
commit f290b836cf
16 changed files with 295 additions and 164 deletions

13
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) 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> </div>
<span id="cycles"></span> <span id="cycles"></span>
</div> </div>
<canvas id="screen"></canvas>
<pre id="ISA"></pre> <pre id="ISA"></pre>
</body> </body>
</html> </html>

View file

@ -10,19 +10,9 @@ export type TempInstrState = {
params: Array<u8>; params: Array<u8>;
}; };
// It would be a shame not to use the `Uint8Array` type JS provides to store memory and other byte arrays.
// Unfortunately Typescript defines indexing a `Uint8Array` to return a generic `number`, not the constrained `u8`.
// This redefines it.
declare global {
interface Uint8Array {
[key: number]: u8;
}
}
const bank_count = 1; // 1 additional bank: video memory
export class Computer { export class Computer {
private memory: Uint8Array = new Uint8Array(256 + 256 * bank_count); private memory: Uint8Array = new Uint8Array(256);
private vram: Uint8Array = new Uint8Array(256);
private registers: Uint8Array = new Uint8Array(8); private registers: Uint8Array = new Uint8Array(8);
private call_stack: Array<u8> = []; private call_stack: Array<u8> = [];
private program_counter: u8 = 0; private program_counter: u8 = 0;
@ -39,7 +29,7 @@ export class Computer {
} }
cycle(): void { cycle(): void {
const current_byte = this.getMemory(this.program_counter, 0); const current_byte = this.getMemorySilent(this.program_counter, 0);
if (this.current_instr === null) { if (this.current_instr === null) {
const parsed_instruction = ISA.getInstruction(current_byte); const parsed_instruction = ISA.getInstruction(current_byte);
@ -104,19 +94,35 @@ export class Computer {
} }
this.events.dispatch(CpuEvent.Cycle); this.events.dispatch(CpuEvent.Cycle);
} }
private getMemorySilent(address: u8, bank_override?: u1): u8 {
const banks = [this.memory, this.vram];
const bank = banks[bank_override ?? this.bank];
const value = bank[address] as u8;
return value;
}
getMemory(address: u8, bank_override?: u1): u8 { getMemory(address: u8, bank_override?: u1): u8 {
if (bank_override !== undefined) { const value = this.getMemorySilent(address, bank_override);
const value = this.memory[address + 256 * bank_override] as u8; this.events.dispatch(CpuEvent.MemoryAccessed, { address, bank: this.bank, value });
return value;
}
const value = this.memory[address + 256 * this.bank] as u8;
return value; return value;
} }
setMemory(address: u8, value: u8): void { setMemory(address: u8, value: u8): void {
this.events.dispatch(CpuEvent.MemoryChanged, { address, value }); let bank: Uint8Array | undefined;
this.memory[address + 256 * bank_count] = value; if (this.bank === 0) {
bank = this.memory;
} else if (this.bank === 1) {
bank = this.vram;
} else {
const _: never = this.bank;
}
if (bank === undefined) {
throw new Error("unreachable");
}
bank[address] = value;
this.events.dispatch(CpuEvent.MemoryChanged, { address, bank: this.bank, value });
} }
getRegister(register_no: u3): u8 { getRegister(register_no: u3): u8 {
@ -148,6 +154,7 @@ export class Computer {
} }
setBank(bank_no: u1): void { setBank(bank_no: u1): void {
this.events.dispatch(CpuEvent.SwitchBank, { bank: bank_no });
this.bank = bank_no; this.bank = bank_no;
} }
@ -175,7 +182,7 @@ export class Computer {
if (this.memory[i] === program[i]) continue; if (this.memory[i] === program[i]) continue;
this.memory[i] = program[i]; 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; this.program_counter = 0;
} }

View file

@ -40,3 +40,5 @@ export function el(type: string, id?: string): HTMLElement | undefined {
element.id = id; element.id = id;
return element; return element;
} }
export type NonEmptyArray<T> = T[] & { 0: T };

View file

@ -42,6 +42,17 @@ export class EventHandler<T> {
callback(event_data); 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 { listen(identifier: T, callback: (event_data: unknown) => void): void {
if (!this.sealed) throw new Error("Event handler must be sealed before adding listener"); if (!this.sealed) throw new Error("Event handler must be sealed before adding listener");
const event = this.events.find((e) => e.identifier === identifier); const event = this.events.find((e) => e.identifier === identifier);

View file

@ -5,7 +5,7 @@
*/ */
import { EventHandler } from "./eventHandler"; import { EventHandler } from "./eventHandler";
import { Instruction, ParameterType } from "./instructionSet"; import { Instruction, ParameterType } from "./instructionSet";
import { u3, u8 } from "./num"; import { u1, u3, u8 } from "./num";
// //
// CPU Event Handler Definition // CPU Event Handler Definition
@ -22,25 +22,26 @@ export enum CpuEvent {
Print, Print,
Reset, Reset,
Halt, Halt,
ClockStarted, // ClockStarted,
ClockStopped, // ClockStopped,
MemoryAccessed,
SwitchBank,
} }
type VoidDataCpuEventList = type VoidDataCpuEventList = CpuEvent.Halt | CpuEvent.Reset | CpuEvent.Cycle;
| CpuEvent.Halt // | CpuEvent.ClockStarted
| CpuEvent.Reset // | CpuEvent.ClockStopped;
| CpuEvent.Cycle
| CpuEvent.ClockStarted
| CpuEvent.ClockStopped;
interface CpuEventMap { interface CpuEventMap {
[CpuEvent.MemoryChanged]: { address: u8; value: u8 }; [CpuEvent.MemoryChanged]: { address: u8; bank: u1; value: u8 };
[CpuEvent.MemoryAccessed]: { address: u8; bank: u1; value: u8 };
[CpuEvent.RegisterChanged]: { register_no: u3; value: u8 }; [CpuEvent.RegisterChanged]: { register_no: u3; value: u8 };
[CpuEvent.ProgramCounterChanged]: { counter: u8 }; [CpuEvent.ProgramCounterChanged]: { counter: u8 };
[CpuEvent.InstructionParsed]: { pos: u8; code: u8; instr: Instruction }; [CpuEvent.InstructionParsed]: { pos: u8; code: u8; instr: Instruction };
[CpuEvent.ParameterParsed]: { pos: u8; code: u8; param: ParameterType }; [CpuEvent.ParameterParsed]: { pos: u8; code: u8; param: ParameterType };
[CpuEvent.InvalidParsed]: { pos: u8; code: u8 }; [CpuEvent.InvalidParsed]: { pos: u8; code: u8 };
[CpuEvent.InstructionExecuted]: { instr: Instruction }; [CpuEvent.InstructionExecuted]: { instr: Instruction };
[CpuEvent.SwitchBank]: { bank: u1 };
[CpuEvent.Print]: string; [CpuEvent.Print]: string;
} }

View file

@ -11,6 +11,7 @@ import { UI } from "./ui";
import { u8 } from "./num"; import { u8 } from "./num";
import "./style.scss"; import "./style.scss";
import { CpuEvent } from "./events";
declare global { declare global {
interface Window { interface Window {
@ -20,9 +21,26 @@ declare global {
} }
function main(): void { 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> = [ const program: Array<u8> = [
0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03, 0x2f, 0x00, 0x00, 0x2f, 0x01, 0xff, 0x21, 0x01, 0x0d, 0xb1, 0x01, 0x21, 0x01, 0x00, 0x31, 0x01, 0xb1, 0x00, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -95,6 +113,10 @@ function main(): void {
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
}); });
// computer.events.firehose((ident, data) => {
// console.log(`New Event: ${CpuEvent[ident]}. data: `, data);
// });
$("save_button").addEventListener("click", () => { $("save_button").addEventListener("click", () => {
const memory = computer.dump_memory(); const memory = computer.dump_memory();
const blob = new Blob([memory], { type: "application/octet-stream" }); const blob = new Blob([memory], { type: "application/octet-stream" });

View file

@ -286,7 +286,7 @@ ISA.insertInstruction(0x66, {
ISA.insertInstruction(0xa0, { ISA.insertInstruction(0xa0, {
name: "Call", name: "Call",
desc: "", desc: "Calls a subroute",
params: [new ConstParam("the subroutine at this memory address")], params: [new ConstParam("the subroutine at this memory address")],
execute(c, p, a) { execute(c, p, a) {
const current_address = c.getProgramCounter(); const current_address = c.getProgramCounter();
@ -302,7 +302,7 @@ ISA.insertInstruction(0xa0, {
ISA.insertInstruction(0xa1, { ISA.insertInstruction(0xa1, {
name: "Return", name: "Return",
desc: "", desc: "returns from a subroutine",
params: [], params: [],
execute(c, p, a) { execute(c, p, a) {
const new_address = c.popCallStack(); const new_address = c.popCallStack();
@ -319,5 +319,11 @@ ISA.insertInstruction(0xb1, {
name: "Set bank", name: "Set bank",
desc: "Selects which bank of memory to write and read to", desc: "Selects which bank of memory to write and read to",
params: [new ConstParam("Bank number")], 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

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

View file

@ -4,6 +4,8 @@ import { InstructionExplainer } from "./ui/instructionExplainer";
import { MemoryView } from "./ui/memoryView"; import { MemoryView } from "./ui/memoryView";
import { frequencyIndicator } from "./ui/frequencyIndicator"; import { frequencyIndicator } from "./ui/frequencyIndicator";
import { RegisterView } from "./ui/registerView"; import { RegisterView } from "./ui/registerView";
import { Screen } from "./ui/screen";
import { UiComponent, UiComponentConstructor } from "./ui/uiComponent.js";
// Certainly the messiest portion of this program // Certainly the messiest portion of this program
// Needs to be broken into components // Needs to be broken into components
// Breaking up into components has started but has yet to conclude // Breaking up into components has started but has yet to conclude
@ -17,10 +19,7 @@ export class UI {
events: UiEventHandler = new UiEventHandler(); events: UiEventHandler = new UiEventHandler();
frequencyIndicator: frequencyIndicator; private components: Array<UiComponent>;
memory: MemoryView;
registers: RegisterView;
instruction_explainer: InstructionExplainer;
constructor() { constructor() {
for (const [, e_type] of Object.entries(UiEvent)) { for (const [, e_type] of Object.entries(UiEvent)) {
@ -28,18 +27,18 @@ export class UI {
} }
this.events.seal(); this.events.seal();
this.memory = new MemoryView($("memory"), this.events); this.components = [];
this.frequencyIndicator = new frequencyIndicator($("cycles"), this.events);
this.instruction_explainer = new InstructionExplainer($("instruction_explainer"), this.events);
this.registers = new RegisterView($("registers"), this.events);
this.register_component(MemoryView, $("memory"));
this.register_component(frequencyIndicator, $("cycles"));
this.register_component(InstructionExplainer, $("instruction_explainer"));
this.register_component(RegisterView, $("registers"));
this.register_component(Screen, $("screen") as HTMLCanvasElement);
this.printout = $("printout"); this.printout = $("printout");
this.auto_running = false; this.auto_running = false;
const pp_button = $("pause_play_button"); const pp_button = $("pause_play_button");
if (pp_button === null) {
throw new Error("Cant find pause_play button");
}
pp_button.addEventListener("click", () => { pp_button.addEventListener("click", () => {
if (this.auto_running) { if (this.auto_running) {
this.stop_auto(); this.stop_auto();
@ -49,7 +48,7 @@ export class UI {
pp_button.textContent = "Storp"; pp_button.textContent = "Storp";
} }
}); });
$("step_button")?.addEventListener("click", () => { $("step_button").addEventListener("click", () => {
if (this.auto_running) { if (this.auto_running) {
this.stop_auto(); this.stop_auto();
} }
@ -62,6 +61,14 @@ export class UI {
// console.log(delay); // 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 { init_events(cpu_events: CpuEventHandler): void {
cpu_events.listen(CpuEvent.Print, (char) => { cpu_events.listen(CpuEvent.Print, (char) => {
@ -71,18 +78,16 @@ export class UI {
this.reset(); this.reset();
}); });
this.registers.init_cpu_events(cpu_events); for (const c of this.components) {
this.frequencyIndicator.init_cpu_events(cpu_events); c.init_cpu_events(cpu_events);
this.memory.init_cpu_events(cpu_events); }
this.instruction_explainer.init_cpu_events(cpu_events);
} }
reset(): void { reset(): void {
this.stop_auto(); this.stop_auto();
this.registers.reset(); for (const c of this.components) {
this.frequencyIndicator.reset(); c.reset();
this.instruction_explainer.reset(); }
this.memory.reset();
this.printout.textContent = ""; 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

@ -1,74 +1,49 @@
import { el, format_hex } from "../etc";
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events"; import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
import { ParamType } from "../instructionSet"; import { ParamType } from "../instructionSet";
import { u8 } from "../num.js"; import { u8 } from "../num.js";
import { UiComponent } from "./uiComponent"; import { UiComponent } from "./uiComponent";
import { CelledViewer } from "./celledViewer";
type MemoryCell = { type MemoryCell = {
el: HTMLDivElement; el: HTMLDivElement;
}; };
export class MemoryView implements UiComponent { export class MemoryView extends CelledViewer implements UiComponent {
element: HTMLElement; program_counter: u8 = 0;
cells: Array<MemoryCell> = []; last_accessed_cell: u8 | null = null;
program_counter: number = 0;
events: UiEventHandler; events: UiEventHandler;
constructor(element: HTMLElement, e: UiEventHandler) { constructor(element: HTMLElement, e: UiEventHandler) {
this.element = element; super(16, 16, element);
this.program_counter = 0;
this.events = e; this.events = e;
for (let i = 0; i < 256; i++) {
const mem_cell_el = el("div");
mem_cell_el.textContent = "00";
element.appendChild(mem_cell_el);
const mem_cell = { el: mem_cell_el, tags: [] };
this.cells.push(mem_cell);
}
this.set_program_counter(0);
} }
add_cell_class(address: u8, ...css_class: string[]): void { set_program_counter(position: u8): void {
for (const str of css_class) { this.remove_cell_class(this.program_counter, "program_counter");
this.cells[address].el.classList.add(str); this.add_cell_class(position, "program_counter");
}
}
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");
this.program_counter = position; this.program_counter = position;
} }
reset(): void { reset(): void {
for (let i = 0; i < 256; i++) { super.reset();
this.cells[i].el.textContent = "00"; this.last_accessed_cell = null;
this.cells[i].el.className = "";
}
this.set_program_counter(0); this.set_program_counter(0);
} }
init_cpu_events(c: CpuEventHandler): void { 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); this.set_cell_value(address, value);
}); });
c.listen(CpuEvent.ProgramCounterChanged, ({ counter }) => { c.listen(CpuEvent.ProgramCounterChanged, ({ counter }) => {
@ -93,8 +68,8 @@ export class MemoryView implements UiComponent {
c.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => { c.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => {
this.remove_all_cell_class("instruction_argument"); this.remove_all_cell_class("instruction_argument");
this.remove_all_cell_class("current_instruction"); this.remove_all_cell_class("current_instruction");
this.add_cell_class(pos, "current_instruction");
this.remove_cell_class(pos, "constant", "register", "memory", "invalid"); this.remove_cell_class(pos, "constant", "register", "memory", "invalid");
this.add_cell_class(pos, "current_instruction");
this.add_cell_class(pos, "instruction"); this.add_cell_class(pos, "instruction");
}); });
c.listen(CpuEvent.InvalidParsed, ({ code, pos }) => { c.listen(CpuEvent.InvalidParsed, ({ code, pos }) => {

View file

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

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,9 +1,19 @@
/**
* @file Definition of what a UI component is in the context of this program
* @copyright Alexander Bass 2024
* @license GPL-3.0
*/
import { CpuEventHandler, UiEventHandler } from "../events"; import { CpuEventHandler, UiEventHandler } from "../events";
export interface UiComponent { export interface UiComponent {
element: HTMLElement; element: HTMLElement;
/** Allows listening and emitting UiEvent's*/
events: UiEventHandler; events: UiEventHandler;
reset: () => void; reset: () => void;
// init_events: (ui: UiEventHandler) => void; /** Allows listening CPUEvent's*/
init_cpu_events: (c: CpuEventHandler) => void; init_cpu_events: (c: CpuEventHandler) => void;
} }
export interface UiComponentConstructor {
new (el: HTMLElement, ue: UiEventHandler): UiComponent;
}

BIN
test.bin Normal file

Binary file not shown.