add screen and memory banking
This commit is contained in:
parent
6bf0c9917a
commit
f290b836cf
13
TODO
13
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)
|
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>
|
</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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
src/index.ts
26
src/index.ts
|
@ -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" });
|
||||||
|
|
|
@ -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);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
45
src/ui.ts
45
src/ui.ts
|
@ -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
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 }) => {
|
||||||
|
|
|
@ -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
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,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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue