Compare commits

..

2 commits

Author SHA1 Message Date
Alexander Bass 7e4d3c8da8 update instruction set to match new version 2024-02-25 00:38:09 -05:00
Alexander Bass e827ac11b5 commit before ISA revamp 2024-02-24 20:33:54 -05:00
13 changed files with 903 additions and 251 deletions

7
TODO
View file

@ -5,9 +5,10 @@ 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
UI for showing which Memory bank is selected
VRAM select instruction
Error log

143
newISA.md Normal file
View file

@ -0,0 +1,143 @@
# ISA V0.7
# Terms
The form of this document is a list of instructions set into categories.
Each instruction starts with the recommended mnemonic and then the parameters for the instruction.
```
<Code> <mnemonic> <parameter 1> <parameter 2> ...
```
Each parameter is is abbreviated into a category
- R: Register
- M: Memory address
- C: Constant
Parts of this document marked with **!!** are uncertain and will likely change
# Instructions
## Memory & Register Management
**COPY** _from_, _to_ - Copies the byte in _from_ to the destination _to_\
0x10 COPY R M\
0x11 COPY M R\
0x12 COPY M M\
0x13 COPY R R
**ZERO** _to_ - Sets the value in _to_ to 0\
0x17 ZERO R\
0x18 ZERO M
0x19 **SET** R C - Sets the value in R to C
0x1F **SETB** R - Sets the current memory bank to the value in R
## Control Flow
0x00 **NOP** - Does nothing
**GOTO** _address_ - Moves instruction counter to _address_\
0x20 GOTO R\
0x21 GOTO C
**GOTRUE** R, _address_ - Moves instruction counter to _address_ if R is true\
0x22 GOTOTRUE R R\
0x23 GOTOTRUE R C
0x2A **GOCRY** _address_ - Moves the instruction counter to _address_ if the cary flag is set
0x2D **CALL** R - Moves the instruction counter to R and pushes the address of the call instruction
0x2E **RET** - Pops the address of the last call instruction off of the call stack and moves the instruction counter past it.
0x2F **HCF** - Halt and catch fire.
## Comparison
**EQU** _result_, _p1_, _p2_ - Sets _result_ to true if the values in _p1_ and _p2_ are the same\
0x30 EQU R R R\
0x31 EQU R R C
**LT** _result_, _p1_, _p2_ - Sets _result_ to true if the value in _p1_ is less than the value in _p2_\
0x32 LT R R R \
0x33 LT R R C
**GT** _result_, _p1_, _p2_ - Sets _result_ to true if the value in _p1_ is greater than the value in _p2_\
0x34 GT R R R \
0x35 GT R R C
Reserved for LEQ GEQ
0x36\
0x37\
0x38\
0x39
## Logic / Bitwise
**OR** R, _value_ - Sets each bit in R to its OR with the respective bit in _value_\
0x40 OR R R\
0x41 OR R C
**AND** R, _value_ - Sets each bit in R to its AND with the respective bit in _value_\
0x42 AND R R\
0x43 AND R C
**XOR** R, _value_ - Sets each bit in R to its XOR with the respective bit in _value_\
0x44 XOR R R\
0x45 XOR R C
**LBS** R, _quantity_ - Shifts each bit in R to the left by _quantity_. Fills new bits with 0\
0x46 LBS R R\
0x47 LBS R C
**RBS** R, _quantity_ - Shifts each bit in R to the right by _quantity_. Fills new bits with 0\
0x48 RBS R R\
0x49 RBS R C
0x4A **NOT** R - Flips each bit in value R
## Arithmetic
**ADD** _to_, _from_ - Adds to the the byte in _to_ with the value in _from_\
0x50 ADD R R\
0x51 ADD R C
**SUB** _to_, _from_ - Subtracts from the value in _to_ by the value in _from_\
0x52 SUB R R\
0x53 SUB R C
0x5E **INC** R - Increments the value in R by 1
0x5F **DEC** R - Decrements the value in R by 1
## IO
!! **INTXT** R - Read 1 byte of input to R
0xF0 **OUTXT** R - Prints the value in R to output as ASCII
0xF1 **OUT** R - Prints the value in R to output as base 10
0xFF **VRB** R - Selects the bank number in R to be used as VRAM
# What is True?
True is defined as having the least significant bit in a byte set to 1. False is defined as having the least significant bit in a byte set to 0.
# What are flags
The CPU has the following flags
- Carry
Flags are set as the result of instructions.
## Carry
When the add instruction runs, the result can be greater than 8 bits in size. In this case, the carry flag is set true.
!! The cary flag is set to false in two cases: At the start of an **ADD** instruction, and after running a **GOCRY** instruction

View file

@ -1,7 +1,7 @@
import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
import { byte_array_to_js_source, format_hex } from "./etc";
import { Instruction, ISA } from "./instructionSet";
import { m256, u1, u3, u8 } from "./num";
import { m256, u1, u2, u3, u8 } from "./num";
export type TempInstrState = {
pos: u8;
@ -10,13 +10,21 @@ export type TempInstrState = {
params: Array<u8>;
};
function init_banks(): [Uint8Array, Uint8Array, Uint8Array, Uint8Array] {
const banks = [];
for (let i = 0; i < 4; i++) {
banks.push(new Uint8Array(256));
}
return banks as [Uint8Array, Uint8Array, Uint8Array, Uint8Array];
}
export class Computer {
private memory: Uint8Array = new Uint8Array(256);
private vram: Uint8Array = new Uint8Array(256);
private banks: [Uint8Array, Uint8Array, Uint8Array, Uint8Array] = init_banks();
private registers: Uint8Array = new Uint8Array(8);
private call_stack: Array<u8> = [];
private carry_flag: boolean = false;
private program_counter: u8 = 0;
private bank: u1 = 0;
private bank: u2 = 0;
private current_instr: TempInstrState | null = null;
events: CpuEventHandler = new CpuEventHandler();
@ -94,34 +102,22 @@ export class Computer {
}
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];
private getMemorySilent(address: u8, bank_override?: u2): u8 {
const bank = this.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?: u2): u8 {
const value = this.getMemorySilent(address, bank_override);
this.events.dispatch(CpuEvent.MemoryAccessed, { address, bank: this.bank, value });
return value;
}
setMemory(address: u8, value: u8): void {
let bank: Uint8Array | undefined;
if (this.bank === 0) {
bank = this.memory;
} else if (this.bank === 1) {
bank = this.vram;
} else {
const _: never = this.bank;
}
if (bank === undefined) {
throw new Error("unreachable");
}
bank[address] = value;
this.banks[this.bank][address] = value;
this.events.dispatch(CpuEvent.MemoryChanged, { address, bank: this.bank, value });
}
@ -153,18 +149,28 @@ export class Computer {
return this.call_stack.pop() ?? null;
}
setBank(bank_no: u1): void {
setBank(bank_no: u2): void {
this.events.dispatch(CpuEvent.SwitchBank, { bank: bank_no });
this.bank = bank_no;
}
setCarry(state: boolean): void {
this.carry_flag = state;
this.events.dispatch(CpuEvent.SetFlagCarry, true);
}
getCarry(): boolean {
return this.carry_flag;
}
reset(): void {
this.events.dispatch(CpuEvent.Reset);
this.memory = new Uint8Array(256);
this.banks = init_banks();
this.registers = new Uint8Array(8);
this.call_stack = [];
this.current_instr = null;
this.program_counter = 0;
this.carry_flag = false;
}
init_events(ui: UiEventHandler): void {
@ -175,20 +181,21 @@ export class Computer {
}
load_memory(program: Array<u8>): void {
// TODO allow loading into other banks
console.log(byte_array_to_js_source(program));
const max_loop: u8 = Math.min(255, program.length) as u8;
for (let i: u8 = 0; i < 255; i++) {
for (let i: u8 = 0; i < 256; i++) {
// Don't fire event if no change is made
if (this.memory[i] === program[i]) continue;
if (this.banks[0][i] === program[i]) continue;
this.memory[i] = program[i];
this.banks[0][i] = program[i];
this.events.dispatch(CpuEvent.MemoryChanged, { address: i as u8, bank: 0, value: program[i] });
}
this.program_counter = 0;
}
dump_memory(): Uint8Array {
return this.memory;
return this.banks[0];
}
private step_forward(): void {

View file

@ -42,3 +42,10 @@ export function el(type: string, id?: string): HTMLElement | undefined {
}
export type NonEmptyArray<T> = T[] & { 0: T };
export const SVG_NS = "http://www.w3.org/2000/svg";
export function in_range(check: number, start: number, end: number): boolean {
if (check >= start && check <= end) return true;
return false;
}

View file

@ -5,7 +5,7 @@
*/
import { EventHandler } from "./eventHandler";
import { Instruction, ParameterType } from "./instructionSet";
import { u1, u3, u8 } from "./num";
import { u1, u2, u3, u8 } from "./num";
//
// CPU Event Handler Definition
@ -26,6 +26,7 @@ export enum CpuEvent {
// ClockStopped,
MemoryAccessed,
SwitchBank,
SetFlagCarry,
}
type VoidDataCpuEventList = CpuEvent.Halt | CpuEvent.Reset | CpuEvent.Cycle;
@ -33,16 +34,17 @@ type VoidDataCpuEventList = CpuEvent.Halt | CpuEvent.Reset | CpuEvent.Cycle;
// | CpuEvent.ClockStopped;
interface CpuEventMap {
[CpuEvent.MemoryChanged]: { address: u8; bank: u1; value: u8 };
[CpuEvent.MemoryAccessed]: { address: u8; bank: u1; value: u8 };
[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.ParameterParsed]: { pos: u8; code: u8; param: ParameterType };
[CpuEvent.InvalidParsed]: { pos: u8; code: u8 };
[CpuEvent.InstructionExecuted]: { instr: Instruction };
[CpuEvent.SwitchBank]: { bank: u1 };
[CpuEvent.SwitchBank]: { bank: u2 };
[CpuEvent.Print]: string;
[CpuEvent.SetFlagCarry]: boolean;
}
export interface CpuEventHandler extends EventHandler<CpuEvent> {

View file

@ -17,6 +17,7 @@ declare global {
interface Window {
comp: Computer;
ui: UI;
firehose: () => void;
}
}
@ -78,7 +79,6 @@ function main(): void {
ui.init_events(computer.events);
computer.load_memory(program);
computer.init_events(ui.events);
window.comp = computer;
window.ui = ui;
@ -112,10 +112,17 @@ function main(): void {
});
reader.readAsArrayBuffer(file);
});
// computer.events.firehose((ident, data) => {
// console.log(`New Event: ${CpuEvent[ident]}. data: `, data);
// });
let fire = false;
window.firehose = (): void => {
if (fire === false) {
computer.events.firehose((ident, data) => {
console.log(`New Event: ${CpuEvent[ident]}. data: `, data);
});
fire = true;
} else {
console.error("Firehose already started");
}
};
$("save_button").addEventListener("click", () => {
const memory = computer.dump_memory();
@ -125,11 +132,15 @@ function main(): void {
const link = document.createElement("a");
link.href = url;
link.download = "bin.bin";
link.style.display = "none";
link.click();
});
}
document.addEventListener("DOMContentLoaded", () => {
main();
// at least you know it's bad
try {
main();
} catch (e) {
alert(e);
}
});

View file

@ -4,8 +4,8 @@
* @license GPL-3.0
*/
import { CpuEvent, CpuEventHandler } from "./events";
import { format_hex } from "./etc";
import { isU3, m256, u1, u3, u8 } from "./num";
import { format_hex, in_range } from "./etc";
import { isU2, isU3, m256, u2, u3, u8 } from "./num";
export enum ParamType {
Const,
@ -47,7 +47,9 @@ interface GenericComputer {
setRegister: (number: u3, value: u8) => void;
pushCallStack: (address: u8) => boolean;
popCallStack: () => u8 | null;
setBank: (bank_no: u1) => void;
setBank: (bank_no: u2) => void;
getCarry(): boolean;
setCarry(state: boolean): void;
}
interface AfterExecutionComputerAction {
@ -67,11 +69,18 @@ export interface Instruction {
) => void;
}
export type InstrCategory = {
start: u8;
end: u8;
name: string;
};
export class InstructionSet {
instructions: Map<u8, Instruction>;
category_ranges: Array<InstrCategory>;
constructor() {
this.instructions = new Map();
this.category_ranges = [];
}
insertInstruction(hexCode: u8, instruction: Instruction): void {
@ -81,24 +90,152 @@ export class InstructionSet {
this.instructions.set(hexCode, instruction);
}
addCategory(c: InstrCategory): void {
// Check for overlap with existing ranges
for (const r of this.category_ranges) {
if (in_range(c.start, r.start, r.end) || in_range(c.end, r.start, r.end)) {
throw new Error(`Range of ${c.start}...${c.end} is already registered`);
}
}
this.category_ranges.push(c);
}
getInstruction(hexCode: u8): Instruction | null {
// console.log(format_hex(hexCode));
return this.instructions.get(hexCode) ?? null;
}
}
function category(start: u8, end: u8, name: string): InstrCategory {
return { start, end, name };
}
export const ISA = new InstructionSet();
ISA.addCategory(category(0x10, 0x1f, "Memory & Register Management"));
ISA.addCategory(category(0x20, 0x2f, "Control Flow"));
ISA.addCategory(category(0x30, 0x3f, "Comparison"));
ISA.addCategory(category(0x40, 0x4f, "Logic / Bitwise"));
ISA.addCategory(category(0x50, 0x5f, "Arithmetic"));
ISA.addCategory(category(0xf0, 0xff, "IO"));
// The definitions for actual instructions.
ISA.insertInstruction(0x00, {
name: "NoOp",
desc: "No operation; do nothing",
params: [],
execute: () => {},
//
// MEMORY & REGISTER MANAGEMENT
// 0x10 -> 0x1F
//
// COPY
ISA.insertInstruction(0x10, {
name: "Copy R -> M",
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")],
execute(c, p) {
const [register_no, mem_address] = p;
if (!isU3(register_no)) throw new Error("TODO");
c.setMemory(mem_address, c.getRegister(register_no));
},
});
ISA.insertInstruction(0x10, {
ISA.insertInstruction(0x11, {
name: "Copy M -> R",
desc: "Copy the byte in memory address (P1) to the register (P2)",
params: [new MemorParam(""), new RegisParam("")],
execute(c, p) {
const [register_no, mem_address] = p;
if (!isU3(register_no)) throw new Error("TODO");
c.setRegister(register_no, c.getMemory(mem_address));
},
});
ISA.insertInstruction(0x12, {
name: "Copy M -> M",
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")],
execute(c, p) {
const [mem_address_1, mem_address_2] = p;
c.setMemory(mem_address_2, c.getMemory(mem_address_1));
},
});
ISA.insertInstruction(0x13, {
name: "Copy R -> R",
desc: "Copy the byte in register (P1) to register (P2)",
params: [new RegisParam("Copy the byte 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.getRegister(register_no_1));
},
});
ISA.insertInstruction(0x17, {
name: "Zero Register",
desc: "Set the byte in register (P1) to 0",
params: [new RegisParam("Set the value in this register to 0")],
execute(c, p) {
const register_no = p[0];
if (!isU3(register_no)) throw new Error("todo");
c.setRegister(register_no, 0);
},
});
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")],
execute(c, p) {
const mem_address = p[0];
c.setMemory(mem_address, 0);
},
});
ISA.insertInstruction(0x19, {
name: "Set Register",
desc: "Assigns constant value (P2) to register (P1)",
params: [new RegisParam("Set this register"), new ConstParam("to this constant")],
execute(c, p) {
const [register_no, value] = p;
if (!isU3(register_no)) throw new Error("TODO");
c.setRegister(register_no, value);
},
});
ISA.insertInstruction(0x1f, {
name: "Set bank",
desc: "Selects which bank of memory to write and read to",
params: [new ConstParam("Bank number")],
execute(c, p) {
const bank_no = p[0];
if (!isU2(bank_no)) {
throw new Error("TODO");
}
c.setBank(bank_no);
},
});
//
// CONTROL FLOW
// 0x20 -> 0x2F
//
ISA.insertInstruction(0x20, {
name: "Goto",
desc: "Moves the CPU instruction counter to the value in register (P1)",
params: [new RegisParam("new instruction counter location")],
execute: (c, p, a) => {
const register_no = p[0];
if (!isU3(register_no)) {
throw new Error("todo");
}
const new_address = c.getRegister(register_no);
c.setProgramCounter(new_address);
a.noStep();
},
});
ISA.insertInstruction(0x21, {
name: "Goto",
desc: "Moves the CPU instruction counter to the value in (P1)",
params: [new ConstParam("new instruction counter location")],
@ -109,184 +246,67 @@ ISA.insertInstruction(0x10, {
},
});
ISA.insertInstruction(0x20, {
name: "LoadToRegister",
desc: "Sets the byte in register (P1) to be the contents of memory cell at address in register (P2)",
params: [new RegisParam("Set this register to"), new RegisParam("the byte held in this memory address")],
execute(c, p) {
const [register_no, register_2] = p;
if (!isU3(register_no)) throw new Error("TODO");
if (!isU3(register_2)) throw new Error("TODO");
const mem_value = c.getMemory(c.getRegister(register_2));
c.setRegister(register_no, mem_value);
},
});
ISA.insertInstruction(0x21, {
name: "SaveToMemory",
desc: "Writes the byte in register (P1) to the memory cell (P2)",
params: [new RegisParam("Write the byte in this register"), new MemorParam("To this memory address")],
execute(c, p) {
const [register_no, mem_address] = p;
if (!isU3(register_no)) throw new Error("TODO");
c.setMemory(mem_address, c.getRegister(register_no));
},
});
ISA.insertInstruction(0x2f, {
name: "AssignRegister",
desc: "Assigns constant value (P2) to register (P1)",
params: [new RegisParam("Set this register"), new ConstParam("to this constant")],
execute(c, p) {
const [register_no, value] = p;
if (!isU3(register_no)) throw new Error("TODO");
c.setRegister(register_no, value);
},
});
ISA.insertInstruction(0x11, {
name: "GotoIfLowBitHigh",
desc: "Moves the CPU instruction counter to the value in (P1) if the value in register (P2) has the lowest bit true",
params: [new ConstParam("Set program counter to this constant"), new RegisParam("if this register's 1 bit is set")],
execute(c, p, a) {
const [new_address, check_register_no] = p;
if (!isU3(check_register_no)) throw new Error("TODO");
if (c.getRegister(check_register_no) % 2 === 1) {
c.setProgramCounter(new_address);
a.noStep();
}
},
});
ISA.insertInstruction(0x30, {
name: "Increment",
desc: "Increments the value within register (P1) by 1",
params: [new RegisParam("register to be incremented")],
execute(c, p) {
const register_no = p[0];
if (!isU3(register_no)) throw new Error("TODO");
const current_value = c.getRegister(register_no);
const new_value = m256(current_value + 1);
c.setRegister(register_no, new_value);
},
});
ISA.insertInstruction(0x31, {
name: "Decrement",
desc: "Decrements the value within register (P1) by 1",
params: [new RegisParam("register to be decremented")],
execute(c, p) {
const register_no = p[0];
if (!isU3(register_no)) throw new Error("TODO");
const current_value = c.getRegister(register_no);
let new_value = current_value - 1;
if (new_value === -1) {
new_value = 255;
}
c.setRegister(register_no, new_value as u8);
},
});
ISA.insertInstruction(0x40, {
name: "Add",
desc: "Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256)",
params: [new RegisParam("set this register to"), new RegisParam("it's sum with the value 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");
const new_value = m256(c.getRegister(register_no_1) + c.getRegister(register_no_2));
c.setRegister(register_no_1, new_value);
},
});
ISA.insertInstruction(0x50, {
name: "Equals",
desc: "If byte in register (P2) equals byte in register (P3), set byte in register (P1) to 0x01",
params: [
new RegisParam("Set this register to be 0x01"),
new RegisParam("if this register and"),
new RegisParam("this register are equal (else 0x00)"),
],
execute(c, p) {
const [register_no_1, register_no_2, register_no_3] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
if (!isU3(register_no_2)) throw new Error("TODO");
if (!isU3(register_no_3)) throw new Error("TODO");
const truth = c.getRegister(register_no_2) === c.getRegister(register_no_3) ? 0x01 : 0x00;
c.setRegister(register_no_1, truth);
},
});
ISA.insertInstruction(0xfe, {
name: "PrintASCII",
desc: "Prints the ASCII byte in register (P1) to console",
params: [new RegisParam("Register to print from")],
execute(c, p, a) {
const register_no = p[0];
if (!isU3(register_no)) throw new Error("TODO");
const asciiByte = c.getRegister(register_no);
const char = String.fromCharCode(asciiByte);
a.dispatch(CpuEvent.Print, char);
},
});
ISA.insertInstruction(0x48, {
name: "Bitwise And",
desc: "Takes each bit in register (P1) and compares to the respective bit in register (P2). Each bit in register (P1) is set to 1 if the respective bit in both registers are 1",
ISA.insertInstruction(0x22, {
name: "Goto if True",
desc: "Moves the instruction counter to the value in register (P2) if the value in register (P1) is true",
params: [new RegisParam(""), new RegisParam("")],
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");
const new_value = c.getRegister(register_no_1) & c.getMemory(register_no_2);
c.setRegister(register_no_1, new_value as u8);
execute: (c, p, a) => {
const register_no_1 = p[0];
if (!isU3(register_no_1)) throw new Error("todo");
const bool = c.getRegister(register_no_1);
if (!bool) return;
const register_no_2 = p[1];
if (!isU3(register_no_2)) throw new Error("todo");
const new_address = c.getRegister(register_no_2);
c.setProgramCounter(new_address);
a.noStep();
},
});
ISA.insertInstruction(0xff, {
name: "Print",
desc: "Prints the byte in register (P1) to console as base 10",
params: [new RegisParam("Register to print from")],
execute(c, p, a) {
ISA.insertInstruction(0x23, {
name: "Goto if True",
desc: "Moves the instruction counter to the value in (P2) if the value in register (P1) is true",
params: [new RegisParam(""), new ConstParam("")],
execute: (c, p, a) => {
const [register_no, constant_value] = p;
if (!isU3(register_no)) throw new Error("todo");
const bool = c.getRegister(register_no);
if (!bool) return;
c.setProgramCounter(constant_value);
a.noStep();
},
});
ISA.insertInstruction(0x28, {
name: "Goto if Carry Flag set",
desc: "Moves the instruction counter to the value in register (P1) if CPU Carry flag is true",
params: [new RegisParam("")],
execute: (c, p, a) => {
if (!c.getCarry()) return;
const register_no = p[0];
if (!isU3(register_no)) throw new Error("TODO");
const byte = c.getRegister(register_no);
a.dispatch(CpuEvent.Print, byte.toString(10));
if (!isU3(register_no)) throw new Error("todo");
const register_value = c.getRegister(register_no);
c.setProgramCounter(register_value);
a.noStep();
c.setCarry(false);
},
});
ISA.insertInstruction(0x29, {
name: "Goto if Carry Flag set",
desc: "Moves the instruction counter to the value in (P1) if CPU Carry flag is true",
params: [new ConstParam("")],
execute: (c, p, a) => {
if (!c.getCarry()) return;
const goto_address = p[0];
c.setProgramCounter(goto_address);
a.noStep();
c.setCarry(false);
},
});
ISA.insertInstruction(0xfd, {
name: "Print 16 bit",
desc: "Prints the byte in register (P1) as the upper half and the byte in register (P2) as the lower half of a 16 bit number. Formats to decimal",
params: [new RegisParam("Upper 8 bits of number"), new RegisParam("Lower 8 bits of number")],
execute(c, p, a) {
const [upper_register_no, lower_register_no] = p;
if (!isU3(upper_register_no)) throw new Error("TODO");
if (!isU3(lower_register_no)) throw new Error("TODO");
const upper = c.getRegister(upper_register_no);
const lower = c.getRegister(lower_register_no);
const sum = upper * 16 * 16 + lower;
a.dispatch(CpuEvent.Print, sum.toString(10));
},
});
ISA.insertInstruction(0x66, {
name: "Halt and Catch Fire",
desc: "Stops program execu..... Fire! FIRE EVERYWHERE!",
params: [],
execute(c, p, a) {
a.dispatch(CpuEvent.Halt);
},
});
ISA.insertInstruction(0xa0, {
ISA.insertInstruction(0x2d, {
name: "Call",
desc: "Calls a subroute",
desc: "",
params: [new ConstParam("the subroutine at this memory address")],
execute(c, p, a) {
const current_address = c.getProgramCounter();
@ -300,30 +320,390 @@ ISA.insertInstruction(0xa0, {
},
});
ISA.insertInstruction(0xa1, {
ISA.insertInstruction(0x2e, {
name: "Return",
desc: "returns from a subroutine",
desc: "",
params: [],
execute(c, p, a) {
const new_address = c.popCallStack();
if (new_address === null) {
throw new Error("TODO handle this");
}
if (new_address === null) throw new Error("TODO handle this");
c.setProgramCounter(m256(new_address + 1));
a.noStep();
},
});
ISA.insertInstruction(0xb1, {
name: "Set bank",
desc: "Selects which bank of memory to write and read to",
params: [new ConstParam("Bank number")],
execute(c, p) {
const bank_no = p[0];
if (!(bank_no === 1 || bank_no === 0)) {
throw new Error("TODO");
}
c.setBank(bank_no);
ISA.insertInstruction(0x2c, {
name: "NoOp",
desc: "No operation; do nothing",
params: [],
execute: () => {},
});
ISA.insertInstruction(0x2f, {
name: "Halt and Catch Fire",
desc: "Stops program execu..... Fire! FIRE EVERYWHERE!",
params: [],
execute(c, p, a) {
a.dispatch(CpuEvent.Halt);
},
});
//
// Comparison
// 0x30 -> 0x3F
//
ISA.insertInstruction(0x30, {
name: "Equals",
desc: "If byte in register (P2) equals byte in register (P3), set byte in register (P1) to true",
params: [
new RegisParam("Set this register to true"),
new RegisParam("if this register and"),
new RegisParam("this register are equal (else false)"),
],
execute(c, p) {
const [register_no_1, register_no_2, register_no_3] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
if (!isU3(register_no_2)) throw new Error("TODO");
if (!isU3(register_no_3)) throw new Error("TODO");
const truth = c.getRegister(register_no_2) === c.getRegister(register_no_3) ? 0x01 : 0x00;
c.setRegister(register_no_1, truth);
},
});
ISA.insertInstruction(0x31, {
name: "Equals",
desc: "If byte in register (P2) equals constant byte (P3), set byte in register (P1) to true",
params: [
new RegisParam("Set this register to true"),
new RegisParam("if this register and"),
new ConstParam("this constant are equal (else false)"),
],
execute(c, p) {
const [register_no_1, register_no_2, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
if (!isU3(register_no_2)) throw new Error("TODO");
const truth = c.getRegister(register_no_2) === constant_value ? 0x01 : 0x00;
c.setRegister(register_no_1, truth);
},
});
ISA.insertInstruction(0x32, {
name: "Less Than",
desc: "Sets register (P1) to true if value in register (P2) is less than the value in register (P3)",
params: [
new RegisParam("Set this register to true"),
new RegisParam("if this register is less than"),
new RegisParam("this register"),
],
execute(c, p) {
const [register_no_1, register_no_2, register_no_3] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
if (!isU3(register_no_2)) throw new Error("TODO");
if (!isU3(register_no_3)) throw new Error("TODO");
const truth = c.getRegister(register_no_2) < c.getRegister(register_no_3) ? 0x01 : 0x00;
c.setRegister(register_no_1, truth);
},
});
ISA.insertInstruction(0x33, {
name: "Less Than",
desc: "Sets register (P1) to true if value in register (P2) is less than the constant value (P3)",
params: [
new RegisParam("Set this register to true"),
new RegisParam("if this register is less than"),
new ConstParam("this constant"),
],
execute(c, p) {
const [register_no_1, register_no_2, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
if (!isU3(register_no_2)) throw new Error("TODO");
const truth = c.getRegister(register_no_2) < constant_value ? 0x01 : 0x00;
c.setRegister(register_no_1, truth);
},
});
ISA.insertInstruction(0x34, {
name: "Greater Than",
desc: "Sets register (P1) to true if value in register (P2) is greater than the value in register (P3)",
params: [
new RegisParam("Set this register to true"),
new RegisParam("if this register is greater than"),
new RegisParam("this register"),
],
execute(c, p) {
const [register_no_1, register_no_2, register_no_3] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
if (!isU3(register_no_2)) throw new Error("TODO");
if (!isU3(register_no_3)) throw new Error("TODO");
const truth = c.getRegister(register_no_2) > c.getRegister(register_no_3) ? 0x01 : 0x00;
c.setRegister(register_no_1, truth);
},
});
ISA.insertInstruction(0x35, {
name: "Greater than",
desc: "Sets register (P1) to true if value in register (P2) is greater than the constant value (P3)",
params: [
new RegisParam("Set this register to true"),
new RegisParam("if this register is greater than"),
new ConstParam("this constant"),
],
execute(c, p) {
const [register_no_1, register_no_2, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
if (!isU3(register_no_2)) throw new Error("TODO");
const truth = c.getRegister(register_no_2) > constant_value ? 0x01 : 0x00;
c.setRegister(register_no_1, truth);
},
});
//
// Logic / Bitwise
// 0x40 -> 0x4F
//
ISA.insertInstruction(0x40, {
name: "Bitwise OR",
desc: "Sets each bit in register (P1) to its OR with the respective bit in register (P2)",
params: [new RegisParam(""), new RegisParam("")],
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");
const new_value = c.getRegister(register_no_1) | c.getRegister(register_no_2);
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x41, {
name: "Bitwise OR",
desc: "Sets each bit in register (P1) to its OR with the respective bit in constant value (P2)",
params: [new RegisParam(""), new ConstParam("")],
execute(c, p) {
const [register_no_1, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
const new_value = c.getRegister(register_no_1) | constant_value;
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x42, {
name: "Bitwise AND",
desc: "Sets each bit in register (P1) to its AND with the respective bit in register (P2)",
params: [new RegisParam(""), new RegisParam("")],
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");
const new_value = c.getRegister(register_no_1) & c.getRegister(register_no_2);
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x43, {
name: "Bitwise AND",
desc: "Sets each bit in register (P1) to its AND with the respective bit in constant value (P2)",
params: [new RegisParam(""), new ConstParam("")],
execute(c, p) {
const [register_no_1, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
const new_value = c.getRegister(register_no_1) & constant_value;
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x44, {
name: "Bitwise XOR",
desc: "Sets each bit in register (P1) to its XOR with the respective bit in register (P2)",
params: [new RegisParam(""), new RegisParam("")],
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");
const new_value = c.getRegister(register_no_1) ^ c.getRegister(register_no_2);
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x45, {
name: "Bitwise XOR",
desc: "Sets each bit in register (P1) to its XOR with the respective bit in constant value (P2)",
params: [new RegisParam(""), new ConstParam("")],
execute(c, p) {
const [register_no_1, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
const new_value = c.getRegister(register_no_1) ^ constant_value;
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x46, {
name: "Left Bit Shift",
desc: "Shifts each bit in register (P1) to the left by the amount in register (P2). Fills new bits with 0",
params: [new RegisParam(""), new RegisParam("")],
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");
const new_value = c.getRegister(register_no_1) << c.getRegister(register_no_2);
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x47, {
name: "Left Bit Shift",
desc: "Shifts each bit in register (P1) to the left by the constant value (P2). Fills new bits with 0",
params: [new RegisParam(""), new ConstParam("")],
execute(c, p) {
const [register_no_1, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
const new_value = c.getRegister(register_no_1) << constant_value;
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x48, {
name: "Right Bit Shift",
desc: "Shifts each bit in register (P1) to the right by the amount in register (P2). Fills new bits with 0",
params: [new RegisParam(""), new RegisParam("")],
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");
const new_value = c.getRegister(register_no_1) >> c.getRegister(register_no_2);
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x49, {
name: "Right Bit Shift",
desc: "Shifts each bit in register (P1) to the right by the constant value (P2). Fills new bits with 0",
params: [new RegisParam(""), new ConstParam("")],
execute(c, p) {
const [register_no_1, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
const new_value = c.getRegister(register_no_1) >> constant_value;
c.setRegister(register_no_1, new_value as u8);
},
});
ISA.insertInstruction(0x4a, {
name: "Bitwise NOT",
desc: "Flips each bit in register (P1)",
params: [new RegisParam("")],
execute(c, p) {
const register_no = p[0];
if (!isU3(register_no)) throw new Error("TODO");
const new_value = ~c.getRegister(register_no);
c.setRegister(register_no, new_value as u8);
},
});
//
// Arithmetic
// 0x50 -> 0x5F
//
ISA.insertInstruction(0x50, {
name: "Add",
desc: "Adds to the byte in register (P1) with the value in register (P2)",
params: [new RegisParam("set this register to"), new RegisParam("it's sum with the value 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");
const sum = c.getRegister(register_no_1) + c.getRegister(register_no_2);
if (sum > 255) {
c.setCarry(true);
}
c.setRegister(register_no_1, m256(sum));
},
});
ISA.insertInstruction(0x51, {
name: "Add",
desc: "Adds to the byte in register (P1) with the value in register (P2)",
params: [new RegisParam("set this register to"), new ConstParam("it's sum with this constant")],
execute(c, p) {
const [register_no_1, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
const sum = c.getRegister(register_no_1) + constant_value;
if (sum > 255) c.setCarry(true);
c.setRegister(register_no_1, m256(sum));
},
});
ISA.insertInstruction(0x52, {
name: "Add",
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")],
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");
const difference = c.getRegister(register_no_1) - c.getRegister(register_no_2);
if (difference < 0) {
c.setCarry(true);
}
c.setRegister(register_no_1, m256(difference));
},
});
ISA.insertInstruction(0x53, {
name: "Add",
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")],
execute(c, p) {
const [register_no_1, constant_value] = p;
if (!isU3(register_no_1)) throw new Error("TODO");
const difference = c.getRegister(register_no_1) + constant_value;
if (difference < 0) c.setCarry(true);
c.setRegister(register_no_1, m256(difference));
},
});
ISA.insertInstruction(0x5e, {
name: "Increment",
desc: "Increments the value within register (P1) by 1",
params: [new RegisParam("register to be incremented")],
execute(c, p) {
const register_no = p[0];
if (!isU3(register_no)) throw new Error("TODO");
const current_value = c.getRegister(register_no);
const incremented = current_value + 1;
if (incremented > 255) c.setCarry(true);
c.setRegister(register_no, m256(incremented));
},
});
ISA.insertInstruction(0x5f, {
name: "Decrement",
desc: "Decrements the value within register (P1) by 1",
params: [new RegisParam("register to be decremented")],
execute(c, p) {
const register_no = p[0];
if (!isU3(register_no)) throw new Error("TODO");
const current_value = c.getRegister(register_no);
const decremented = current_value - 1;
if (decremented < 0) c.setCarry(true);
c.setRegister(register_no, m256(decremented) as u8);
},
});
//
// IO
// 0xF0 -> 0xFF
//
ISA.insertInstruction(0xf0, {
name: "PrintASCII",
desc: "Prints the ASCII byte in register (P1) to console",
params: [new RegisParam("Register to print from")],
execute(c, p, a) {
const register_no = p[0];
if (!isU3(register_no)) throw new Error("TODO");
const asciiByte = c.getRegister(register_no);
const char = String.fromCharCode(asciiByte);
a.dispatch(CpuEvent.Print, char);
},
});
ISA.insertInstruction(0xf1, {
name: "Print",
desc: "Prints the byte in register (P1) to console as base 10",
params: [new RegisParam("Register to print from")],
execute(c, p, a) {
const register_no = p[0];
if (!isU3(register_no)) throw new Error("TODO");
const byte = c.getRegister(register_no);
a.dispatch(CpuEvent.Print, byte.toString(10));
},
});

View file

@ -3,8 +3,8 @@
* @copyright Alexander Bass 2024
* @license GPL-3.0
*/
import { format_hex } from "./etc";
import { Instruction, InstructionSet } from "./instructionSet";
import { format_hex, in_range } from "./etc";
import { InstrCategory, Instruction, InstructionSet, ParameterType, ParamType } from "./instructionSet";
import { u8 } from "./num.js";
export function generate_isa(iset: InstructionSet): string {
@ -17,12 +17,43 @@ export function generate_isa(iset: InstructionSet): string {
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) => in_range(instruction[0], 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`;
current_category = cat;
}
const hex_code = format_hex(instruction[0]);
const short_description = instruction[1].name.padEnd(max_instr_name_len, " ");
const parameter_count = instruction[1].params.length;
const parameters = parameter_description(instruction[1].params);
const description = instruction[1].desc;
output_string += `0x${hex_code}: ${short_description} - ${parameter_count} Parameter - ${description}\n`;
output_string += `0x${hex_code}: ${short_description}`;
if (parameters.length !== 0) {
output_string += ` -${parameters}- `;
} else {
output_string += " - ";
}
output_string += `${description}\n`;
}
return output_string;
}
function parameter_description(params: Array<ParameterType>): string {
let str = "";
if (params.length !== 0) {
str += " ";
}
for (const p of params) {
const p_map = { [ParamType.Const]: "C", [ParamType.Memory]: "M", [ParamType.Register]: "R" };
const char = p_map[p.type];
str += char;
str += " ";
}
return str;
}

View file

@ -30,6 +30,13 @@ export type u4 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14
export const m256 = (number: number): u8 => (number % 256) as u8;
export function isU2(n: number): n is u2 {
if (n < 4 && n >= 0) {
return true;
}
return false;
}
/**
* Determines whether a number is a u3 type (unsigned 3-bit integer).
* Does not check for non integers

View file

@ -52,7 +52,7 @@ body {
"title . . . ."
"title . . . printout "
"title . . . printout "
". buttons buttons buttons buttons";
". buttons buttons . .";
#memory {
grid-column: 2/4;
grid-row: 2/6;
@ -198,6 +198,53 @@ label.button:hover {
#controls_bar {
grid-area: buttons;
display: flex;
gap: 10px;
}
input[type="range"] {
background-color: transparent;
-webkit-appearance: none;
appearance: none;
margin: 18px 0;
// width: 100%;
}
input[type="range"]:focus {
outline: none;
}
// 2024 and we still have to do this
input[type="range"]::-webkit-slider-runnable-track {
height: 0.5em;
cursor: pointer;
background: yellow;
border-radius: 0px;
}
input[type="range"]::-webkit-slider-thumb {
border: 4px solid yellow;
background-color: black;
height: 42px;
width: 20px;
border-radius: 0px;
cursor: pointer;
margin-top: -18px;
-webkit-appearance: none;
}
input[type="range"]:focus::-webkit-slider-runnable-track {
background: yellow;
}
input[type="range"]::-moz-range-track {
height: 0.5em;
cursor: pointer;
background: yellow;
border-radius: 0px;
}
input[type="range"]::-moz-range-thumb {
border: 4px solid yellow;
background-color: black;
height: 36px;
width: 16px;
border-radius: 0px;
cursor: pointer;
}

22
src/ui/bankIndicator.ts Normal file
View file

@ -0,0 +1,22 @@
import { UiEventHandler, CpuEventHandler, CpuEvent } from "../events";
import { u1, u2 } from "../num";
import { UiComponent } from "./uiComponent";
class BankIndicator implements UiComponent {
element: HTMLElement;
events: UiEventHandler;
constructor(element: HTMLElement, events: UiEventHandler) {
this.element = element;
this.events = events;
}
reset(): void {}
select_bank(bank_no: u2): void {}
init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.SwitchBank, ({ bank }) => {
this.select_bank(bank);
});
}
}

View file

@ -31,7 +31,8 @@ export class MemoryView extends CelledViewer implements UiComponent {
}
init_cpu_events(c: CpuEventHandler): void {
c.listen(CpuEvent.MemoryAccessed, ({ address, value }) => {
c.listen(CpuEvent.MemoryAccessed, ({ address, bank, value }) => {
if (bank !== 0) return;
if (this.last_accessed_cell !== address) {
if (this.last_accessed_cell !== null) {
this.remove_cell_class(this.last_accessed_cell, "last_access");

View file

@ -43,12 +43,6 @@ export class Screen implements UiComponent {
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;
@ -58,7 +52,6 @@ export class Screen implements UiComponent {
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);
}