final commit before major rework
This commit is contained in:
parent
b550a62af8
commit
f850b21869
|
@ -37,7 +37,7 @@
|
|||
"quote-props" : ["warn","as-needed"],
|
||||
"dot-notation":"warn",
|
||||
"one-var":["warn","never"],
|
||||
"no-use-before-define":"warn",
|
||||
"no-use-before-define":"off",
|
||||
"no-multi-assign":"warn",
|
||||
"no-else-return":"warn",
|
||||
"spaced-comment":"warn",
|
||||
|
|
|
@ -4,11 +4,18 @@ import { Instruction, ISA } from "./instructionSet";
|
|||
import { m256, u2, u3, u8 } from "./num";
|
||||
import { DEFAULT_VRAM_BANK } from "./constants";
|
||||
|
||||
interface ParsedParameter {
|
||||
pos: u8;
|
||||
code: u8;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
export type TempInstrState = {
|
||||
pos: u8;
|
||||
params_found: number;
|
||||
instr: Instruction;
|
||||
params: Array<u8>;
|
||||
valid: boolean;
|
||||
params: Array<ParsedParameter>;
|
||||
};
|
||||
|
||||
function initBanks(): [Uint8Array, Uint8Array, Uint8Array, Uint8Array] {
|
||||
|
@ -22,7 +29,7 @@ function initBanks(): [Uint8Array, Uint8Array, Uint8Array, Uint8Array] {
|
|||
export default class Computer {
|
||||
private banks: [Uint8Array, Uint8Array, Uint8Array, Uint8Array] = initBanks();
|
||||
private registers: Uint8Array = new Uint8Array(8);
|
||||
private call_stack: Array<u8> = [];
|
||||
// private call_stack: Array<u8> = [];
|
||||
private carry_flag: boolean = false;
|
||||
private program_counter: u8 = 0;
|
||||
private bank: u2 = 0;
|
||||
|
@ -36,7 +43,7 @@ export default class Computer {
|
|||
if (this.current_instr === null) {
|
||||
const parsed_instruction = ISA.getInstruction(current_byte);
|
||||
if (parsed_instruction === null) {
|
||||
this.events.dispatch(CpuEvent.InvalidParsed, {
|
||||
this.events.dispatch(CpuEvent.InvalidInstructionParsed, {
|
||||
pos: this.program_counter,
|
||||
code: current_byte,
|
||||
});
|
||||
|
@ -45,35 +52,55 @@ export default class Computer {
|
|||
this.events.dispatch(CpuEvent.Cycle);
|
||||
return;
|
||||
}
|
||||
|
||||
this.current_instr = {
|
||||
pos: this.program_counter,
|
||||
instr: parsed_instruction,
|
||||
params_found: 0,
|
||||
params: new Array<u8>(parsed_instruction.params.length),
|
||||
valid: true,
|
||||
params: new Array<ParsedParameter>(parsed_instruction.params.length),
|
||||
};
|
||||
this.events.dispatch(CpuEvent.InstructionParsed, {
|
||||
|
||||
this.events.dispatch(CpuEvent.InstructionParseBegin, {
|
||||
pos: this.program_counter,
|
||||
instr: parsed_instruction,
|
||||
code: current_byte,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) {
|
||||
this.stepForward();
|
||||
this.events.dispatch(CpuEvent.Cycle);
|
||||
return;
|
||||
}
|
||||
|
||||
// if (this.current_instr.pos === this.program_counter && this.current_instr.params.length > 0) {
|
||||
// this.stepForward();
|
||||
// this.events.dispatch(CpuEvent.Cycle);
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (this.current_instr.params.length !== this.current_instr.params_found) {
|
||||
const b = this.current_instr.instr.params[this.current_instr.params_found];
|
||||
|
||||
const valid = b.validate(current_byte);
|
||||
if (valid) {
|
||||
this.events.dispatch(CpuEvent.ParameterParsed, {
|
||||
param: b,
|
||||
pos: this.program_counter,
|
||||
code: current_byte,
|
||||
});
|
||||
this.current_instr.params[this.current_instr.params_found] = current_byte;
|
||||
} else {
|
||||
this.events.dispatch(CpuEvent.InvalidParameterParsed, {
|
||||
param: b,
|
||||
pos: this.program_counter,
|
||||
code: current_byte,
|
||||
});
|
||||
this.current_instr.valid = false;
|
||||
}
|
||||
|
||||
const param = { pos: this.program_counter, code: current_byte, valid };
|
||||
|
||||
this.current_instr.params[this.current_instr.params_found] = param;
|
||||
this.current_instr.params_found += 1;
|
||||
|
||||
if (this.current_instr.params.length !== this.current_instr.params_found) {
|
||||
this.stepForward();
|
||||
this.events.dispatch(CpuEvent.Cycle);
|
||||
|
@ -87,8 +114,12 @@ export default class Computer {
|
|||
},
|
||||
dispatch: this.events.dispatch.bind(this.events),
|
||||
};
|
||||
this.current_instr.instr.execute(this, this.current_instr.params, execution_post_action_state);
|
||||
if (this.current_instr.valid) {
|
||||
const params: Array<u8> = this.current_instr.params.map((p) => p.code);
|
||||
this.current_instr.instr.execute(this, params, execution_post_action_state);
|
||||
this.events.dispatch(CpuEvent.InstructionExecuted, { instr: this.current_instr.instr });
|
||||
}
|
||||
this.events.dispatch(CpuEvent.InstructionParseEnd);
|
||||
this.current_instr = null;
|
||||
|
||||
if (execution_post_action_state.should_step) {
|
||||
|
@ -134,15 +165,15 @@ export default class Computer {
|
|||
this.program_counter = new_value;
|
||||
}
|
||||
|
||||
pushCallStack(address: u8): boolean {
|
||||
if (this.call_stack.length >= 8) return false;
|
||||
this.call_stack.push(address);
|
||||
return true;
|
||||
}
|
||||
// pushCallStack(address: u8): boolean {
|
||||
// if (this.call_stack.length >= 8) return false;
|
||||
// this.call_stack.push(address);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
popCallStack(): u8 | null {
|
||||
return this.call_stack.pop() ?? null;
|
||||
}
|
||||
// popCallStack(): u8 | null {
|
||||
// return this.call_stack.pop() ?? null;
|
||||
// }
|
||||
|
||||
setBank(bank: u2): void {
|
||||
this.events.dispatch(CpuEvent.SwitchBank, { bank: bank });
|
||||
|
@ -170,17 +201,17 @@ export default class Computer {
|
|||
ui.listen(UiCpuSignal.RequestMemoryChange, ({ address, bank, value }) => this.setMemory(address, value, bank));
|
||||
ui.listen(UiCpuSignal.RequestRegisterChange, ({ register_no, value }) => this.setRegister(register_no, value));
|
||||
ui.listen(UiCpuSignal.RequestMemoryDump, (callback) => callback(this.dumpMemory()));
|
||||
ui.listen(UiCpuSignal.RequestCpuReset, () => this.reset());
|
||||
ui.listen(UiCpuSignal.RequestProgramCounterChange, ({ address }) => {
|
||||
this.setProgramCounter(address);
|
||||
});
|
||||
ui.listen(UiCpuSignal.RequestCpuReset, () => this.reset());
|
||||
ui.listen(UiCpuSignal.RequestCpuSoftReset, () => this.softReset());
|
||||
}
|
||||
|
||||
softReset(): void {
|
||||
this.events.dispatch(CpuEvent.SoftReset);
|
||||
for (let i = 0; i < 8; i++) this.setRegister(i as u3, 0);
|
||||
while (this.popCallStack() !== null) 0;
|
||||
// while (this.popCallStack() !== null) 0;
|
||||
this.setVramBank(DEFAULT_VRAM_BANK);
|
||||
this.setCarry(false);
|
||||
this.current_instr = null;
|
||||
|
@ -193,7 +224,7 @@ export default class Computer {
|
|||
this.banks = initBanks();
|
||||
// Soft reset
|
||||
for (let i = 0; i < 8; i++) this.setRegister(i as u3, 0);
|
||||
while (this.popCallStack() !== null) 0;
|
||||
// while (this.popCallStack() !== null) 0;
|
||||
this.setVramBank(DEFAULT_VRAM_BANK);
|
||||
this.setCarry(false);
|
||||
this.current_instr = null;
|
||||
|
|
|
@ -14,9 +14,11 @@ export enum CpuEvent {
|
|||
MemoryChanged,
|
||||
RegisterChanged,
|
||||
ProgramCounterChanged,
|
||||
InstructionParsed,
|
||||
InstructionParseBegin,
|
||||
InstructionParseEnd,
|
||||
ParameterParsed,
|
||||
InvalidParsed,
|
||||
InvalidParameterParsed,
|
||||
InvalidInstructionParsed,
|
||||
InstructionExecuted,
|
||||
Cycle,
|
||||
Print,
|
||||
|
@ -29,16 +31,22 @@ export enum CpuEvent {
|
|||
SetVramBank,
|
||||
}
|
||||
|
||||
type VoidDataCpuEventList = CpuEvent.Halt | CpuEvent.Reset | CpuEvent.SoftReset | CpuEvent.Cycle;
|
||||
type VoidDataCpuEventList =
|
||||
| CpuEvent.Halt
|
||||
| CpuEvent.Reset
|
||||
| CpuEvent.SoftReset
|
||||
| CpuEvent.Cycle
|
||||
| CpuEvent.InstructionParseEnd;
|
||||
|
||||
interface CpuEventMap {
|
||||
[CpuEvent.MemoryChanged]: { address: u8; bank: u2; value: u8 };
|
||||
[CpuEvent.MemoryAccessed]: { address: u8; bank: u2; value: u8 };
|
||||
[CpuEvent.RegisterChanged]: { register_no: u3; value: u8 };
|
||||
[CpuEvent.ProgramCounterChanged]: { counter: u8 };
|
||||
[CpuEvent.InstructionParsed]: { pos: u8; code: u8; instr: Instruction };
|
||||
[CpuEvent.InstructionParseBegin]: { pos: u8; code: u8; instr: Instruction };
|
||||
[CpuEvent.ParameterParsed]: { pos: u8; code: u8; param: ParameterType };
|
||||
[CpuEvent.InvalidParsed]: { pos: u8; code: u8 };
|
||||
[CpuEvent.InvalidParameterParsed]: { pos: u8; code: u8; param: ParameterType };
|
||||
[CpuEvent.InvalidInstructionParsed]: { pos: u8; code: u8 };
|
||||
[CpuEvent.InstructionExecuted]: { instr: Instruction };
|
||||
[CpuEvent.SwitchBank]: { bank: u2 };
|
||||
[CpuEvent.SetVramBank]: { bank: u2 };
|
||||
|
|
|
@ -11,24 +11,33 @@
|
|||
This computer requires JavaScript. Your browser either doesn't support it, or you have it disabled.
|
||||
</noscript>
|
||||
<main>
|
||||
<div id="left_column">
|
||||
<div id="program_meta">
|
||||
<div id="title_div">
|
||||
<input type="text" name="" id="" value="New Program" />
|
||||
</div>
|
||||
<div id="btn_div">
|
||||
<span id="save_load_buttons"></span>
|
||||
<button>🔗</button>
|
||||
<button id="trash_button">🗑</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="grid">
|
||||
<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="reg_label">↯REGISTERS</div> -->
|
||||
<!-- <div id="mem_label">MEMORY↯</div> -->
|
||||
<div id="memory"></div>
|
||||
|
||||
<div id="controls_bar">
|
||||
<span id="controls_buttons"></span>
|
||||
<span id="save_load_buttons"></span>
|
||||
|
||||
<button type="button" id="edit_button"></button>
|
||||
<div id="reset_button"></div>
|
||||
</div>
|
||||
<span id="cycles"></span>
|
||||
<div id="memory_bank_view"></div>
|
||||
|
||||
<div id="reset_buttons"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="window_box">
|
||||
|
@ -63,6 +72,20 @@
|
|||
</div>
|
||||
</main>
|
||||
|
||||
<article>
|
||||
<details id="isa_container">
|
||||
<summary>Instruction Set</summary>
|
||||
<pre id="ISA"></pre>
|
||||
</details>
|
||||
|
||||
<h1>Virtual 8-Bit Computer</h1>
|
||||
<p>
|
||||
Above is a virtual virtual virtual Lorem ipsum dolor sit amet consectetur adipisicing elit. Provident architecto
|
||||
voluptas repudiandae eligendi mollitia necessitatibus tempore autem ea pariatur ex quae eum itaque ducimus,
|
||||
velit nam voluptatem deserunt ad nesciunt.
|
||||
</p>
|
||||
<h2>How it works</h2>
|
||||
<p></p>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -7,7 +7,7 @@ import Computer from "./computer";
|
|||
import UI from "./ui";
|
||||
import { $ } from "./etc";
|
||||
import { ISA } from "./instructionSet";
|
||||
import { generateIsa } from "./isaGenerator";
|
||||
import { generateIsaTable } from "./isaGenerator";
|
||||
import { u8 } from "./num";
|
||||
|
||||
import "./style/style.scss";
|
||||
|
@ -49,7 +49,7 @@ function main(): void {
|
|||
|
||||
// Todo, move to ui component
|
||||
// or move to documentation
|
||||
$("ISA").textContent = generateIsa(ISA);
|
||||
$("ISA").replaceWith(generateIsaTable(ISA));
|
||||
|
||||
let fire = false;
|
||||
window.firehose = (): void => {
|
||||
|
|
|
@ -10,7 +10,10 @@ import { isU2, isU3, m256, u2, u3, u8 } from "./num";
|
|||
export enum ParamType {
|
||||
Const,
|
||||
Register,
|
||||
Memory,
|
||||
ConstMemory,
|
||||
Bank,
|
||||
// A register which holds a memory address
|
||||
RegisterAddress,
|
||||
}
|
||||
|
||||
export abstract class ParameterType {
|
||||
|
@ -20,6 +23,9 @@ export abstract class ParameterType {
|
|||
this.desc = description;
|
||||
this.type = p_type;
|
||||
}
|
||||
validate(n: number): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ConstParam extends ParameterType {
|
||||
|
@ -31,10 +37,28 @@ class RegisParam extends ParameterType {
|
|||
constructor(d: string) {
|
||||
super(d, ParamType.Register);
|
||||
}
|
||||
validate(n: number): boolean {
|
||||
return isU3(n);
|
||||
}
|
||||
class MemorParam extends ParameterType {
|
||||
}
|
||||
|
||||
class BankParam extends ParameterType {
|
||||
constructor(d: string) {
|
||||
super(d, ParamType.Memory);
|
||||
super(d, ParamType.Bank);
|
||||
}
|
||||
validate(n: number): boolean {
|
||||
return isU2(n);
|
||||
}
|
||||
}
|
||||
|
||||
class ConstMemorParam extends ParameterType {
|
||||
constructor(d: string) {
|
||||
super(d, ParamType.ConstMemory);
|
||||
}
|
||||
}
|
||||
class RegisAddrParam extends ParameterType {
|
||||
constructor(d: string) {
|
||||
super(d, ParamType.RegisterAddress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,12 +69,13 @@ interface GenericComputer {
|
|||
getProgramCounter: () => u8;
|
||||
getRegister: (number: u3) => u8;
|
||||
setRegister: (number: u3, value: u8) => void;
|
||||
pushCallStack: (address: u8) => boolean;
|
||||
popCallStack: () => u8 | null;
|
||||
// pushCallStack: (address: u8) => boolean;
|
||||
// popCallStack: () => u8 | null;
|
||||
setBank: (bank_no: u2) => void;
|
||||
getCarry(): boolean;
|
||||
setCarry(state: boolean): void;
|
||||
setVramBank(bank: u2): void;
|
||||
softReset(): void;
|
||||
}
|
||||
|
||||
interface AfterExecutionComputerAction {
|
||||
|
@ -129,9 +154,9 @@ ISA.addCategory(category(0xf0, 0xff, "IO"));
|
|||
|
||||
// COPY
|
||||
ISA.insertInstruction(0x10, {
|
||||
name: "Copy R -> M",
|
||||
name: "Copy CR -> CA",
|
||||
desc: "Copy the byte in register (P1) to the memory address (P2)",
|
||||
params: [new RegisParam("Write the byte in this register"), new MemorParam("To this memory address")],
|
||||
params: [new RegisParam("Write the byte in this register"), new ConstMemorParam("To this memory address")],
|
||||
execute(c, p) {
|
||||
const [register_no, mem_address] = p;
|
||||
if (!isU3(register_no)) throw new Error("TODO");
|
||||
|
@ -140,9 +165,9 @@ ISA.insertInstruction(0x10, {
|
|||
});
|
||||
|
||||
ISA.insertInstruction(0x11, {
|
||||
name: "Copy M -> R",
|
||||
name: "Copy CA -> R",
|
||||
desc: "Copy the byte in memory address (P1) to the register (P2)",
|
||||
params: [new MemorParam(""), new RegisParam("")],
|
||||
params: [new ConstMemorParam(""), new RegisParam("")],
|
||||
execute(c, p) {
|
||||
const [register_no, mem_address] = p;
|
||||
if (!isU3(register_no)) throw new Error("TODO");
|
||||
|
@ -151,9 +176,9 @@ ISA.insertInstruction(0x11, {
|
|||
});
|
||||
|
||||
ISA.insertInstruction(0x12, {
|
||||
name: "Copy M -> M",
|
||||
name: "Copy CM -> CA",
|
||||
desc: "Copy the byte in memory address (P1) to memory address (P2)",
|
||||
params: [new MemorParam("Copy the byte in this memory address"), new MemorParam("To this memory address")],
|
||||
params: [new ConstMemorParam("Copy the byte in this memory address"), new ConstMemorParam("To this memory address")],
|
||||
execute(c, p) {
|
||||
const [mem_address_1, mem_address_2] = p;
|
||||
c.setMemory(mem_address_2, c.getMemory(mem_address_1));
|
||||
|
@ -172,10 +197,10 @@ ISA.insertInstruction(0x13, {
|
|||
},
|
||||
});
|
||||
ISA.insertInstruction(0x14, {
|
||||
name: "Load RM -> R",
|
||||
name: "Load RA -> 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 RegisAddrParam("Copy the byte in the memory cell addressed in this register"),
|
||||
new RegisParam("To this register"),
|
||||
],
|
||||
execute(c, p) {
|
||||
|
@ -186,11 +211,11 @@ ISA.insertInstruction(0x14, {
|
|||
},
|
||||
});
|
||||
ISA.insertInstruction(0x15, {
|
||||
name: "Save R -> RM",
|
||||
name: "Save R -> RA",
|
||||
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"),
|
||||
new RegisAddrParam("To the memory cell addressed in this register"),
|
||||
],
|
||||
execute(c, p) {
|
||||
const [register_no_1, register_no_2] = p;
|
||||
|
@ -213,7 +238,7 @@ ISA.insertInstruction(0x17, {
|
|||
ISA.insertInstruction(0x18, {
|
||||
name: "Zero Memory",
|
||||
desc: "Set the byte in memory address (P1) to 0",
|
||||
params: [new RegisParam("Set the value in this memory address to 0")],
|
||||
params: [new RegisAddrParam("Set the value in this memory address to 0")],
|
||||
execute(c, p) {
|
||||
const mem_address = p[0];
|
||||
c.setMemory(mem_address, 0);
|
||||
|
@ -234,7 +259,7 @@ ISA.insertInstruction(0x19, {
|
|||
ISA.insertInstruction(0x1f, {
|
||||
name: "Set bank",
|
||||
desc: "Selects which bank of memory to write and read to",
|
||||
params: [new ConstParam("Bank number")],
|
||||
params: [new BankParam("Bank number")],
|
||||
execute(c, p) {
|
||||
const bank_no = p[0];
|
||||
if (!isU2(bank_no)) {
|
||||
|
@ -255,9 +280,8 @@ ISA.insertInstruction(0x20, {
|
|||
params: [new RegisParam("new instruction counter location")],
|
||||
execute: (c, p, a) => {
|
||||
const register_no = p[0];
|
||||
if (!isU3(register_no)) {
|
||||
throw new Error("todo");
|
||||
}
|
||||
if (!isU3(register_no)) throw new Error("todo");
|
||||
|
||||
const new_address = c.getRegister(register_no);
|
||||
c.setProgramCounter(new_address);
|
||||
a.noStep();
|
||||
|
@ -333,33 +357,33 @@ ISA.insertInstruction(0x29, {
|
|||
},
|
||||
});
|
||||
|
||||
ISA.insertInstruction(0x2d, {
|
||||
name: "Call",
|
||||
desc: "",
|
||||
params: [new ConstParam("the subroutine at this memory address")],
|
||||
execute(c, p, a) {
|
||||
const current_address = c.getProgramCounter();
|
||||
const success = c.pushCallStack(current_address);
|
||||
// TODO Handle success/failure
|
||||
// ISA.insertInstruction(0x2d, {
|
||||
// name: "Call",
|
||||
// desc: "",
|
||||
// params: [new ConstParam("the subroutine at this memory address")],
|
||||
// execute(c, p, a) {
|
||||
// const current_address = c.getProgramCounter();
|
||||
// const success = c.pushCallStack(current_address);
|
||||
// // TODO Handle success/failure
|
||||
|
||||
const new_address = p[0];
|
||||
c.setProgramCounter(new_address);
|
||||
// const new_address = p[0];
|
||||
// c.setProgramCounter(new_address);
|
||||
|
||||
a.noStep();
|
||||
},
|
||||
});
|
||||
// a.noStep();
|
||||
// },
|
||||
// });
|
||||
|
||||
ISA.insertInstruction(0x2e, {
|
||||
name: "Return",
|
||||
desc: "",
|
||||
params: [],
|
||||
execute(c, p, a) {
|
||||
const new_address = c.popCallStack();
|
||||
if (new_address === null) throw new Error("TODO handle this");
|
||||
c.setProgramCounter(m256(new_address + 1));
|
||||
a.noStep();
|
||||
},
|
||||
});
|
||||
// ISA.insertInstruction(0x2e, {
|
||||
// name: "Return",
|
||||
// desc: "",
|
||||
// params: [],
|
||||
// execute(c, p, a) {
|
||||
// const new_address = c.popCallStack();
|
||||
// if (new_address === null) throw new Error("TODO handle this");
|
||||
// c.setProgramCounter(m256(new_address + 1));
|
||||
// a.noStep();
|
||||
// },
|
||||
// });
|
||||
|
||||
ISA.insertInstruction(0x2c, {
|
||||
name: "NoOp",
|
||||
|
@ -369,11 +393,12 @@ ISA.insertInstruction(0x2c, {
|
|||
});
|
||||
|
||||
ISA.insertInstruction(0x2f, {
|
||||
name: "Halt and Catch Fire",
|
||||
desc: "Stops program execu..... Fire! FIRE EVERYWHERE!",
|
||||
name: "Halt",
|
||||
desc: "Stops CPU execution and soft resets",
|
||||
params: [],
|
||||
execute(c, p, a) {
|
||||
a.dispatch(CpuEvent.Halt);
|
||||
c.softReset();
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -743,7 +768,7 @@ ISA.insertInstruction(0xf1, {
|
|||
ISA.insertInstruction(0xff, {
|
||||
name: "Set VRAM Bank",
|
||||
desc: "Set memory bank which screen gets pixels from memory bank (P1)",
|
||||
params: [new ConstParam("memory bank to select")],
|
||||
params: [new BankParam("memory bank to select")],
|
||||
execute(c, p, a) {
|
||||
const bank_no = p[0];
|
||||
if (!isU2(bank_no)) throw new Error("TO2O");
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* @copyright Alexander Bass 2024
|
||||
* @license GPL-3.0
|
||||
*/
|
||||
import { formatHex, inRange } from "./etc";
|
||||
import { el, formatHex, inRange } from "./etc";
|
||||
import { InstrCategory, Instruction, InstructionSet, ParameterType, ParamType } from "./instructionSet";
|
||||
import { u8 } from "./num";
|
||||
|
||||
|
@ -13,7 +13,13 @@ function parameterDescription(params: Array<ParameterType>): string {
|
|||
str += " ";
|
||||
}
|
||||
for (const p of params) {
|
||||
const p_map = { [ParamType.Const]: "C", [ParamType.Memory]: "M", [ParamType.Register]: "R" };
|
||||
const p_map = {
|
||||
[ParamType.Const]: "C",
|
||||
[ParamType.ConstMemory]: "CM",
|
||||
[ParamType.Register]: "R",
|
||||
[ParamType.Bank]: "B",
|
||||
[ParamType.RegisterAddress]: "RA",
|
||||
};
|
||||
const char = p_map[p.type];
|
||||
str += char;
|
||||
str += " ";
|
||||
|
@ -21,39 +27,46 @@ function parameterDescription(params: Array<ParameterType>): string {
|
|||
return str;
|
||||
}
|
||||
|
||||
export function generateIsa(iset: InstructionSet): string {
|
||||
export function generateIsaTable(iset: InstructionSet): HTMLTableElement {
|
||||
const table = el("table").fin();
|
||||
|
||||
const headings = el("tr").fin();
|
||||
|
||||
headings.appendChild(el("td").tx("Code").fin());
|
||||
headings.appendChild(el("td").tx("Parameters").fin());
|
||||
headings.appendChild(el("td").tx("Name").fin());
|
||||
headings.appendChild(el("td").tx("Description").fin());
|
||||
table.appendChild(headings);
|
||||
|
||||
const instructions: Array<[u8, Instruction]> = [];
|
||||
|
||||
for (const kv of iset.instructions.entries()) instructions.push(kv);
|
||||
|
||||
let output_string = "INSTRUCTIONS\n";
|
||||
|
||||
const max_instr_name_len = instructions.map((i) => i[1].name.length).reduce((acc, p) => Math.max(p, acc), 0);
|
||||
instructions.sort((a, b) => a[0] - b[0]);
|
||||
|
||||
let current_category: InstrCategory | null = null;
|
||||
|
||||
for (const instruction of instructions) {
|
||||
const cat = iset.category_ranges.find((i) => inRange(instruction[0], i.start, i.end));
|
||||
for (const [code, instr] of instructions) {
|
||||
const cat = iset.category_ranges.find((i) => inRange(code, i.start, i.end));
|
||||
if (cat === undefined) {
|
||||
throw new Error("Instruction found which is not part of category");
|
||||
}
|
||||
if (current_category !== cat) {
|
||||
output_string += `-- ${cat.name.toUpperCase()} --\n`;
|
||||
const category_row = el("tr").fin();
|
||||
category_row.appendChild(el("td").tx(cat.name.toUpperCase()).at("colspan", "4").cl("category").fin());
|
||||
table.appendChild(category_row);
|
||||
current_category = cat;
|
||||
}
|
||||
const hex_code = formatHex(instruction[0]);
|
||||
const row = el("tr").fin();
|
||||
const instr_text = instr.name;
|
||||
|
||||
const short_description = instruction[1].name.padEnd(max_instr_name_len, " ");
|
||||
const parameters = parameterDescription(instruction[1].params);
|
||||
const description = instruction[1].desc;
|
||||
output_string += `0x${hex_code}: ${short_description}`;
|
||||
if (parameters.length !== 0) {
|
||||
output_string += ` -${parameters}- `;
|
||||
} else {
|
||||
output_string += " - ";
|
||||
row.appendChild(
|
||||
el("td")
|
||||
.tx(`0x${formatHex(code)}`)
|
||||
.fin()
|
||||
);
|
||||
row.appendChild(el("td").tx(parameterDescription(instr.params)).fin());
|
||||
row.appendChild(el("td").tx(instr_text).fin());
|
||||
row.appendChild(el("td").tx(instr.desc).fin());
|
||||
table.appendChild(row);
|
||||
}
|
||||
output_string += `${description}\n`;
|
||||
}
|
||||
return output_string;
|
||||
|
||||
return table;
|
||||
}
|
||||
|
|
25
src/style/article.scss
Normal file
25
src/style/article.scss
Normal file
|
@ -0,0 +1,25 @@
|
|||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
color: white;
|
||||
font-family: charter;
|
||||
margin-block: unset;
|
||||
// margin-bottom: unset;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: unset;
|
||||
font-size: 1.4rem;
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
article {
|
||||
margin-top: 0.5rem;
|
||||
width: 1000px;
|
||||
font-family: Helvetica, sans-serif;
|
||||
margin-inline: auto;
|
||||
}
|
|
@ -1,9 +1,16 @@
|
|||
@use "memory_registers";
|
||||
@use "hover_text_box";
|
||||
@use "article";
|
||||
@use "windows";
|
||||
@use "top_meta";
|
||||
@use "buttons";
|
||||
@use "table";
|
||||
@use "vars";
|
||||
|
||||
#isa_container summary {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
@ -35,9 +42,9 @@ main {
|
|||
grid-template-columns: min-content min-content min-content;
|
||||
grid-template-rows: min-content min-content min-content;
|
||||
grid-template-areas:
|
||||
". regmemlabel . cycles"
|
||||
". reglabel . ."
|
||||
". registers . bank "
|
||||
"reset memory memory memory"
|
||||
"memlabel memory memory memory"
|
||||
"title memory memory memory"
|
||||
". buttons buttons buttons";
|
||||
|
||||
|
@ -50,8 +57,11 @@ main {
|
|||
#memory_bank_view {
|
||||
grid-area: bank;
|
||||
}
|
||||
#labelcontainer {
|
||||
grid-area: regmemlabel;
|
||||
#reg_label {
|
||||
grid-area: reglabel;
|
||||
}
|
||||
#mem_label {
|
||||
grid-area: memlabel;
|
||||
}
|
||||
#cycles {
|
||||
grid-area: cycles;
|
||||
|
@ -70,9 +80,6 @@ main {
|
|||
#memory {
|
||||
grid-area: memory;
|
||||
}
|
||||
#reset_buttons {
|
||||
grid-area: reset;
|
||||
}
|
||||
}
|
||||
|
||||
#reset_buttons {
|
||||
|
@ -88,15 +95,20 @@ main {
|
|||
}
|
||||
}
|
||||
|
||||
#labelcontainer {
|
||||
column-gap: 18px;
|
||||
#reg_label,
|
||||
#mem_label {
|
||||
font-size: 0.75em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#mem_label {
|
||||
writing-mode: vertical-lr;
|
||||
text-align: right;
|
||||
margin-top: 5px;
|
||||
user-select: none;
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
|
||||
#memory_bank_view {
|
||||
display: flex;
|
||||
#bank_boxes {
|
||||
|
|
34
src/style/table.scss
Normal file
34
src/style/table.scss
Normal file
|
@ -0,0 +1,34 @@
|
|||
table {
|
||||
border-collapse: collapse;
|
||||
// border: 2px solid lightgray;
|
||||
letter-spacing: 1px;
|
||||
font-size: 0.9rem;
|
||||
color: lightgray;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1px solid rgb(190, 190, 190);
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: rgb(235, 235, 235);
|
||||
}
|
||||
|
||||
.category {
|
||||
border-left: 2px solid yellow;
|
||||
}
|
||||
|
||||
tr:nth-child(even) td {
|
||||
background-color: rgb(22, 22, 22);
|
||||
}
|
||||
|
||||
tr:nth-child(odd) td {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding: 10px;
|
||||
}
|
55
src/style/top_meta.scss
Normal file
55
src/style/top_meta.scss
Normal file
|
@ -0,0 +1,55 @@
|
|||
input[type="text"] {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-family: monospace;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div#program_meta {
|
||||
align-items: center;
|
||||
#title_div {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
#btn_div {
|
||||
align-items: center;
|
||||
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
#save_load_buttons {
|
||||
display: flex;
|
||||
gap: inherit;
|
||||
}
|
||||
}
|
||||
justify-content: space-between;
|
||||
|
||||
border-bottom: 2px solid yellow;
|
||||
margin-bottom: 7px;
|
||||
button,
|
||||
label.button {
|
||||
font-size: 28px;
|
||||
|
||||
margin: unset;
|
||||
padding: unset;
|
||||
border: unset;
|
||||
}
|
||||
|
||||
#trash_button:hover {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
text-decoration: underline;
|
||||
margin-block: auto;
|
||||
padding: 10px;
|
||||
&:hover {
|
||||
background-color: darkslategray;
|
||||
}
|
||||
&:focus {
|
||||
background-color: darkslategray;
|
||||
}
|
||||
}
|
||||
display: flex;
|
||||
}
|
|
@ -9,7 +9,8 @@ import BankSelector from "./ui/components/bankViewSelector";
|
|||
import EditButton from "./ui/components/editButton";
|
||||
import pausePlay from "./ui/components/pausePlay";
|
||||
import SaveLoad from "./ui/components/saveLoad";
|
||||
import ResetButtons from "./ui/components/resetButtons";
|
||||
import ResetButton from "./ui/components/resetButton";
|
||||
import TrashButton from "./ui/components/trashButton";
|
||||
// Window Components
|
||||
import InstructionExplainer from "./ui/windows/instructionExplainer";
|
||||
import Screen from "./ui/windows/screen";
|
||||
|
@ -23,7 +24,7 @@ export default class UI {
|
|||
|
||||
constructor() {
|
||||
this.register_component(MemoryView, $("memory"));
|
||||
this.register_component(frequencyIndicator, $("cycles"));
|
||||
// this.register_component(frequencyIndicator, $("cycles"));
|
||||
this.register_component(InstructionExplainer, $("instruction_explainer"));
|
||||
this.register_component(RegisterView, $("registers"));
|
||||
this.register_component(Screen, $("tv"));
|
||||
|
@ -33,7 +34,8 @@ export default class UI {
|
|||
this.register_component(SaveLoad, $("save_load_buttons"));
|
||||
this.register_component(BankSelector, $("memory_bank_view"));
|
||||
this.register_component(BankVisualizer, $("bank_viz"));
|
||||
this.register_component(ResetButtons, $("reset_buttons"));
|
||||
this.register_component(ResetButton, $("reset_button"));
|
||||
this.register_component(TrashButton, $("trash_button"));
|
||||
}
|
||||
|
||||
private register_component(ctor: UiComponentConstructor, e: HTMLElement): void {
|
||||
|
|
|
@ -5,6 +5,14 @@ import UiComponent from "../uiComponent";
|
|||
import { el } from "../../etc";
|
||||
import CelledViewer from "../celledViewer";
|
||||
|
||||
const p_map = {
|
||||
[ParamType.Const]: "constant",
|
||||
[ParamType.ConstMemory]: "memory",
|
||||
[ParamType.Register]: "register",
|
||||
[ParamType.Bank]: "bank",
|
||||
[ParamType.RegisterAddress]: "regaddr",
|
||||
};
|
||||
|
||||
/** Only to be run once */
|
||||
function createBanks(
|
||||
element: HTMLElement,
|
||||
|
@ -106,26 +114,17 @@ export default class MemoryView implements UiComponent {
|
|||
this.program.addCellClass(pos, "instruction_argument");
|
||||
const t = param.type;
|
||||
this.program.removeCellClass(pos, "constant", "register", "memory", "instruction", "invalid");
|
||||
let name: string = "";
|
||||
if (t === ParamType.Const) {
|
||||
name = "constant";
|
||||
} else if (t === ParamType.Memory) {
|
||||
name = "memory";
|
||||
} else if (t === ParamType.Register) {
|
||||
name = "register";
|
||||
} else {
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
const name = p_map[t];
|
||||
this.program.addCellClass(pos, name);
|
||||
});
|
||||
c.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => {
|
||||
c.listen(CpuEvent.InstructionParseBegin, ({ instr, code, pos }) => {
|
||||
this.program.removeAllCellClass("instruction_argument");
|
||||
this.program.removeAllCellClass("current_instruction");
|
||||
this.program.removeCellClass(pos, "constant", "register", "memory", "invalid");
|
||||
this.program.addCellClass(pos, "current_instruction");
|
||||
this.program.addCellClass(pos, "instruction");
|
||||
});
|
||||
c.listen(CpuEvent.InvalidParsed, ({ code, pos }) => {
|
||||
c.listen(CpuEvent.InvalidInstructionParsed, ({ code, pos }) => {
|
||||
this.program.removeCellClass(pos, "constant", "register", "memory", "instruction");
|
||||
this.program.addCellClass(pos, "invalid");
|
||||
});
|
||||
|
|
|
@ -42,8 +42,12 @@ export default class pausePlay implements UiComponent {
|
|||
|
||||
this.events.listen(UiEvent.EditOn, () => this.disable());
|
||||
this.events.listen(UiEvent.EditOff, () => this.enable());
|
||||
const tb = new HoverTextBox(this.start_button, el("span").tx("hover test").st("color", "yellow").fin(), "left", 10);
|
||||
tb.show();
|
||||
|
||||
const s_width = this.start_button.offsetWidth;
|
||||
this.start_button.style.width = `${s_width.toString()}px`;
|
||||
|
||||
// const tb = new HoverTextBox(this.start_button, el("span").tx("hover test").st("color", "yellow").fin(), "left", 10);
|
||||
// tb.show();
|
||||
}
|
||||
|
||||
disable(): void {
|
||||
|
@ -68,7 +72,7 @@ export default class pausePlay implements UiComponent {
|
|||
} else {
|
||||
this.on = true;
|
||||
this.cycle();
|
||||
this.start_button.textContent = "Storp";
|
||||
this.start_button.textContent = "Stop";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
18
src/ui/components/resetButton.ts
Normal file
18
src/ui/components/resetButton.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { el } from "../../etc";
|
||||
import { UiEventHandler, UiCpuSignalHandler, UiCpuSignal } from "../../events";
|
||||
import UiComponent from "../uiComponent";
|
||||
|
||||
export default class ResetButton implements UiComponent {
|
||||
container: HTMLElement;
|
||||
events: UiEventHandler;
|
||||
cpu_signals: UiCpuSignalHandler;
|
||||
constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) {
|
||||
this.container = element;
|
||||
this.events = events;
|
||||
this.cpu_signals = cpu_signals;
|
||||
const reset_button = el("button").ti("Reset State").tx("⟳").fin();
|
||||
|
||||
reset_button.addEventListener("click", () => this.cpu_signals.dispatch(UiCpuSignal.RequestCpuSoftReset));
|
||||
this.container.appendChild(reset_button);
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import { el } from "../../etc";
|
||||
import { UiEventHandler, UiCpuSignalHandler, UiCpuSignal } from "../../events";
|
||||
import UiComponent from "../uiComponent";
|
||||
|
||||
export default class ResetButtons implements UiComponent {
|
||||
container: HTMLElement;
|
||||
events: UiEventHandler;
|
||||
cpu_signals: UiCpuSignalHandler;
|
||||
constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) {
|
||||
this.container = element;
|
||||
this.events = events;
|
||||
this.cpu_signals = cpu_signals;
|
||||
const reset_button = el("button").cl("nostyle").ti("Reset State").tx("⟳").fin();
|
||||
const trash_button = el("button").cl("nostyle").ti("Delete Code").tx("🗑").fin();
|
||||
|
||||
reset_button.addEventListener("click", () => this.resetClicked());
|
||||
trash_button.addEventListener("click", () => this.trashClicked());
|
||||
this.container.append(reset_button, trash_button);
|
||||
}
|
||||
|
||||
resetClicked(): void {
|
||||
this.cpu_signals.dispatch(UiCpuSignal.RequestCpuSoftReset);
|
||||
}
|
||||
|
||||
trashClicked(): void {
|
||||
const a = confirm("Clear all code? Irreversible");
|
||||
if (a) {
|
||||
this.cpu_signals.dispatch(UiCpuSignal.RequestCpuReset);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ export default class SaveLoad implements UiComponent {
|
|||
this.cpu_signals.dispatch(UiCpuSignal.RequestMemoryDump, (memory) => {
|
||||
const flattened = new Uint8Array(256 * memory.length);
|
||||
for (let x = 0; x < 4; x++) {
|
||||
for (let y = 0; y < 256; x++) {
|
||||
for (let y = 0; y < 256; y++) {
|
||||
flattened[256 * x + y] = memory[x][y];
|
||||
}
|
||||
}
|
||||
|
|
23
src/ui/components/trashButton.ts
Normal file
23
src/ui/components/trashButton.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { el } from "../../etc";
|
||||
import { UiEventHandler, UiCpuSignalHandler, UiCpuSignal } from "../../events";
|
||||
import UiComponent from "../uiComponent";
|
||||
|
||||
export default class TrashButton implements UiComponent {
|
||||
container: HTMLElement;
|
||||
events: UiEventHandler;
|
||||
cpu_signals: UiCpuSignalHandler;
|
||||
constructor(element: HTMLElement, events: UiEventHandler, cpu_signals: UiCpuSignalHandler) {
|
||||
this.container = element;
|
||||
this.events = events;
|
||||
this.cpu_signals = cpu_signals;
|
||||
// const trash_button = el("button").cl("nostyle").ti("Delete Code").tx("🗑").fin();
|
||||
|
||||
this.container.addEventListener("click", () => this.trashClicked());
|
||||
}
|
||||
|
||||
trashClicked(): void {
|
||||
if (confirm("Clear all code? Irreversible")) {
|
||||
this.cpu_signals.dispatch(UiCpuSignal.RequestCpuReset);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,14 @@ import { u8 } from "../../num";
|
|||
import WindowBox from "../windowBox";
|
||||
import UiComponent from "../uiComponent";
|
||||
|
||||
const p_map = {
|
||||
[ParamType.Const]: "constant",
|
||||
[ParamType.ConstMemory]: "memory",
|
||||
[ParamType.Register]: "register",
|
||||
[ParamType.Bank]: "bank",
|
||||
[ParamType.RegisterAddress]: "regaddr",
|
||||
};
|
||||
|
||||
export default class InstructionExplainer extends WindowBox implements UiComponent {
|
||||
events: UiEventHandler;
|
||||
cpu_signals: UiCpuSignalHandler;
|
||||
|
@ -34,33 +42,33 @@ export default class InstructionExplainer extends WindowBox implements UiCompone
|
|||
|
||||
addParameter(param: ParameterType, pos: u8, byte: u8): void {
|
||||
const t = param.type;
|
||||
let name;
|
||||
if (t === ParamType.Const) {
|
||||
name = "constant";
|
||||
} else if (t === ParamType.Memory) {
|
||||
name = "memory";
|
||||
} else if (t === ParamType.Register) {
|
||||
name = "register";
|
||||
} else {
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
this.addBox(formatHex(byte), param.desc, name);
|
||||
|
||||
this.addBox(formatHex(byte), param.desc, p_map[t]);
|
||||
}
|
||||
|
||||
addInvalid(pos: u8, byte: u8): void {
|
||||
addInvalidParam(param: ParameterType, pos: u8, byte: u8): void {
|
||||
const t = param.type;
|
||||
|
||||
this.addBox(formatHex(byte), param.desc, `${p_map[t]} invalid`);
|
||||
}
|
||||
|
||||
addInvalidInstr(pos: u8, byte: u8): void {
|
||||
this.reset();
|
||||
this.addBox(formatHex(byte), "Invalid Instruction", "invalid");
|
||||
}
|
||||
|
||||
initCpuEvents(c: CpuEventHandler): void {
|
||||
c.listen(CpuEvent.InstructionParseBegin, ({ instr, code, pos }) => {
|
||||
this.addInstruction(instr, pos, code);
|
||||
});
|
||||
c.listen(CpuEvent.ParameterParsed, ({ param, code, pos }) => {
|
||||
this.addParameter(param, pos, code);
|
||||
});
|
||||
c.listen(CpuEvent.InstructionParsed, ({ instr, code, pos }) => {
|
||||
this.addInstruction(instr, pos, code);
|
||||
c.listen(CpuEvent.InvalidParameterParsed, ({ param, code, pos }) => {
|
||||
this.addInvalidParam(param, code, pos);
|
||||
});
|
||||
c.listen(CpuEvent.InvalidParsed, ({ code, pos }) => {
|
||||
this.addInvalid(pos, code);
|
||||
c.listen(CpuEvent.InvalidInstructionParsed, ({ code, pos }) => {
|
||||
this.addInvalidInstr(pos, code);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue