Add editable memory cells
This commit is contained in:
parent
7e4d3c8da8
commit
8a38dded92
29
ISA.txt
29
ISA.txt
|
@ -1,29 +0,0 @@
|
||||||
INSTRUCTIONS
|
|
||||||
--- Control Flow ---
|
|
||||||
0x00: NoOp - 0 Parameter
|
|
||||||
0x10: Goto - 1 Parameter - moves the program counter to (P1) in program memory
|
|
||||||
0x11: Goto if low bit - 2 Parameter - Moves the program counter to (P1) if register (P2)'s least significant bit is 1.
|
|
||||||
--- Memory and register management ---
|
|
||||||
0x20: Load to register - 2 Parameter - Loads into register (P1) the byte at processing memory location (P2)
|
|
||||||
0x21: Write register to memory - 2 Parameter - Writes the byte in register (P1) to the processing memory location (P2)
|
|
||||||
0x28: Copy Register -> Register - 2 Parameter - Copies byte from register (P1) to register (P2)
|
|
||||||
0x2F: Assign value to register - 2 Parameter - Assigns register (P1) to value (P2)
|
|
||||||
--- Operations ---
|
|
||||||
0x30: increment register - 1 Parameter - Increments register (P1) by 1.
|
|
||||||
0x31: decrement register - 1 Parameter - Decrements register (P1) by 1.
|
|
||||||
0x40: Add registers - 2 Parameter - Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256)
|
|
||||||
0x41: Reserved
|
|
||||||
--- Bit Operations ---
|
|
||||||
0x48: Bitwise and - 2 Parameter - Ands & each bit of register (P1) and register (P2) and stores result to register (P1)
|
|
||||||
0x49: Bitwise or - you get the point
|
|
||||||
0x4A: Bitwise not - 1 Parameter - Inverts each bit of register (P1)
|
|
||||||
0x4B: Left bit shift - 2 Parameter - Shifts bits in register (P1) to the left by (P2) and stores result to register (P1)
|
|
||||||
0x4C: right bit shift- 2 Parameter - same as left
|
|
||||||
--- Comparison Operations ---
|
|
||||||
0x50: Equals - 3 Parameter - If byte in register (P1) equals byte in register (P2), set byte in register (P3) to 0x01
|
|
||||||
0x51: Less than - 3 Parameter - If byte in register (P1) less than byte in register (P2), set byte in register (P3) to 0x01
|
|
||||||
0x52: Greater than - 3 Parameter - If byte in register (P1) greater than byte in register (P2), set byte in register (P3) to 0x01
|
|
||||||
--- Development ---
|
|
||||||
0xFE: Print byte as ASCII from register - 1 Parameter - Prints the ASCII byte in register (P1) to console
|
|
||||||
0xFF: Print byte from register - 1 Parameter - Prints the byte in register (P1) to console
|
|
||||||
0x66: Halt and Catch Fire - 0 Parameter - Fire! FIRE EVERYWHERE!!!!!
|
|
14
TODO
14
TODO
|
@ -1,14 +0,0 @@
|
||||||
Live memory and register editing (Probably should pause autostep when it reaches the cell you're modifying)
|
|
||||||
HCF flames
|
|
||||||
add hcf
|
|
||||||
Move start/stop/auto logic into computer
|
|
||||||
Speed control slider behavior
|
|
||||||
Speed control slider styling
|
|
||||||
|
|
||||||
UI for screen (toggling (click an icon?))
|
|
||||||
UI for togging other UI elements
|
|
||||||
|
|
||||||
UI for showing which Memory bank is selected
|
|
||||||
VRAM select instruction
|
|
||||||
|
|
||||||
Error log
|
|
33
index.html
33
index.html
|
@ -1,33 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<script src="./dist/main.js"></script>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Virtual 8-Bit Computer</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="main">
|
|
||||||
<div id="title">VIRTUAL 8-BIT COMPUTER</div>
|
|
||||||
<div id="registers"></div>
|
|
||||||
<div id="labelcontainer">
|
|
||||||
<div id="registers_label">←REGISTERS</div>
|
|
||||||
<div id="memory_label">MEMORY↯</div>
|
|
||||||
</div>
|
|
||||||
<div id="memory"></div>
|
|
||||||
<div id="instruction_explainer"></div>
|
|
||||||
<div id="printout"></div>
|
|
||||||
<div id="controls_bar">
|
|
||||||
<button type="button" id="pause_play_button">Start</button>
|
|
||||||
<button type="button" id="step_button">Step</button>
|
|
||||||
<label for="binary_upload" class="button">Load Binary</label>
|
|
||||||
<input id="binary_upload" name="binary_upload" id="binary_upload" style="display: none" type="file" />
|
|
||||||
<button type="button" id="save_button">Save</button>
|
|
||||||
<input type="range" name="" id="delay_range" min="0" max="1500" />
|
|
||||||
</div>
|
|
||||||
<span id="cycles"></span>
|
|
||||||
</div>
|
|
||||||
<canvas id="screen"></canvas>
|
|
||||||
<pre id="ISA"></pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
|
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
|
||||||
import { byte_array_to_js_source, format_hex } from "./etc";
|
import { byte_array_to_js_source, format_hex } from "./etc";
|
||||||
import { Instruction, ISA } from "./instructionSet";
|
import { Instruction, ISA } from "./instructionSet";
|
||||||
import { m256, u1, u2, u3, u8 } from "./num";
|
import { m256, u2, u3, u8 } from "./num";
|
||||||
|
|
||||||
export type TempInstrState = {
|
export type TempInstrState = {
|
||||||
pos: u8;
|
pos: u8;
|
||||||
|
@ -178,6 +178,7 @@ export class Computer {
|
||||||
for (let i = 0; i < cycle_count; i++) this.cycle();
|
for (let i = 0; i < cycle_count; i++) this.cycle();
|
||||||
});
|
});
|
||||||
ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
|
ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
|
||||||
|
ui.listen(UiEvent.RequestRegisterChange, ({ register_no, value }) => this.setRegister(register_no, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
load_memory(program: Array<u8>): void {
|
load_memory(program: Array<u8>): void {
|
||||||
|
|
27
src/etc.ts
27
src/etc.ts
|
@ -31,13 +31,20 @@ export const byte_array_to_js_source = (bytes: Array<u8>): string => {
|
||||||
* @param type
|
* @param type
|
||||||
* @param id id attribute to set
|
* @param id id attribute to set
|
||||||
*/
|
*/
|
||||||
export function el<E extends keyof HTMLElementTagNameMap>(type: E, id?: string): HTMLElementTagNameMap[E];
|
export function el<E extends keyof HTMLElementTagNameMap>(
|
||||||
export function el(type: string, id?: string): HTMLElement | undefined {
|
type: E,
|
||||||
|
id?: string,
|
||||||
|
class_list?: string
|
||||||
|
): HTMLElementTagNameMap[E];
|
||||||
|
export function el(type: string, id?: string, class_list?: string): HTMLElement | undefined {
|
||||||
const element = document.createElement(type);
|
const element = document.createElement(type);
|
||||||
if (id === undefined) {
|
if (id !== undefined) {
|
||||||
return element;
|
|
||||||
}
|
|
||||||
element.id = id;
|
element.id = id;
|
||||||
|
}
|
||||||
|
if (class_list !== undefined) {
|
||||||
|
element.className = class_list;
|
||||||
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,3 +56,13 @@ export function in_range(check: number, start: number, end: number): boolean {
|
||||||
if (check >= start && check <= end) return true;
|
if (check >= start && check <= end) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function at<T>(l: Array<T>, i: number): T | null {
|
||||||
|
if (i < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (i >= l.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return l[i];
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
import { EventHandler } from "./eventHandler";
|
import { EventHandler } from "./eventHandler";
|
||||||
import { Instruction, ParameterType } from "./instructionSet";
|
import { Instruction, ParameterType } from "./instructionSet";
|
||||||
import { u1, u2, u3, u8 } from "./num";
|
import { u2, u3, u8 } from "./num";
|
||||||
|
|
||||||
//
|
//
|
||||||
// CPU Event Handler Definition
|
// CPU Event Handler Definition
|
||||||
|
@ -65,18 +65,28 @@ export const CpuEventHandler = EventHandler<CpuEvent> as CpuEventHandlerConstruc
|
||||||
//
|
//
|
||||||
|
|
||||||
export enum UiEvent {
|
export enum UiEvent {
|
||||||
|
// Maybe move these into a UI -> CPU signal system?
|
||||||
RequestCpuCycle,
|
RequestCpuCycle,
|
||||||
RequestMemoryChange,
|
RequestMemoryChange,
|
||||||
|
RequestRegisterChange,
|
||||||
|
// Ui Events
|
||||||
|
EditOn,
|
||||||
|
EditOff,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UiEventMap {
|
interface UiEventMap {
|
||||||
[UiEvent.RequestCpuCycle]: number;
|
[UiEvent.RequestCpuCycle]: number;
|
||||||
[UiEvent.RequestMemoryChange]: { address: u8; value: u8 };
|
[UiEvent.RequestMemoryChange]: { address: u8; value: u8 };
|
||||||
|
[UiEvent.RequestRegisterChange]: { register_no: u3; value: u8 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VoidDataUiEventList = UiEvent.EditOn | UiEvent.EditOff;
|
||||||
|
|
||||||
export interface UiEventHandler extends EventHandler<UiEvent> {
|
export interface UiEventHandler extends EventHandler<UiEvent> {
|
||||||
listen<E extends keyof UiEventMap>(type: E, listener: (ev: UiEventMap[E]) => void): void;
|
listen<E extends keyof UiEventMap>(type: E, listener: (ev: UiEventMap[E]) => void): void;
|
||||||
dispatch<E extends keyof UiEventMap>(type: E, data: UiEventMap[E]): void;
|
dispatch<E extends keyof UiEventMap>(type: E, data: UiEventMap[E]): void;
|
||||||
|
listen<E extends VoidDataUiEventList>(type: E, listener: () => void): void;
|
||||||
|
dispatch<E extends VoidDataUiEventList>(type: E): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UiEventHandlerConstructor {
|
interface UiEventHandlerConstructor {
|
||||||
|
|
44
src/index.ts
44
src/index.ts
|
@ -22,26 +22,9 @@ 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, 0x00, 0x2f, 0x01, 0xff, 0x21, 0x01, 0x0d, 0xb1, 0x01, 0x21, 0x01, 0x00, 0x31, 0x01, 0xb1, 0x00, 0x10,
|
0x19, 0x00, 0xf0, 0x14, 0x00, 0x01, 0x5e, 0x00, 0xf0, 0x01, 0x21, 0x03, 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,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
@ -55,24 +38,6 @@ function main(): void {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
|
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,
|
0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
|
||||||
];
|
];
|
||||||
|
|
||||||
// const program = [
|
|
||||||
// 0x01, 0x00, 0x01, 0x00, 0xa0, 0x10, 0xff, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xa1,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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 computer = new Computer();
|
const computer = new Computer();
|
||||||
|
|
||||||
const ui = new UI();
|
const ui = new UI();
|
||||||
|
@ -137,10 +102,5 @@ function main(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
// at least you know it's bad
|
|
||||||
try {
|
|
||||||
main();
|
main();
|
||||||
} catch (e) {
|
|
||||||
alert(e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -170,6 +170,34 @@ ISA.insertInstruction(0x13, {
|
||||||
c.setRegister(register_no_2, c.getRegister(register_no_1));
|
c.setRegister(register_no_2, c.getRegister(register_no_1));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
ISA.insertInstruction(0x14, {
|
||||||
|
name: "Load RM -> R",
|
||||||
|
desc: "Copy the byte in memory addressed by register (P1) to register (P2)",
|
||||||
|
params: [
|
||||||
|
new RegisParam("Copy the byte in the memory cell addressed in this register"),
|
||||||
|
new RegisParam("To this register"),
|
||||||
|
],
|
||||||
|
execute(c, p) {
|
||||||
|
const [register_no_1, register_no_2] = p;
|
||||||
|
if (!isU3(register_no_1)) throw new Error("todo");
|
||||||
|
if (!isU3(register_no_2)) throw new Error("todo");
|
||||||
|
c.setRegister(register_no_2, c.getMemory(c.getRegister(register_no_1)));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ISA.insertInstruction(0x15, {
|
||||||
|
name: "Save R -> RM",
|
||||||
|
desc: "Copy the byte in register (P1) to the memory cell addressed in register (P2)",
|
||||||
|
params: [
|
||||||
|
new RegisParam("Copy the value in this register"),
|
||||||
|
new RegisParam("To the memory cell addressed in this register"),
|
||||||
|
],
|
||||||
|
execute(c, p) {
|
||||||
|
const [register_no_1, register_no_2] = p;
|
||||||
|
if (!isU3(register_no_1)) throw new Error("todo");
|
||||||
|
if (!isU3(register_no_2)) throw new Error("todo");
|
||||||
|
c.setMemory(c.getRegister(register_no_2), c.getRegister(register_no_1));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
ISA.insertInstruction(0x17, {
|
ISA.insertInstruction(0x17, {
|
||||||
name: "Zero Register",
|
name: "Zero Register",
|
||||||
|
@ -608,6 +636,7 @@ ISA.insertInstruction(0x50, {
|
||||||
c.setRegister(register_no_1, m256(sum));
|
c.setRegister(register_no_1, m256(sum));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
ISA.insertInstruction(0x51, {
|
ISA.insertInstruction(0x51, {
|
||||||
name: "Add",
|
name: "Add",
|
||||||
desc: "Adds to the byte in register (P1) with the value in register (P2)",
|
desc: "Adds to the byte in register (P1) with the value in register (P2)",
|
||||||
|
@ -620,8 +649,9 @@ ISA.insertInstruction(0x51, {
|
||||||
c.setRegister(register_no_1, m256(sum));
|
c.setRegister(register_no_1, m256(sum));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
ISA.insertInstruction(0x52, {
|
ISA.insertInstruction(0x52, {
|
||||||
name: "Add",
|
name: "Subtract",
|
||||||
desc: "Subtracts from the value in register (P1) by the value in register (P2)",
|
desc: "Subtracts from the value in register (P1) by the value in register (P2)",
|
||||||
params: [new RegisParam("set this register to"), new RegisParam("it's difference with the value in this register")],
|
params: [new RegisParam("set this register to"), new RegisParam("it's difference with the value in this register")],
|
||||||
execute(c, p) {
|
execute(c, p) {
|
||||||
|
@ -635,8 +665,9 @@ ISA.insertInstruction(0x52, {
|
||||||
c.setRegister(register_no_1, m256(difference));
|
c.setRegister(register_no_1, m256(difference));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
ISA.insertInstruction(0x53, {
|
ISA.insertInstruction(0x53, {
|
||||||
name: "Add",
|
name: "Subtract",
|
||||||
desc: "Subtracts from the value in register (P1) by the constant value (P2)",
|
desc: "Subtracts from the value in register (P1) by the constant value (P2)",
|
||||||
params: [new RegisParam("set this register to"), new ConstParam("it's difference with this constant")],
|
params: [new RegisParam("set this register to"), new ConstParam("it's difference with this constant")],
|
||||||
execute(c, p) {
|
execute(c, p) {
|
||||||
|
|
|
@ -8,16 +8,6 @@ pre {
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--Border: #ffff00;
|
--Border: #ffff00;
|
||||||
// --mem-instruction: #adff2f;
|
|
||||||
// --mem-register: #800080;
|
|
||||||
// --mem-constant: #d3d3d3;
|
|
||||||
// --mem-memory: #d3d3d3;
|
|
||||||
// --mem-invalid: #ff0000;
|
|
||||||
// --mem-instruction: #2f962a;
|
|
||||||
// --mem-register: #dc21d1;
|
|
||||||
// --mem-constant: #d3d3d3;
|
|
||||||
// --mem-memory: #4d86f0;
|
|
||||||
// --mem-invalid: #bf2e2e;
|
|
||||||
--mem-instruction: #3af78f;
|
--mem-instruction: #3af78f;
|
||||||
--mem-memory: #ff26a8;
|
--mem-memory: #ff26a8;
|
||||||
--mem-register: #9e0ef7;
|
--mem-register: #9e0ef7;
|
||||||
|
@ -25,6 +15,10 @@ pre {
|
||||||
--mem-invalid: #bf2e2e;
|
--mem-invalid: #bf2e2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: #808080;
|
color: #808080;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
|
@ -44,13 +38,13 @@ body {
|
||||||
#main {
|
#main {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content max-content max-content 10px 500px;
|
grid-template-columns: min-content max-content max-content min-content 500px;
|
||||||
grid-template-rows: min-content 1.5fr 10px 2fr 2fr min-content;
|
grid-template-rows: min-content 1.5fr 10px 2fr 2fr min-content;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"cycles registers regmemlabel . explainer "
|
"cycles registers regmemlabel . explainer "
|
||||||
"title . . . explainer "
|
"title . . ribbon explainer "
|
||||||
"title . . . ."
|
"title . . ribbon ."
|
||||||
"title . . . printout "
|
"title . . ribbon printout "
|
||||||
"title . . . printout "
|
"title . . . printout "
|
||||||
". buttons buttons . .";
|
". buttons buttons . .";
|
||||||
#memory {
|
#memory {
|
||||||
|
@ -70,6 +64,9 @@ body {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transform: scale(-1, -1);
|
transform: scale(-1, -1);
|
||||||
}
|
}
|
||||||
|
#ribbon_menu {
|
||||||
|
grid-area: ribbon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#printout {
|
#printout {
|
||||||
|
@ -124,6 +121,17 @@ body {
|
||||||
--color: var(--mem-instruction);
|
--color: var(--mem-instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div[contenteditable] {
|
||||||
|
&.caret_selected {
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: 2px solid red;
|
||||||
|
}
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending_edit {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
#memory {
|
#memory {
|
||||||
grid-area: memory;
|
grid-area: memory;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -131,21 +139,29 @@ body {
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 5px solid yellow;
|
border: 5px solid yellow;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
|
user-select: none;
|
||||||
|
caret-color: transparent;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.program_counter {
|
.program_counter {
|
||||||
outline: 3px solid orange;
|
outline: 3px solid orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
.instruction_argument,
|
.instruction_argument,
|
||||||
.current_instruction {
|
.current_instruction {
|
||||||
outline: 3px dashed var(--color);
|
outline: 3px dashed var(--color);
|
||||||
}
|
}
|
||||||
|
.recent_edit {
|
||||||
|
color: lime;
|
||||||
|
}
|
||||||
div.last_access {
|
div.last_access {
|
||||||
color: orange;
|
color: orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invalid {
|
.invalid {
|
||||||
&::after {
|
&::after {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -159,10 +175,31 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div#main.editor {
|
||||||
|
#memory,
|
||||||
|
#registers {
|
||||||
|
border-style: dashed;
|
||||||
|
div {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ribbon_menu {
|
||||||
|
margin-inline: 8px;
|
||||||
|
.editor_toggle {
|
||||||
|
//TODO CHANGE COLORS WHen
|
||||||
|
&.off {
|
||||||
|
}
|
||||||
|
&.on {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#registers {
|
#registers {
|
||||||
grid-area: registers;
|
grid-area: registers;
|
||||||
border: 5px solid yellow;
|
border: 5px solid yellow;
|
||||||
border-bottom: none;
|
border-bottom: none !important;
|
||||||
|
|
||||||
grid-template-columns: repeat(8, min-content);
|
grid-template-columns: repeat(8, min-content);
|
||||||
max-width: fit-content;
|
max-width: fit-content;
|
||||||
|
@ -191,6 +228,25 @@ label.button {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
border: 4px solid rgb(255, 255, 128);
|
||||||
|
color: orange;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.no_style {
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
button:hover,
|
button:hover,
|
||||||
label.button:hover {
|
label.button:hover {
|
||||||
color: white;
|
color: white;
|
||||||
|
@ -201,6 +257,10 @@ label.button:hover {
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
#controls_buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
|
@ -208,8 +268,8 @@ input[type="range"] {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
margin: 18px 0;
|
margin: 18px 0;
|
||||||
// width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"]:focus {
|
input[type="range"]:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
57
src/ui.ts
57
src/ui.ts
|
@ -1,16 +1,13 @@
|
||||||
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
|
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
|
||||||
import { $, el, format_hex } from "./etc";
|
import { $ } from "./etc";
|
||||||
import { InstructionExplainer } from "./ui/instructionExplainer";
|
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 { Screen } from "./ui/screen";
|
||||||
import { UiComponent, UiComponentConstructor } from "./ui/uiComponent.js";
|
import { Ribbon } from "./ui/ribbon";
|
||||||
// Certainly the messiest portion of this program
|
import { UiComponent, UiComponentConstructor } from "./ui/uiComponent";
|
||||||
// Needs to be broken into components
|
import { pausePlay } from "./ui/pausePlay";
|
||||||
// Breaking up into components has started but has yet to conclude
|
|
||||||
|
|
||||||
let delay = 100;
|
|
||||||
|
|
||||||
export class UI {
|
export class UI {
|
||||||
printout: HTMLElement;
|
printout: HTMLElement;
|
||||||
|
@ -34,32 +31,12 @@ export class UI {
|
||||||
this.register_component(InstructionExplainer, $("instruction_explainer"));
|
this.register_component(InstructionExplainer, $("instruction_explainer"));
|
||||||
this.register_component(RegisterView, $("registers"));
|
this.register_component(RegisterView, $("registers"));
|
||||||
this.register_component(Screen, $("screen") as HTMLCanvasElement);
|
this.register_component(Screen, $("screen") as HTMLCanvasElement);
|
||||||
|
this.register_component(Ribbon, $("ribbon_menu"));
|
||||||
|
this.register_component(pausePlay, $("controls_buttons"));
|
||||||
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");
|
||||||
|
|
||||||
pp_button.addEventListener("click", () => {
|
|
||||||
if (this.auto_running) {
|
|
||||||
this.stop_auto();
|
|
||||||
pp_button.textContent = "Starp";
|
|
||||||
} else {
|
|
||||||
this.start_auto();
|
|
||||||
pp_button.textContent = "Storp";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$("step_button").addEventListener("click", () => {
|
|
||||||
if (this.auto_running) {
|
|
||||||
this.stop_auto();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("delay_range").addEventListener("input", (e) => {
|
|
||||||
delay = parseInt((e.target as HTMLInputElement).value, 10);
|
|
||||||
// console.log(delay);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
private register_component(c: UiComponentConstructor, e: HTMLElement): void {
|
private register_component(c: UiComponentConstructor, e: HTMLElement): void {
|
||||||
if (e === undefined) {
|
if (e === undefined) {
|
||||||
|
@ -79,34 +56,14 @@ export class UI {
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const c of this.components) {
|
for (const c of this.components) {
|
||||||
c.init_cpu_events(cpu_events);
|
if (c.init_cpu_events) c.init_cpu_events(cpu_events);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.stop_auto();
|
|
||||||
for (const c of this.components) {
|
for (const c of this.components) {
|
||||||
c.reset();
|
c.reset();
|
||||||
}
|
}
|
||||||
this.printout.textContent = "";
|
this.printout.textContent = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
start_auto(speed: number = 200): void {
|
|
||||||
if (this.auto_running) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.auto_running = true;
|
|
||||||
const loop = (): void => {
|
|
||||||
if (this.auto_running === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
|
||||||
setTimeout(loop, delay);
|
|
||||||
};
|
|
||||||
loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
stop_auto(): void {
|
|
||||||
this.auto_running = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
import { NonEmptyArray, el, format_hex } from "../etc";
|
import { NonEmptyArray, el, format_hex } from "../etc";
|
||||||
import { u8 } from "../num";
|
import { u8 } from "../num";
|
||||||
|
|
||||||
// TODO, make generic
|
|
||||||
interface GenericCell {
|
interface GenericCell {
|
||||||
el: HTMLElement;
|
el: HTMLElement;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +21,7 @@ export abstract class CelledViewer {
|
||||||
this.height = height;
|
this.height = height;
|
||||||
for (let i = 0; i < this.width * this.height; i++) {
|
for (let i = 0; i < this.width * this.height; i++) {
|
||||||
const mem_cell_el = el("div");
|
const mem_cell_el = el("div");
|
||||||
mem_cell_el.textContent = "00";
|
mem_cell_el.append("0", "0");
|
||||||
this.element.appendChild(mem_cell_el);
|
this.element.appendChild(mem_cell_el);
|
||||||
const mem_cell = { el: mem_cell_el };
|
const mem_cell = { el: mem_cell_el };
|
||||||
this.cells.push(mem_cell);
|
this.cells.push(mem_cell);
|
||||||
|
@ -31,7 +30,7 @@ export abstract class CelledViewer {
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
for (let i = 0; i < this.height * this.width; i++) {
|
for (let i = 0; i < this.height * this.width; i++) {
|
||||||
this.cells[i].el.textContent = "00";
|
this.set_cell_value(i as u8, 0);
|
||||||
this.cells[i].el.className = "";
|
this.cells[i].el.className = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +58,10 @@ export abstract class CelledViewer {
|
||||||
}
|
}
|
||||||
|
|
||||||
set_cell_value(address: u8, value: u8): void {
|
set_cell_value(address: u8, value: u8): void {
|
||||||
this.cells[address].el.textContent = format_hex(value);
|
const str = format_hex(value);
|
||||||
|
const a = str[0];
|
||||||
|
const b = str[1];
|
||||||
|
this.cells[address].el.textContent = "";
|
||||||
|
this.cells[address].el.append(a, b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,88 +1,81 @@
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// This file was cobbled together and is the messiest part of this project
|
||||||
function set_caret(el: any, pos: number): boolean {
|
|
||||||
const selection = window.getSelection();
|
|
||||||
const range = document.createRange();
|
|
||||||
if (selection === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
selection.removeAllRanges();
|
import { at } from "../etc";
|
||||||
range.selectNode(el);
|
import { u8 } from "../num";
|
||||||
|
|
||||||
range.setStart(el, pos);
|
const HEX_CHARACTERS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
|
||||||
range.setEnd(el, pos);
|
|
||||||
range.collapse(true);
|
|
||||||
selection.removeAllRanges();
|
|
||||||
selection.addRange(range);
|
|
||||||
el.focus();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_caret(el: HTMLElement): null | number {
|
export class EditorContext {
|
||||||
const sel = window.getSelection();
|
private list: Array<HTMLElement>;
|
||||||
if (sel === null) {
|
private width: number;
|
||||||
return null;
|
private height: number;
|
||||||
}
|
private enabled: boolean = false;
|
||||||
const pos = sel.getRangeAt(0).startOffset;
|
private current_cell_info: { left?: string; right?: string; old?: string };
|
||||||
const endPos = pos + Array.from(el.innerHTML.slice(0, pos)).length - el.innerHTML.slice(0, pos).split("").length;
|
private edit_callback: (n: number, value: u8) => void;
|
||||||
return endPos;
|
constructor(list: Array<HTMLElement>, width: number, height: number, callback: (n: number, value: u8) => void) {
|
||||||
}
|
this.list = list;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.edit_callback = callback;
|
||||||
|
this.current_cell_info = {};
|
||||||
|
|
||||||
const hex_characters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
|
for (const [i, cell] of this.list.entries()) {
|
||||||
function replace_non_hex(c: string): string {
|
|
||||||
if (hex_characters.includes(c)) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
return "0";
|
|
||||||
}
|
|
||||||
|
|
||||||
function editable_constraints(e: Event): boolean {
|
|
||||||
const target = e.target as HTMLDivElement;
|
|
||||||
const text = target.innerHTML ?? "";
|
|
||||||
if (text.length !== 2) {
|
|
||||||
const pos = get_caret(target);
|
|
||||||
const new_str = [...(target.textContent ?? "").substring(0, 2).padStart(2, "0").toUpperCase()]
|
|
||||||
.map(replace_non_hex)
|
|
||||||
.join("");
|
|
||||||
target.innerHTML = "";
|
|
||||||
// For the caret selection to work right, each character must be its own node, complicating this greatly
|
|
||||||
target.append(new_str.substring(0, 1), new_str.substring(1));
|
|
||||||
|
|
||||||
if (pos !== null) {
|
|
||||||
if (pos >= 2) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
set_caret(target, pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function at<T>(l: Array<T>, i: number): T | null {
|
|
||||||
if (i < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (i >= l.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return l[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function make_editable(
|
|
||||||
list: Array<HTMLElement>,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
on_edit: (n: number, value: string) => void
|
|
||||||
): void {
|
|
||||||
for (const [i, cell] of list.entries()) {
|
|
||||||
cell.setAttribute("contenteditable", "true");
|
|
||||||
cell.setAttribute("spellcheck", "false");
|
cell.setAttribute("spellcheck", "false");
|
||||||
const next: null | HTMLElement = at(list, i + 1);
|
|
||||||
const prev: null | HTMLElement = at(list, i - 1);
|
|
||||||
const up: null | HTMLElement = at(list, i - width);
|
|
||||||
const down: null | HTMLElement = at(list, i + width);
|
|
||||||
cell.addEventListener("keydown", (e) => {
|
cell.addEventListener("keydown", (e) => {
|
||||||
const caret_position = get_caret(cell);
|
this.keydown(e, i);
|
||||||
|
});
|
||||||
|
cell.addEventListener("focus", () => {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
this.current_cell_info.old = cell.textContent ?? "00";
|
||||||
|
this.current_cell_info.left = undefined;
|
||||||
|
this.current_cell_info.right = undefined;
|
||||||
|
cell.classList.add("caret_selected");
|
||||||
|
|
||||||
|
// Reset cursor position (I know there's an API for this, but this is a simpler, more robust solution)
|
||||||
|
cell.textContent = cell.textContent ?? "00";
|
||||||
|
});
|
||||||
|
|
||||||
|
cell.addEventListener("blur", () => {
|
||||||
|
const left = this.current_cell_info.left;
|
||||||
|
const right = this.current_cell_info.right;
|
||||||
|
cell.classList.remove("caret_selected");
|
||||||
|
if (left === undefined || right === undefined) {
|
||||||
|
cell.textContent = this.current_cell_info.old ?? "00";
|
||||||
|
} else if (left !== undefined && right !== undefined) {
|
||||||
|
const text = `${left}${right}`;
|
||||||
|
cell.textContent = text;
|
||||||
|
const val = Number.parseInt(text, 16) as u8;
|
||||||
|
this.edit_callback(i, val);
|
||||||
|
cell.classList.add("recent_edit");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enable(): void {
|
||||||
|
this.enabled = true;
|
||||||
|
for (const cell of this.list) {
|
||||||
|
cell.setAttribute("contenteditable", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disable(): void {
|
||||||
|
this.enabled = false;
|
||||||
|
for (const cell of this.list) {
|
||||||
|
cell.removeAttribute("contenteditable");
|
||||||
|
cell.blur();
|
||||||
|
}
|
||||||
|
this.current_cell_info = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private keydown(e: KeyboardEvent, cell_index: number): void {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
const cell = e.target as HTMLElement;
|
||||||
|
|
||||||
|
const next: null | HTMLElement = at(this.list, cell_index + 1);
|
||||||
|
const prev: null | HTMLElement = at(this.list, cell_index - 1);
|
||||||
|
const up: null | HTMLElement = at(this.list, cell_index - this.width);
|
||||||
|
const down: null | HTMLElement = at(this.list, cell_index + this.width);
|
||||||
|
|
||||||
const k = e.key;
|
const k = e.key;
|
||||||
if (k === "ArrowUp") {
|
if (k === "ArrowUp") {
|
||||||
(up ?? prev)?.focus();
|
(up ?? prev)?.focus();
|
||||||
|
@ -90,10 +83,10 @@ export function make_editable(
|
||||||
} else if (k === "ArrowDown") {
|
} else if (k === "ArrowDown") {
|
||||||
(down ?? next)?.focus();
|
(down ?? next)?.focus();
|
||||||
cell.blur();
|
cell.blur();
|
||||||
} else if ((k === "ArrowLeft" || k === "Backspace") && caret_position === 0) {
|
} else if (k === "ArrowLeft" || k === "Backspace") {
|
||||||
prev?.focus();
|
prev?.focus();
|
||||||
cell.blur();
|
cell.blur();
|
||||||
} else if (k === "ArrowRight" && caret_position === 1) {
|
} else if (k === "ArrowRight") {
|
||||||
next?.focus();
|
next?.focus();
|
||||||
cell.blur();
|
cell.blur();
|
||||||
} else if (k === "Enter") {
|
} else if (k === "Enter") {
|
||||||
|
@ -101,22 +94,19 @@ export function make_editable(
|
||||||
} else if (k === "Escape") {
|
} else if (k === "Escape") {
|
||||||
cell.blur();
|
cell.blur();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else if (HEX_CHARACTERS.includes(k.toUpperCase())) {
|
||||||
return;
|
if (this.current_cell_info.left === undefined) {
|
||||||
}
|
this.current_cell_info.left = k.toUpperCase();
|
||||||
e.preventDefault();
|
cell.innerHTML = `<span class="pending_edit">${this.current_cell_info.left}</span>0`;
|
||||||
});
|
} else if (this.current_cell_info.right === undefined) {
|
||||||
let previous_text = cell.textContent ?? "";
|
this.current_cell_info.right = k.toUpperCase();
|
||||||
cell.addEventListener("input", (e) => {
|
cell.textContent = `${this.current_cell_info.left}${this.current_cell_info.right}`;
|
||||||
const current_text = cell.textContent ?? "";
|
|
||||||
if (current_text !== previous_text) {
|
|
||||||
previous_text = cell.textContent ?? "";
|
|
||||||
on_edit(i, current_text);
|
|
||||||
}
|
|
||||||
if (editable_constraints(e) === true) {
|
|
||||||
next?.focus();
|
next?.focus();
|
||||||
cell.blur();
|
cell.blur();
|
||||||
}
|
}
|
||||||
});
|
} else if (k === "Tab") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
|
import { CpuEvent, CpuEventHandler, UiEvent, 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";
|
import { CelledViewer } from "./celledViewer";
|
||||||
|
import { EditorContext } from "./editableHex";
|
||||||
|
|
||||||
type MemoryCell = {
|
type MemoryCell = {
|
||||||
el: HTMLDivElement;
|
el: HTMLDivElement;
|
||||||
|
@ -16,6 +17,23 @@ export class MemoryView extends CelledViewer implements UiComponent {
|
||||||
super(16, 16, element);
|
super(16, 16, element);
|
||||||
this.program_counter = 0;
|
this.program_counter = 0;
|
||||||
this.events = e;
|
this.events = e;
|
||||||
|
|
||||||
|
const list = this.cells.map((c) => c.el);
|
||||||
|
const editor = new EditorContext(list, this.width, this.height, (i, value) => {
|
||||||
|
this.events.dispatch(UiEvent.RequestMemoryChange, { address: i as u8, value });
|
||||||
|
});
|
||||||
|
this.events.listen(UiEvent.EditOn, () => {
|
||||||
|
editor.enable();
|
||||||
|
for (const cell of this.cells) {
|
||||||
|
cell.el.className = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.events.listen(UiEvent.EditOff, () => {
|
||||||
|
editor.disable();
|
||||||
|
for (const cell of this.cells) {
|
||||||
|
cell.el.className = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
set_program_counter(position: u8): void {
|
set_program_counter(position: u8): void {
|
||||||
|
|
104
src/ui/pausePlay.ts
Normal file
104
src/ui/pausePlay.ts
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import { el } from "../etc";
|
||||||
|
import { UiEventHandler, CpuEventHandler, UiEvent } from "../events";
|
||||||
|
import { UiComponent } from "./uiComponent";
|
||||||
|
|
||||||
|
const MAX_SLIDER = 1000;
|
||||||
|
|
||||||
|
export class pausePlay implements UiComponent {
|
||||||
|
element: HTMLElement;
|
||||||
|
start_button: HTMLButtonElement;
|
||||||
|
step_button: HTMLButtonElement;
|
||||||
|
range: HTMLInputElement;
|
||||||
|
events: UiEventHandler;
|
||||||
|
on: boolean = false;
|
||||||
|
cycle_delay: number;
|
||||||
|
constructor(element: HTMLElement, events: UiEventHandler) {
|
||||||
|
this.element = element;
|
||||||
|
this.events = events;
|
||||||
|
this.start_button = el("button", "pause_play_button");
|
||||||
|
this.step_button = el("button", "step_button");
|
||||||
|
this.range = el("input", "speed_range");
|
||||||
|
this.range.max = MAX_SLIDER.toString();
|
||||||
|
this.range.min = "0";
|
||||||
|
this.range.type = "range";
|
||||||
|
this.start_button.addEventListener("click", () => this.toggle());
|
||||||
|
this.step_button.addEventListener("click", () => this.step());
|
||||||
|
this.range.addEventListener("input", (e) => {
|
||||||
|
const delay = MAX_SLIDER - parseInt((e.target as HTMLInputElement).value, 10) + 10;
|
||||||
|
this.cycle_delay = delay;
|
||||||
|
});
|
||||||
|
this.start_button.textContent = "Start";
|
||||||
|
this.step_button.textContent = "Step";
|
||||||
|
this.element.appendChild(this.start_button);
|
||||||
|
this.element.appendChild(this.step_button);
|
||||||
|
this.element.appendChild(this.range);
|
||||||
|
this.cycle_delay = 1000;
|
||||||
|
this.range.value = "0";
|
||||||
|
|
||||||
|
this.events.listen(UiEvent.EditOn, () => {
|
||||||
|
this.disable();
|
||||||
|
});
|
||||||
|
this.events.listen(UiEvent.EditOff, () => {
|
||||||
|
this.enable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disable(): void {
|
||||||
|
this.stop();
|
||||||
|
this.start_button.setAttribute("disabled", "true");
|
||||||
|
this.step_button.setAttribute("disabled", "true");
|
||||||
|
this.range.setAttribute("disabled", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
enable(): void {
|
||||||
|
this.start_button.removeAttribute("disabled");
|
||||||
|
this.step_button.removeAttribute("disabled");
|
||||||
|
this.range.removeAttribute("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(): void {
|
||||||
|
if (this.on) {
|
||||||
|
this.start_button.textContent = "Start";
|
||||||
|
this.on = false;
|
||||||
|
} else {
|
||||||
|
this.on = true;
|
||||||
|
this.cycle();
|
||||||
|
this.start_button.textContent = "Storp";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private cycle(): void {
|
||||||
|
const loop = (): void => {
|
||||||
|
if (this.on === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
||||||
|
setTimeout(loop, this.cycle_delay);
|
||||||
|
};
|
||||||
|
loop();
|
||||||
|
}
|
||||||
|
private step(): void {
|
||||||
|
if (this.on) {
|
||||||
|
this.stop();
|
||||||
|
} else {
|
||||||
|
this.events.dispatch(UiEvent.RequestCpuCycle, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start(): void {
|
||||||
|
if (!this.on) {
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
if (this.on) {
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.stop();
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
|
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "../events";
|
||||||
|
import { u3 } from "../num";
|
||||||
import { CelledViewer } from "./celledViewer";
|
import { CelledViewer } from "./celledViewer";
|
||||||
|
import { EditorContext } from "./editableHex";
|
||||||
import { UiComponent } from "./uiComponent";
|
import { UiComponent } from "./uiComponent";
|
||||||
|
|
||||||
export class RegisterView extends CelledViewer implements UiComponent {
|
export class RegisterView extends CelledViewer implements UiComponent {
|
||||||
|
@ -7,6 +9,23 @@ export class RegisterView extends CelledViewer implements UiComponent {
|
||||||
constructor(element: HTMLElement, e: UiEventHandler) {
|
constructor(element: HTMLElement, e: UiEventHandler) {
|
||||||
super(8, 1, element);
|
super(8, 1, element);
|
||||||
this.events = e;
|
this.events = e;
|
||||||
|
|
||||||
|
const list = this.cells.map((c) => c.el);
|
||||||
|
const editor = new EditorContext(list, this.width, this.height, (i, value) => {
|
||||||
|
this.events.dispatch(UiEvent.RequestRegisterChange, { register_no: i as u3, value });
|
||||||
|
});
|
||||||
|
this.events.listen(UiEvent.EditOn, () => {
|
||||||
|
editor.enable();
|
||||||
|
for (const cell of this.cells) {
|
||||||
|
cell.el.className = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.events.listen(UiEvent.EditOff, () => {
|
||||||
|
editor.disable();
|
||||||
|
for (const cell of this.cells) {
|
||||||
|
cell.el.className = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init_cpu_events(c: CpuEventHandler): void {
|
init_cpu_events(c: CpuEventHandler): void {
|
||||||
|
|
60
src/ui/ribbon.ts
Normal file
60
src/ui/ribbon.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { el, $ } from "../etc";
|
||||||
|
import { UiEventHandler, UiEvent } from "../events";
|
||||||
|
import { UiComponent } from "./uiComponent";
|
||||||
|
|
||||||
|
function new_button(name: string, img_path: string, additional_class?: string): HTMLButtonElement {
|
||||||
|
const button = el("button", "", "no_style ribbon_button");
|
||||||
|
const image = el("img");
|
||||||
|
image.src = img_path;
|
||||||
|
image.width = 64;
|
||||||
|
image.height = 64;
|
||||||
|
if (additional_class !== undefined) {
|
||||||
|
button.classList.add(additional_class);
|
||||||
|
}
|
||||||
|
button.appendChild(image);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Ribbon implements UiComponent {
|
||||||
|
element: HTMLElement;
|
||||||
|
events: UiEventHandler;
|
||||||
|
edit_button: HTMLButtonElement;
|
||||||
|
console_button: HTMLButtonElement;
|
||||||
|
display_button: HTMLButtonElement;
|
||||||
|
explainer_button: HTMLButtonElement;
|
||||||
|
constructor(element: HTMLElement, event: UiEventHandler) {
|
||||||
|
this.element = element;
|
||||||
|
this.events = event;
|
||||||
|
|
||||||
|
this.edit_button = new_button("Edit", "pencil.png", "editor_toggle");
|
||||||
|
this.console_button = new_button("Console", "texout.png");
|
||||||
|
this.display_button = new_button("Video", "tv.png");
|
||||||
|
this.explainer_button = new_button("Explainer", "explainer.png");
|
||||||
|
this.edit_button.addEventListener("click", () => this.edit_toggle());
|
||||||
|
this.element.appendChild(this.edit_button);
|
||||||
|
this.element.appendChild(this.console_button);
|
||||||
|
this.element.appendChild(this.display_button);
|
||||||
|
this.element.appendChild(this.explainer_button);
|
||||||
|
}
|
||||||
|
reset(): void {
|
||||||
|
const is_on = this.edit_button.classList.contains("on");
|
||||||
|
if (is_on) {
|
||||||
|
this.edit_toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
edit_toggle(): void {
|
||||||
|
const is_on = this.edit_button.classList.contains("on");
|
||||||
|
if (is_on) {
|
||||||
|
this.edit_button.classList.remove("on");
|
||||||
|
$("main").classList.remove("editor");
|
||||||
|
this.edit_button.classList.add("off");
|
||||||
|
this.events.dispatch(UiEvent.EditOff);
|
||||||
|
} else {
|
||||||
|
this.events.dispatch(UiEvent.EditOn);
|
||||||
|
$("main").classList.add("editor");
|
||||||
|
this.edit_button.classList.add("on");
|
||||||
|
this.edit_button.classList.remove("off");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,11 +7,11 @@ import { CpuEventHandler, UiEventHandler } from "../events";
|
||||||
|
|
||||||
export interface UiComponent {
|
export interface UiComponent {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
/** Allows listening and emitting UiEvent's*/
|
/** Allows listening and emitting UiEvents*/
|
||||||
events: UiEventHandler;
|
events: UiEventHandler;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
/** Allows listening CPUEvent's*/
|
/** Allows listening CPUEvents*/
|
||||||
init_cpu_events: (c: CpuEventHandler) => void;
|
init_cpu_events?: (c: CpuEventHandler) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UiComponentConstructor {
|
export interface UiComponentConstructor {
|
||||||
|
|
36
todo.md
Normal file
36
todo.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
Edit Mode
|
||||||
|
|
||||||
|
- Select where program counter is
|
||||||
|
|
||||||
|
Implement HCF
|
||||||
|
Move start/stop/auto logic into computer
|
||||||
|
|
||||||
|
Speed control slider behavior
|
||||||
|
Speed control slider styling
|
||||||
|
Overclock Box
|
||||||
|
|
||||||
|
error in instruction when number out of range (fix new Error("todo"))
|
||||||
|
|
||||||
|
UI for screen (toggling (click an icon?))
|
||||||
|
UI for togging other UI elements
|
||||||
|
|
||||||
|
UI for showing which Memory bank is selected
|
||||||
|
VRAM select instruction
|
||||||
|
|
||||||
|
Improve instruction explainer. Clearly show what is an instruction and what is a parameter
|
||||||
|
|
||||||
|
Verify mod256 behavior on negatives
|
||||||
|
|
||||||
|
UI showing CPU flag(s) (Carry)
|
||||||
|
|
||||||
|
Error log
|
||||||
|
|
||||||
|
Responsive layout
|
||||||
|
|
||||||
|
standardize names of all things
|
||||||
|
|
||||||
|
Documentation with standard names
|
||||||
|
|
||||||
|
Example Programs
|
||||||
|
|
||||||
|
Ui for togging your mother.
|
Loading…
Reference in a new issue