mirror of
https://github.com/Asraelite/littlebigcomputer.git
synced 2025-07-17 16:16:51 +00:00
Init commit
This commit is contained in:
commit
c45ad79440
48 changed files with 6786 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
node_modules/
|
||||||
|
.DS_Store
|
||||||
|
secret.js
|
||||||
|
package-lock.json
|
||||||
|
assembler/**/*.js
|
||||||
|
!assembler/ldt/**
|
||||||
|
c/
|
34
README.md
Normal file
34
README.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# LittleBigComputer
|
||||||
|
|
||||||
|
Tools for working with computers built in the game LittleBigPlanet.
|
||||||
|
|
||||||
|
Available online at http://littlebigcomputer.com
|
||||||
|
|
||||||
|
## Assembler
|
||||||
|
|
||||||
|
Located in the `assembler/` directory.
|
||||||
|
|
||||||
|
Assemblers for computers built in LittleBigPlanet.
|
||||||
|
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
* Install Typescript: `npm install -g typescript`
|
||||||
|
|
||||||
|
* Compile with `tsc main.ts --target es2020`
|
||||||
|
|
||||||
|
* Host the files on a static server.
|
||||||
|
|
||||||
|
|
||||||
|
### Emulator
|
||||||
|
|
||||||
|
There are emulators for Parva and the V8 included in the assembler. Click "Reset" before running any program with them.
|
||||||
|
|
||||||
|
There is currently no support for interrupts.
|
||||||
|
|
||||||
|
|
||||||
|
## Chatbot
|
||||||
|
|
||||||
|
The assembler is capable for sending data directly to LittleBigPlanet using the in-game Livestream Sensor. This uses a chatbot which sends messages to your Twitch stream.
|
||||||
|
|
||||||
|
I've only configured it for personal use. If you are interested in using it yourself, message me.
|
63
assembler/assembly.ts
Normal file
63
assembler/assembly.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
export type Offset = string | number;
|
||||||
|
export type Value = string | number;
|
||||||
|
export type Resolvable = string | LabelReference;
|
||||||
|
export type LabelReference = {
|
||||||
|
tag: 'label';
|
||||||
|
type: 'relative' | 'absolute';
|
||||||
|
address: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ArchSpecification = {
|
||||||
|
syntaxHighlighting: any; // : Parser, TODO
|
||||||
|
assemble: Assemble;
|
||||||
|
wordSize: number;
|
||||||
|
maxWordsPerInstruction: number;
|
||||||
|
documentation: string;
|
||||||
|
emulator: null | Emulator;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Emulator {
|
||||||
|
pc: number;
|
||||||
|
init: (memory: Array<number>) => void;
|
||||||
|
step: () => void;
|
||||||
|
printState: () => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LineSource = {
|
||||||
|
lineNumber: number;
|
||||||
|
realInstruction: string;
|
||||||
|
sourceInstruction: string;
|
||||||
|
sourceInstructionCommented: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AssemblyInput = {
|
||||||
|
source: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InputError = {
|
||||||
|
line: number;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AssemblyOutput = {
|
||||||
|
lines: Array<OutputLine>;
|
||||||
|
errors: Array<InputError>;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OutputLine = InstructionOutputLine | LabelOutputLine;
|
||||||
|
|
||||||
|
export type InstructionOutputLine = {
|
||||||
|
tag: 'instruction';
|
||||||
|
address: number;
|
||||||
|
bits: string;
|
||||||
|
source: LineSource;
|
||||||
|
};
|
||||||
|
export type LabelOutputLine = { tag: 'label', name: string };
|
||||||
|
|
||||||
|
export type Assemble = (program: AssemblyInput) => AssemblyOutput;
|
||||||
|
|
||||||
|
export const Parser = window['Parser']; // TODO
|
||||||
|
|
||||||
|
export type ArchName = 'v8' | 'parva_0_1';
|
298
assembler/bit_utils.ts
Normal file
298
assembler/bit_utils.ts
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
const EPSILON = 2 ** (-24);
|
||||||
|
|
||||||
|
class Analog {
|
||||||
|
value: Float32Array;
|
||||||
|
private constructor(value: number) {
|
||||||
|
this.value = new Float32Array(1);
|
||||||
|
this.value[0] = Math.max(Math.min(value, 1), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromRaw(value: number): Analog {
|
||||||
|
return new Analog(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromEpsilon(value: number): Analog {
|
||||||
|
return new Analog(EPSILON * value);
|
||||||
|
}
|
||||||
|
|
||||||
|
asInternal(): string {
|
||||||
|
const view = new DataView(this.value.buffer);
|
||||||
|
const int = view.getUint32(0, true);
|
||||||
|
const bits = int.toString(2).padStart(32, '0');
|
||||||
|
const sign = bits[0];
|
||||||
|
const exponent = bits.slice(1, 9);
|
||||||
|
const mantissa = bits.slice(9);
|
||||||
|
return `${sign} ${exponent} ${mantissa}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
rawValue(): number {
|
||||||
|
return this.value[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
asBinary(sectionSize: number = 24, showFraction = true): string {
|
||||||
|
if (this.value[0] >= 1) {
|
||||||
|
return '>=1';
|
||||||
|
}
|
||||||
|
const sign = this.value[0] < 0 ? '-' : ' ';
|
||||||
|
const number = Math.abs(this.value[0]) * (2 ** 24);
|
||||||
|
const bits = (number | 0).toString(2).padStart(24, '0');
|
||||||
|
const fraction = (number - (number | 0));
|
||||||
|
const fractionBits = (fraction * (2 ** 24) | 0).toString(2).padStart(24, '0');
|
||||||
|
const parts = bits.match(new RegExp(`.{1,${sectionSize}}`, 'g'))!;
|
||||||
|
if (showFraction) {
|
||||||
|
parts.push('.', ...fractionBits.match(/.{1,6}/g)!);
|
||||||
|
}
|
||||||
|
return `${sign}${parts.join(' ')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
asHex(): string {
|
||||||
|
const number = Math.abs(this.value[0]) * (2 ** 24);
|
||||||
|
const hex = (number | 0).toString(16).padStart(6, '0');
|
||||||
|
return `0x${hex}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
asNote(): string {
|
||||||
|
const max = (2 ** 19);
|
||||||
|
const result = this.value[0] * max;
|
||||||
|
return `${result.toFixed(5)} x32`;
|
||||||
|
}
|
||||||
|
|
||||||
|
asEpsilon(): number {
|
||||||
|
return this.value[0] / EPSILON;
|
||||||
|
}
|
||||||
|
|
||||||
|
get v() {
|
||||||
|
return this.rawValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
get i() {
|
||||||
|
return this.asInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
get b() {
|
||||||
|
return this.asBinary();
|
||||||
|
}
|
||||||
|
|
||||||
|
get bs() {
|
||||||
|
return this.asBinary(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
get b8() {
|
||||||
|
return this.asBinary(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
get bh() {
|
||||||
|
return this.asBinary(6, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
get h() {
|
||||||
|
return this.asHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
get n() {
|
||||||
|
return this.asNote();
|
||||||
|
}
|
||||||
|
|
||||||
|
get e() {
|
||||||
|
return this.asEpsilon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function int24(value: number): Analog {
|
||||||
|
return Analog.fromEpsilon(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function raw(value: number): Analog {
|
||||||
|
return Analog.fromRaw(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rand(): Analog {
|
||||||
|
return Analog.fromEpsilon((Math.random() * (2 ** 24)) | 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function battery(percentage: number): Analog {
|
||||||
|
return Analog.fromRaw(percentage / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function add(...values: Array<Analog>): Analog {
|
||||||
|
if (values.length === 1) return values[0];
|
||||||
|
const result = values[0].value[0] + values[1].value[0];
|
||||||
|
return add(Analog.fromRaw(result), ...values.slice(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addOverflowing(a: Analog, b: Analog): [Analog, Analog] {
|
||||||
|
let result = a.v + b.v;
|
||||||
|
let carry = 0;
|
||||||
|
if (result > 1) {
|
||||||
|
carry = 1;
|
||||||
|
result -= 1;
|
||||||
|
}
|
||||||
|
return [Analog.fromRaw(result), Analog.fromRaw(carry)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function sub(first: Analog, ...values: Array<Analog>): Analog {
|
||||||
|
if (values.length === 0) return first;
|
||||||
|
const result = first.value[0] - values[0].value[0];
|
||||||
|
return sub(Analog.fromRaw(result), ...values.slice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function compare(first: Analog, second: Analog): Analog {
|
||||||
|
if (Math.abs(first.value[0]) >= Math.abs(second.value[0])) {
|
||||||
|
return first;
|
||||||
|
} else {
|
||||||
|
return second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mul(first: Analog, ...values: Array<Analog>): Analog {
|
||||||
|
if (values.length === 0) return first;
|
||||||
|
const result = first.value[0] * values[0].value[0];
|
||||||
|
return mul(Analog.fromRaw(result), ...values.slice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mulRaw(first: Analog, ...values: Array<Analog>): Analog {
|
||||||
|
if (values.length === 0) return first;
|
||||||
|
const result = first.value[0] * values[0].value[0];
|
||||||
|
return mul(Analog.fromRaw(result), ...values.slice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function recip(x: number): Analog {
|
||||||
|
const value = ((2 ** 25) / x + 1) & 0xffffff;
|
||||||
|
return int24(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function not(first: Analog): Analog {
|
||||||
|
const result = 1.0 - first.value[0];
|
||||||
|
return Analog.fromRaw(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
function raise(value: Analog, inputs: number, repetitions: number): Analog {
|
||||||
|
let result = value;
|
||||||
|
for (let i = 0; i < repetitions; i++) {
|
||||||
|
result = add(...Array(inputs).fill(result));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lower(value: Analog, repetitions: number): Analog {
|
||||||
|
let result = value;
|
||||||
|
for (let i = 0; i < repetitions; i++) {
|
||||||
|
result = mul(result, battery(50));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exactlyEquals(first: Analog, second: Analog): boolean {
|
||||||
|
return first.value[0] === second.value[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function topBit(value: Analog): boolean {
|
||||||
|
return value.value[0] >= 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
function truncate(value: Analog, keepBits: number): Analog {
|
||||||
|
const truncated = value.asEpsilon() & (((2 ** keepBits) - 1) << (24 - keepBits));
|
||||||
|
return Analog.fromEpsilon(truncated);
|
||||||
|
}
|
||||||
|
|
||||||
|
function negate(value: Analog): Analog {
|
||||||
|
return mul(value, battery(-100));
|
||||||
|
}
|
||||||
|
|
||||||
|
function split(value: Analog, raise = true): [Analog, Analog] {
|
||||||
|
let bottom = value.asEpsilon() & ((2 ** 12) - 1);
|
||||||
|
if (raise) bottom *= (2 ** 12)
|
||||||
|
return [truncate(value, 12), Analog.fromEpsilon(bottom)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rshift(value: Analog, shift: number): Analog {
|
||||||
|
return lower(value, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lshift(value: Analog, shift: number): Analog {
|
||||||
|
return raise(value, 2, shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expose() {
|
||||||
|
window['int24'] = int24;
|
||||||
|
window['i'] = int24;
|
||||||
|
window['raw'] = raw;
|
||||||
|
window['rand'] = rand;
|
||||||
|
window['battery'] = battery;
|
||||||
|
window['b'] = battery;
|
||||||
|
window['add'] = add;
|
||||||
|
window['addOverflowing'] = addOverflowing;
|
||||||
|
window['sub'] = sub;
|
||||||
|
window['compare'] = compare;
|
||||||
|
window['mul'] = mul;
|
||||||
|
window['mulRaw'] = mulRaw;
|
||||||
|
window['recip'] = recip;
|
||||||
|
window['not'] = not;
|
||||||
|
window['raise'] = raise;
|
||||||
|
window['lower'] = lower;
|
||||||
|
window['exactlyEquals'] = exactlyEquals;
|
||||||
|
window['topBit'] = topBit;
|
||||||
|
window['truncate'] = truncate;
|
||||||
|
window['negate'] = negate;
|
||||||
|
window['split'] = split;
|
||||||
|
window['rshift'] = rshift;
|
||||||
|
window['lshift'] = lshift;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function run() {
|
||||||
|
// setTimeout(() => location.reload(), 2000);
|
||||||
|
|
||||||
|
for (let i = 0; i < 1; i++) {
|
||||||
|
const a = int24(0xffffff);
|
||||||
|
const b = int24(0xffffff);
|
||||||
|
// const a = rand();
|
||||||
|
// const b = rand();
|
||||||
|
if (!calc(a, b)) {
|
||||||
|
console.log('failed', a.e, b.e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calc(a: Analog, b: Analog): boolean {
|
||||||
|
const aVal = a.e;
|
||||||
|
const bVal = b.e;
|
||||||
|
const productBigInt = BigInt(aVal) * BigInt(bVal);
|
||||||
|
const expectedHigh = int24(Number((productBigInt >> 24n) & 0xffffffn));
|
||||||
|
const expectedLow = int24(Number(productBigInt & 0xffffffn));
|
||||||
|
|
||||||
|
print('multiply', a.bs, b.bs, expectedHigh.asBinary(6, false) + ' /' + expectedLow.asBinary(6, false), `${a.e} * ${b.e} = ${a.e * b.e}`);
|
||||||
|
|
||||||
|
const [ah, al] = split(a);
|
||||||
|
const [bh, bl] = split(b);
|
||||||
|
|
||||||
|
print('split', ah.bh, al.bh, bh.bh, bl.bh);
|
||||||
|
|
||||||
|
const high = mul(ah, bh);
|
||||||
|
const middle1 = mul(ah, bl);
|
||||||
|
const middle2 = mul(al, bh);
|
||||||
|
const low = mul(al, bl);
|
||||||
|
let [middle, middleCarry] = addOverflowing(middle1, middle2);
|
||||||
|
let [middleHigh, middleLow] = split(middle);
|
||||||
|
middleHigh = rshift(middleHigh, 12);
|
||||||
|
middleCarry = rshift(middleCarry, 12);
|
||||||
|
|
||||||
|
print('middle1', middle1.bh);
|
||||||
|
print('middle2', middle2.bh);
|
||||||
|
|
||||||
|
print('high', high.bh);
|
||||||
|
print('middle', middleHigh.bh, middleLow.bh, middleCarry.bh);
|
||||||
|
print('low', high.bh);
|
||||||
|
|
||||||
|
const [lowSum, lowCarry] = addOverflowing(low, middleLow);
|
||||||
|
print('lowCarry', lowCarry.bh);
|
||||||
|
const highSum = add(high, middleHigh, middleCarry, rshift(lowCarry, 24));
|
||||||
|
|
||||||
|
const resultValue = (BigInt(highSum.e) * (2n ** 24n)) + BigInt(lowSum.e);
|
||||||
|
|
||||||
|
print('result', highSum.bs, lowSum.bs, highSum.bh + ' /' + lowSum.bh, resultValue, productBigInt);
|
||||||
|
return resultValue === productBigInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function print(...values: Array<any>) {
|
||||||
|
console.log(values.join('\n') + '\n');
|
||||||
|
}
|
119
assembler/index.html
Normal file
119
assembler/index.html
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png">
|
||||||
|
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||||
|
|
||||||
|
<script src="ldt/lib/Parser.js" type="text/javascript"></script>
|
||||||
|
<script src="ldt/lib/TextareaDecorator.js" type="text/javascript"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
|
||||||
|
<script type="module" src="main.js"></script>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="page">
|
||||||
|
<div id="controls">
|
||||||
|
<form>
|
||||||
|
<div id="config-target-arch">
|
||||||
|
<label for="config-target-arch-select">target: </label>
|
||||||
|
<select id="config-target-arch-select">
|
||||||
|
<option value="v8">V8</option>
|
||||||
|
<option value="parva_0_1">Parva 0.1</option>
|
||||||
|
<option value="lodestar">Lodestar</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" id="config-target-arch-docs-button">Show docs</button>
|
||||||
|
</div>
|
||||||
|
<div id="config-syntax-highlighting">
|
||||||
|
<label for="config-syntax-highlighting-select">syntax highlighting: </label>
|
||||||
|
<select id="config-syntax-highlighting-select">
|
||||||
|
<option value="rars">RARS</option>
|
||||||
|
<option value="none">none</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="config-raw-output">
|
||||||
|
<label for="config-raw-output-input">raw output: </label>
|
||||||
|
<input type="checkbox" id="config-raw-output-input">
|
||||||
|
</div>
|
||||||
|
<!-- <div id="config-line-numbers">
|
||||||
|
<label for="config-line-numbers-input">line numbers: </label>
|
||||||
|
<input type="checkbox" id="config-labels-input">
|
||||||
|
</div> -->
|
||||||
|
<div id="config-output-format">
|
||||||
|
<label for="config-output-format-select">output format: </label>
|
||||||
|
<select id="config-output-format-select">
|
||||||
|
<option value="hex">hex</option>
|
||||||
|
<option value="binary">binary</option>
|
||||||
|
<option value="decimal">decimal</option>
|
||||||
|
<option value="note">note</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="config-address-format">
|
||||||
|
<label for="config-address-format-select">address format: </label>
|
||||||
|
<select id="config-address-format-select">
|
||||||
|
<option value="hex">hex</option>
|
||||||
|
<option value="binary">binary</option>
|
||||||
|
<option value="decimal">decimal</option>
|
||||||
|
<option value="note">note</option>
|
||||||
|
<option value="none">none</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="config-labels">
|
||||||
|
<label for="config-labels-input">labels in output: </label>
|
||||||
|
<input type="checkbox" id="config-labels-input">
|
||||||
|
</div>
|
||||||
|
<div id="config-breakponts">
|
||||||
|
<label for="config-breakpoints">enable breakpoints: </label>
|
||||||
|
<input type="checkbox" id="config-breakpoints">
|
||||||
|
</div>
|
||||||
|
<div id="config-source">
|
||||||
|
<label for="config-source-select">inline source: </label>
|
||||||
|
<select id="config-source-select">
|
||||||
|
<option value="none">none</option>
|
||||||
|
<option value="instruction">real instruction</option>
|
||||||
|
<option value="source">source instruction</option>
|
||||||
|
<option value="comments">source instruction with comments</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="control-send">
|
||||||
|
<label for="control-send-speed">delay (ms): </label>
|
||||||
|
<input type="number" id="control-send-speed" value="1500" min="300" max="10000">
|
||||||
|
<label for="control-send-start">start address: </label>
|
||||||
|
<input type="number" id="control-send-start" value="0" min="0" max="4095">
|
||||||
|
<br />
|
||||||
|
<button id="control-send-button">Send to LBP</button>
|
||||||
|
<button id="control-send-cancel-button" disabled>Cancel</button>
|
||||||
|
<pre id="control-send-status"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="assembly">
|
||||||
|
<div id="tabs"></div>
|
||||||
|
<div class="line-numbers"></div>
|
||||||
|
<textarea id="assembly-input" autofocus></textarea>
|
||||||
|
</div>
|
||||||
|
<pre id="assembly-status"></pre>
|
||||||
|
<div id="machine-code-text">
|
||||||
|
<!-- <div class="line-numbers"></div>
|
||||||
|
<div class="text"></div>
|
||||||
|
<div class="source"></div> -->
|
||||||
|
</div>
|
||||||
|
<div id="emulator">
|
||||||
|
<div id="emulator-controls">
|
||||||
|
<button id="emulator-control-reset">Reset</button>
|
||||||
|
<button id="emulator-control-step">Step</button>
|
||||||
|
<button id="emulator-control-step-10">Step 10</button>
|
||||||
|
<button id="emulator-control-run">Run</button>
|
||||||
|
</div>
|
||||||
|
<div id="emulator-output">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</head>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
93
assembler/ldt/README.md
Normal file
93
assembler/ldt/README.md
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# Lightweight Decorator for Textareas
|
||||||
|
## In browser live syntax highlighting
|
||||||
|
|
||||||
|
LDT aims to provide a simple, lightweight, and highly extensible alternative to existing in-browser live syntax highlighting solutions by leveraging clever CSS and native functionality. Other solutions often re-implement large parts of the user interaction, or use inconsistent pseudo-standard features such as contentEditable and designMode. This results in either a lack of native functionality or a large code-base to compensate or both.
|
||||||
|
|
||||||
|
It behaves (mostly) like a textarea because it *is* a (transparent) textarea! The decorator maintains a styled copy of the content in a display layer which is aligned underneath the real textarea. By using a real textarea, we get all the native functionality for free! This usually includes keyboard input (one would hope), navigating with a blinking cursor, making selections, cut, copy, & paste; sometimes drag & drop and undo & redo*.
|
||||||
|
|
||||||
|
The idea of displaying content under a (semi) transparent input is by no means a new idea. In fact, Google uses this technique to offer suggestions in their search bar. Facebook used it to highlight the name of a friend while composing a post. Other live syntax highlighting solutions have also used this, such as EditArea and to a degree CodeMirror 2.
|
||||||
|
|
||||||
|
In a sense, LDT takes the UNIX approach; make small programs that do one thing really well. LDT is modular, consisting of the decorator object which maintains the display layer for the editor, and an optional parser object which tells the decorator how to style the content. I try not to do anything above providing a syntax highlighting in a native textarea. This way we keep it as lightweight as possible, but still extensible so it may be used for many applications. Since it is still really a textarea at heart, you can still hook in extended functionality. Two such modules are included, SelectHelper for selection utilities and modifying content programmatically, and Keybinder for mapping hotkeys.
|
||||||
|
|
||||||
|
The optional Parser is included to make it easy to generate fast highlightings using regular expressions. All you have to do is provide a mapping of CSS class names to RegExp objects. In your CSS you can specify styles to apply to each token class. You can also apply multiple classes to a token, just provide a space separated list in quotes. You can also write your own parser if you have your own way of generating tokens, just follow the parser interface.
|
||||||
|
|
||||||
|
LDT was developed by Colin Kuebler originally as part of *The Koala Project*. Special thanks to the *Rensselaer Center for Open Source* for their support.
|
||||||
|
|
||||||
|
*\* Undo & redo has been known to break when you modify the textarea's contents programmatically (which is why LDT doesn't do this by default). It might be possible to regain this functionality by implementing your own undo stack.*
|
||||||
|
|
||||||
|
## Using LDT
|
||||||
|
Making an auto highlighting `textarea` is easy with LDT. Make sure to include the modules you need either directly in your code (less server requests) or using the HTML `script` tag. Minify in production for bandwidths sake. Below is a simple example of LDT usage. See `examples` directory for more.
|
||||||
|
### HTML
|
||||||
|
```html
|
||||||
|
<!-- normal textarea fall-back, add an id to access it from javascript -->
|
||||||
|
<textarea id='codeArea' class='ldt'></textarea>
|
||||||
|
<noscript>Please enable JavaScript to allow syntax highlighting.</noscript>
|
||||||
|
```
|
||||||
|
### JS
|
||||||
|
```js
|
||||||
|
// create a parser with a mapping of css classes to regular expressions
|
||||||
|
// everything must be matched, so 'whitespace' and 'other' are commonly included
|
||||||
|
var parser = new Parser(
|
||||||
|
{ whitespace: /\s+/,
|
||||||
|
comment: /\/\/[^\r\n]*/,
|
||||||
|
other: /\S/ } );
|
||||||
|
// get the textarea with $ (document.getElementById)
|
||||||
|
// pass the textarea element and parser to LDT
|
||||||
|
var ldt = new TextareaDecorator( $('codeArea'), parser );
|
||||||
|
```
|
||||||
|
### CSS
|
||||||
|
```css
|
||||||
|
/* editor styles */
|
||||||
|
.ldt {
|
||||||
|
width: 400px;
|
||||||
|
height: 300px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
/* styles applied to comment tokens */
|
||||||
|
.ldt .comment {
|
||||||
|
color: silver;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Browser Support
|
||||||
|
LDT has been tested on
|
||||||
|
|
||||||
|
* Firefox 3.6 - 80
|
||||||
|
* Internet Explorer 8 - 11
|
||||||
|
* Chromium & Google Chrome 16 - 85
|
||||||
|
* Midori 4.1
|
||||||
|
* Opera 11.61
|
||||||
|
* Epiphany
|
||||||
|
|
||||||
|
## API
|
||||||
|
### TextareaDecorator
|
||||||
|
|
||||||
|
+ `new TextareaDecorator( textarea, parser )` Converts a HTML `textarea` element into an auto highlighting TextareaDecorator. `parser` is used to determine how to subdivide and style the content. `parser` can be any object which defines the `tokenize` and `identify` methods as described in the Parser API below.
|
||||||
|
+ `.input` The input layer of the LDT, a `textarea` element.
|
||||||
|
+ `.output` The output layer of the LDT, a `pre` element.
|
||||||
|
+ `.update()` Updates the highlighting of the LDT. It is automatically called on user input. You shouldn't need to call this unless you programmatically changed the contents of the `textarea`.
|
||||||
|
|
||||||
|
### Parser
|
||||||
|
|
||||||
|
+ `new Parser( [rules], [i] )` Creates a parser. `rules` is an object whose keys are CSS classes and values are the regular expressions which match each token. `i` is a boolean which determines if the matching is case insensitive, it defaults to `false`.
|
||||||
|
+ `.add( rules )` Adds a mapping of CSS class names to regular expressions.
|
||||||
|
+ `.tokenize( string )` Splits `string` into an array of tokens as defined by `.rules`.
|
||||||
|
+ `.identify( string )` Finds the CSS class name associated with the token `string`.
|
||||||
|
|
||||||
|
### Keybinder
|
||||||
|
This is a singleton, you do not need to instantiate this object.
|
||||||
|
|
||||||
|
+ `.bind( element, [keymap] )` Adds Keybinder methods to `element`, optionally setting the element's `keymap`.
|
||||||
|
+ `element.keymap` A mapping of key names to callbacks.
|
||||||
|
|
||||||
|
### SelectHelper
|
||||||
|
This is a singleton, you do not need to instantiate this object.
|
||||||
|
|
||||||
|
+ `.add( element )` Adds SelectHelper methods to `element`.
|
||||||
|
+ `element.insertAtCursor( string )` Inserts `string` into the `element` before the current cursor position.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
You can help by testing browser compatibility, submitting bug reports and fixes, and providing any sort of feedback. Optionally let me know if you end up using LDT, I would love to see what you do with it. Thank you for supporting open source software!
|
||||||
|
|
||||||
|
## License
|
||||||
|
LDT is open sourced under your choice of GPL v3 or MIT. Full text for both licenses should be available in this directory.
|
85
assembler/ldt/lib/Keybinder.js
Normal file
85
assembler/ldt/lib/Keybinder.js
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/* Keybinder.js
|
||||||
|
* written by Colin Kuebler 2012
|
||||||
|
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||||
|
* Simplifies the creation of keybindings on any element
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Keybinder = {
|
||||||
|
bind: function( element, keymap ){
|
||||||
|
element.keymap = keymap;
|
||||||
|
var keyNames = {
|
||||||
|
8: "Backspace",
|
||||||
|
9: "Tab",
|
||||||
|
13: "Enter",
|
||||||
|
16: "Shift",
|
||||||
|
17: "Ctrl",
|
||||||
|
18: "Alt",
|
||||||
|
19: "Pause",
|
||||||
|
20: "CapsLk",
|
||||||
|
27: "Esc",
|
||||||
|
33: "PgUp",
|
||||||
|
34: "PgDn",
|
||||||
|
35: "End",
|
||||||
|
36: "Home",
|
||||||
|
37: "Left",
|
||||||
|
38: "Up",
|
||||||
|
39: "Right",
|
||||||
|
40: "Down",
|
||||||
|
45: "Insert",
|
||||||
|
46: "Delete",
|
||||||
|
112: "F1",
|
||||||
|
113: "F2",
|
||||||
|
114: "F3",
|
||||||
|
115: "F4",
|
||||||
|
116: "F5",
|
||||||
|
117: "F6",
|
||||||
|
118: "F7",
|
||||||
|
119: "F8",
|
||||||
|
120: "F9",
|
||||||
|
121: "F10",
|
||||||
|
122: "F11",
|
||||||
|
123: "F12",
|
||||||
|
145: "ScrLk" };
|
||||||
|
var keyEventNormalizer = function(e){
|
||||||
|
// get the event object and start constructing a query
|
||||||
|
var e = e || window.event;
|
||||||
|
var query = "";
|
||||||
|
// add in prefixes for each key modifier
|
||||||
|
e.shiftKey && (query += "Shift-");
|
||||||
|
e.ctrlKey && (query += "Ctrl-");
|
||||||
|
e.altKey && (query += "Alt-");
|
||||||
|
e.metaKey && (query += "Meta-");
|
||||||
|
// determine the key code
|
||||||
|
var key = e.which || e.keyCode || e.charCode;
|
||||||
|
// if we have a name for it, use it
|
||||||
|
if( keyNames[key] )
|
||||||
|
query += keyNames[key];
|
||||||
|
// otherwise turn it into a string
|
||||||
|
else
|
||||||
|
query += String.fromCharCode(key).toUpperCase();
|
||||||
|
/* DEBUG */
|
||||||
|
//console.log("keyEvent: "+query);
|
||||||
|
// try to run the keybinding, cancel the event if it returns true
|
||||||
|
if( element.keymap[query] && element.keymap[query]() ){
|
||||||
|
e.preventDefault && e.preventDefault();
|
||||||
|
e.stopPropagation && e.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
// capture onkeydown and onkeypress events to capture repeating key events
|
||||||
|
// maintain a boolean so we only fire once per character
|
||||||
|
var fireOnKeyPress = true;
|
||||||
|
element.onkeydown = function(e){
|
||||||
|
fireOnKeyPress = false;
|
||||||
|
return keyEventNormalizer(e);
|
||||||
|
};
|
||||||
|
element.onkeypress = function(e){
|
||||||
|
if( fireOnKeyPress )
|
||||||
|
return keyEventNormalizer(e);
|
||||||
|
fireOnKeyPress = true;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
55
assembler/ldt/lib/Parser.js
Normal file
55
assembler/ldt/lib/Parser.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/* Parser.js
|
||||||
|
* written by Colin Kuebler 2012
|
||||||
|
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||||
|
* Generates a tokenizer from regular expressions for TextareaDecorator
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Parser(rules, useI) {
|
||||||
|
/* INIT */
|
||||||
|
const api = this;
|
||||||
|
|
||||||
|
// variables used internally
|
||||||
|
const i = useI ? 'i' : '';
|
||||||
|
let parseRegex = null;
|
||||||
|
let ruleSrc = [];
|
||||||
|
let ruleNames = [];
|
||||||
|
let ruleMap = {};
|
||||||
|
|
||||||
|
api.add = function (rules) {
|
||||||
|
for (const [name, rule] of Object.entries(rules)) {
|
||||||
|
let s = rule.source;
|
||||||
|
s = '(?<' + name + '>' + s + ')';
|
||||||
|
ruleSrc.push(s);
|
||||||
|
// ruleMap[rule] = new RegExp('^(' + s + ')$', i);
|
||||||
|
ruleNames.push(name);
|
||||||
|
}
|
||||||
|
parseRegex = new RegExp(ruleSrc.join('|'), 'gm' + i);
|
||||||
|
};
|
||||||
|
api.tokenize = function (input) {
|
||||||
|
const tokens = [];
|
||||||
|
const lines = input.match(/.*\n?/gm);
|
||||||
|
for (const [lineNumber, line] of lines.entries()) {
|
||||||
|
for (const match of line.matchAll(parseRegex)) {
|
||||||
|
for (const ruleName of ruleNames) {
|
||||||
|
if (match.groups[ruleName] !== undefined) {
|
||||||
|
tokens.push({
|
||||||
|
tag: ruleName,
|
||||||
|
text: match[0],
|
||||||
|
lineNumber,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
};
|
||||||
|
api.identify = function (token) {
|
||||||
|
return token.tag;
|
||||||
|
};
|
||||||
|
|
||||||
|
api.add(rules);
|
||||||
|
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
|
25
assembler/ldt/lib/SelectHelper.js
Normal file
25
assembler/ldt/lib/SelectHelper.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/* SelectHelper.js
|
||||||
|
* written by Colin Kuebler 2012
|
||||||
|
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||||
|
* Convenient utilities for cross browser textarea selection manipulation
|
||||||
|
*/
|
||||||
|
|
||||||
|
var SelectHelper = {
|
||||||
|
add: function( element ){
|
||||||
|
element.insertAtCursor = element.createTextRange ?
|
||||||
|
// IE version
|
||||||
|
function(x){
|
||||||
|
document.selection.createRange().text = x;
|
||||||
|
} :
|
||||||
|
// standards version
|
||||||
|
function(x){
|
||||||
|
var s = element.selectionStart,
|
||||||
|
e = element.selectionEnd,
|
||||||
|
v = element.value;
|
||||||
|
element.value = v.substring(0, s) + x + v.substring(e);
|
||||||
|
s += x.length;
|
||||||
|
element.setSelectionRange(s, s);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
65
assembler/ldt/lib/TextareaDecorator.css
Normal file
65
assembler/ldt/lib/TextareaDecorator.css
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/* TextareaDecorator.css
|
||||||
|
* written by Colin Kuebler 2012
|
||||||
|
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||||
|
* Provides styles for rendering a textarea on top of a pre with scrollbars
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* settings you can play with */
|
||||||
|
|
||||||
|
.ldt, .ldt label {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt, .ldt pre, .ldt textarea {
|
||||||
|
font-size: 16px !important;
|
||||||
|
/* resize algorithm depends on a monospaced font */
|
||||||
|
font-family: monospace !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt textarea {
|
||||||
|
/* hide the text but show the text caret */
|
||||||
|
color: transparent;
|
||||||
|
/* Firefox caret position is slow to update when color is transparent */
|
||||||
|
color: rgba(0, 0, 0, 0.004);
|
||||||
|
caret-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* settings you shouldn't play with unless you have a good reason */
|
||||||
|
|
||||||
|
.ldt {
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt pre {
|
||||||
|
margin: 0;
|
||||||
|
overflow: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: inline;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt textarea {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: 0;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
/* IE doesn't support rgba textarea, so use vendor specific alpha filter */
|
||||||
|
filter: alpha(opacity = 20);
|
||||||
|
}
|
||||||
|
|
111
assembler/ldt/lib/TextareaDecorator.js
Normal file
111
assembler/ldt/lib/TextareaDecorator.js
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/* TextareaDecorator.js
|
||||||
|
* written by Colin Kuebler 2012
|
||||||
|
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||||
|
* Builds and maintains a styled output layer under a textarea input layer
|
||||||
|
*/
|
||||||
|
function TextareaDecorator(textarea, parser) {
|
||||||
|
/* INIT */
|
||||||
|
var api = this;
|
||||||
|
this.parser = parser;
|
||||||
|
this.errorMap = {};
|
||||||
|
|
||||||
|
// construct editor DOM
|
||||||
|
var parent = document.createElement("div");
|
||||||
|
var output = document.createElement("pre");
|
||||||
|
parent.appendChild(output);
|
||||||
|
var label = document.createElement("label");
|
||||||
|
parent.appendChild(label);
|
||||||
|
// replace the textarea with RTA DOM and reattach on label
|
||||||
|
textarea.parentNode.replaceChild(parent, textarea);
|
||||||
|
label.appendChild(textarea);
|
||||||
|
// transfer the CSS styles to our editor
|
||||||
|
parent.className = 'ldt ' + textarea.className;
|
||||||
|
textarea.className = '';
|
||||||
|
// turn off built-in spellchecking in firefox
|
||||||
|
textarea.spellcheck = false;
|
||||||
|
// turn off word wrap
|
||||||
|
textarea.wrap = "off";
|
||||||
|
|
||||||
|
var getParser = () => {
|
||||||
|
return this.parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
var getErrorMap = () => {
|
||||||
|
return this.errorMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// coloring algorithm
|
||||||
|
var color = function (input, output, parser) {
|
||||||
|
var oldTokens = output.childNodes;
|
||||||
|
var newTokens = parser.tokenize(input);
|
||||||
|
var firstDiff, lastDiffNew, lastDiffOld;
|
||||||
|
output.innerHTML = '';
|
||||||
|
|
||||||
|
// add in modified spans
|
||||||
|
for (const token of newTokens) {
|
||||||
|
var span = document.createElement("span");
|
||||||
|
span.className = token.tag;
|
||||||
|
if (getErrorMap()[token.lineNumber]) {
|
||||||
|
span.classList.add('error');
|
||||||
|
}
|
||||||
|
span.textContent = span.innerText = token.text;
|
||||||
|
output.appendChild(span);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.input = textarea;
|
||||||
|
api.output = output;
|
||||||
|
api.update = function () {
|
||||||
|
var input = textarea.value;
|
||||||
|
if (input) {
|
||||||
|
color(input, output, getParser());
|
||||||
|
// determine the best size for the textarea
|
||||||
|
var lines = input.split('\n');
|
||||||
|
// find the number of columns
|
||||||
|
var maxlen = 0, curlen;
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
// calculate the width of each tab
|
||||||
|
var tabLength = 0, offset = -1;
|
||||||
|
while ((offset = lines[i].indexOf('\t', offset + 1)) > -1) {
|
||||||
|
tabLength += 7 - (tabLength + offset) % 8;
|
||||||
|
}
|
||||||
|
var curlen = lines[i].length + tabLength;
|
||||||
|
// store the greatest line length thus far
|
||||||
|
maxlen = maxlen > curlen ? maxlen : curlen;
|
||||||
|
}
|
||||||
|
textarea.cols = maxlen + 1;
|
||||||
|
textarea.rows = lines.length + 2;
|
||||||
|
} else {
|
||||||
|
// clear the display
|
||||||
|
output.innerHTML = '';
|
||||||
|
// reset textarea rows/cols
|
||||||
|
textarea.cols = textarea.rows = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// detect all changes to the textarea,
|
||||||
|
// including keyboard input, cut/copy/paste, drag & drop, etc
|
||||||
|
if (textarea.addEventListener) {
|
||||||
|
// standards browsers: oninput event
|
||||||
|
textarea.addEventListener("input", api.update, false);
|
||||||
|
} else {
|
||||||
|
// MSIE: detect changes to the 'value' property
|
||||||
|
textarea.attachEvent("onpropertychange",
|
||||||
|
function (e) {
|
||||||
|
if (e.propertyName.toLowerCase() === 'value') {
|
||||||
|
api.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// initial highlighting
|
||||||
|
api.update();
|
||||||
|
|
||||||
|
api.setErrorMap = (errors) => {
|
||||||
|
this.errorMap = errors;
|
||||||
|
api.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
|
543
assembler/main.ts
Normal file
543
assembler/main.ts
Normal file
|
@ -0,0 +1,543 @@
|
||||||
|
import * as bitUtils from './bit_utils.js';
|
||||||
|
|
||||||
|
import targetV8 from './targets/v8.js';
|
||||||
|
import targetParva_0_1 from './targets/parva_0_1.js';
|
||||||
|
import targetLodestar from './targets/lodestar.js';
|
||||||
|
import * as targetTest from './targets/test/test.js';
|
||||||
|
import { ArchName, ArchSpecification, AssemblyInput, AssemblyOutput, InstructionOutputLine } from './assembly.js';
|
||||||
|
import { toNote } from './util.js';
|
||||||
|
|
||||||
|
const CONFIG_VERSION: string = '1';
|
||||||
|
const EMULATOR_SPEED = 60;
|
||||||
|
|
||||||
|
window.addEventListener('load', init);
|
||||||
|
|
||||||
|
let assemblyInput: HTMLTextAreaElement;
|
||||||
|
let machineCodeOutput: HTMLPreElement;
|
||||||
|
let assemblyStatusOutput: HTMLSpanElement;
|
||||||
|
|
||||||
|
let emulatorOutput: HTMLDivElement;
|
||||||
|
let emulatorResetButton: HTMLButtonElement;
|
||||||
|
let emulatorStepButton: HTMLButtonElement;
|
||||||
|
let emulatorStep10Button: HTMLButtonElement;
|
||||||
|
let emulatorRunButton: HTMLButtonElement;
|
||||||
|
|
||||||
|
let controlSendButton: HTMLButtonElement;
|
||||||
|
let controlSendCancelButton: HTMLButtonElement;
|
||||||
|
let controlSendStatus: HTMLPreElement;
|
||||||
|
let controlSendStartInput: HTMLInputElement;;
|
||||||
|
|
||||||
|
let textDecorator: any;
|
||||||
|
let showDocs: boolean = false;
|
||||||
|
let emulatorRunning: boolean = false;
|
||||||
|
let emulatorBreakpoints: Set<number> = new Set();
|
||||||
|
|
||||||
|
let targetArch: ArchSpecification;
|
||||||
|
let assemblyOutput: AssemblyOutput;
|
||||||
|
|
||||||
|
let lastInputReceivedTime = 0;
|
||||||
|
|
||||||
|
let configuration: Configuration;
|
||||||
|
|
||||||
|
type OutputFormat = 'binary' | 'hex' | 'note' | 'decimal' | 'none';
|
||||||
|
|
||||||
|
type ConfigurationSpecification = {
|
||||||
|
version: typeof CONFIG_VERSION;
|
||||||
|
|
||||||
|
targetArch: ArchName;
|
||||||
|
assemblyOutputFormat: OutputFormat;
|
||||||
|
machineCodeOutputFormat: OutputFormat;
|
||||||
|
machineCodeShowLabels: boolean;
|
||||||
|
syntaxHighlighting: string;
|
||||||
|
inlineSourceFormat: string;
|
||||||
|
rawOutput: boolean;
|
||||||
|
sendDelay: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Configuration {
|
||||||
|
machineCodeOutputFormat: OutputFormat;
|
||||||
|
addressOutputFormat: OutputFormat;
|
||||||
|
machineCodeShowLabels: boolean;
|
||||||
|
targetArch: ArchName;
|
||||||
|
syntaxHighlighting: string;
|
||||||
|
inlineSourceFormat: string;
|
||||||
|
rawOutput: boolean;
|
||||||
|
sendDelay: number;
|
||||||
|
|
||||||
|
private formElement: HTMLFormElement;
|
||||||
|
private outputFormatSelect: HTMLSelectElement;
|
||||||
|
private addressFormatSelect: HTMLSelectElement;
|
||||||
|
private labelsCheckbox: HTMLInputElement;
|
||||||
|
private inlineSourceSelect: HTMLSelectElement;
|
||||||
|
private targetArchSelect: HTMLSelectElement;
|
||||||
|
private targetArchDocsButton: HTMLButtonElement;
|
||||||
|
private syntaxHighlightingSelect: HTMLSelectElement;
|
||||||
|
private rawOutputCheckbox: HTMLInputElement;
|
||||||
|
private sendDelayInput: HTMLInputElement;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.formElement = document.querySelector('#controls form') as HTMLFormElement;
|
||||||
|
this.outputFormatSelect = document.getElementById('config-output-format-select') as HTMLSelectElement;
|
||||||
|
this.addressFormatSelect = document.getElementById('config-address-format-select') as HTMLSelectElement;
|
||||||
|
this.labelsCheckbox = document.getElementById('config-labels-input') as HTMLInputElement;
|
||||||
|
this.inlineSourceSelect = document.getElementById('config-source-select') as HTMLSelectElement;
|
||||||
|
this.targetArchSelect = document.getElementById('config-target-arch-select') as HTMLSelectElement;
|
||||||
|
this.targetArchDocsButton = document.getElementById('config-target-arch-docs-button') as HTMLButtonElement;
|
||||||
|
this.syntaxHighlightingSelect = document.getElementById('config-syntax-highlighting-select') as HTMLSelectElement;
|
||||||
|
this.rawOutputCheckbox = document.getElementById('config-raw-output-input') as HTMLInputElement;
|
||||||
|
this.sendDelayInput = document.getElementById('control-send-speed') as HTMLInputElement;
|
||||||
|
|
||||||
|
this.formElement.addEventListener('change', () => {
|
||||||
|
this.update();
|
||||||
|
updateOutput();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sendDelayInput.addEventListener('change', () => this.update());
|
||||||
|
|
||||||
|
this.targetArchDocsButton.addEventListener('click', () => {
|
||||||
|
showDocs = !showDocs;
|
||||||
|
this.targetArchDocsButton.innerText = showDocs ? 'Show assembly' : 'Show docs';
|
||||||
|
updateOutput();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Add change event listeners
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
if (this.targetArch !== this.targetArchSelect.value) {
|
||||||
|
updateTargetArch(this.targetArchSelect.value as ArchName);
|
||||||
|
}
|
||||||
|
this.targetArch = this.targetArchSelect.value as ArchName;
|
||||||
|
this.syntaxHighlighting = this.syntaxHighlightingSelect.value;
|
||||||
|
this.machineCodeOutputFormat = this.outputFormatSelect.value as OutputFormat;
|
||||||
|
this.addressOutputFormat = this.addressFormatSelect.value as OutputFormat;
|
||||||
|
this.machineCodeShowLabels = this.labelsCheckbox.checked;
|
||||||
|
this.inlineSourceFormat = this.inlineSourceSelect.value;
|
||||||
|
this.rawOutput = this.rawOutputCheckbox.checked;
|
||||||
|
this.sendDelay = parseInt(this.sendDelayInput.value);
|
||||||
|
|
||||||
|
document.getElementById('assembly').className = `theme-${this.syntaxHighlighting}`;
|
||||||
|
|
||||||
|
this.saveToLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromLocalStorage() {
|
||||||
|
const configuration = new Configuration();
|
||||||
|
const configurationStorageItem = localStorage.getItem('configuration');
|
||||||
|
|
||||||
|
if (configurationStorageItem !== null) {
|
||||||
|
const parsed: ConfigurationSpecification = JSON.parse(configurationStorageItem);
|
||||||
|
|
||||||
|
if (parsed.version !== CONFIG_VERSION) {
|
||||||
|
console.warn(`Saved configuration version mismatch: found ${parsed.version}, expected ${CONFIG_VERSION}`);
|
||||||
|
localStorage.removeItem('configuration');
|
||||||
|
} else {
|
||||||
|
configuration.addressOutputFormat = parsed.assemblyOutputFormat;
|
||||||
|
configuration.machineCodeOutputFormat = parsed.machineCodeOutputFormat;
|
||||||
|
configuration.machineCodeShowLabels = parsed.machineCodeShowLabels;
|
||||||
|
configuration.targetArch = parsed.targetArch;
|
||||||
|
configuration.syntaxHighlighting = parsed.syntaxHighlighting;
|
||||||
|
configuration.inlineSourceFormat = parsed.inlineSourceFormat;
|
||||||
|
configuration.rawOutput = parsed.rawOutput;
|
||||||
|
configuration.sendDelay = parsed.sendDelay;
|
||||||
|
|
||||||
|
configuration.outputFormatSelect.value = configuration.machineCodeOutputFormat;
|
||||||
|
configuration.addressFormatSelect.value = configuration.addressOutputFormat;
|
||||||
|
configuration.labelsCheckbox.checked = configuration.machineCodeShowLabels;
|
||||||
|
configuration.targetArchSelect.value = configuration.targetArch;
|
||||||
|
configuration.syntaxHighlightingSelect.value = configuration.syntaxHighlighting;
|
||||||
|
configuration.inlineSourceSelect.value = configuration.inlineSourceFormat;
|
||||||
|
configuration.rawOutputCheckbox.checked = configuration.rawOutput;
|
||||||
|
configuration.sendDelayInput.value = configuration.sendDelay.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.update();
|
||||||
|
updateTargetArch(configuration.targetArch);
|
||||||
|
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveToLocalStorage() {
|
||||||
|
const values: ConfigurationSpecification = {
|
||||||
|
version: CONFIG_VERSION,
|
||||||
|
targetArch: this.targetArch,
|
||||||
|
assemblyOutputFormat: this.addressOutputFormat,
|
||||||
|
machineCodeOutputFormat: this.machineCodeOutputFormat,
|
||||||
|
machineCodeShowLabels: this.machineCodeShowLabels,
|
||||||
|
inlineSourceFormat: this.inlineSourceFormat,
|
||||||
|
syntaxHighlighting: this.syntaxHighlighting,
|
||||||
|
rawOutput: this.rawOutput,
|
||||||
|
sendDelay: this.sendDelay,
|
||||||
|
};
|
||||||
|
localStorage.setItem('configuration', JSON.stringify(values));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArch(name: string): ArchSpecification {
|
||||||
|
if (name === 'v8') {
|
||||||
|
return targetV8;
|
||||||
|
} else if (name === 'parva_0_1') {
|
||||||
|
return targetParva_0_1;
|
||||||
|
} else if (name === 'lodestar') {
|
||||||
|
return targetLodestar as any;
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown architecture \'' + name + '\'');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
assemblyInput = document.getElementById('assembly-input') as HTMLTextAreaElement;
|
||||||
|
assemblyStatusOutput = document.getElementById('assembly-status') as HTMLSpanElement;
|
||||||
|
machineCodeOutput = document.getElementById('machine-code-text') as HTMLPreElement;
|
||||||
|
|
||||||
|
controlSendButton = document.getElementById('control-send-button') as HTMLButtonElement;
|
||||||
|
controlSendCancelButton = document.getElementById('control-send-cancel-button') as HTMLButtonElement;
|
||||||
|
controlSendStatus = document.getElementById('control-send-status') as HTMLPreElement;
|
||||||
|
controlSendStartInput = document.getElementById('control-send-start') as HTMLInputElement;
|
||||||
|
controlSendButton.addEventListener('click', sendToLbp);
|
||||||
|
|
||||||
|
emulatorOutput = document.getElementById('emulator-output') as HTMLDivElement;
|
||||||
|
emulatorResetButton = document.getElementById('emulator-control-reset') as HTMLButtonElement;
|
||||||
|
emulatorStepButton = document.getElementById('emulator-control-step') as HTMLButtonElement;
|
||||||
|
emulatorStep10Button = document.getElementById('emulator-control-step-10') as HTMLButtonElement;
|
||||||
|
emulatorRunButton = document.getElementById('emulator-control-run') as HTMLButtonElement;
|
||||||
|
|
||||||
|
emulatorResetButton.addEventListener('click', () => {
|
||||||
|
const memory = [];
|
||||||
|
for (const line of assemblyOutput.lines) {
|
||||||
|
if (line.tag === 'instruction') {
|
||||||
|
const bytes = line.bits.match(new RegExp(`.{1,${targetArch.wordSize}}`, 'g'))!;
|
||||||
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
|
memory[line.address + i] = parseInt(bytes[i], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetArch.emulator?.init(memory);
|
||||||
|
updateEmulatorOutput();
|
||||||
|
});
|
||||||
|
emulatorStepButton.addEventListener('click', () => {
|
||||||
|
targetArch.emulator?.step();
|
||||||
|
updateEmulatorOutput();
|
||||||
|
});
|
||||||
|
emulatorStep10Button.addEventListener('click', () => {
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
targetArch.emulator?.step();
|
||||||
|
}
|
||||||
|
updateEmulatorOutput();
|
||||||
|
});
|
||||||
|
emulatorRunButton.addEventListener('click', () => {
|
||||||
|
if (emulatorRunning) {
|
||||||
|
emulatorRunning = false;
|
||||||
|
emulatorRunButton.innerText = 'Run';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emulatorRunning = true;
|
||||||
|
emulatorRunButton.innerText = 'Stop';
|
||||||
|
const run = async () => {
|
||||||
|
let nextStepTime = null;
|
||||||
|
while (true) {
|
||||||
|
if (!emulatorRunning) {
|
||||||
|
nextStepTime = null;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
break;
|
||||||
|
} else if (nextStepTime === null) {
|
||||||
|
nextStepTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (Date.now() >= nextStepTime) {
|
||||||
|
targetArch.emulator?.step();
|
||||||
|
nextStepTime += 1000 / EMULATOR_SPEED;
|
||||||
|
|
||||||
|
if (emulatorBreakpoints.has(targetArch.emulator?.pc)) {
|
||||||
|
emulatorRunning = false;
|
||||||
|
emulatorRunButton.innerText = 'Run';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOutput();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, nextStepTime - Date.now()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
run();
|
||||||
|
});
|
||||||
|
|
||||||
|
bitUtils.expose();
|
||||||
|
|
||||||
|
assemblyInput.value = localStorage.getItem('assembly') ?? '';
|
||||||
|
|
||||||
|
var textarea = document.getElementById('assembly-input') as HTMLTextAreaElement;
|
||||||
|
textDecorator = new window['TextareaDecorator'](textarea, new window['Parser']({}));
|
||||||
|
|
||||||
|
configuration = Configuration.fromLocalStorage();
|
||||||
|
|
||||||
|
updateOutput();
|
||||||
|
|
||||||
|
assemblyInput.addEventListener('input', () => {
|
||||||
|
lastInputReceivedTime = Date.now();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (Date.now() - lastInputReceivedTime > 300) {
|
||||||
|
localStorage.setItem('assembly', assemblyInput.value);
|
||||||
|
updateOutput();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
assemblyInput.addEventListener('keydown', function (event) {
|
||||||
|
if (event.key == 'Tab') {
|
||||||
|
event.preventDefault();
|
||||||
|
var start = this.selectionStart;
|
||||||
|
var end = this.selectionEnd;
|
||||||
|
|
||||||
|
this.value = this.value.substring(0, start) +
|
||||||
|
"\t" + this.value.substring(end);
|
||||||
|
|
||||||
|
this.selectionStart =
|
||||||
|
this.selectionEnd = start + 1;
|
||||||
|
textDecorator.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---
|
||||||
|
// Tests
|
||||||
|
// ---
|
||||||
|
|
||||||
|
targetTest.runTests();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTargetArch(name: ArchName) {
|
||||||
|
targetArch = getArch(name);
|
||||||
|
|
||||||
|
window['emulator'] = targetArch?.emulator;
|
||||||
|
|
||||||
|
const parser = targetArch.syntaxHighlighting;
|
||||||
|
textDecorator.parser = parser;
|
||||||
|
textDecorator.update();
|
||||||
|
// get the textarea
|
||||||
|
// wait for the page to finish loading before accessing the DOM
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateEmulatorOutput() {
|
||||||
|
emulatorOutput.innerHTML = targetArch.emulator?.printState() ?? '';
|
||||||
|
|
||||||
|
for (const line of Array.from(machineCodeOutput.querySelectorAll('pre.line'))) {
|
||||||
|
const address = parseInt(line.getAttribute('data-address')!);
|
||||||
|
if (targetArch.emulator?.pc === address) {
|
||||||
|
line.classList.add('current');
|
||||||
|
} else {
|
||||||
|
line.classList.remove('current');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOutput() {
|
||||||
|
|
||||||
|
if (showDocs) {
|
||||||
|
machineCodeOutput.innerHTML = '';
|
||||||
|
const element = document.createElement('div');
|
||||||
|
element.innerHTML = window['marked'].parse(targetArch.documentation, { breaks: true });
|
||||||
|
machineCodeOutput.appendChild(element);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const input: AssemblyInput = {
|
||||||
|
source: assemblyInput.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
assemblyOutput = targetArch.assemble(input);
|
||||||
|
const errorMap = {};
|
||||||
|
for (const error of assemblyOutput.errors) {
|
||||||
|
errorMap[error.line] = [...(errorMap[error.line] ?? []), error.message];
|
||||||
|
}
|
||||||
|
textDecorator.setErrorMap(errorMap);
|
||||||
|
|
||||||
|
assemblyStatusOutput.innerText = assemblyOutput.errors.map(e => `Line ${e.line + 1}: ${e.message}`).join('\n');
|
||||||
|
|
||||||
|
const outputFormat = configuration.machineCodeOutputFormat
|
||||||
|
const addressFormat = configuration.addressOutputFormat;
|
||||||
|
const sourceFormat = configuration.inlineSourceFormat;
|
||||||
|
|
||||||
|
machineCodeOutput.innerHTML = '';
|
||||||
|
|
||||||
|
for (const line of assemblyOutput.lines) {
|
||||||
|
if (line.tag === 'label') {
|
||||||
|
if (configuration.machineCodeShowLabels) {
|
||||||
|
if (configuration.rawOutput) {
|
||||||
|
machineCodeOutput.innerText += `${line.name}:\n`;
|
||||||
|
} else {
|
||||||
|
const element = document.createElement('pre');
|
||||||
|
element.classList.add('label');
|
||||||
|
element.innerText = `${line.name}:`;
|
||||||
|
machineCodeOutput.appendChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const binary = line.bits;
|
||||||
|
const binaryValue = parseInt(binary, 2);
|
||||||
|
const address = line.address;
|
||||||
|
const maximumBitCount = targetArch.maxWordsPerInstruction * targetArch.wordSize;
|
||||||
|
|
||||||
|
let lineRepresentation = '';
|
||||||
|
if (outputFormat === 'binary') {
|
||||||
|
const maximumStringLength = (maximumBitCount / 8) * 9;
|
||||||
|
const binaryString = binary.match(/.{1,8}/g)!.map(s => parseInt(s, 2).toString(2).padStart(8, '0')).join(' ');
|
||||||
|
lineRepresentation = binaryString.padEnd(maximumStringLength + 2, ' ');
|
||||||
|
} else if (outputFormat === 'hex') {
|
||||||
|
const maximumStringLength = (maximumBitCount / 8) * 3;
|
||||||
|
const hexString = binary.match(/.{1,8}/g)!.map(s => parseInt(s, 2).toString(16).padStart(2, '0')).join(' ');
|
||||||
|
lineRepresentation = hexString.padEnd(maximumStringLength + 2, ' ');
|
||||||
|
} else if (outputFormat === 'note') {
|
||||||
|
lineRepresentation = toNote(binaryValue, targetArch.wordSize).padStart(16, ' ');
|
||||||
|
} else if (outputFormat === 'decimal') {
|
||||||
|
const maximumStringLength = (maximumBitCount / 3);
|
||||||
|
const decimalString = binary.match(new RegExp(`.{1,${targetArch.wordSize}}`, 'g'))!
|
||||||
|
.map(s => parseInt(s, 2).toString(10).padStart(8, ' ')).join(' ');
|
||||||
|
lineRepresentation = decimalString.padEnd(maximumStringLength, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
let addressRepresentation = '';
|
||||||
|
if (addressFormat === 'binary') {
|
||||||
|
addressRepresentation = address.toString(2).padStart(16, '0');
|
||||||
|
} else if (addressFormat === 'hex') {
|
||||||
|
addressRepresentation = address.toString(16).padStart(2, '0').padStart(4, ' ');
|
||||||
|
} else if (addressFormat === 'note') {
|
||||||
|
addressRepresentation = toNote(address, targetArch.wordSize).padStart(14, ' ');
|
||||||
|
} else if (addressFormat === 'decimal') {
|
||||||
|
addressRepresentation = address.toString(10).padStart(3, ' ');
|
||||||
|
} else if (addressFormat === 'none') {
|
||||||
|
addressRepresentation = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addressFormat !== 'none') {
|
||||||
|
addressRepresentation += ': ';
|
||||||
|
}
|
||||||
|
|
||||||
|
let sourceRepresentation = '';
|
||||||
|
if (sourceFormat === 'instruction') {
|
||||||
|
sourceRepresentation = '; ' + line.source.realInstruction;
|
||||||
|
} else if (sourceFormat === 'source') {
|
||||||
|
sourceRepresentation = '; ' + line.source.sourceInstruction;
|
||||||
|
} else if (sourceFormat === 'comments') {
|
||||||
|
sourceRepresentation = '; ' + line.source.sourceInstructionCommented;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuration.rawOutput) {
|
||||||
|
machineCodeOutput.innerText += `${addressRepresentation}${lineRepresentation} ${sourceRepresentation}\n`;
|
||||||
|
} else {
|
||||||
|
const lineElement = document.createElement('pre');
|
||||||
|
lineElement.classList.add('line');
|
||||||
|
const addressElement = document.createElement('span');
|
||||||
|
const outputElement = document.createElement('span');
|
||||||
|
const sourceElement = document.createElement('span');
|
||||||
|
addressElement.innerText = `${addressRepresentation}`;
|
||||||
|
addressElement.classList.add('address');
|
||||||
|
outputElement.innerText = `${lineRepresentation}`;
|
||||||
|
outputElement.classList.add('binary');
|
||||||
|
sourceElement.innerText = `${sourceRepresentation}`;
|
||||||
|
sourceElement.classList.add('source');
|
||||||
|
|
||||||
|
lineElement.appendChild(addressElement);
|
||||||
|
lineElement.appendChild(outputElement);
|
||||||
|
lineElement.appendChild(sourceElement);
|
||||||
|
|
||||||
|
addressElement.addEventListener('click', () => {
|
||||||
|
if (emulatorBreakpoints.has(address)) {
|
||||||
|
emulatorBreakpoints.delete(address);
|
||||||
|
lineElement.classList.remove('breakpoint');
|
||||||
|
} else {
|
||||||
|
emulatorBreakpoints.add(address);
|
||||||
|
lineElement.classList.add('breakpoint');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lineElement.setAttribute('data-address', address.toString());
|
||||||
|
|
||||||
|
if (emulatorBreakpoints.has(address)) {
|
||||||
|
lineElement.classList.add('breakpoint');
|
||||||
|
}
|
||||||
|
|
||||||
|
machineCodeOutput.appendChild(lineElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateEmulatorOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendToLbp() {
|
||||||
|
const startAddress = parseInt(controlSendStartInput.value) ?? 0;
|
||||||
|
|
||||||
|
const lbpData = [];
|
||||||
|
for (const line of assemblyOutput.lines) {
|
||||||
|
if (line.tag === 'label') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const binary = line.bits;
|
||||||
|
const wordSize = targetArch.wordSize;
|
||||||
|
const words = binary.match(new RegExp(`.{1,${wordSize}}`, 'g'))!;
|
||||||
|
let address = line.address;
|
||||||
|
if (address < startAddress) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const word of words) {
|
||||||
|
const binaryValue = parseInt(word, 2);
|
||||||
|
// lbpData.push([address & 0xfff, address >> 12, binaryValue & 0xfff, binaryValue >> 12]);
|
||||||
|
lbpData.push([address, binaryValue]);
|
||||||
|
address += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const payload = JSON.stringify(lbpData);
|
||||||
|
const delay = configuration.sendDelay;
|
||||||
|
|
||||||
|
// send to websocket
|
||||||
|
const socket = new WebSocket('ws://localhost:8090');
|
||||||
|
controlSendStatus.innerText = 'Connecting...';
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
controlSendCancelButton.innerText = 'Cancelling...';
|
||||||
|
socket.send(JSON.stringify({ type: 'cancel' }));
|
||||||
|
};
|
||||||
|
let complete = false;
|
||||||
|
|
||||||
|
socket.addEventListener('open', () => {
|
||||||
|
socket.send(JSON.stringify({ type: 'data', data: payload, speed: delay }));
|
||||||
|
|
||||||
|
controlSendCancelButton.disabled = false;
|
||||||
|
controlSendCancelButton.addEventListener('click', cancel);
|
||||||
|
|
||||||
|
socket.addEventListener('message', (event) => {
|
||||||
|
const payload = JSON.parse(event.data);
|
||||||
|
controlSendStatus.innerText = `${payload.message}\nChecksum: ${toNote(payload.checksum, 24)}`;
|
||||||
|
if (payload.status === 'complete') {
|
||||||
|
controlSendCancelButton.disabled = true;
|
||||||
|
complete = true;
|
||||||
|
socket.close();
|
||||||
|
} else if (payload.status === 'cancelled') {
|
||||||
|
controlSendCancelButton.innerText = 'Cancel';
|
||||||
|
controlSendCancelButton.disabled = true;
|
||||||
|
complete = true;
|
||||||
|
controlSendCancelButton.removeEventListener('click', cancel);
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
socket.addEventListener('error', () => {
|
||||||
|
controlSendStatus.innerText = 'Error connecting';
|
||||||
|
controlSendCancelButton.disabled = true;
|
||||||
|
});
|
||||||
|
socket.addEventListener('close', () => {
|
||||||
|
if (!complete) {
|
||||||
|
controlSendStatus.innerText = 'Disconnected';
|
||||||
|
}
|
||||||
|
controlSendCancelButton.disabled = true;
|
||||||
|
controlSendCancelButton.removeEventListener('click', cancel);
|
||||||
|
controlSendCancelButton.innerText = 'Cancel';
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
44
assembler/parse.ts
Normal file
44
assembler/parse.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
type Span = {
|
||||||
|
start: [number, number];
|
||||||
|
end: [number, number];
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
type Token = {
|
||||||
|
tag: 'opcode' | 'label' | 'string' | 'number' | 'register' | 'comment' | 'whitespace' | 'newline' | ''
|
||||||
|
source: Span
|
||||||
|
};
|
||||||
|
|
||||||
|
type Parser = (s: string, context: Context) => [Array<Token>, string] | null;
|
||||||
|
|
||||||
|
const literal = (stringLiteral) => (s, context) => s.startsWith(stringLiteral) ? [{
|
||||||
|
tag: 'literal',
|
||||||
|
source: {
|
||||||
|
start: context.position,
|
||||||
|
end: [context.position[0], context.position[1] + stringLiteral.length],
|
||||||
|
text: stringLiteral
|
||||||
|
}
|
||||||
|
}, s.slice(stringLiteral.length)] : null;
|
||||||
|
const wordBoundary = (s, context) => s.startsWith(' ') ? [{tag: 'whitespace', source: {start: context.position, end: [context.position[0], context.position[1] + 1], text: ' '}}, s.slice(1)] : null;
|
||||||
|
|
||||||
|
// const add: Parser = (s, context) => {
|
||||||
|
// seq(reg4, reg3, reg).then((d, a, b) => {
|
||||||
|
// return [{
|
||||||
|
// tag: 'aluInstruction',
|
||||||
|
|
||||||
|
// }];
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
// return null;
|
||||||
|
// };
|
||||||
|
|
||||||
|
export function parse(text: string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
346
assembler/styles.css
Normal file
346
assembler/styles.css
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
html {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
overflow: -moz-hidden-unscrollable;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #eee;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
flex-direction: column;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-rows: 0fr 1fr 150px;
|
||||||
|
grid-template-areas:
|
||||||
|
"controls controls"
|
||||||
|
"assembly machine-code"
|
||||||
|
"assembly-status machine-code-status";
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
#page {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 0fr 1fr 100px 1fr 100px;
|
||||||
|
grid-template-areas:
|
||||||
|
"controls"
|
||||||
|
"assembly"
|
||||||
|
"assembly-status"
|
||||||
|
"machine-code"
|
||||||
|
"machine-code-status";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls {
|
||||||
|
justify-self: start;
|
||||||
|
grid-area: controls;
|
||||||
|
display: flex;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls>form {
|
||||||
|
display: grid;
|
||||||
|
column-gap: 20px;
|
||||||
|
grid-template-rows: 1fr 1fr 1fr;
|
||||||
|
grid-template-areas:
|
||||||
|
"target-arch syntax-highlighting labels"
|
||||||
|
"output-format source raw-output"
|
||||||
|
"address-format none none";
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls>form>div {
|
||||||
|
padding: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#control-send {
|
||||||
|
padding: 0.2em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-syntax-highlighting {
|
||||||
|
grid-area: syntax-highlighting;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-raw-output {
|
||||||
|
grid-area: raw-output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-line-numbers {
|
||||||
|
grid-area: config-line-numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-target-arch {
|
||||||
|
grid-area: target-arch;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-target-arch button {
|
||||||
|
min-width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-output-format {
|
||||||
|
grid-area: output-format;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-address-format {
|
||||||
|
grid-area: address-format;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-labels {
|
||||||
|
grid-area: labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-source {
|
||||||
|
grid-area: source;
|
||||||
|
}
|
||||||
|
|
||||||
|
#control-send {
|
||||||
|
grid-area: send;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assembly {
|
||||||
|
overflow: auto;
|
||||||
|
grid-area: assembly;
|
||||||
|
display: grid;
|
||||||
|
/* TODO: tabs and line numbers */
|
||||||
|
grid-template-columns: 0px 1fr;
|
||||||
|
grid-template-rows: 0px 1fr;
|
||||||
|
grid-template-areas:
|
||||||
|
"tabs tabs"
|
||||||
|
"lines text";
|
||||||
|
}
|
||||||
|
|
||||||
|
#assembly.show-line-numbers {
|
||||||
|
grid-template-columns: 30px 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assembly .ldt {
|
||||||
|
grid-area: text;
|
||||||
|
flex: 1px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
border: none;
|
||||||
|
resize: none;
|
||||||
|
tab-size: 4;
|
||||||
|
/* font-size: 1em; */
|
||||||
|
/* padding: 0.5em; */
|
||||||
|
background-color: #fff;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assembly .ldt pre .error {
|
||||||
|
text-decoration: underline dotted red;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assembly #tabs {
|
||||||
|
grid-area: tabs;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assembly #line-numbers {
|
||||||
|
grid-area: lines;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assembly-status {
|
||||||
|
padding: 0.5em;
|
||||||
|
background-color: #eee;
|
||||||
|
overflow: auto;
|
||||||
|
grid-area: assembly-status;
|
||||||
|
}
|
||||||
|
|
||||||
|
#machine-code {
|
||||||
|
grid-area: machine-code;
|
||||||
|
}
|
||||||
|
|
||||||
|
#machine-code-status {
|
||||||
|
padding: 0.5em;
|
||||||
|
grid-area: machine-code-status;
|
||||||
|
}
|
||||||
|
|
||||||
|
#machine-code-text {
|
||||||
|
display: block;
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.5em;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #000;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: nowrap
|
||||||
|
}
|
||||||
|
|
||||||
|
#machine-code-text .address {
|
||||||
|
margin-right: 2em;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
#machine-code-text .source {
|
||||||
|
margin-left: 2em;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
#machine-code-text .label {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
#machine-code-text > pre.current {
|
||||||
|
background-color: #ffe3c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#machine-code-text > pre .address {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#machine-code-text > pre.breakpoint .address {
|
||||||
|
background-color: #ffcccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#emulator {
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TextareaDecorator.css
|
||||||
|
* written by Colin Kuebler 2012
|
||||||
|
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||||
|
* Provides styles for rendering a textarea on top of a pre with scrollbars
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* settings you can play with */
|
||||||
|
|
||||||
|
.ldt,
|
||||||
|
.ldt label {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt,
|
||||||
|
.ldt pre,
|
||||||
|
.ldt textarea {
|
||||||
|
font-size: 1em !important;
|
||||||
|
/* resize algorithm depends on a monospaced font */
|
||||||
|
font-family: monospace !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt textarea {
|
||||||
|
/* hide the text but show the text caret */
|
||||||
|
color: transparent;
|
||||||
|
/* Firefox caret position is slow to update when color is transparent */
|
||||||
|
color: rgba(0, 0, 0, 0.004);
|
||||||
|
caret-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* settings you shouldn't play with unless you have a good reason */
|
||||||
|
|
||||||
|
.ldt {
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: inline;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt textarea {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: 0;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
/* IE doesn't support rgba textarea, so use vendor specific alpha filter */
|
||||||
|
filter: alpha(opacity=20);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* --- custom --- */
|
||||||
|
|
||||||
|
|
||||||
|
/* .ldt .comment {
|
||||||
|
color: rgb(150, 150, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt .directive {
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgb(0, 90, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt .number {
|
||||||
|
color: navy;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt .label {
|
||||||
|
color: rgb(208, 42, 163);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt .instruction {
|
||||||
|
color: rgb(103, 100, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ldt .register {
|
||||||
|
color: rgb(53, 179, 164);
|
||||||
|
} */
|
||||||
|
|
||||||
|
.theme-rars .ldt .comment {
|
||||||
|
color: #35d048;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-rars .ldt .directive {
|
||||||
|
color: #ff5aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-rars .ldt .number {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-rars .ldt .label {
|
||||||
|
color: black;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-rars .ldt .instruction {
|
||||||
|
color: #1e1eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-rars .ldt .register {
|
||||||
|
color: #ff231d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-rars .ldt .string {
|
||||||
|
color: green;
|
||||||
|
}
|
497
assembler/targets/lodestar.ts
Normal file
497
assembler/targets/lodestar.ts
Normal file
|
@ -0,0 +1,497 @@
|
||||||
|
import type { OutputLine, AssemblyInput, ArchSpecification, AssemblyOutput, InputError } from '../assembly';
|
||||||
|
import { toBitString } from '../util.js';
|
||||||
|
|
||||||
|
export type Resolvable = {
|
||||||
|
sourceAddress: number;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InstructionPart = string | Resolvable;
|
||||||
|
|
||||||
|
const REGISTER_MAP = {
|
||||||
|
'a': '000',
|
||||||
|
'b': '001',
|
||||||
|
'c': '010',
|
||||||
|
'd': '011',
|
||||||
|
'e': '100',
|
||||||
|
'f': '101',
|
||||||
|
'x': '110',
|
||||||
|
'y': '111',
|
||||||
|
};
|
||||||
|
|
||||||
|
function reg(register: string): string {
|
||||||
|
const bits = REGISTER_MAP[register];
|
||||||
|
if (bits === undefined) {
|
||||||
|
throw new Error(`Unknown register: ${register}`);
|
||||||
|
}
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
function immediate(n: string | number): string {
|
||||||
|
if (isNaN(Number(n))) {
|
||||||
|
throw new Error(`Literal value '${n}' is not a number`);
|
||||||
|
}
|
||||||
|
const value = Number(n);
|
||||||
|
if (value < -128 || value > 255) {
|
||||||
|
throw new Error(`Literal value '${n}' is out of range`);
|
||||||
|
}
|
||||||
|
return toBitString(value, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryParseInt(value: string): number | null {
|
||||||
|
let result = null;
|
||||||
|
if (value.startsWith('$')) {
|
||||||
|
result = parseInt(value.slice(1), 16);
|
||||||
|
} else if (value.startsWith('%')) {
|
||||||
|
result = parseInt(value.slice(1), 2);
|
||||||
|
} else {
|
||||||
|
result = parseInt(value, 10);
|
||||||
|
}
|
||||||
|
if (isNaN(result)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParsedLabel = {
|
||||||
|
tag: 'label';
|
||||||
|
name: string;
|
||||||
|
sourceLine: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ParsedInstruction = {
|
||||||
|
tag: 'instruction';
|
||||||
|
fullSource: string;
|
||||||
|
instructionSource: string;
|
||||||
|
sourceLine: number;
|
||||||
|
parts: Array<InstructionPart>;
|
||||||
|
address: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ParsedLine = ParsedInstruction | ParsedLabel;
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
currentAddress: number;
|
||||||
|
currentSource: string;
|
||||||
|
output: Array<ParsedLine>;
|
||||||
|
labels: Map<string, number>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.currentAddress = 0;
|
||||||
|
this.output = [];
|
||||||
|
this.labels = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
instruction(fullSource: string, instructionSource: string, sourceLineNumber: number, parts: Array<InstructionPart>, byteCount: number) {
|
||||||
|
this.output.push({
|
||||||
|
tag: 'instruction',
|
||||||
|
fullSource: fullSource,
|
||||||
|
instructionSource: instructionSource,
|
||||||
|
sourceLine: sourceLineNumber,
|
||||||
|
address: this.currentAddress,
|
||||||
|
parts,
|
||||||
|
});
|
||||||
|
this.currentAddress += byteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
label(name: string, sourceLineNumber: number) {
|
||||||
|
if (this.labels.has(name)) {
|
||||||
|
throw new Error(`Label '${name}' already defined`);
|
||||||
|
}
|
||||||
|
this.labels.set(name, this.currentAddress);
|
||||||
|
this.output.push({
|
||||||
|
tag: 'label',
|
||||||
|
name,
|
||||||
|
sourceLine: sourceLineNumber,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(line: string, lineNumber: number) {
|
||||||
|
// Remove comments beginning with ; and //
|
||||||
|
let commentIndex = line.length;
|
||||||
|
if (line.indexOf(';') !== -1) {
|
||||||
|
commentIndex = Math.min(commentIndex, line.indexOf(';'));
|
||||||
|
}
|
||||||
|
if (line.indexOf('//') !== -1) {
|
||||||
|
commentIndex = Math.min(commentIndex, line.indexOf('//'));
|
||||||
|
}
|
||||||
|
const fullSource = line;
|
||||||
|
line = line.slice(0, commentIndex);
|
||||||
|
line = line.trim();
|
||||||
|
const instructionSource = line;
|
||||||
|
|
||||||
|
line = line.toLowerCase();
|
||||||
|
|
||||||
|
if (line === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = (s: string): Resolvable => ({
|
||||||
|
sourceAddress: this.currentAddress,
|
||||||
|
value: s,
|
||||||
|
});
|
||||||
|
|
||||||
|
let matchFound = false;
|
||||||
|
|
||||||
|
const match = (...args) => {
|
||||||
|
if (matchFound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fn = args.pop();
|
||||||
|
const expression = new RegExp('^' + args.join('') + '$');
|
||||||
|
const result = line.match(expression);
|
||||||
|
if (result === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fn(...result.slice(1));
|
||||||
|
matchFound = true;
|
||||||
|
}
|
||||||
|
const i = (...parts: Array<InstructionPart>) => this.instruction(fullSource, instructionSource, lineNumber, parts, 1);
|
||||||
|
const i2 = (...parts: Array<InstructionPart>) => this.instruction(fullSource, instructionSource, lineNumber, parts, 2);
|
||||||
|
|
||||||
|
const r = '([a-fxy])';
|
||||||
|
const s = '\\s+';
|
||||||
|
const so = '\\s*?';
|
||||||
|
const label = '([a-z_][a-z0-9_]+)';
|
||||||
|
const number = '([%$]?-?[0-9a-f_]+)';
|
||||||
|
const imm = '#?((?:(?<=#)[%$]?-?[0-9a-f_]+)|(?:(?<!#)[a-z_][a-z0-9_]+))';
|
||||||
|
const abs = '((?:[%$]?-?[0-9a-f_]+)|(?:[a-z_][a-z0-9_]+))';
|
||||||
|
const aai = number + so + ',' + so + 'a';
|
||||||
|
const ind = '\\(' + abs + '\\)';
|
||||||
|
const opa = r + so + ',' + so + 'a';
|
||||||
|
|
||||||
|
match('hlt', () => i('00000000'));
|
||||||
|
match('nop', () => i('00000001'));
|
||||||
|
match('jmp', s, abs, (a) => { i2('00000011', value(a)) });
|
||||||
|
match('jmp', s, imm, (a) => { i2('00000010', value(a)) });
|
||||||
|
match('jz', s, imm, (a) => { i2('00000100', value(a)) });
|
||||||
|
match('jnz', s, imm, (a) => { i2('00000101', value(a)) });
|
||||||
|
match('jc', s, imm, (a) => { i2('00000110', value(a)) });
|
||||||
|
match('jnc', s, imm, (a) => { i2('00000111', value(a)) });
|
||||||
|
match('jsr', s, imm, (a) => { i2('00001000', value(a)) });
|
||||||
|
match('jsr', s, imm, (a) => { i2('00001000', value(a)) });
|
||||||
|
match('ret', () => i('00001010'));
|
||||||
|
match('rti', () => i('00001011'));
|
||||||
|
match('sec', () => i('00001100'));
|
||||||
|
match('clc', () => i('00001101'));
|
||||||
|
match('eni', () => i('00001110'));
|
||||||
|
match('dsi', () => i('00001111'));
|
||||||
|
match('adc', s, opa, (a) => i('00010', reg(a)));
|
||||||
|
match('inc', s, r, (a) => i('00011', reg(a)));
|
||||||
|
match('sbc', s, opa, (a) => i('00100', reg(a)));
|
||||||
|
match('dec', s, r, (a) => i('00101', reg(a)));
|
||||||
|
match('not', s, r, (a) => i('00110', reg(a)));
|
||||||
|
match('xor', s, opa, (a) => i('00111', reg(a)));
|
||||||
|
match('and', s, opa, (a) => i('01000', reg(a)));
|
||||||
|
match('or', s, opa, (a) => i('01001', reg(a)));
|
||||||
|
match('slc', s, r, (a) => i('01010', reg(a)));
|
||||||
|
match('src', s, r, (a) => i('01011', reg(a)));
|
||||||
|
match('rol', s, r, (a) => i('01100', reg(a)));
|
||||||
|
match('ror', s, r, (a) => i('01101', reg(a)));
|
||||||
|
match('cmp', s, opa, (a) => i('01110', reg(a)));
|
||||||
|
match('ld', r, s, r, (a, b) => i('11', reg(a), reg(b)));
|
||||||
|
match('ld', r, s, abs, (a, b) => { i2('10001', reg(a), value(b)) });
|
||||||
|
match('ld', r, s, ind, (a, b) => { i2('10101', reg(a), value(b)) });
|
||||||
|
match('ld', r, s, imm, (a, b) => { i2('01111', reg(a), value(b)) });
|
||||||
|
match('ld', r, s, aai, (a, b) => { i2('10011', reg(a), value(b)) });
|
||||||
|
match('st', r, s, aai, (a, b) => { i2('10010', reg(a), value(b)) });
|
||||||
|
match('st', r, s, ind, (a, b) => { i2('10100', reg(a), value(b)) });
|
||||||
|
match('st', r, s, abs, (a, b) => { i2('10000', reg(a), value(b)) });
|
||||||
|
match('push', s, r, (a) => i('10110', reg(a)));
|
||||||
|
match('pull', s, r, (a) => i('10111', reg(a)));
|
||||||
|
match('push', s, r, (a) => i('10110', reg(a)));
|
||||||
|
match('\\.data', s, '(.*)', (a) => this.data(a.split(/\s*,\s*/), fullSource, lineNumber));
|
||||||
|
match('\\.address', s, number, (a) => this.address(tryParseInt(a)));
|
||||||
|
match(label, ':', (a) => this.label(a, lineNumber));
|
||||||
|
|
||||||
|
if (!matchFound) {
|
||||||
|
throw new Error(`Unknown instruction: ${instructionSource}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
address(value) {
|
||||||
|
this.currentAddress = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
data(values: Array<string>, source: string, sourceLineNumber: number) {
|
||||||
|
const numbers: Array<Resolvable> = values.map(n => ({ sourceAddress: 0, value: n }));
|
||||||
|
for (const number of numbers) {
|
||||||
|
this.instruction(source, ".data", sourceLineNumber, [number], 1);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveParsedLine(instruction: ParsedLine): OutputLine {
|
||||||
|
if (instruction.tag === 'label') {
|
||||||
|
return {
|
||||||
|
tag: 'label',
|
||||||
|
name: instruction.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let bits = '';
|
||||||
|
for (const part of instruction.parts) {
|
||||||
|
bits += this.resolve(part);
|
||||||
|
}
|
||||||
|
if (bits.length % 8 !== 0) {
|
||||||
|
throw new Error(`Instruction ${instruction.instructionSource} is ${bits.length} bits long, but should be a multiple of 8`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
tag: 'instruction',
|
||||||
|
bits,
|
||||||
|
address: instruction.address,
|
||||||
|
source: {
|
||||||
|
sourceInstructionCommented: instruction.fullSource,
|
||||||
|
realInstruction: instruction.instructionSource.toUpperCase(),
|
||||||
|
lineNumber: instruction.sourceLine,
|
||||||
|
sourceInstruction: instruction.instructionSource,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(part: InstructionPart): string {
|
||||||
|
if (typeof part === 'string') {
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedInt = tryParseInt(part.value);
|
||||||
|
if (parsedInt !== null) {
|
||||||
|
return immediate(parsedInt);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetLabelAddress = this.labels.get(part.value);
|
||||||
|
if (targetLabelAddress === undefined) {
|
||||||
|
throw new Error(`Unknown label: ${part.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return immediate(targetLabelAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assemble(input: AssemblyInput): AssemblyOutput {
|
||||||
|
const program = new Program();
|
||||||
|
const errors: Array<InputError> = [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
lines: [],
|
||||||
|
errors: [{ line: -1, message: 'This target architecture is not yet implemented' }],
|
||||||
|
message: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [lineNumber, line] of input.source.split('\n').entries()) {
|
||||||
|
try {
|
||||||
|
program.parse(line, lineNumber);
|
||||||
|
} catch (e) {
|
||||||
|
errors.push({
|
||||||
|
line: lineNumber,
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines: Array<OutputLine> = [];
|
||||||
|
for (const instruction of program.output) {
|
||||||
|
let resolved;
|
||||||
|
try {
|
||||||
|
resolved = program.resolveParsedLine(instruction);
|
||||||
|
lines.push(resolved);
|
||||||
|
} catch (e) {
|
||||||
|
if (instruction.tag === 'instruction') {
|
||||||
|
errors.push({
|
||||||
|
line: instruction.sourceLine,
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errors.push({
|
||||||
|
line: instruction.sourceLine,
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lines,
|
||||||
|
errors,
|
||||||
|
message: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const syntaxHighlighting = new window['Parser']({
|
||||||
|
whitespace: /\s+/,
|
||||||
|
number: /#?(\$-?[\dA-Fa-f_]+|-?(\d+)|%-?[01_]+)/,
|
||||||
|
comment: /\/\/[^\r\n]*|;[^\r\n]*/,
|
||||||
|
directive: /\.[a-zA-Z0-9_]+/,
|
||||||
|
label: /[a-zA-Z0-9_]+:/,
|
||||||
|
string: /"(\\.|[^"\r\n])*"|'(\\.|[^'\r\n])*'/,
|
||||||
|
register: /\b[a-fA-FxXyY]\b/,
|
||||||
|
instruction: /^[a-zA-Z0-9\.]+/,
|
||||||
|
other: /\S/,
|
||||||
|
});
|
||||||
|
|
||||||
|
const archSpecification: ArchSpecification = {
|
||||||
|
documentation: '',
|
||||||
|
syntaxHighlighting,
|
||||||
|
assemble,
|
||||||
|
maxWordsPerInstruction: 2,
|
||||||
|
wordSize: 8,
|
||||||
|
emulator: null,
|
||||||
|
};
|
||||||
|
export default archSpecification;
|
||||||
|
|
||||||
|
archSpecification.documentation = `
|
||||||
|
# Lodestar
|
||||||
|
|
||||||
|
Lodestar, a.k.a. the computer that's not as good as the V8.
|
||||||
|
|
||||||
|
https://github.com/Fawaox/Lodestar
|
||||||
|
|
||||||
|
https://www.littlebigforum.net/index.php?t=1277
|
||||||
|
|
||||||
|
Over the last couple of months I've developed an 8-bit CPU and 256-byte RAM chip. Together I believe they represent the fastest and most versatile computer made in LBP to date. I call this system the Lodestar.
|
||||||
|
|
||||||
|
http://i.imgur.com/iOpsaVv.jpg
|
||||||
|
|
||||||
|
A Lodestar system drawing a bunny. Why? Because bunnies.
|
||||||
|
|
||||||
|
1 INTRODUCTION
|
||||||
|
1.1 SPECIFICATION
|
||||||
|
1.2 DESIGN AND INTERFACE
|
||||||
|
1.3 INSTRUCTION SET
|
||||||
|
2 PROGRAMMING
|
||||||
|
2.1 YOUR FIRST PROGRAM
|
||||||
|
2.2 ADDITION
|
||||||
|
2.3 MULTIPLICATION
|
||||||
|
2.4 INPUT AND OUTPUT
|
||||||
|
2.5 GRAPHICS
|
||||||
|
2.6 SOUND
|
||||||
|
3 WHERE TO GET IT
|
||||||
|
|
||||||
|
1 INTRODUCTION
|
||||||
|
|
||||||
|
The Lodestar is an 8-bit microcomputer that supports arithmetic, logic, conditional jumps, a stack, subroutines, serial I/O and interrupts. It can also generate music.
|
||||||
|
|
||||||
|
The most challenging part of developing the CPU were signal timing issues. To make the best use of each frame the system runs unclocked at 30 Hz, resulting in speeds of around 3 instructions per second.
|
||||||
|
|
||||||
|
One of the problems with past computers in LBP is how poorly documented they were by their authors. With this post I hope to provide a comprehensive guide to the system.
|
||||||
|
|
||||||
|
1.1 SPECIFICATION
|
||||||
|
|
||||||
|
- Accumulator architecture
|
||||||
|
- 256 bytes of memory
|
||||||
|
- 2 addressing modes
|
||||||
|
- Serial I/O ports
|
||||||
|
- 3 bars of thermo
|
||||||
|
|
||||||
|
Internally the system is split into 2 chips: the CPU and RAM. Together they use 4000 components total.
|
||||||
|
|
||||||
|
http://i.imgur.com/eBHmNTj.jpg
|
||||||
|
|
||||||
|
These chips are mounted on a board above the switch assembly.
|
||||||
|
|
||||||
|
1.2 DESIGN AND INTERFACE
|
||||||
|
|
||||||
|
The Lodestar's front panel has 20 lights and 12 flip switches. Since I'm a fan of vintage computers the front panel is inspired by machines like the PDP-8 and Altair 8800.
|
||||||
|
|
||||||
|
http://i.imgur.com/N2rwbME.jpg
|
||||||
|
|
||||||
|
There are 4 control switches: RUN starts and stops a program, EXA examines a memory address, DEP deposits a value into memory, DAT toggles display of the accumulator or data bus.
|
||||||
|
|
||||||
|
The address lights show the current memory address. The data lights show either the contents of memory or the accumulator depending on the position of the DAT switch. The status lights indicate the internal state of the machine, such as overflow and I/O port activity.
|
||||||
|
|
||||||
|
On the right side of the machine are the serial input and output ports.
|
||||||
|
|
||||||
|
1.3 INSTRUCTION SET
|
||||||
|
|
||||||
|
The Lodestar instruction set features 36 documented instructions.
|
||||||
|
|
||||||
|
http://fs1.directupload.net/images/150419/4beky8gk.png
|
||||||
|
|
||||||
|
There are two addressing modes for the LDA, STA, ADD, SUB and bitwise instructions: immediate and absolute. This is determined by the 1th bit of the instruction as demonstrated below.
|
||||||
|
|
||||||
|
http://fs1.directupload.net/images/150419/8q92abea.png
|
||||||
|
|
||||||
|
These addressing modes allow the use of variables in programs.
|
||||||
|
|
||||||
|
2 PROGRAMMING
|
||||||
|
|
||||||
|
A Lodestar program is a series of instructions. When you flip the RUN switch the program will begin executing from the current memory address.
|
||||||
|
|
||||||
|
Programs are entered byte by byte. To input a byte into memory:
|
||||||
|
|
||||||
|
|
||||||
|
1. Set input switches to desired address and examine
|
||||||
|
2. Set input switches to desired value and deposit
|
||||||
|
|
||||||
|
To input the value 44 at address 08 for instance you would first examine 08 and then deposit 44.
|
||||||
|
|
||||||
|
What follows is a tutorial that introduces the instruction set through 6 programs. All the programs begin from address 0.
|
||||||
|
|
||||||
|
2.1 YOUR FIRST PROGRAM
|
||||||
|
|
||||||
|
A program can be as simple as a single instruction.
|
||||||
|
|
||||||
|
http://fs2.directupload.net/images/150419/orqdp4nm.png
|
||||||
|
|
||||||
|
This program uses the jump instruction to jump to itself, creating an infinite loop.
|
||||||
|
|
||||||
|
2.2 ADDITION
|
||||||
|
|
||||||
|
Performing arithmetic is just as simple.
|
||||||
|
|
||||||
|
http://fs2.directupload.net/images/150419/whjqdvgg.png
|
||||||
|
|
||||||
|
This program loads 5 into the accumulator, then adds 3 to it.
|
||||||
|
|
||||||
|
As the name implies the accumulator accumulates the result of all operations. To see the result of this program displayed on the data lights flip the DAT switch up.
|
||||||
|
|
||||||
|
2.3 MULTIPLICATION
|
||||||
|
|
||||||
|
There's no multiply instruction. Instead, repeated addition can be used.
|
||||||
|
|
||||||
|
http://fs2.directupload.net/images/150419/t53dixve.png
|
||||||
|
|
||||||
|
This program performs 5 x 4. There are two variables used: x and i. The variable x is initially set to zero and accumulates the result with each iteration of the loop. The variable i is decremented once per iteration and acts as a counter, when it reaches zero the program halts.
|
||||||
|
|
||||||
|
2.4 INPUT AND OUTPUT
|
||||||
|
|
||||||
|
Located on the right side of the Lodestar are both the serial input port and serial output port. These can be used to connect peripherals or to create networks of Lodestar systems.
|
||||||
|
|
||||||
|
On receiving a byte via the input port an interrupt is triggered. An interrupt pushes the current memory address to the stack and the CPU jumps to address 0. After an interrupt has been handled a RTI instruction can be used to return the CPU to the prior address.
|
||||||
|
|
||||||
|
http://fs2.directupload.net/images/150419/3u6yenl7.png
|
||||||
|
|
||||||
|
This is a powerful program that listens to the input port, copying whatever it receives to memory. This means we can hook up a keyboard to the system, so programs can be entered easier and
|
||||||
|
|
||||||
|
faster. In fact, all the programs in this tutorial were written using this program and a keyboard.
|
||||||
|
|
||||||
|
Although not necessary, this program uses the stack to temporarily store the interrupt. The stack starts at address FF and decrements down. This is a handy way to pass data around without using lots of LDA and STA instructions.
|
||||||
|
|
||||||
|
2.5 GRAPHICS
|
||||||
|
|
||||||
|
Using the serial output port it's possible to communicate with peripherals such as a monitor.
|
||||||
|
|
||||||
|
http://fs2.directupload.net/images/150419/wgkt3ojh.png
|
||||||
|
|
||||||
|
This program sends a region of memory to the serial output port, byte by byte. The monitor treats these bytes as pixel coordinates and updates the screen. In this case, the pixel data is 3 bytes long and draws a triangle.
|
||||||
|
|
||||||
|
https://i.imgur.com/LKKAIFa.jpg
|
||||||
|
|
||||||
|
By combining this program with the program in the previous section it's possible to copy programs and data from one Lodestar system to another.
|
||||||
|
|
||||||
|
2.6 SOUND
|
||||||
|
|
||||||
|
Lastly, the Lodestar is also capable of generating music. The lowest 4 bits of the accumulator determine the pitch when the SPK instruction is used.
|
||||||
|
|
||||||
|
http://fs2.directupload.net/images/150419/bv2yhvin.png
|
||||||
|
|
||||||
|
This program plays a spooky song.
|
||||||
|
|
||||||
|
3 WHERE TO GET IT
|
||||||
|
|
||||||
|
The level, which features a running system, can be found here:
|
||||||
|
|
||||||
|
https://lbp.me/v/qvz2ch0
|
||||||
|
|
||||||
|
Simply complete the level to collect the Lodestar, monitor and keyboard as shareable prizes.
|
||||||
|
`;
|
1143
assembler/targets/parva_0_1.ts
Normal file
1143
assembler/targets/parva_0_1.ts
Normal file
File diff suppressed because it is too large
Load diff
135
assembler/targets/test/parva_0_1_test.ts
Normal file
135
assembler/targets/test/parva_0_1_test.ts
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import { ArchSpecification, InstructionOutputLine } from "../../assembly.js";
|
||||||
|
|
||||||
|
import targetParva_0_1 from "../parva_0_1.js";
|
||||||
|
|
||||||
|
const target: ArchSpecification = targetParva_0_1;
|
||||||
|
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
name: 'binary_to_decimal_1',
|
||||||
|
input: String.raw`
|
||||||
|
lw x0, value
|
||||||
|
li x1, 0
|
||||||
|
|
||||||
|
dec_to_bin_loop:
|
||||||
|
lw x4, ten_powers(x1)
|
||||||
|
beqz x4, end_dec_to_bin_loop
|
||||||
|
lw x3, ten_divisors(x1)
|
||||||
|
mulhu x3, x0, x3
|
||||||
|
srli x3, x3, 1 # x3 = digit of result
|
||||||
|
sw x3, result_string(x1)
|
||||||
|
mulu x3, x3, x4
|
||||||
|
sub x0, x0, x3
|
||||||
|
addi x1, x1, 1
|
||||||
|
b dec_to_bin_loop
|
||||||
|
end_dec_to_bin_loop:
|
||||||
|
|
||||||
|
sw x0, result_string(x1)
|
||||||
|
li x0, -1
|
||||||
|
li x6, 1 # cursor x
|
||||||
|
li x7, 1 # cursor y
|
||||||
|
sw x0, 0b01_0100_000000(upper) # gpu clear screen
|
||||||
|
|
||||||
|
skip_zeroes:
|
||||||
|
addi x0, x0, 1
|
||||||
|
lw x1, result_string(x0)
|
||||||
|
beqz x1, skip_zeroes
|
||||||
|
print_loop:
|
||||||
|
sd x67, 0b01_0000_000000(upper) # gpu move cursor
|
||||||
|
lw x1, result_string(x0)
|
||||||
|
bltz x1, end_print_loop
|
||||||
|
slli x1, x1, 1
|
||||||
|
lw x2, char_pixels_upper(x1)
|
||||||
|
lw x3, char_pixels_lower(x1)
|
||||||
|
sd x23, 0b01_0010_000000(upper) # gpu print char
|
||||||
|
addi x6, x6, 4
|
||||||
|
addi x0, x0, 1
|
||||||
|
b print_loop
|
||||||
|
end_print_loop:
|
||||||
|
|
||||||
|
sd x01, 0b01_0011_000000(upper) # gpu show buffer
|
||||||
|
wfi
|
||||||
|
|
||||||
|
ten_powers:
|
||||||
|
.data 10000000
|
||||||
|
.data 1000000
|
||||||
|
.data 100000
|
||||||
|
.data 10000
|
||||||
|
.data 1000
|
||||||
|
.data 100
|
||||||
|
.data 10
|
||||||
|
.data 0
|
||||||
|
|
||||||
|
ten_divisors:
|
||||||
|
.data 0x000004 # 10000000
|
||||||
|
.data 0x000022 # 1000000
|
||||||
|
.data 0x000150 # 100000
|
||||||
|
.data 0x000d1c # 10000
|
||||||
|
.data 0x008313 # 1000
|
||||||
|
.data 0x051eb9 # 100
|
||||||
|
.data 0x333334 # 10
|
||||||
|
|
||||||
|
char_pixels_upper:
|
||||||
|
.data 0b111000_101000_101000_101000 # 0
|
||||||
|
char_pixels_lower:
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b001000_001000_001000_001000 # 1
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_111000_100000 # 2
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_111000_001000 # 3
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b101000_101000_111000_001000 # 4
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_100000_111000_001000 # 5
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_100000_111000_101000 # 6
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_001000_010000 # 7
|
||||||
|
.data 0b010000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_101000_111000_101000 # 8
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_101000_111000_001000 # 9
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
result_string:
|
||||||
|
.repeat 0 8
|
||||||
|
.data -1
|
||||||
|
|
||||||
|
value:
|
||||||
|
.data 69420
|
||||||
|
`,
|
||||||
|
expected: [0x84004c, 0x041000, 0x80c020, 0xc44009, 0x80b028, 0x2e3600, 0x31b001, 0x90b043, 0x0fb800, 0x180600, 0x009001, 0xc00ff7, 0x908043, 0x040fff, 0x046001, 0x047001, 0x958500, 0x000001, 0x801043, 0xc41ffe, 0xb5e400, 0x801043, 0xf08008, 0x209001, 0x80a02f, 0x80b030, 0xb5a480, 0x036004, 0x000001, 0xc00ff7, 0xb584c0, 0xc00000, 0x989680, 0x0f4240, 0x0186a0, 0x002710, 0x0003e8, 0x000064, 0x00000a, 0x000000, 0x000004, 0x000022, 0x000150, 0x000d1c, 0x008313, 0x051eb9, 0x333334, 0xe28a28, 0xe00000, 0x208208, 0x200000, 0xe08e20, 0xe00000, 0xe08e08, 0xe00000, 0xa28e08, 0x200000, 0xe20e08, 0xe00000, 0xe20e28, 0xe00000, 0xe08210, 0x400000, 0xe28e28, 0xe00000, 0xe28e08, 0x200000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xffffff, 0x010f2c],
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export function runTests() {
|
||||||
|
testLoop: for (const { input, expected, name } of tests) {
|
||||||
|
const actualOutputLines = target.assemble({
|
||||||
|
source: input,
|
||||||
|
}).lines.filter((line): line is InstructionOutputLine => line.tag === 'instruction');
|
||||||
|
const actualOutputValues = actualOutputLines
|
||||||
|
.map(line => parseInt(line.bits, 2));
|
||||||
|
for (let i = 0; i < expected.length; i++) {
|
||||||
|
const sourceLine = actualOutputLines[i];
|
||||||
|
const actualValue = actualOutputValues[i];
|
||||||
|
const expectedValue = expected[i];
|
||||||
|
|
||||||
|
if (actualValue !== expectedValue) {
|
||||||
|
const actualValueHex = actualValue.toString(16).padStart(6, '0');
|
||||||
|
const expectedValueHex = expectedValue.toString(16).padStart(6, '0');
|
||||||
|
console.error(`Test '${name}' failed on line ${i}: '${sourceLine.source.sourceInstructionCommented.trim()}'`);
|
||||||
|
console.error(`Expected 0x${expectedValueHex}, got 0x${actualValueHex}`);
|
||||||
|
continue testLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
assembler/targets/test/test.ts
Normal file
7
assembler/targets/test/test.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import * as v8 from "./v8_test.js";
|
||||||
|
import * as parva_0_1 from "./parva_0_1_test.js";
|
||||||
|
|
||||||
|
export function runTests() {
|
||||||
|
parva_0_1.runTests();
|
||||||
|
v8.runTests();
|
||||||
|
}
|
101
assembler/targets/test/v8_test.ts
Normal file
101
assembler/targets/test/v8_test.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import { InstructionOutputLine } from "../../assembly.js";
|
||||||
|
import targetV8 from "../v8.js";
|
||||||
|
const target = targetV8;
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
name: 'pixels_1',
|
||||||
|
input: String.raw`
|
||||||
|
.address $80
|
||||||
|
reset:
|
||||||
|
LDB #$80
|
||||||
|
LDX #$00
|
||||||
|
JMP idle
|
||||||
|
|
||||||
|
int:
|
||||||
|
LDD $00
|
||||||
|
LDC #$08
|
||||||
|
drawLoop:
|
||||||
|
LDA D
|
||||||
|
AND B,A
|
||||||
|
JZ noPixel
|
||||||
|
STX $00
|
||||||
|
noPixel:
|
||||||
|
ROR B
|
||||||
|
INC X
|
||||||
|
DEC C
|
||||||
|
JNZ drawLoop
|
||||||
|
RTI
|
||||||
|
|
||||||
|
idle:
|
||||||
|
HLT
|
||||||
|
JMP idle
|
||||||
|
|
||||||
|
.address $FE
|
||||||
|
.data int
|
||||||
|
.data reset
|
||||||
|
`,
|
||||||
|
expected: [0x7980, 0x7e00, 0x0296, 0x8b00, 0x7a08, 0xc3, 0x41, 0x0490, 0x8600, 0x69, 0x1e, 0x2a, 0x058a, 0x0b, 0x00, 0x0296, 0x86, 0x80],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'memory_copy_1',
|
||||||
|
input: String.raw`
|
||||||
|
; Bank switch: destroys A, B
|
||||||
|
; ROM: bank number is stored in D's high nibble, push return address, clear carry, use jmp instead of jsr
|
||||||
|
; RAM: bank number is stored in D's low nibble, set carry
|
||||||
|
|
||||||
|
LDB #$F0
|
||||||
|
JNC bankNoCarry
|
||||||
|
NOT B
|
||||||
|
bankNoCarry:
|
||||||
|
LDA $00
|
||||||
|
AND B,A
|
||||||
|
OR D,A
|
||||||
|
STA $00
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Memory copy: A=start, B=offset, length=C, destroys D
|
||||||
|
copyLoop:
|
||||||
|
LDD $00,A
|
||||||
|
ADC B,A
|
||||||
|
STD $00,A
|
||||||
|
DEC C
|
||||||
|
JNZ copyLoop
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Memory copy to screen: A=start, destroys C, D, screen address register
|
||||||
|
LDD #$80
|
||||||
|
LDC D
|
||||||
|
STD $03
|
||||||
|
screenCopyLoop:
|
||||||
|
|
||||||
|
LDD $00,A
|
||||||
|
STD $02
|
||||||
|
DEC C
|
||||||
|
JNZ screenCopyLoop
|
||||||
|
RET
|
||||||
|
`,
|
||||||
|
expected: [0x79f0, 0x0705, 0x31, 0x8800, 0x41, 0x4b, 0x8000, 0x0a, 0x9b00, 0x11, 0x9300, 0x2a, 0x050c, 0x0a, 0x7b80, 0xd3, 0x8303, 0x9b00, 0x8302, 0x2a, 0x051a, 0x0a]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export function runTests() {
|
||||||
|
testLoop: for (const { input, expected, name } of tests) {
|
||||||
|
const actualOutputLines = target.assemble({
|
||||||
|
source: input,
|
||||||
|
}).lines.filter((line): line is InstructionOutputLine => line.tag === 'instruction');
|
||||||
|
const actualOutputValues = actualOutputLines
|
||||||
|
.map(line => parseInt(line.bits, 2));
|
||||||
|
for (let i = 0; i < expected.length; i++) {
|
||||||
|
const sourceLine = actualOutputLines[i];
|
||||||
|
const actualValue = actualOutputValues[i];
|
||||||
|
const expectedValue = expected[i];
|
||||||
|
if (actualValue !== expectedValue) {
|
||||||
|
const actualValueHex = actualValue.toString(16).padStart(4, '0');
|
||||||
|
const expectedValueHex = expectedValue.toString(16).padStart(4, '0');
|
||||||
|
console.error(`Test '${name}' failed on line ${i}: '${sourceLine.source.sourceInstructionCommented.trim()}'`);
|
||||||
|
console.error(`Expected 0x${expectedValueHex}, got 0x${actualValueHex}`);
|
||||||
|
continue testLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
957
assembler/targets/v8.ts
Normal file
957
assembler/targets/v8.ts
Normal file
|
@ -0,0 +1,957 @@
|
||||||
|
import type { OutputLine, AssemblyInput, ArchSpecification, AssemblyOutput, InputError, LineSource, Emulator } from '../assembly';
|
||||||
|
import { toBitString } from '../util.js';
|
||||||
|
|
||||||
|
export type Reference = {
|
||||||
|
sourceAddress: number;
|
||||||
|
addressRelative: boolean;
|
||||||
|
bitCount: number;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ParsedLabel = {
|
||||||
|
tag: 'label';
|
||||||
|
source: LineSource,
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ParsedInstruction = {
|
||||||
|
tag: 'instruction';
|
||||||
|
source: LineSource,
|
||||||
|
parts: Array<ParsedLinePart>;
|
||||||
|
address: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ParsedLinePart = string | Reference;
|
||||||
|
type ParsedLine = ParsedInstruction | ParsedLabel;
|
||||||
|
|
||||||
|
const REG_MAP = {
|
||||||
|
'A': '000',
|
||||||
|
'B': '001',
|
||||||
|
'C': '010',
|
||||||
|
'D': '011',
|
||||||
|
'E': '100',
|
||||||
|
'F': '101',
|
||||||
|
'X': '110',
|
||||||
|
'Y': '111',
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseRegister(registerString: string): string {
|
||||||
|
if (!(registerString in REG_MAP)) {
|
||||||
|
throw new Error(`'${registerString}' is not a valid register`);
|
||||||
|
}
|
||||||
|
return REG_MAP[registerString];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNumber(value: string): boolean {
|
||||||
|
return toNumber(value) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumber(value: string): number {
|
||||||
|
let result = null;
|
||||||
|
if (value.startsWith('$')) {
|
||||||
|
result = parseInt(value.slice(1), 16);
|
||||||
|
} else if (value.startsWith('%')) {
|
||||||
|
result = parseInt(value.slice(1), 2);
|
||||||
|
} else if (value.startsWith('@')) {
|
||||||
|
result = parseInt(value.slice(1), 8);
|
||||||
|
} else {
|
||||||
|
result = parseInt(value, 10);
|
||||||
|
}
|
||||||
|
if (isNaN(result)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Binding = { tag: 'binding', name: string };
|
||||||
|
|
||||||
|
const NUMBER_RE = String.raw`%-?[01][01_]*|\$-?[\da-f][\da-f_]*|@-?[0-7][0-7_]*|-?\d[\d_]*`;
|
||||||
|
const VALUE_ATOM_RE = String.raw`(?:${NUMBER_RE}|[a-z0-9_-]+)`;
|
||||||
|
const VALUE_PAIR_RE = String.raw`\((${VALUE_ATOM_RE})\s*([\+\-])\s*(${VALUE_ATOM_RE})\)`;
|
||||||
|
const VALUE_RE = String.raw`${VALUE_ATOM_RE}|${VALUE_PAIR_RE}`;
|
||||||
|
const REGISTER_RE = Object.keys(REG_MAP).join('|');
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
currentAddress: number;
|
||||||
|
output: Array<ParsedLine>;
|
||||||
|
labels: Map<string, number>;
|
||||||
|
constants: Map<string, number>;
|
||||||
|
outputEnabled: boolean | null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.currentAddress = 0;
|
||||||
|
this.output = [];
|
||||||
|
this.labels = new Map();
|
||||||
|
this.constants = new Map();
|
||||||
|
this.outputEnabled = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
instruction(parts: Array<ParsedLinePart>, wordCount: number, source: LineSource, count: number) {
|
||||||
|
if (this.outputEnabled === null || this.outputEnabled) {
|
||||||
|
this.output.push({
|
||||||
|
tag: 'instruction',
|
||||||
|
source,
|
||||||
|
address: this.currentAddress,
|
||||||
|
parts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.currentAddress += wordCount * count;
|
||||||
|
}
|
||||||
|
|
||||||
|
label(name: string, source: LineSource) {
|
||||||
|
if (this.labels.has(name)) {
|
||||||
|
throw new Error(`Label '${name}' already defined`);
|
||||||
|
}
|
||||||
|
this.labels.set(name, this.currentAddress);
|
||||||
|
this.output.push({
|
||||||
|
tag: 'label',
|
||||||
|
source,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
address(value: number) {
|
||||||
|
this.currentAddress = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
align(value: number) {
|
||||||
|
this.currentAddress = Math.ceil(this.currentAddress / value) * value;
|
||||||
|
}
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
if (this.outputEnabled === null) {
|
||||||
|
this.output = [];
|
||||||
|
}
|
||||||
|
this.outputEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
this.outputEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
data(values: Array<string>, source: LineSource) {
|
||||||
|
const numbers: Array<ParsedLinePart> = values.map(n => this.value(n, 8, false, false));
|
||||||
|
for (const number of numbers) {
|
||||||
|
this.instruction([number], 1, { ...source, realInstruction: '(data)' }, 1);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constant(name: string, value: number) {
|
||||||
|
this.constants.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
value(numberOrLabelText: string, bits: number, literalSigned: boolean, labelRelative: boolean): ParsedLinePart {
|
||||||
|
if (isNumber(numberOrLabelText)) {
|
||||||
|
return toBitString(toNumber(numberOrLabelText), bits, literalSigned);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
addressRelative: labelRelative,
|
||||||
|
sourceAddress: this.currentAddress,
|
||||||
|
bitCount: bits,
|
||||||
|
value: numberOrLabelText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
literal(numberText: string, bits: number, signed: boolean): number {
|
||||||
|
if (!isNumber(numberText)) {
|
||||||
|
throw new Error(`Expected number, got ${numberText}`);
|
||||||
|
} else {
|
||||||
|
return toNumber(numberText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseSourceLine(sourceLine: string, lineNumber: number) {
|
||||||
|
// Remove comments beginning with ; and #
|
||||||
|
let commentIndex = sourceLine.length;
|
||||||
|
if (sourceLine.indexOf(';') !== -1) {
|
||||||
|
commentIndex = Math.min(commentIndex, sourceLine.indexOf(';'));
|
||||||
|
}
|
||||||
|
if (sourceLine.indexOf('//') !== -1) {
|
||||||
|
commentIndex = Math.min(commentIndex, sourceLine.indexOf('//'));
|
||||||
|
}
|
||||||
|
const uncommented = sourceLine.slice(0, commentIndex).trim();
|
||||||
|
this.parse({
|
||||||
|
lineNumber,
|
||||||
|
realInstruction: uncommented,
|
||||||
|
sourceInstruction: uncommented,
|
||||||
|
sourceInstructionCommented: sourceLine,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(source: LineSource) {
|
||||||
|
const line = source.realInstruction;
|
||||||
|
|
||||||
|
if (line === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let matchFound = false;
|
||||||
|
|
||||||
|
const match = (...args: Array<string | ((bindings: { [key: string]: string }) => void)>) => {
|
||||||
|
if (matchFound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fn = args.pop() as any;
|
||||||
|
const expression = new RegExp('^' + args.join('') + '$', 'i');
|
||||||
|
const result = line.match(expression);
|
||||||
|
if (result === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fn(result.groups);
|
||||||
|
matchFound = true;
|
||||||
|
}
|
||||||
|
const i = (...parts: Array<ParsedLinePart>) => {
|
||||||
|
this.instruction(parts, 1, source, 1);
|
||||||
|
};
|
||||||
|
const i2 = (...parts: Array<ParsedLinePart>) => {
|
||||||
|
this.instruction(parts, 1, source, 2);
|
||||||
|
};
|
||||||
|
const pseudo = (realInstruction: string) => this.parse({ ...source, realInstruction });
|
||||||
|
const binding = (name: string): Binding => ({ tag: 'binding', name });
|
||||||
|
const r = binding('r');
|
||||||
|
const a = binding('a');
|
||||||
|
const b = binding('b');
|
||||||
|
|
||||||
|
const bindablePattern = (regex: string, prefix: string = '', suffix: string = '') => (binding: Binding) =>
|
||||||
|
`${prefix}(?<${binding.name}>${regex})${suffix}`;
|
||||||
|
const token = bindablePattern(VALUE_RE);
|
||||||
|
const register = bindablePattern(REGISTER_RE);
|
||||||
|
const immediate = bindablePattern(VALUE_RE, '#');
|
||||||
|
const remainder = bindablePattern('.*');
|
||||||
|
|
||||||
|
window['numberRegex'] = NUMBER_RE;
|
||||||
|
|
||||||
|
const s = String.raw`\s+`;
|
||||||
|
const so = String.raw`\s*`;
|
||||||
|
const sep = String.raw`\s*[,\s]\s*`;
|
||||||
|
const hardSep = String.raw`\s*,\s*`;
|
||||||
|
|
||||||
|
const indirect = bindablePattern(VALUE_RE, '\\(', '\\)');
|
||||||
|
const registerAndA = bindablePattern(REGISTER_RE, '', `${so},${so}A`);
|
||||||
|
const valueAndA = bindablePattern(VALUE_RE, '', `${so},${so}A`);
|
||||||
|
|
||||||
|
match('hlt', () => i('00000000'));
|
||||||
|
match('nop', () => i('00000001'));
|
||||||
|
match('ret', () => i('00001010'));
|
||||||
|
match('rti', () => i('00001011'));
|
||||||
|
match('sec', () => i('00001100'));
|
||||||
|
match('clc', () => i('00001101'));
|
||||||
|
match('eni', () => i('00001110'));
|
||||||
|
match('dsi', () => i('00001111'));
|
||||||
|
|
||||||
|
match('jmp', s, indirect(a), ({ a }) => { i2('00000011', this.value(a, 8, false, false)) });
|
||||||
|
match('jsr', s, indirect(a), ({ a }) => { i2('00001001', this.value(a, 8, false, false)) });
|
||||||
|
|
||||||
|
match('jmp', s, token(a), ({ a }) => { i2('00000010', this.value(a, 8, false, false)) });
|
||||||
|
match('jz', s, token(a), ({ a }) => { i2('00000100', this.value(a, 8, false, false)) });
|
||||||
|
match('jnz', s, token(a), ({ a }) => { i2('00000101', this.value(a, 8, false, false)) });
|
||||||
|
match('jc', s, token(a), ({ a }) => { i2('00000110', this.value(a, 8, false, false)) });
|
||||||
|
match('jnc', s, token(a), ({ a }) => { i2('00000111', this.value(a, 8, false, false)) });
|
||||||
|
match('jsr', s, token(a), ({ a }) => { i2('00001000', this.value(a, 8, false, false)) });
|
||||||
|
|
||||||
|
match('adc', s, registerAndA(r), ({ r }) => i('00010', parseRegister(r)));
|
||||||
|
match('sbc', s, registerAndA(r), ({ r }) => i('00100', parseRegister(r)));
|
||||||
|
match('xor', s, registerAndA(r), ({ r }) => i('00111', parseRegister(r)));
|
||||||
|
match('and', s, registerAndA(r), ({ r }) => i('01000', parseRegister(r)));
|
||||||
|
match('or', s, registerAndA(r), ({ r }) => i('01001', parseRegister(r)));
|
||||||
|
match('cmp', s, registerAndA(r), ({ r }) => i('01110', parseRegister(r)));
|
||||||
|
|
||||||
|
match('inc', s, register(r), ({ r }) => i('00011', parseRegister(r)));
|
||||||
|
match('dec', s, register(r), ({ r }) => i('00101', parseRegister(r)));
|
||||||
|
match('not', s, register(r), ({ r }) => i('00110', parseRegister(r)));
|
||||||
|
match('slc', s, register(r), ({ r }) => i('01010', parseRegister(r)));
|
||||||
|
match('src', s, register(r), ({ r }) => i('01011', parseRegister(r)));
|
||||||
|
match('rol', s, register(r), ({ r }) => i('01100', parseRegister(r)));
|
||||||
|
match('ror', s, register(r), ({ r }) => i('01101', parseRegister(r)));
|
||||||
|
|
||||||
|
match('ld', register(a), sep, register(b), ({ a, b }) => i('11', parseRegister(a), parseRegister(b)));
|
||||||
|
match('ld', register(a), sep, valueAndA(b), ({ a, b }) => { i2('10011', parseRegister(a), this.value(b, 8, false, false)); });
|
||||||
|
match('st', register(a), sep, valueAndA(b), ({ a, b }) => { i2('10010', parseRegister(a), this.value(b, 8, false, false)); });
|
||||||
|
match('ld', register(a), sep, indirect(b), ({ a, b }) => { i2('10101', parseRegister(a), this.value(b, 8, false, false)); });
|
||||||
|
match('st', register(a), sep, indirect(b), ({ a, b }) => { i2('10100', parseRegister(a), this.value(b, 8, false, false)); });
|
||||||
|
match('ld', register(a), sep, immediate(b), ({ a, b }) => { i2('01111', parseRegister(a), this.value(b, 8, false, false)); });
|
||||||
|
match('ld', register(a), sep, token(b), ({ a, b }) => { i2('10001', parseRegister(a), this.value(b, 8, false, false)); });
|
||||||
|
match('st', register(a), sep, token(b), ({ a, b }) => { i2('10000', parseRegister(a), this.value(b, 8, false, false)); });
|
||||||
|
|
||||||
|
match('push', s, token(r), ({ r }) => i('10110', parseRegister(r)));
|
||||||
|
match('pull', s, token(r), ({ r }) => i('10111', parseRegister(r)));
|
||||||
|
|
||||||
|
// Directives
|
||||||
|
|
||||||
|
match(token(a), ':', ({ a }) => this.label(a, source));
|
||||||
|
match('.data', s, remainder(a),
|
||||||
|
({ a }) => this.data(a.split(new RegExp(hardSep)), { ...source, sourceInstruction: '.data' }));
|
||||||
|
match('.repeat', s, token(a), sep, token(b),
|
||||||
|
({ a, b }) => this.data(new Array(this.literal(b, 8, false)).fill(a), { ...source, sourceInstruction: '.repeat' }));
|
||||||
|
match('.eq', s, token(a), sep, token(b), ({ a, b }) => this.constant(a, this.literal(b, 8, false)));
|
||||||
|
match('.address', s, token(a), ({ a }) => this.address(this.literal(a, 8, false)));
|
||||||
|
match('.align', s, token(a), ({ a }) => this.align(this.literal(a, 8, false)));
|
||||||
|
match('.start', () => this.enable());
|
||||||
|
match('.end', () => this.disable());
|
||||||
|
|
||||||
|
if (!matchFound) {
|
||||||
|
throw new Error(`Unknown instruction: ${line}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveParsedLine(instruction: ParsedLine): OutputLine {
|
||||||
|
if (instruction.tag === 'label') {
|
||||||
|
return {
|
||||||
|
tag: 'label',
|
||||||
|
name: instruction.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let bits = '';
|
||||||
|
for (const part of instruction.parts) {
|
||||||
|
bits += this.resolveParsedLinePart(part);
|
||||||
|
}
|
||||||
|
if (bits.length % 8 !== 0) {
|
||||||
|
throw new Error(`Instruction ${instruction.source.realInstruction} is ${bits.length} bits long, but should be a multiple of 8`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
tag: 'instruction',
|
||||||
|
bits,
|
||||||
|
address: instruction.address,
|
||||||
|
source: instruction.source,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveParsedLinePart(part: ParsedLinePart): string {
|
||||||
|
if (typeof part === 'string') {
|
||||||
|
return part;
|
||||||
|
} else if (isNumber(part.value)) {
|
||||||
|
return toBitString(toNumber(part.value), part.bitCount, part.addressRelative);
|
||||||
|
} else if (this.labels.has(part.value)) {
|
||||||
|
const targetLabelAddress = this.labels.get(part.value);
|
||||||
|
const value = part.addressRelative ? targetLabelAddress - part.sourceAddress : targetLabelAddress;
|
||||||
|
return toBitString(value, part.bitCount, part.addressRelative);
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetMatch = part.value.match(new RegExp(VALUE_PAIR_RE, 'i'));
|
||||||
|
if (offsetMatch) {
|
||||||
|
const [base, operator, offset] = offsetMatch.slice(1);
|
||||||
|
|
||||||
|
const baseValue = parseInt(this.resolveParsedLinePart({
|
||||||
|
...part,
|
||||||
|
bitCount: 8,
|
||||||
|
value: base,
|
||||||
|
}), 2);
|
||||||
|
const offsetValue = parseInt(this.resolveParsedLinePart({
|
||||||
|
...part,
|
||||||
|
bitCount: 8,
|
||||||
|
addressRelative: false,
|
||||||
|
value: offset,
|
||||||
|
}), 2) * (operator === '+' ? 1 : -1);
|
||||||
|
return toBitString(baseValue + offsetValue, part.bitCount, part.addressRelative);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.constants.has(part.value)) {
|
||||||
|
const value = this.constants.get(part.value);
|
||||||
|
return toBitString(value, part.bitCount, false);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown label: ${part.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assemble(input: AssemblyInput): AssemblyOutput {
|
||||||
|
const program = new Program();
|
||||||
|
const errors: Array<InputError> = [];
|
||||||
|
|
||||||
|
for (const [lineNumber, line] of input.source.split('\n').entries()) {
|
||||||
|
try {
|
||||||
|
program.parseSourceLine(line, lineNumber);
|
||||||
|
} catch (e) {
|
||||||
|
errors.push({
|
||||||
|
line: lineNumber,
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputLines: Array<OutputLine> = [];
|
||||||
|
for (const instruction of program.output) {
|
||||||
|
let resolved: OutputLine;
|
||||||
|
try {
|
||||||
|
resolved = program.resolveParsedLine(instruction);
|
||||||
|
outputLines.push(resolved);
|
||||||
|
} catch (e) {
|
||||||
|
errors.push({
|
||||||
|
line: instruction.source.lineNumber,
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lines: outputLines,
|
||||||
|
errors,
|
||||||
|
message: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const STACK_POINTER_ADDRESS = 0x7f;
|
||||||
|
const INTERRUPT_VECTOR_ADDRESS = 0xfe;
|
||||||
|
const RESET_VECTOR_ADDRESS = 0xff;
|
||||||
|
|
||||||
|
class V8Emulator implements Emulator {
|
||||||
|
memory: Array<number> = [];
|
||||||
|
registers: Array<number> = [];
|
||||||
|
pc: number;
|
||||||
|
cycle: number;
|
||||||
|
carryFlag: boolean;
|
||||||
|
zeroFlag: boolean;
|
||||||
|
interruptsEnabled: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.init([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(memory: Array<number>) {
|
||||||
|
this.memory = memory;
|
||||||
|
this.memory[STACK_POINTER_ADDRESS] = STACK_POINTER_ADDRESS - 1;
|
||||||
|
this.registers = new Array(8).fill(0);
|
||||||
|
this.pc = this.memory[RESET_VECTOR_ADDRESS] ?? 0;
|
||||||
|
this.cycle = 0;
|
||||||
|
this.carryFlag = false;
|
||||||
|
this.zeroFlag = false;
|
||||||
|
this.interruptsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
step() {
|
||||||
|
const instruction = this.memory[this.pc] ?? 0;
|
||||||
|
const immediate = this.memory[this.pc + 1] ?? 0;
|
||||||
|
const bits = instruction.toString(2).padStart(8, '0');
|
||||||
|
const operand = bits.slice(5, 8);
|
||||||
|
|
||||||
|
if (bits === '00000000') {
|
||||||
|
// hlt
|
||||||
|
} else if (bits === '00000001') {
|
||||||
|
// nop
|
||||||
|
this.pc += 1;
|
||||||
|
} else if (bits === '00000010') {
|
||||||
|
// jmp imm
|
||||||
|
this.pc = immediate;
|
||||||
|
} else if (bits === '00000011') {
|
||||||
|
// jmp abs
|
||||||
|
this.pc = this.memory[immediate] ?? 0;
|
||||||
|
} else if (bits === '00000100') {
|
||||||
|
// jz imm
|
||||||
|
this.pc = this.registers[0] === 0 ? immediate : this.pc + 1;
|
||||||
|
} else if (bits === '00000101') {
|
||||||
|
// jnz imm
|
||||||
|
this.pc = this.registers[0] !== 0 ? immediate : this.pc + 1;
|
||||||
|
} else if (bits === '00000110') {
|
||||||
|
// jc imm
|
||||||
|
this.pc = this.carryFlag ? immediate : this.pc + 1;
|
||||||
|
} else if (bits === '00000111') {
|
||||||
|
// jnc imm
|
||||||
|
this.pc = !this.carryFlag ? immediate : this.pc + 1;
|
||||||
|
} else if (bits === '00001000') {
|
||||||
|
// jsr imm
|
||||||
|
this.push(this.pc + 2);
|
||||||
|
this.pc = immediate;
|
||||||
|
} else if (bits === '00001001') {
|
||||||
|
// jsr abs
|
||||||
|
this.push(this.pc + 2);
|
||||||
|
this.pc = this.memory[immediate] ?? 0;
|
||||||
|
} else if (bits === '00001010') {
|
||||||
|
// ret
|
||||||
|
this.pc = this.pop();
|
||||||
|
} else if (bits === '00001011') {
|
||||||
|
// rti
|
||||||
|
this.pc = this.pop();
|
||||||
|
this.interruptsEnabled = true;
|
||||||
|
} else if (bits === '00001100') {
|
||||||
|
// sec
|
||||||
|
this.carryFlag = true;
|
||||||
|
this.pc += 1;
|
||||||
|
} else if (bits === '00001101') {
|
||||||
|
// clc
|
||||||
|
this.carryFlag = false;
|
||||||
|
this.pc += 1;
|
||||||
|
} else if (bits === '00001110') {
|
||||||
|
// eni
|
||||||
|
this.interruptsEnabled = true;
|
||||||
|
this.pc += 1;
|
||||||
|
} else if (bits === '00001111') {
|
||||||
|
// dsi
|
||||||
|
this.interruptsEnabled = false;
|
||||||
|
this.pc += 1;
|
||||||
|
} else if (bits.startsWith('00010')) {
|
||||||
|
// adc
|
||||||
|
this.aluOp(operand, (x, a) => x + a + (this.carryFlag ? 1 : 0));
|
||||||
|
} else if (bits.startsWith('00011')) {
|
||||||
|
// inc
|
||||||
|
this.aluOp(operand, (x, a) => x + 1);
|
||||||
|
} else if (bits.startsWith('00100')) {
|
||||||
|
// sbc
|
||||||
|
this.aluOp(operand, (x, a) => x - a - (this.carryFlag ? 1 : 0));
|
||||||
|
} else if (bits.startsWith('00101')) {
|
||||||
|
// dec
|
||||||
|
this.aluOp(operand, (x, a) => x - 1);
|
||||||
|
} else if (bits.startsWith('00110')) {
|
||||||
|
// not
|
||||||
|
this.aluOp(operand, (x, a) => (~x) & 0xff);
|
||||||
|
} else if (bits.startsWith('00111')) {
|
||||||
|
// xor
|
||||||
|
this.aluOp(operand, (x, a) => x ^ a);
|
||||||
|
} else if (bits.startsWith('01000')) {
|
||||||
|
// and
|
||||||
|
this.aluOp(operand, (x, a) => x & a);
|
||||||
|
} else if (bits.startsWith('01001')) {
|
||||||
|
// or
|
||||||
|
this.aluOp(operand, (x, a) => x | a);
|
||||||
|
} else if (bits.startsWith('01010')) {
|
||||||
|
// slc
|
||||||
|
this.aluOp(operand, (x, a) => (x << 1) + (this.carryFlag ? 1 : 0));
|
||||||
|
} else if (bits.startsWith('01011')) {
|
||||||
|
// src
|
||||||
|
const operandValue = this.getRegister(operand);
|
||||||
|
const aValue = this.registers[0];
|
||||||
|
const result = operandValue >>> 1 + (this.carryFlag ? 0x80 : 0);
|
||||||
|
this.zeroFlag = result === 0;
|
||||||
|
this.carryFlag = (operandValue & 0x01) === 1;
|
||||||
|
this.registers[0] = result & 0xff;
|
||||||
|
this.pc += 1;
|
||||||
|
} else if (bits.startsWith('01100')) {
|
||||||
|
// rol
|
||||||
|
this.aluOp(operand, (x, a) => (x << 1) + (x >>> 7));
|
||||||
|
} else if (bits.startsWith('01101')) {
|
||||||
|
// ror
|
||||||
|
const initialValue = this.getRegister(operand);
|
||||||
|
this.aluOp(operand, (x, a) => (x >>> 1) + ((x & 1) << 7));
|
||||||
|
this.carryFlag = (initialValue & 1) === 1;
|
||||||
|
} else if (bits.startsWith('01110')) {
|
||||||
|
// cmp
|
||||||
|
const operandValue = this.getRegister(operand);
|
||||||
|
const aValue = this.registers[0];
|
||||||
|
const result = aValue - operandValue;
|
||||||
|
this.zeroFlag = result === 0;
|
||||||
|
this.carryFlag = result > 0;
|
||||||
|
this.pc += 1;
|
||||||
|
} else if (bits.startsWith('01111')) {
|
||||||
|
// ldR imm
|
||||||
|
this.setRegister(operand, immediate);
|
||||||
|
this.pc += 2;
|
||||||
|
} else if (bits.startsWith('10000')) {
|
||||||
|
// stR abs
|
||||||
|
this.memory[immediate] = this.getRegister(operand);
|
||||||
|
this.pc += 2;
|
||||||
|
} else if (bits.startsWith('10001')) {
|
||||||
|
// ldR abs
|
||||||
|
this.setRegister(operand, this.memory[immediate] ?? 0);
|
||||||
|
this.pc += 2;
|
||||||
|
} else if (bits.startsWith('10010')) {
|
||||||
|
// stR aai
|
||||||
|
this.memory[immediate + this.registers[0]] = this.getRegister(operand);
|
||||||
|
this.pc += 2;
|
||||||
|
} else if (bits.startsWith('10011')) {
|
||||||
|
// ldR aai
|
||||||
|
this.setRegister(operand, this.memory[immediate + this.registers[0]] ?? 0);
|
||||||
|
this.pc += 2;
|
||||||
|
} else if (bits.startsWith('10100')) {
|
||||||
|
// stR ind
|
||||||
|
this.memory[this.memory[immediate] ?? 0] = this.getRegister(operand);
|
||||||
|
this.pc += 2;
|
||||||
|
} else if (bits.startsWith('10101')) {
|
||||||
|
this.setRegister(operand, this.memory[this.memory[immediate] ?? 0] ?? 0);
|
||||||
|
this.pc += 2;
|
||||||
|
} else if (bits.startsWith('10110')) {
|
||||||
|
// push
|
||||||
|
this.push(this.getRegister(operand));
|
||||||
|
this.pc += 1;
|
||||||
|
} else if (bits.startsWith('10111')) {
|
||||||
|
// pull
|
||||||
|
this.setRegister(operand, this.pop());
|
||||||
|
this.pc += 1;
|
||||||
|
} else if (bits.startsWith('11')) {
|
||||||
|
// ldR R
|
||||||
|
this.setRegister(bits.slice(2, 5), this.getRegister(operand));
|
||||||
|
this.pc += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aluOp(register: string, fn: (x: number, a: number) => number) {
|
||||||
|
const operandValue = this.getRegister(register);
|
||||||
|
const aValue = this.registers[0];
|
||||||
|
const result = fn(operandValue, aValue);
|
||||||
|
this.zeroFlag = result === 0;
|
||||||
|
this.carryFlag = result > 0xff;
|
||||||
|
this.registers[0] = result & 0xff;
|
||||||
|
this.pc += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
push(value: number) {
|
||||||
|
this.memory[this.memory[STACK_POINTER_ADDRESS]--] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pop(): number {
|
||||||
|
return this.memory[++this.memory[STACK_POINTER_ADDRESS]];
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegisterA(): number {
|
||||||
|
return this.registers[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegister(register: string): number {
|
||||||
|
const index = parseInt(register, 2);
|
||||||
|
return this.registers[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
setRegister(register: string, value: number) {
|
||||||
|
const index = parseInt(register, 2);
|
||||||
|
this.registers[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
printState(): string {
|
||||||
|
const registerNames = ['A', 'B', 'C', 'D', 'E', 'F', 'X', 'Y'];
|
||||||
|
const registersString = this.registers.map((value, index) => `${registerNames[index]}=${value.toString(16).padStart(2, '0')}`).join(' ');
|
||||||
|
let memoryString = ' ';
|
||||||
|
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
memoryString += `<span style="color: gray">x${i.toString(16)} </span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
memoryString += `\n<span style="color: gray">${i.toString(16)}x </span>`;
|
||||||
|
for (let j = 0; j < 16; j++) {
|
||||||
|
const address = i * 16 + j;
|
||||||
|
const value = this.memory[address] ?? 0;
|
||||||
|
let style = '';
|
||||||
|
if (address === this.memory[STACK_POINTER_ADDRESS]) {
|
||||||
|
style += 'background-color: cyan;';
|
||||||
|
}
|
||||||
|
if (address === this.pc) {
|
||||||
|
style += 'background-color: #fcb55b;';
|
||||||
|
}
|
||||||
|
memoryString += `<span style="${style}">`;
|
||||||
|
memoryString += `${value.toString(16).padStart(2, '0')}`;
|
||||||
|
memoryString += '</span>';
|
||||||
|
memoryString += ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<pre><span style="background-color: #fcb55b">pc: ${this.pc.toString(16).padStart(2, '0')}</span>,<span style="background-color: cyan"> stack pointer: ${this.memory[STACK_POINTER_ADDRESS].toString(16).padStart(2, '0')}</span></pre>
|
||||||
|
<pre>${registersString}</pre>
|
||||||
|
<pre>memory:\n${memoryString}</pre>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const syntaxHighlighting = new window['Parser']({
|
||||||
|
whitespace: /\s+/,
|
||||||
|
number: /#?(\$-?[\dA-Fa-f_]+|-?(\d+)|%-?[01_]+|@-?[0-7_]+)/,
|
||||||
|
comment: /\/\/[^\r\n]*|;[^\r\n]*/,
|
||||||
|
directive: /\.[a-zA-Z0-9_]+/,
|
||||||
|
label: /[a-zA-Z0-9_]+:/,
|
||||||
|
string: /"(\\.|[^"\r\n])*"|'(\\.|[^'\r\n])*'/,
|
||||||
|
register: /\b[a-fA-FxXyY]\b/,
|
||||||
|
instruction: /^[a-zA-Z0-9\.]+/,
|
||||||
|
other: /\S/,
|
||||||
|
});
|
||||||
|
|
||||||
|
const archSpecification: ArchSpecification = {
|
||||||
|
documentation: '',
|
||||||
|
syntaxHighlighting,
|
||||||
|
assemble,
|
||||||
|
maxWordsPerInstruction: 2,
|
||||||
|
wordSize: 8,
|
||||||
|
emulator: new V8Emulator(),
|
||||||
|
};
|
||||||
|
export default archSpecification;
|
||||||
|
|
||||||
|
archSpecification.documentation = `
|
||||||
|
\`\`\`
|
||||||
|
V V 888
|
||||||
|
V V 8 8
|
||||||
|
V V 8 8
|
||||||
|
V V 888
|
||||||
|
V V 8 8
|
||||||
|
V V 8 8
|
||||||
|
V 888
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
The V8 is an 8-bit RISC processor with an 8-bit address bus, 8 registers and 2 flags
|
||||||
|
developed in LBP2 by Neon. It was optimized for efficiency rather than speed (WHAT
|
||||||
|
DOES THAT MEAN???!?!? (this means that efficiency (component count) was favoured
|
||||||
|
over speed in the past (keyword: was))) and can be clocked up to 7.5 Hz.
|
||||||
|
|
||||||
|
## OPCODE SHEET
|
||||||
|
\`\`\`
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
|OPCODES| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 0 | HLT | NOP |JMP imm|JMP abs|JZ imm |JNZ imm|JC imm |JNC imm|JSR imm|JSR abs| RET | RTI | SEC | CLC | ENI | DSI |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 1 |ADC A,A|ADC B,A|ADC C,A|ADC D,A|ADC E,A|ADC F,A|ADC X,A|ADC Y,A| INC A | INC B | INC C | INC D | INC E | INC F | INC X | INC Y |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 2 |SBC A,A|SBC B,A|SBC C,A|SBC D,A|SBC E,A|SBC F,A|SBC X,A|SBC Y,A| DEC A | DEC B | DEC C | DEC D | DEC E | DEC F | DEC X | DEC Y |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 3 | NOT A | NOT B | NOT C | NOT D | NOT E | NOT F | NOT X | NOT Y |XOR A,A|XOR B,A|XOR C,A|XOR D,A|XOR E,A|XOR F,A|XOR X,A|XOR Y,A|
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 4 |AND A,A|AND B,A|AND C,A|AND D,A|AND E,A|AND F,A|AND X,A|AND Y,A|OR A,A |OR B,A |OR C,A |OR D,A |OR E,A |OR F,A |OR X,A |OR Y,A |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 5 | SLC A | SLC B | SLC C | SLC D | SLC E | SLC F | SLC X | SRC Y | SRC A | SRC B | SRC C | SRC D | SRC E | SRC F | SRC X | SRC Y |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 6 | ROL A | ROL B | ROL C | ROL D | ROL E | ROL F | ROL X | ROL Y | ROR A | ROR B | ROR C | ROR D | ROR E | ROR F | ROR X | ROR Y |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 7 |CMP A,A|CMP B,A|CMP C,A|CMP D,A|CMP E,A|CMP F,A|CMP X,A|CMP Y,A|LDA imm|LDB imm|LDC imm|LDD imm|LDE imm|LDF imm|LDX imm|LDY imm|
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 8 |STA abs|STB abs|STC abs|STD abs|STE abs|STF abs|STX abs|STY abs|LDA abs|LDB abs|LDC abs|LDD abs|LDE abs|LDF abs|LDX abs|LDY abs|
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| 9 |STA aai|STB aai|STC aai|STD aai|STE aai|STF aai|STX aai|STY aai|LDA aai|LDB aai|LDC aai|LDD aai|LDE aai|LDF aai|LDX aai|LDY aai|
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| A |STA ind|STB ind|STC ind|STD ind|STE ind|STF ind|STX ind|STY ind|LDA ind|LDB ind|LDC ind|LDD ind|LDE ind|LDF ind|LDX ind|LDY ind|
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| B |PUSH A |PUSH B |PUSH C |PUSH D |PUSH E |PUSH F |PUSH X |PUSH Y |PULL A |PULL B |PULL C |PULL D |PULL E |PULL F |PULL X |PULL Y |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| C | LDA A | LDA B | LDA C | LDA D | LDA E | LDA F | LDA X | LDA Y | LDB A | LDB B | LDB C | LDB D | LDB E | LDB F | LDB X | LDB Y |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| D | LDC A | LDC B | LDC C | LDC D | LDC E | LDC F | LDC X | LDC Y | LDD A | LDD B | LDD C | LDD D | LDD E | LDD F | LDD X | LDD Y |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| E | LDE A | LDE B | LDE C | LDE D | LDE E | LDE F | LDE X | LDE Y | LDF A | LDF B | LDF C | LDF D | LDF E | LDF F | LDF X | LDF Y |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
| F | LDX A | LDX B | LDX C | LDX D | LDX E | LDX F | LDX X | LDX Y | LDY A | LDY B | LDY C | LDY D | LDY E | LDY F | LDY X | LDY Y |
|
||||||
|
+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## REGISTERS
|
||||||
|
A (Accumulator): acts like a normal register, but certain operations will overwrite it's contents
|
||||||
|
B,C,D,E,F,X,Y: general purpose registers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## ADDRESSING MODES
|
||||||
|
Here is an overview of the addressing modes used in the opcode sheet and in the instruction summary, as well as how they would look in an
|
||||||
|
assembler using LDA as an example. "*" represents the byte immediately after the instruction.
|
||||||
|
|
||||||
|
(R) (Register): (R) is used as a placeholder and represents any of the 8 registers.
|
||||||
|
assembler: LDA r
|
||||||
|
|
||||||
|
imm (IMMediate): the data used is * itself.
|
||||||
|
assembler: LDA #*
|
||||||
|
|
||||||
|
abs (ABSolute): * points to the memory address to interact with.
|
||||||
|
assembler: LDA *
|
||||||
|
|
||||||
|
aai (Absolute, A Indexed): *+A points to the memory address to interact with.
|
||||||
|
assembler: LDA *,A
|
||||||
|
|
||||||
|
ind (INDirect): * points to a memory address, and the byte at that address points to the address to interact with.
|
||||||
|
assembler: LDA (*)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## FLAGS
|
||||||
|
The V8 has 2 flags: Zero (Z) and Carry (C). zero is set whenever the last read data=$00 and Carry is naturally set whenever the ALU
|
||||||
|
outputs a carry-out of sorts, but can also be manually set or reset.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## INTERRUPTS
|
||||||
|
interrupts are triggered by hardware and cause the processor to stop what it's doing and jump to the address pointed to by the interrupt
|
||||||
|
vector located at memory address $FE, saving a return address to the stack. Once an interrupt occurs, no further interrupts can occur
|
||||||
|
until interrupts are re-enabled. Interrupts can only be handled while CLK is high, interrupts are enabled and the processor isn't already
|
||||||
|
executing an instruction. If an interrupt is triggered while these conditions aren't met, it will be handled once the conditions for an
|
||||||
|
interrupt to occur are met. Furthermore, the interrupt input must be brought low and then high again for another interrupt to occur.
|
||||||
|
Once triggered, it takes 4 clock cycles for the processor to jump to where the interrupt vector points.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## RESETS
|
||||||
|
Bringing the reset input high will cause the processor to initialize itself. Resets take priority over interrupts and are handled when
|
||||||
|
CLK is high, but can be triggered at any time, similar to interrupts. Resets take 2 clock cycles to be handled, during which all 8
|
||||||
|
registers are set to $00, the stack pointer is set to $7E, interrupts are disabled and the processor jumps to where the reset vector,
|
||||||
|
located at memory address $FF, points to.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## THE STACK
|
||||||
|
The stack is a region of memory used as a large LIFO (Last In First Out) buffer. The stack pointer is located at memory address $7F and
|
||||||
|
points to the next free layer. The stack pointer initially points to memory address $7E by default, but can be relocated elsewhere if
|
||||||
|
needed. The stack cascades downwards, meaning pushing something onto the stack will decrement the stack pointer and pulling something
|
||||||
|
from it will increment the pointer.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## INSTRUCTIONS
|
||||||
|
This is a basic overview of the V8's 31 instructions along with how many clock cycles (CCs) they take and which flags they affect.
|
||||||
|
|
||||||
|
|
||||||
|
-ADC (R),A: ADd A to (R) with Carry input, sum is written to A
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C,Z
|
||||||
|
|
||||||
|
-AND r,A: bitwise AND between (R) and A, result is written to A
|
||||||
|
CCs: 1
|
||||||
|
flags affected: Z
|
||||||
|
|
||||||
|
-CLC: CLears the Carry flag
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C
|
||||||
|
|
||||||
|
-CMP (R),A: CoMPare (R) to A and set the flags accordingly, functionally the same as subtracting (R) from A and discarding the difference
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C,Z
|
||||||
|
|
||||||
|
-DEC (R): DECrement (R) by 1
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C,Z
|
||||||
|
|
||||||
|
-DSI: DiSable Interrupts
|
||||||
|
CCs: 1
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-ENI: ENable Interrupts
|
||||||
|
CCs: 1
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-HLT: enable interrupts and HaLT the processor until an interrupt or reset occurs
|
||||||
|
CCs: N/A
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-INC (R): INCrement (R) by 1
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C,Z
|
||||||
|
|
||||||
|
-JC: Jump if the Carry flag is set
|
||||||
|
CCs: 1 or 2*
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-JMP: JuMP to a new address
|
||||||
|
CCs:
|
||||||
|
JMP imm: 2
|
||||||
|
JMP abs: 3
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-JNC (Jump on No Carry) jump if the carry flag is Not set
|
||||||
|
CCs: 1 or 2*
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-JNZ (jump on No Zero) jump if the zero flag isn't set
|
||||||
|
CCs: 1 or 2*
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-JSR (Jump to SubRoutine): push a return address onto the stack and jump to a new address
|
||||||
|
CCs:
|
||||||
|
JSR imm: 4
|
||||||
|
JSR abs: 5
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-JZ: jump if the zero flag is set
|
||||||
|
CCs: 1 or 2*
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-LDr: LoaD data into r
|
||||||
|
CCs:
|
||||||
|
LD(R) (R): 1
|
||||||
|
LD(R) imm: 2
|
||||||
|
LD(R) abs: 3
|
||||||
|
LD(R) aii: 3
|
||||||
|
LD(R) ind: 4
|
||||||
|
flags affected: Z
|
||||||
|
|
||||||
|
-NOP: NO OPeration is performed
|
||||||
|
CCs: 1
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-NOT r: bitwise NOT on r
|
||||||
|
CCs: 1
|
||||||
|
flags affected: Z
|
||||||
|
|
||||||
|
-OR (R),A: bitwise OR between (R) and A, result is written to A
|
||||||
|
CCs: 1
|
||||||
|
flags affected: Z
|
||||||
|
|
||||||
|
-PULL (R): PULL the last stack entry into (R)
|
||||||
|
CCs: 3
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-PUSH (R): PUSH (R) onto the stack
|
||||||
|
CCs: 3
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-RET (RETurn from subroutine): pull the last saved return address from the stack and jump to it
|
||||||
|
CCs: 3
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-ROL (R): ROtate (R) one bit to the Left
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C,Z
|
||||||
|
|
||||||
|
-ROR (R): ROtate (R) one bit to the Right
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C,Z
|
||||||
|
|
||||||
|
-RTI (ReTurn from Interrupt): pull the last saved return address from the stack and jump to it and enable interrupts
|
||||||
|
CCs: 3
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-SEC: SEt the Carry flag
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C
|
||||||
|
|
||||||
|
-SLC (R): Shifts (R) one bit to the Left with Carry input
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C,Z
|
||||||
|
|
||||||
|
-SRC (R): Shifts (R) one bit to the Right with Carry input
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C,Z
|
||||||
|
|
||||||
|
-ST(R): STore (R) into memory
|
||||||
|
CCs:
|
||||||
|
ST(R) abs: 3
|
||||||
|
ST(R) aii: 3
|
||||||
|
ST(R) ind: 4
|
||||||
|
flags affected: none
|
||||||
|
|
||||||
|
-SBC (R),A: SuBtract A from (R) with Carry input, difference is written to A
|
||||||
|
CCs: 1
|
||||||
|
flags affected: C,Z
|
||||||
|
|
||||||
|
-XOR (R),A: bitwise XOR between (R) and A, result is written to A
|
||||||
|
CCs: 1
|
||||||
|
flags affected: Z
|
||||||
|
|
||||||
|
*: Conditional jumps take 1 clock cycle if the condition is false and 2 if it's true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## PINOUT
|
||||||
|
Here is a description of what each input/output does and where they are located on the processor:
|
||||||
|
\`\`\`
|
||||||
|
__ __
|
||||||
|
| \\_/ |- AD7
|
||||||
|
| |- AD6
|
||||||
|
| |- AD5
|
||||||
|
CLK -| |- AD4
|
||||||
|
INT -| |- AD3
|
||||||
|
RST -| |- AD2
|
||||||
|
DI7 -| |- AD1
|
||||||
|
DI6 -| |- AD0
|
||||||
|
DI5 -| |- DO7
|
||||||
|
DI4 -| |- DO6
|
||||||
|
DI3 -| |- DO5
|
||||||
|
DI2 -| |- DO4
|
||||||
|
DI1 -| |- DO3
|
||||||
|
DI0 -| |- DO2
|
||||||
|
| |- DO1
|
||||||
|
| |- DO0
|
||||||
|
| |- R
|
||||||
|
|_______|- W
|
||||||
|
|
||||||
|
CLK: CLocK input
|
||||||
|
INT: INTerrupt
|
||||||
|
RST: ReSeT
|
||||||
|
DI7-DI0: Data Input bus
|
||||||
|
AD7-AD0: ADdress bus
|
||||||
|
DO7-DO0: Data Output bus
|
||||||
|
R: Read
|
||||||
|
W: Write
|
||||||
|
\`\`\`
|
||||||
|
`;
|
8
assembler/tsconfig.json
Normal file
8
assembler/tsconfig.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "es2022",
|
||||||
|
"lib": ["esnext", "DOM", "es2023"],
|
||||||
|
"target": "esnext"
|
||||||
|
},
|
||||||
|
"target": "esnext"
|
||||||
|
}
|
40
assembler/util.ts
Normal file
40
assembler/util.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
export function toBitString(n: number, bits: number, signed: boolean = false): string {
|
||||||
|
const minimum = -(2 ** (bits - 1));
|
||||||
|
const maximum = signed ? (2 ** (bits - 1)) - 1 : 2 ** bits - 1;
|
||||||
|
|
||||||
|
if (n < minimum || n > maximum) {
|
||||||
|
throw new Error(`Value ${n} out of range for ${bits}-bit ${signed ? 'signed' : 'unsigned'} integer`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < 0) {
|
||||||
|
n = (2 ** bits) + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < bits; i++) {
|
||||||
|
result = (n & 1) + result;
|
||||||
|
n >>= 1;
|
||||||
|
}
|
||||||
|
if (n !== 0) {
|
||||||
|
throw new Error(`Internal error: ${n} not zero after conversion to ${bits}-bit ${signed ? 'signed' : 'unsigned'} integer`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toNote(n: number, bits: number): string {
|
||||||
|
if (bits <= 19) {
|
||||||
|
return `${n}`;
|
||||||
|
} else {
|
||||||
|
const scaling = 2 ** (bits - 19);
|
||||||
|
return `${(n / scaling).toFixed(5)} x${scaling}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toNote24(n: number): string {
|
||||||
|
return toNote(n, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toNote8(n: number): string {
|
||||||
|
return toNote(n, 8);
|
||||||
|
}
|
||||||
|
|
191
chatbot/bot.js
Normal file
191
chatbot/bot.js
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
import tmi from 'tmi.js';
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
|
import * as secrets from './secret.js';
|
||||||
|
|
||||||
|
const CHECKSUM_SEND_INTERVAL = 8;
|
||||||
|
|
||||||
|
// Define configuration options
|
||||||
|
const opts = {
|
||||||
|
options: { debug: false },
|
||||||
|
identity: {
|
||||||
|
username: secrets.USERNAME,
|
||||||
|
password: secrets.ACCESS_TOKEN,
|
||||||
|
},
|
||||||
|
channels: [secrets.CHANNEL_NAME]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a client with our options
|
||||||
|
const client = new tmi.Client(opts);
|
||||||
|
|
||||||
|
client.connect().catch(console.error);
|
||||||
|
|
||||||
|
client.on('connected', onConnectedHandler);
|
||||||
|
|
||||||
|
function onConnectedHandler(addr, port) {
|
||||||
|
console.log(`* Connected to ${addr}:${port}`);
|
||||||
|
|
||||||
|
listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
function listen() {
|
||||||
|
const wss = new WebSocketServer({ port: 8090 });
|
||||||
|
|
||||||
|
wss.on('connection', (ws) => {
|
||||||
|
let cancelRequested = false;
|
||||||
|
|
||||||
|
ws.on('message', (message) => {
|
||||||
|
const payload = JSON.parse(message);
|
||||||
|
if (payload.type === 'data') {
|
||||||
|
const data = JSON.parse(payload.data);
|
||||||
|
console.log(`sending ${data.length} values with inveral ${payload.speed}ms...`);
|
||||||
|
write(data, (update) => {
|
||||||
|
ws.send(update);
|
||||||
|
}, () => cancelRequested, payload.speed);
|
||||||
|
} else if (payload.type === 'cancel') {
|
||||||
|
cancelRequested = true;
|
||||||
|
console.log('cancel requested');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands:
|
||||||
|
// 0: reset checksum
|
||||||
|
// 1: address low
|
||||||
|
// 2: address high
|
||||||
|
// 3: value low; write; increment address
|
||||||
|
// 4: value high
|
||||||
|
// 5: checksum low; compare
|
||||||
|
// 6: checksum high
|
||||||
|
// 7: unlock address
|
||||||
|
async function write(data, progressCallback, isCancelRequested, waitIntervalMs) {
|
||||||
|
let previousAddress = null;
|
||||||
|
let previousVhValue = null;
|
||||||
|
let checksum = 0;
|
||||||
|
|
||||||
|
// Reset checksum
|
||||||
|
send(0, 0);
|
||||||
|
await wait(waitIntervalMs);
|
||||||
|
|
||||||
|
for (const i in data) {
|
||||||
|
const remaining = (data.length - i) + (data.length / CHECKSUM_SEND_INTERVAL);
|
||||||
|
const eta = remaining * (waitIntervalMs * 2) / 1000;
|
||||||
|
progressCallback(JSON.stringify({
|
||||||
|
status: 'sending',
|
||||||
|
message: `${i}/${data.length} (${(i / data.length * 100).toFixed(1)}%) ETA: ${formatDuration(eta)}`,
|
||||||
|
checksum,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (i % CHECKSUM_SEND_INTERVAL === 0 && i != 0) {
|
||||||
|
await sendChecksum(checksum, waitIntervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [address, value] = data[i];
|
||||||
|
checksum += address + value;
|
||||||
|
checksum &= 0xffffff;
|
||||||
|
const [al, ah] = [address & 0xfff, address >> 12];
|
||||||
|
const [vl, vh] = [value & 0xfff, value >> 12];
|
||||||
|
|
||||||
|
if (address !== previousAddress + 1) {
|
||||||
|
send(0, 7); // unlock address
|
||||||
|
await wait(waitIntervalMs);
|
||||||
|
send(al, 1);
|
||||||
|
await wait(waitIntervalMs);
|
||||||
|
send(ah, 2);
|
||||||
|
await wait(waitIntervalMs);
|
||||||
|
}
|
||||||
|
previousAddress = address;
|
||||||
|
|
||||||
|
if (vh !== previousVhValue) {
|
||||||
|
send(vh, 4);
|
||||||
|
await wait(waitIntervalMs);
|
||||||
|
}
|
||||||
|
previousVhValue = vh;
|
||||||
|
send(vl, 3);
|
||||||
|
await wait(waitIntervalMs);
|
||||||
|
|
||||||
|
if (isCancelRequested()) {
|
||||||
|
console.log('cancelled');
|
||||||
|
progressCallback(JSON.stringify({
|
||||||
|
status: 'cancelled',
|
||||||
|
message: `Cancelled`,
|
||||||
|
checksum,
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendChecksum(checksum, waitIntervalMs);
|
||||||
|
|
||||||
|
progressCallback(JSON.stringify({
|
||||||
|
status: 'complete',
|
||||||
|
message: `Sent ${data.length} values`,
|
||||||
|
checksum,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wait(timeMs = 1500) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, timeMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(value, command) {
|
||||||
|
// console.log(`sending ${command}: ${value} `);
|
||||||
|
say(encode(value, command));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendChecksum(checksum, waitIntervalMs) {
|
||||||
|
const [cl, ch] = [checksum & 0xfff, checksum >> 12];
|
||||||
|
send(ch, 6);
|
||||||
|
await wait(waitIntervalMs);
|
||||||
|
send(cl, 5);
|
||||||
|
await wait(waitIntervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function say(message) {
|
||||||
|
client.say(secrets.CHANNEL_NAME, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(seconds) {
|
||||||
|
seconds = Math.ceil(seconds);
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainingSeconds = seconds % 60;
|
||||||
|
if (minutes === 0) {
|
||||||
|
return `${remainingSeconds}s`;
|
||||||
|
} else {
|
||||||
|
return `${minutes}m${remainingSeconds}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 1000: al
|
||||||
|
// 1001: ah
|
||||||
|
// 1010: vl
|
||||||
|
// 1011: vh
|
||||||
|
|
||||||
|
function encode(value, command) {
|
||||||
|
const aBitCount = 4;
|
||||||
|
const bBitCount = 4;
|
||||||
|
const cBitCount = 4;
|
||||||
|
const dBitCount = 4;
|
||||||
|
|
||||||
|
const valueBitString = value.toString(2).padStart(12, '0');
|
||||||
|
const commandBitString = command.toString(2).padStart(4, '0');
|
||||||
|
const bitString = commandBitString + valueBitString;
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
const a = parseInt(bitString.slice(0, aBitCount), 2);
|
||||||
|
index += aBitCount;
|
||||||
|
const b = parseInt(bitString.slice(index, index + bBitCount), 2);
|
||||||
|
index += bBitCount;
|
||||||
|
const c = parseInt(bitString.slice(index, index + cBitCount), 2);
|
||||||
|
index += cBitCount;
|
||||||
|
const d = parseInt(bitString.slice(index, index + dBitCount), 2);
|
||||||
|
index += dBitCount;
|
||||||
|
|
||||||
|
const maxTotal = 2 ** aBitCount + 2 ** bBitCount + 2 ** cBitCount + 2 ** dBitCount;
|
||||||
|
const e = maxTotal - (a + b + c + d);
|
||||||
|
|
||||||
|
const values = [...Array(a).fill('a'), ...Array(b).fill('b'), ...Array(c).fill('c'), ...Array(d).fill('d'), ...Array(e).fill('e')];
|
||||||
|
return values.join(' ');
|
||||||
|
}
|
18
chatbot/data.js
Normal file
18
chatbot/data.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
export default [
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// [0x000, 0x000, 0x003, 0x046],
|
||||||
|
[0x001, 0x000, 0x005, 0x047],
|
||||||
|
// [0x002, 0x000, 0x000, 0x044],
|
||||||
|
// [0x003, 0x000, 0x001, 0x73d],
|
||||||
|
// [0x004, 0x000, 0x002, 0xc45],
|
||||||
|
// [0x005, 0x000, 0xc00, 0x0a4],
|
||||||
|
// [0x006, 0x000, 0x001, 0x236],
|
||||||
|
// [0x007, 0x000, 0x001, 0x33f],
|
||||||
|
// [0x008, 0x000, 0xffb, 0xcc7],
|
||||||
|
// [0x009, 0x000, 0x000, 0x026],
|
||||||
|
// [0x00a, 0x000, 0x000, 0xfc4],
|
||||||
|
|
||||||
|
|
||||||
|
];
|
51
chatbot/notes.txt
Normal file
51
chatbot/notes.txt
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
Entering chat into Twitch
|
||||||
|
|
||||||
|
In the PS5, when livestream messages are configured to display:
|
||||||
|
>=101 characters => does not show anything
|
||||||
|
99 or 100 characters => the final 3 turn into an ellipsis
|
||||||
|
<=98 characters => all show
|
||||||
|
|
||||||
|
a b c d e, result
|
||||||
|
|
||||||
|
100 characters is some kind of limit
|
||||||
|
150 is the limit?
|
||||||
|
140?
|
||||||
|
other chars affect the result (limit is based on all message chars, not just matching ones?)
|
||||||
|
|
||||||
|
a*100 + b + e = 98 1 1
|
||||||
|
ab*100 + e = 50 50 0
|
||||||
|
ab*50 + e = 49 50 1
|
||||||
|
a*50 + b*50 = 50 50
|
||||||
|
a*51 + b*51 = 49 51
|
||||||
|
x*50 + a*50 + b*50 = 55.56 44.44
|
||||||
|
a*200 + b*200 = 100 0
|
||||||
|
a*120 + b*120 = 80 20
|
||||||
|
a*150 + b = 100 0
|
||||||
|
a*149 + b = 100 0
|
||||||
|
a*140 + b*10 = 100 0
|
||||||
|
a*130 + b*20 = 90 10
|
||||||
|
a*130 + b*10 = 90 10
|
||||||
|
a*130 + b = 99 1
|
||||||
|
a*130 + b*2 = 98 2
|
||||||
|
a*130 + b*5 = 95 5
|
||||||
|
'aaaaaaaaaa '*13 + b*5 = 100 0
|
||||||
|
|
||||||
|
it's only counting at most 10 b's?
|
||||||
|
|
||||||
|
'a '*60 + 'b '*40 = 85.71 14.29 (6x more a, 60 a, 10 b)
|
||||||
|
'a '*100 + 'b '*100 = 100 0
|
||||||
|
'a '*50 + 'b '*50 = 71.43 28.57 (2.5x more a, 50 a, 20 b)
|
||||||
|
'd ' + 'a '*50 + 'b '*50 = 71.43 27.14 1.43
|
||||||
|
|
||||||
|
a + b*100 = 0 100
|
||||||
|
a + 'b '*100 = 1.41 98.59 (70x more b)
|
||||||
|
ac + 'b '*100 = 1.41 97.13 1.41
|
||||||
|
ac + 'b '*150 = 1.41 97.13 1.41
|
||||||
|
|
||||||
|
if a term appears at least once, 10 slots are allocated to it, 150 slots in total?
|
||||||
|
|
||||||
|
acd + 'b '*150 = 1.39 95.83 1.39 1.39
|
||||||
|
'acd ' + 'b '*150 = 1.41 95.77 + 1.41 + 1.41 (67.92x more b)
|
||||||
|
|
||||||
|
a*70 + b*10 + c*10 + d*10 = 70 10 10 10
|
||||||
|
a*80 + b*10 + c*10 + d*10 = 70 10 10 10
|
15
chatbot/package.json
Normal file
15
chatbot/package.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "lbpcomputerbot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "bot.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"tmi.js": "^1.8.5"
|
||||||
|
}
|
||||||
|
}
|
32
chatbot/tools.js
Normal file
32
chatbot/tools.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
function encode(value, upper = false, address = false) {
|
||||||
|
// const aBitCount = 7;
|
||||||
|
// const bBitCount = 7;
|
||||||
|
// const cBitCount = 7;
|
||||||
|
// const dBitCount = 6;
|
||||||
|
const aBitCount = 4;
|
||||||
|
const bBitCount = 4;
|
||||||
|
const cBitCount = 4;
|
||||||
|
const dBitCount = 4;
|
||||||
|
|
||||||
|
const valueBitString = value.toString(2).padStart(12, '0');
|
||||||
|
const bitString = '10' + (address ? '0' : '1') + (upper ? '1' : '0') + valueBitString;
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
const a = parseInt(bitString.slice(0, aBitCount), 2);
|
||||||
|
index += aBitCount;
|
||||||
|
const b = parseInt(bitString.slice(index, index + bBitCount), 2);
|
||||||
|
index += bBitCount;
|
||||||
|
const c = parseInt(bitString.slice(index, index + cBitCount), 2);
|
||||||
|
index += cBitCount;
|
||||||
|
const d = parseInt(bitString.slice(index, index + dBitCount), 2);
|
||||||
|
index += dBitCount;
|
||||||
|
|
||||||
|
const maxTotal = 2 ** aBitCount + 2 ** bBitCount + 2 ** cBitCount + 2 ** dBitCount;
|
||||||
|
const e = maxTotal - (a + b + c + d);
|
||||||
|
|
||||||
|
console.log(bitString);
|
||||||
|
console.log(a, b, c, d, e);
|
||||||
|
|
||||||
|
const values = [...Array(a).fill('a'), ...Array(b).fill('b'), ...Array(c).fill('c'), ...Array(d).fill('d'), ...Array(e).fill('e')];
|
||||||
|
return values.join(' ');
|
||||||
|
}
|
253
doc/parva/assembly_0.1.md
Normal file
253
doc/parva/assembly_0.1.md
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
; LBP 24-bit CPU assembly version 0.1
|
||||||
|
|
||||||
|
# Registers
|
||||||
|
|
||||||
|
0000 | x0/ra | caller
|
||||||
|
0001 | x1/sp | callee
|
||||||
|
0010 | x2/bp | caller
|
||||||
|
0011 | x3/s0 | callee
|
||||||
|
0100 | x4/t0 | caller
|
||||||
|
0101 | x5/t1 | caller
|
||||||
|
0110 | x6/a0 | caller
|
||||||
|
0111 | x7/a1 | caller
|
||||||
|
|
||||||
|
; Read-only registers
|
||||||
|
|
||||||
|
1000 | zero
|
||||||
|
1001 | pc
|
||||||
|
1010 | cycle
|
||||||
|
1011 | upper (0xFFF000)
|
||||||
|
1100 | (unused, may trigger different instruction)
|
||||||
|
1101 | (unused, may trigger different instruction)
|
||||||
|
1110 | (unused, may trigger different instruction)
|
||||||
|
1111 | (unused, may trigger different instruction)
|
||||||
|
|
||||||
|
; Double aliases
|
||||||
|
|
||||||
|
000 | x01 | x0 + x1
|
||||||
|
010 | x23 | x2 + x3
|
||||||
|
100 | x45 | x4 + x5
|
||||||
|
110 | x67 | x6 + x7
|
||||||
|
|
||||||
|
|
||||||
|
# Encoding
|
||||||
|
|
||||||
|
Instructions are fixed-width and 24 bits long. The following layouts are used:
|
||||||
|
|
||||||
|
[ 5, opcode ] [ 4, source register A ] [ 3, dest register ] [ 3, source register B ] [ 9, padding ]
|
||||||
|
[ 5, opcode ] [ 4, source register A ] [ 3, dest register ] [ 12, unsigned immediate ]
|
||||||
|
[ 5, opcode ] [ 4, source register A ] [ 3, dest register ] [ 12, signed immediate ]
|
||||||
|
[ 4, opcode ] [ 1, source register A ] [ 2, ones ] [ 2, source register A ] [ 3, dest register ] [ 12, signed immediate ]
|
||||||
|
|
||||||
|
Depending on the instruction, the role of the registers may be swapped around.
|
||||||
|
|
||||||
|
|
||||||
|
# ALU instructions (0)
|
||||||
|
|
||||||
|
; addi/subi/mulu/mulhu immediate values are signed and will be sign-extended, others are unsigned
|
||||||
|
; TODO: Don't sign-extend certain immediates
|
||||||
|
; AAAA must be between 0000 and 1011, higher values may change the instruction
|
||||||
|
|
||||||
|
00000 AAAA DDD IIIIII IIIIII | addi xA, xD, I
|
||||||
|
00001 AAAA DDD BBB000 000000 | add xA, xD, xB
|
||||||
|
00010 1000 DDD IIIIII IIIIII | lui xD, I ; load I into the upper 12 bits of xD
|
||||||
|
00011 AAAA DDD BBB000 000000 | sub xA, xD, xB
|
||||||
|
00100 AAAA DDD IIIIII IIIIII | slli xA, xD, I
|
||||||
|
00101 AAAA DDD BBB000 000000 | sll xA, xD, xB
|
||||||
|
00110 AAAA DDD IIIIII IIIIII | srli xA, xD, I
|
||||||
|
00111 AAAA DDD BBB000 000000 | srl xA, xD, xB
|
||||||
|
01000 AAAA DDD IIIIII IIIIII | srai xA, xD, I
|
||||||
|
01001 AAAA DDD BBB000 000000 | sra xA, xD, xB
|
||||||
|
01010 AAAA DDD IIIIII IIIIII | xori xA, xD, I
|
||||||
|
01011 AAAA DDD BBB000 000000 | xor xA, xD, xB
|
||||||
|
01010 AAAA DDD IIIIII IIIIII | ori xA, xD, I
|
||||||
|
01011 AAAA DDD BBB000 000000 | or xA, xD, xB
|
||||||
|
01110 AAAA DDD IIIIII IIIIII | andi xA, xD, I
|
||||||
|
01111 AAAA DDD BBB000 000000 | and xA, xD, xB
|
||||||
|
|
||||||
|
000A0 11AA DDD IIIIII IIIIII | mului xA, xD, I ; unsigned multiply, lower 24 bits of result
|
||||||
|
000A1 11AA DDD BBB000 000000 | mulu xA, xD, xB
|
||||||
|
001A0 11AA DDD IIIIII IIIIII | mulhu xA, xD, I ; unsigned multiply, upper 24 bits of result
|
||||||
|
001A1 11AA DDD BBB000 000000 | mulhui xA, xD, xB
|
||||||
|
010A0 11AA DDD IIIIII IIIIII | divui xA, xD, I ; unsigned integer divide, quotient
|
||||||
|
010A1 11AA DDD BBB000 000000 | divu xA, xD, xB
|
||||||
|
011A0 11AA DDD IIIIII IIIIII | remui xA, xD, I ; unsigned integer divide, remainder
|
||||||
|
011A1 11AA DDD BBB000 000000 | remu xA, xD, xB
|
||||||
|
|
||||||
|
|
||||||
|
; Pseudo-instructions
|
||||||
|
|
||||||
|
nop => addi x0, x0, 0
|
||||||
|
li xD, I => addi zero, xD, I
|
||||||
|
lli xD, I => or xD, zero, I
|
||||||
|
la xD, I => addi pc, xD, I
|
||||||
|
li xD, imm24 =>
|
||||||
|
lui xD, imm24[23:12]
|
||||||
|
ori xD, xD, imm24[11:0]
|
||||||
|
rdcycle xD => addi xD, cycle, 0
|
||||||
|
mv xD, xA => addi xD, xA, 0
|
||||||
|
not xD, xA =>
|
||||||
|
xor xD, upper, xA
|
||||||
|
xori xD, xA, -1
|
||||||
|
|
||||||
|
|
||||||
|
# Data instructions (10)
|
||||||
|
|
||||||
|
; lli and lui immediates are unsigned, other instruction immediates are signed
|
||||||
|
; When loading/storing doubles, addresses must be a multiple of 2
|
||||||
|
|
||||||
|
10000 AAAA DDD IIIIII IIIIII | lw xD, I(xA) ; load word at [xA + I] into xD
|
||||||
|
10010 AAAA DDD IIIIII IIIIII | sw xD, I(xA) ; store xD into address [xA + I]
|
||||||
|
10100 AAAA DDD IIIIII IIIIII | ld xDD, I(xA) ; load double [xA + I] into xDD
|
||||||
|
10110 AAAA DDD IIIIII IIIIII | sd xDD, I(xA) ; store xDD into address [xA + I]
|
||||||
|
|
||||||
|
; Not implemented
|
||||||
|
|
||||||
|
10001 AAAA DDD BBB000 000000 | lw xD, xB(xA) ; load word at [xA + xB] into xD
|
||||||
|
10011 AAAA DDD BBB000 000000 | sw xD, xB(xA) ; store xD into address [xA + xB]
|
||||||
|
10101 AAAA DDD BBB000 000000 | ld xDD, xB(xA) ; load double [xA + xB] into xDD
|
||||||
|
10111 AAAA DDD BBB000 000000 | sd xDD, xB(xA) ; store xDD into address [xA + xB]
|
||||||
|
|
||||||
|
; Pseudo-instructions
|
||||||
|
|
||||||
|
lw/sw/ld/sd xD, I => lw/sw/ld/sd xD, I(zero)
|
||||||
|
|
||||||
|
|
||||||
|
# Memory-mapped I/O
|
||||||
|
|
||||||
|
; Memory addresses after 0xFFF000 represent I/O and special registers. The next bit after 0xFFF is always 0, and the following 2 bits specify the device.
|
||||||
|
; E.g. the memory range 0xFFF400 to 0xFFF5FF accesses I/O device 2
|
||||||
|
; The read-only register "upper" (1011) can be used to generate I/O addresses easily.
|
||||||
|
; E.g. sw x0, 0x205(upper)
|
||||||
|
; To execute commands or set I/O device memory, write to a device address.
|
||||||
|
; Commands with no arguments can be executed by writing `zero`.
|
||||||
|
; Commands with one argument can be executed by writing a word.
|
||||||
|
; Commands with two arguments can be executed by writing a doubleword.
|
||||||
|
; Each I/O device has an associated input buffer which it can write to.
|
||||||
|
; To pull data from this buffer, read from address 0 for the device.
|
||||||
|
|
||||||
|
; Standard device memory ranges and immediate aliases
|
||||||
|
|
||||||
|
111111 111111 000XXX XXXXXX | csr | special registers
|
||||||
|
111111 111111 001XXX XXXXXX | kb | keyboard
|
||||||
|
111111 111111 010XXX XXXXXX | gpu | GPU
|
||||||
|
111111 111111 011XXX XXXXXX | snd | sound card
|
||||||
|
|
||||||
|
|
||||||
|
; Special registers
|
||||||
|
|
||||||
|
write | 0xFFF 000000 000000 | | request current clock time
|
||||||
|
write | 0xFFF 000001 000000 | | request current clock time
|
||||||
|
read | get requested value
|
||||||
|
|
||||||
|
|
||||||
|
; Keyboard commands
|
||||||
|
|
||||||
|
read | get keycode (bottom 12 bits) and modifiers (upper 12 bits), 0 if nothing pressed
|
||||||
|
|
||||||
|
|
||||||
|
; GPU commands (TODO)
|
||||||
|
|
||||||
|
write | 0xFFF 0111II IIIIII | A, B | write doubleword AB to VRAM address I
|
||||||
|
write | 0xFFF 010001 000000 | A, B | move cursor to X position A and Y position B
|
||||||
|
write | 0xFFF 010010 IIIIII | A | set character shape to A (0=6x8, 1=6x1)
|
||||||
|
write | 0xFFF 010011 IIIIII | A | set current group to A
|
||||||
|
write | 0xFFF 010100 000000 | A, B | print character to buffer with pixel values AB
|
||||||
|
write | 0xFFF 010101 000000 | A | print character to buffer with pixel values at VRAM address [A]
|
||||||
|
write | 0xFFF 010110 000000 | A, B | print character to screen with pixel values AB
|
||||||
|
write | 0xFFF 010111 000000 | A | print character to screen with pixel values at VRAM address [A]
|
||||||
|
write | 0xFFF 010000 000000 | | clear buffer of current group
|
||||||
|
write | 0xFFF 010001 000000 | | clear current group on screen
|
||||||
|
write | 0xFFF 010010 000000 | | print buffer of current group to screen
|
||||||
|
write | 0xFFF 010011 000000 | | clear current group on screen and print its buffer
|
||||||
|
write | 0xFFF 010100 000000 | | clear buffer of all groups
|
||||||
|
write | 0xFFF 010101 000000 | | clear screen
|
||||||
|
write | 0xFFF 010110 000000 | | print buffer of all groups to screen
|
||||||
|
write | 0xFFF 010111 000000 | | clear screen and print buffer of all groups
|
||||||
|
read | (not used)
|
||||||
|
|
||||||
|
; Temporary simplified GPU commands
|
||||||
|
|
||||||
|
010000 000000 | A, B | move cursor to X position A and Y position B
|
||||||
|
010001 000000 | A | print 6x1 pixels A to buffer
|
||||||
|
010010 000000 | A, B | print 6x8 pixels AB to buffer
|
||||||
|
010011 000000 | | print buffer to screen
|
||||||
|
010100 000000 | | clear screen
|
||||||
|
010101 000000 | | clear screen and print buffer to screen
|
||||||
|
|
||||||
|
|
||||||
|
; Sound card commands
|
||||||
|
|
||||||
|
write | 0xFFF 011000 000000 | A | play beep with pitch A
|
||||||
|
|
||||||
|
|
||||||
|
; Pseudo-instructions
|
||||||
|
|
||||||
|
iolw xD, V => lw xD, (V << 9)(upper) ; read from input buffer for I/O device V into xD
|
||||||
|
iosw xD, V, I => sw xD, (V << 9 + I)(upper) ; execute command I of device V with word argument xD
|
||||||
|
iosd xDD, V, I => sd xDD, (V << 9 + I)(upper) ; execute command I of device V with double argument xDD
|
||||||
|
|
||||||
|
|
||||||
|
# Branching instructions (11)
|
||||||
|
|
||||||
|
; Branch (b) immediates are signed, jump (j) immediates are unsigned
|
||||||
|
; Branching is relative, jumping is absolute
|
||||||
|
; AAAA must be between 0000 and 1011
|
||||||
|
|
||||||
|
11CCC AAAA DDD IIIIII IIIIII | bC xA, xD, I ; compare xA and xD for condition C and branch to [pc + I]
|
||||||
|
11110 AAAA 000 IIIIII IIIIII | blt xA, zero, I
|
||||||
|
11111 AAAA 000 IIIIII IIIIII | bge xA, zero, I
|
||||||
|
11110 AAAA 100 IIIIII IIIIII | j I(xA) ; jump to [xA + I]
|
||||||
|
11111 AAAA 100 BBB000 000000 | j xB(xA) ; jump to [xA + xB]
|
||||||
|
|
||||||
|
; Conditions (C)
|
||||||
|
|
||||||
|
000 | beq, equals
|
||||||
|
010 | bltu, less than unsigned
|
||||||
|
100 | blt, less than
|
||||||
|
110 | blt, less than (with special operand)
|
||||||
|
001 | bne, not equals
|
||||||
|
011 | bgeu, greater than or equal unsigned
|
||||||
|
101 | bge, greater than or equal
|
||||||
|
111 | bge, greater than or equal (with special operand)
|
||||||
|
|
||||||
|
beq: Z==1
|
||||||
|
bne: Z==0
|
||||||
|
blt: N!=V
|
||||||
|
bltu: C==0
|
||||||
|
bge: N==V
|
||||||
|
bgeu: C==1
|
||||||
|
|
||||||
|
; Pseudo-instructions
|
||||||
|
|
||||||
|
b I => beq x0, x0, I
|
||||||
|
b xB => j xB(pc)
|
||||||
|
j xB => j xB(zero)
|
||||||
|
wfi => beq x0, x0, 0
|
||||||
|
|
||||||
|
bgt xD, xA, I => blt xA, xD, I
|
||||||
|
bgtu xD, xA, I => bltu xA, xD, I
|
||||||
|
ble xD, xA, I => bge xA, xD, I
|
||||||
|
bleu xD, xA, I => bgeu xA, xD, I
|
||||||
|
|
||||||
|
beqz xD, I => beq zero, xD, I
|
||||||
|
bltz xD, I => blt xD, zero, I
|
||||||
|
bnez xD, I => bne zero, xD, I
|
||||||
|
bgez xD, I => bge xD, zero, I
|
||||||
|
bgtz xD, I => blt zero, xD, I
|
||||||
|
blez xD, I => bge zero, xD, I
|
||||||
|
|
||||||
|
|
||||||
|
# Microcode
|
||||||
|
|
||||||
|
; Channels
|
||||||
|
|
||||||
|
i24 | alu_a: ALU input value A
|
||||||
|
i24 | alu_b: ALU input value B
|
||||||
|
b4 | ins_a: instruction operand A
|
||||||
|
b3 | ins_d: instruction operand D
|
||||||
|
b3 | ins_b: instruction operand B
|
||||||
|
i12 | imm: instruction immediate value
|
||||||
|
i24 | mout_a: memory write value a
|
||||||
|
i24 | mout_b: memory write value b
|
||||||
|
i24 | b_offset: branching offset
|
1
programs/parva/add_carry_1.lbpasm
Normal file
1
programs/parva/add_carry_1.lbpasm
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.asm_version 0.1
|
48
programs/parva/assembler_1.lbpasm
Normal file
48
programs/parva/assembler_1.lbpasm
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
hash_table:
|
||||||
|
.data blablabla
|
||||||
|
|
||||||
|
|
||||||
|
parse_line:
|
||||||
|
li
|
||||||
|
rw t0, 0(a0)
|
||||||
|
srli t1, 18
|
||||||
|
beqz end_of_line
|
||||||
|
sub t1, t0, t1
|
||||||
|
|
||||||
|
end_of_line:
|
||||||
|
|
||||||
|
.char_encoding lbpc1
|
||||||
|
|
||||||
|
get_instruction:
|
||||||
|
|
||||||
|
a-z 0-9 () , space enter
|
||||||
|
|
||||||
|
li x2, 0 ; cursor position in line
|
||||||
|
|
||||||
|
listen:
|
||||||
|
iord x0, 1, 0 ; get char from keyboard (device 1, buffer 0)
|
||||||
|
beqz listen
|
||||||
|
sub x1, x0, 'a'
|
||||||
|
.eq diff 'z' - 'a'
|
||||||
|
bleu x1, diff, okay
|
||||||
|
sub x1, x0, '0'
|
||||||
|
.eq diff '9' - '0'
|
||||||
|
bleu x1, diff, okay
|
||||||
|
|
||||||
|
beq x0, ' ', okay
|
||||||
|
beq x0, '\n', okay
|
||||||
|
beq x0, '(', okay
|
||||||
|
beq x0, ')', okay
|
||||||
|
beq x0, ',', okay
|
||||||
|
b invalid_input
|
||||||
|
|
||||||
|
invalid_input:
|
||||||
|
iow 3, 1, 0o1214 ; play buzzing sound (sound card is device 3, play direct value command is 1)
|
||||||
|
b listen
|
||||||
|
|
||||||
|
okay:
|
||||||
|
iow 2, 2, x0, 0 ; print character x0 (gpu is device 2, print char command is 2)
|
||||||
|
addi x2, x2, 1 ; move cursor right
|
||||||
|
|
||||||
|
|
||||||
|
|
103
programs/parva/binary_to_decimal_1.lbpasm
Normal file
103
programs/parva/binary_to_decimal_1.lbpasm
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
lw x0, value
|
||||||
|
li x1, 0
|
||||||
|
|
||||||
|
# https://ridiculousfish.com/blog/posts/labor-of-division-episode-i.html
|
||||||
|
dec_to_bin_loop:
|
||||||
|
lw x4, ten_powers(x1)
|
||||||
|
beqz x4, end_dec_to_bin_loop
|
||||||
|
slli x5, x1, 1
|
||||||
|
lw x3, ten_divisors(x5)
|
||||||
|
lw x5, (ten_divisors + 1)(x5)
|
||||||
|
mulhu x6, x0, x3
|
||||||
|
sub x7, x0, x6
|
||||||
|
srli x7, x7, 1
|
||||||
|
add x3, x7, x6
|
||||||
|
srl x3, x3, x5 # x3 = digit of result
|
||||||
|
sw x3, result_string(x1)
|
||||||
|
mulu x3, x3, x4
|
||||||
|
sub x0, x0, x3
|
||||||
|
addi x1, x1, 1
|
||||||
|
b dec_to_bin_loop
|
||||||
|
end_dec_to_bin_loop:
|
||||||
|
|
||||||
|
sw x0, result_string(x1)
|
||||||
|
li x0, -1
|
||||||
|
li x6, 1 # cursor x
|
||||||
|
li x7, 1 # cursor y
|
||||||
|
sw x0, 0b01_0100_000000(upper) # gpu clear screen
|
||||||
|
|
||||||
|
skip_zeroes:
|
||||||
|
addi x0, x0, 1
|
||||||
|
lw x1, result_string(x0)
|
||||||
|
beqz x1, skip_zeroes
|
||||||
|
print_loop:
|
||||||
|
sd x67, 0b01_0000_000000(upper) # gpu move cursor
|
||||||
|
lw x1, result_string(x0)
|
||||||
|
bltz x1, end_print_loop
|
||||||
|
slli x1, x1, 1
|
||||||
|
lw x2, char_pixels(x1)
|
||||||
|
lw x3, (char_pixels + 1)(x1)
|
||||||
|
sd x23, 0b01_0010_000000(upper) # gpu print char
|
||||||
|
addi x6, x6, 4
|
||||||
|
addi x0, x0, 1
|
||||||
|
b print_loop
|
||||||
|
end_print_loop:
|
||||||
|
|
||||||
|
sd x01, 0b01_0011_000000(upper) # gpu show buffer
|
||||||
|
wfi
|
||||||
|
|
||||||
|
ten_powers:
|
||||||
|
.data 10000000
|
||||||
|
.data 1000000
|
||||||
|
.data 100000
|
||||||
|
.data 10000
|
||||||
|
.data 1000
|
||||||
|
.data 100
|
||||||
|
.data 10
|
||||||
|
.data 0
|
||||||
|
|
||||||
|
ten_divisors:
|
||||||
|
.data 0xad7f2a 23 # 10000000
|
||||||
|
.data 0x0c6f7b 19 # 1000000
|
||||||
|
.data 0x4f8b59 16 # 100000
|
||||||
|
.data 0xa36e2f 13 # 10000
|
||||||
|
.data 0x0624de 9 # 1000
|
||||||
|
.data 0x47ae15 6 # 100
|
||||||
|
.data 0x99999a 3 # 10
|
||||||
|
|
||||||
|
char_pixels:
|
||||||
|
.data 0b111000_101000_101000_101000 # 0
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b001000_001000_001000_001000 # 1
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_111000_100000 # 2
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_111000_001000 # 3
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b101000_101000_111000_001000 # 4
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_100000_111000_001000 # 5
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_100000_111000_101000 # 6
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_001000_010000 # 7
|
||||||
|
.data 0b010000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_101000_111000_101000 # 8
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_101000_111000_001000 # 9
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
result_string:
|
||||||
|
.repeat 0 8
|
||||||
|
.data -1
|
||||||
|
|
||||||
|
value:
|
||||||
|
.data 6942069
|
109
programs/parva/binary_to_decimal_2.lbpasm
Normal file
109
programs/parva/binary_to_decimal_2.lbpasm
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
li sp, stack
|
||||||
|
|
||||||
|
j main
|
||||||
|
|
||||||
|
main:
|
||||||
|
sw x0, gpu_clear_screen(upper)
|
||||||
|
|
||||||
|
li x6, 1
|
||||||
|
li x7, 1
|
||||||
|
sd x67, cursor_pos
|
||||||
|
li a0, 6942069
|
||||||
|
call print_number
|
||||||
|
|
||||||
|
li x6, 1
|
||||||
|
li x7, 7
|
||||||
|
sd x67, cursor_pos
|
||||||
|
li a0, 12345678
|
||||||
|
call print_number
|
||||||
|
|
||||||
|
wfi
|
||||||
|
|
||||||
|
.eq gpu_clear_screen 0b01_0100_000000
|
||||||
|
.eq gpu_move_cursor 0b01_0000_000000
|
||||||
|
.eq gpu_print_char 0b01_0010_000000
|
||||||
|
.eq gpu_show_buffer 0b01_0011_000000
|
||||||
|
|
||||||
|
|
||||||
|
# void print_number(*void return: ra, int number: a0)
|
||||||
|
.address 128
|
||||||
|
print_number:
|
||||||
|
push ra
|
||||||
|
mv x0, a0
|
||||||
|
li x2, 0
|
||||||
|
li x4, 10000000
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
print_number__bin_to_dec_loop:
|
||||||
|
divu x3, x0, x4
|
||||||
|
remu x0, x0, x4
|
||||||
|
sw x3, print_number__result_string(x2)
|
||||||
|
divui x4, x4, 10
|
||||||
|
addi x2, x2, 1
|
||||||
|
bnez x4, print_number__bin_to_dec_loop
|
||||||
|
|
||||||
|
li x0, -1
|
||||||
|
ld x67, cursor_pos
|
||||||
|
print_number__skip_zeroes:
|
||||||
|
addi x0, x0, 1
|
||||||
|
lw x4, print_number__result_string(x0)
|
||||||
|
beqz x4, print_number__skip_zeroes
|
||||||
|
print_number__print_loop:
|
||||||
|
lw x4, print_number__result_string(x0)
|
||||||
|
bltz x4, print_number__end_print_loop
|
||||||
|
sd x67, gpu_move_cursor(upper)
|
||||||
|
slli x4, x4, 1
|
||||||
|
ld x23, print_number__char_pixels(x4)
|
||||||
|
addi x6, x6, 4
|
||||||
|
addi x0, x0, 1
|
||||||
|
sd x23, gpu_print_char(upper)
|
||||||
|
b print_number__print_loop
|
||||||
|
print_number__end_print_loop:
|
||||||
|
sd x01, gpu_show_buffer(upper) # gpu show buffer
|
||||||
|
sw x6, (cursor_pos + 0)
|
||||||
|
pop ra
|
||||||
|
j ra
|
||||||
|
|
||||||
|
print_number__char_pixels:
|
||||||
|
.data 0b111000_101000_101000_101000 # 0
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b001000_001000_001000_001000 # 1
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_111000_100000 # 2
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_111000_001000 # 3
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b101000_101000_111000_001000 # 4
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_100000_111000_001000 # 5
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_100000_111000_101000 # 6
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_001000_010000 # 7
|
||||||
|
.data 0b010000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_101000_111000_101000 # 8
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_101000_111000_001000 # 9
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
print_number__result_string:
|
||||||
|
.repeat 0 8
|
||||||
|
.data -1
|
||||||
|
# end print_number()
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
cursor_pos:
|
||||||
|
.data 1
|
||||||
|
.data 1
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
stack:
|
||||||
|
.data 0
|
96
programs/parva/calculator_1.lbpasm
Normal file
96
programs/parva/calculator_1.lbpasm
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# target parva_0_1
|
||||||
|
|
||||||
|
li sp, stack
|
||||||
|
|
||||||
|
# function
|
||||||
|
tokenize:
|
||||||
|
push ra
|
||||||
|
addi x2, a0, -1 # x2 = pointer to input string
|
||||||
|
|
||||||
|
li x0, -1
|
||||||
|
push x0 # mark the beginning of the token stack
|
||||||
|
|
||||||
|
tokenize__loop:
|
||||||
|
addi x2, x2, 1
|
||||||
|
lw x3, x2
|
||||||
|
beqz x3, tokenize__end
|
||||||
|
li x4, '0'
|
||||||
|
blt x3, x4, tokenize__symbol
|
||||||
|
li x5, '9'
|
||||||
|
bgt x3, x5, tokenize__symbol
|
||||||
|
|
||||||
|
mv x6, sp
|
||||||
|
.align 2
|
||||||
|
tokenize__number_gather:
|
||||||
|
push x3
|
||||||
|
addi x2, x2, 1
|
||||||
|
addi x3, x3, -'0'
|
||||||
|
lw x3, x2
|
||||||
|
blt x3, x4, tokenize__number_gather_end
|
||||||
|
bgt x3, x5, tokenize__number_gather_end
|
||||||
|
b tokenize__number_gather
|
||||||
|
tokenize__number_gather_end:
|
||||||
|
li x0, 0 # x0 = sum
|
||||||
|
li x7, 1 # x7 = power of 10
|
||||||
|
.align 2
|
||||||
|
tokenize__number_sum:
|
||||||
|
pop x3
|
||||||
|
mulu x3, x3, x7
|
||||||
|
add x0, x0, x3
|
||||||
|
mului x7, x7, 10
|
||||||
|
bne x6, sp, tokenize__number_sum
|
||||||
|
|
||||||
|
li x3, 1 # token type = 1 = number
|
||||||
|
push x3
|
||||||
|
push x0
|
||||||
|
b tokenize__loop
|
||||||
|
|
||||||
|
tokenize__symbol:
|
||||||
|
xori x3, x3, 42 # todo, set to appropriate value for hashing symbols
|
||||||
|
lw x3, tokenize__symbol_hashtable(x3)
|
||||||
|
beqz x3, error_invalid_input
|
||||||
|
push x3
|
||||||
|
b tokenize__loop
|
||||||
|
|
||||||
|
tokenize__end:
|
||||||
|
|
||||||
|
tokenize__shunting_yard:
|
||||||
|
pop x3
|
||||||
|
bnez x3, 3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
b end
|
||||||
|
|
||||||
|
tokenize__output_queue:
|
||||||
|
.repeat 0 40
|
||||||
|
|
||||||
|
tokenize__symbol_hashtable:
|
||||||
|
.data 0
|
||||||
|
.data 1
|
||||||
|
.repeat 0 30 # todo
|
||||||
|
|
||||||
|
error_invalid_input:
|
||||||
|
li a0, error_message_invalid_input
|
||||||
|
li a1, 1
|
||||||
|
call print
|
||||||
|
b end
|
||||||
|
|
||||||
|
# function
|
||||||
|
print:
|
||||||
|
# todo
|
||||||
|
ret
|
||||||
|
|
||||||
|
end:
|
||||||
|
wfi
|
||||||
|
|
||||||
|
input_string:
|
||||||
|
.string_encoding terminal
|
||||||
|
.string "125+23*8"
|
||||||
|
.string "\0"
|
||||||
|
|
||||||
|
error_message_invalid_input:
|
||||||
|
.string_encoding ascii # todo, use correct encoding
|
||||||
|
.string "Invalid input\0"
|
||||||
|
|
||||||
|
stack:
|
13
programs/parva/cli_1.lbpasm
Normal file
13
programs/parva/cli_1.lbpasm
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
split_args: ; fn([str8; len]) -> ( [[str8; len]; len] )
|
||||||
|
push ra
|
||||||
|
ld x01, 0(r0) ; x0 = pointer to first char, x1 = length
|
||||||
|
loop:
|
||||||
|
lw t0, 0(a0)
|
||||||
|
andi t0, a0, 0o77
|
||||||
|
beqz end
|
||||||
|
|
||||||
|
end:
|
||||||
|
|
||||||
|
hash:
|
||||||
|
|
||||||
|
|
12
programs/parva/fibonacci_1.lbpasm
Normal file
12
programs/parva/fibonacci_1.lbpasm
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
li x0, 0
|
||||||
|
li x1, 1
|
||||||
|
li x2, 20
|
||||||
|
|
||||||
|
loop:
|
||||||
|
|
||||||
|
add x0, x0, x1
|
||||||
|
add x1, x0, x1
|
||||||
|
subi x2, x2, 1
|
||||||
|
bnez x2, loop
|
||||||
|
|
||||||
|
wfi
|
24
programs/parva/fibonacci_2.lbpasm
Normal file
24
programs/parva/fibonacci_2.lbpasm
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
li x0, 1
|
||||||
|
li x1, 0
|
||||||
|
|
||||||
|
li x2, 30 ; nth fibonacci number to calculate
|
||||||
|
|
||||||
|
andi x4, x2, 1 ; x4 = n odd?
|
||||||
|
srli x2, x2, 1 ; n /= 2
|
||||||
|
|
||||||
|
loop:
|
||||||
|
|
||||||
|
add x0, x0, x1
|
||||||
|
add x1, x0, x1
|
||||||
|
subi x2, x2, 1
|
||||||
|
bnez x2, loop
|
||||||
|
|
||||||
|
beqz x4, no_final_iteration
|
||||||
|
|
||||||
|
add x1, x0, x1
|
||||||
|
|
||||||
|
no_final_iteration:
|
||||||
|
|
||||||
|
mv x0, x1
|
||||||
|
li x1, 0
|
||||||
|
wfi
|
24
programs/parva/fibonacci_3.lbpasm
Normal file
24
programs/parva/fibonacci_3.lbpasm
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
li x0, 1
|
||||||
|
li x1, 0
|
||||||
|
|
||||||
|
li x2, 30 ; nth fibonacci number to calculate
|
||||||
|
|
||||||
|
andi x4, x2, 1 ; x4 = n odd?
|
||||||
|
srli x2, x2, 1 ; n /= 2
|
||||||
|
|
||||||
|
loop:
|
||||||
|
|
||||||
|
add x0, x0, x1
|
||||||
|
add x1, x0, x1
|
||||||
|
subi x2, x2, 1
|
||||||
|
bnez x2, loop
|
||||||
|
|
||||||
|
beqz x4, no_final_iteration
|
||||||
|
|
||||||
|
add x1, x0, x1
|
||||||
|
|
||||||
|
no_final_iteration:
|
||||||
|
|
||||||
|
mv x0, x1
|
||||||
|
li x1, 0
|
||||||
|
wfi
|
93
programs/parva/gpu_hello_world_1.lbpasm
Normal file
93
programs/parva/gpu_hello_world_1.lbpasm
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
; CPU version 0.1
|
||||||
|
; GPU version 0.1 on IO port 1
|
||||||
|
|
||||||
|
nop
|
||||||
|
b _start
|
||||||
|
|
||||||
|
characters:
|
||||||
|
char_H: ; 0
|
||||||
|
.data 0b000000_100010_100010_111110 0b100010_100010_100010_000000
|
||||||
|
char_e: ; 1
|
||||||
|
.data 0b000000_000000_011000_100100 0b111100_100000_011100_000000
|
||||||
|
char_l: ; 2
|
||||||
|
.data 0b000000_100000_100000_100000 0b100000_100000_010000_000000
|
||||||
|
char_o: ; 3
|
||||||
|
.data 0b000000_000000_011000_100100 0b100100_100100_011000_000000
|
||||||
|
char_w: ; 4
|
||||||
|
.data 0b000000_000000_101010_101010 0b101010_101010_010100_000000
|
||||||
|
char_r: ; 5
|
||||||
|
.data 0b000000_000000_111000_100000 0b100000_100000_100000_000000
|
||||||
|
char_d: ; 6
|
||||||
|
.data 0b000100_011100_100100_100100 0b100100_100100_011100_000000
|
||||||
|
|
||||||
|
_start:
|
||||||
|
|
||||||
|
li x6, 1 ; x position
|
||||||
|
li x7, 1 ; y position
|
||||||
|
|
||||||
|
sw zero, 0b01_0100_000000(upper) ; gpu clear screen
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; H
|
||||||
|
lw x0, 0(char_H)(zero) ; upper 24 pixels
|
||||||
|
lw x1, 1(char_H)(zero) ; lower 24 pixels
|
||||||
|
sd x01, 0b01_0010_000000(upper) ; gpu print 6x8 char
|
||||||
|
addi x6, x6, 6 ; x position += 6
|
||||||
|
|
||||||
|
; e
|
||||||
|
lw x0, 0(char_e)(zero)
|
||||||
|
lw x1, 1(char_e)(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
|
||||||
|
; l
|
||||||
|
lw x0, 0(char_l)(zero)
|
||||||
|
lw x1, 1(char_l)(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
|
||||||
|
; l
|
||||||
|
lw x0, 0(char_l)(zero)
|
||||||
|
lw x1, 1(char_l)(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
|
||||||
|
; o
|
||||||
|
lw x0, 0(char_o)(zero)
|
||||||
|
lw x1, 1(char_o)(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
|
||||||
|
; w
|
||||||
|
lw x0, 0(char_w)(zero)
|
||||||
|
lw x1, 1(char_w)(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
|
||||||
|
; o
|
||||||
|
lw x0, 0(char_o)(zero)
|
||||||
|
lw x1, 1(char_o)(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
|
||||||
|
; r
|
||||||
|
lw x0, 0(char_r)(zero)
|
||||||
|
lw x1, 1(char_r)(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
|
||||||
|
; l
|
||||||
|
lw x0, 0(char_l)(zero)
|
||||||
|
lw x1, 1(char_l)(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
|
||||||
|
; d
|
||||||
|
lw x0, 0(char_d)(zero)
|
||||||
|
lw x1, 1(char_d)(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
|
||||||
|
sd x01, 0b01_0011_000000(upper) ; gpu print buffer to screen
|
||||||
|
|
||||||
|
wfi
|
117
programs/parva/gpu_hello_world_2.lbpasm
Normal file
117
programs/parva/gpu_hello_world_2.lbpasm
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
; CPU version 0.1
|
||||||
|
; GPU version 0.1 on IO port 1
|
||||||
|
|
||||||
|
nop
|
||||||
|
b _start
|
||||||
|
|
||||||
|
characters:
|
||||||
|
char_H_top: ; 0
|
||||||
|
.data 0b000000_100010_100010_111110
|
||||||
|
char_H_bottom:
|
||||||
|
.data 0b100010_100010_100010_000000
|
||||||
|
char_e_top: ; 1
|
||||||
|
.data 0b000000_000000_011000_100100
|
||||||
|
char_e_bottom:
|
||||||
|
.data 0b111100_100000_011100_000000
|
||||||
|
char_l_top: ; 2
|
||||||
|
.data 0b000000_100000_100000_100000
|
||||||
|
char_l_bottom:
|
||||||
|
.data 0b100000_100000_010000_000000
|
||||||
|
char_o_top: ; 3
|
||||||
|
.data 0b000000_000000_011000_100100
|
||||||
|
char_o_bottom:
|
||||||
|
.data 0b100100_100100_011000_000000
|
||||||
|
char_w_top: ; 4
|
||||||
|
.data 0b000000_000000_101010_101010
|
||||||
|
char_w_bottom:
|
||||||
|
.data 0b101010_101010_010100_000000
|
||||||
|
char_r_top: ; 5
|
||||||
|
.data 0b000000_000000_111000_100000
|
||||||
|
char_r_bottom:
|
||||||
|
.data 0b100000_100000_100000_000000
|
||||||
|
char_d_top: ; 6
|
||||||
|
.data 0b000100_011100_100100_100100
|
||||||
|
char_d_bottom:
|
||||||
|
.data 0b100100_100100_011100_000000
|
||||||
|
|
||||||
|
_start:
|
||||||
|
|
||||||
|
li x6, 1 ; x position
|
||||||
|
li x7, 1 ; y position
|
||||||
|
|
||||||
|
sw x0, 0b01_0100_000000(upper) ; gpu clear screen
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; H
|
||||||
|
lw x0, char_H_top(zero) ; upper 24 pixels
|
||||||
|
lw x1, char_H_bottom(zero) ; lower 24 pixels
|
||||||
|
sd x01, 0b01_0010_000000(upper) ; gpu print 6x8 char
|
||||||
|
addi x6, x6, 6 ; x position += 6
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; e
|
||||||
|
lw x0, char_e_top(zero)
|
||||||
|
lw x1, char_e_bottom(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; l
|
||||||
|
lw x0, char_l_top(zero)
|
||||||
|
lw x1, char_l_bottom(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; l
|
||||||
|
lw x0, char_l_top(zero)
|
||||||
|
lw x1, char_l_bottom(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; o
|
||||||
|
lw x0, char_o_top(zero)
|
||||||
|
lw x1, char_o_bottom(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; w
|
||||||
|
lw x0, char_w_top(zero)
|
||||||
|
lw x1, char_w_bottom(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; o
|
||||||
|
lw x0, char_o_top(zero)
|
||||||
|
lw x1, char_o_bottom(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; r
|
||||||
|
lw x0, char_r_top(zero)
|
||||||
|
lw x1, char_r_bottom(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; l
|
||||||
|
lw x0, char_l_top(zero)
|
||||||
|
lw x1, char_l_bottom(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
addi x6, x6, 6
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
|
||||||
|
; d
|
||||||
|
lw x0, char_d_top(zero)
|
||||||
|
lw x1, char_d_bottom(zero)
|
||||||
|
sd x01, 0b01_0010_000000(upper)
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop ; wait for print to finish
|
||||||
|
sd x01, 0b01_0011_000000(upper) ; gpu flush buffer to screen
|
||||||
|
|
||||||
|
wfi
|
59
programs/parva/gpu_hello_world_3.lbpasm
Normal file
59
programs/parva/gpu_hello_world_3.lbpasm
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
; Parva CPU version 0.1
|
||||||
|
; Parva GPU version 0.1 on IO port 0x1
|
||||||
|
|
||||||
|
nop
|
||||||
|
b _start
|
||||||
|
|
||||||
|
string:
|
||||||
|
.data 1 2 3 3 4 8 5 4 6 3 7 0
|
||||||
|
|
||||||
|
characters:
|
||||||
|
; upper 24 pixels, lower 24 pixels, width
|
||||||
|
char_null: ; 0
|
||||||
|
.data 0
|
||||||
|
characters_plus_1:
|
||||||
|
.data 0
|
||||||
|
characters_plus_2:
|
||||||
|
.data 0
|
||||||
|
char_H: ; 1
|
||||||
|
.data 0b000000_100010_100010_111110 0b100010_100010_100010_000000 6
|
||||||
|
char_e: ; 2
|
||||||
|
.data 0b000000_000000_011000_100100 0b111100_100000_011100_000000 5
|
||||||
|
char_l: ; 3
|
||||||
|
.data 0b000000_100000_100000_100000 0b100000_100000_010000_000000 3
|
||||||
|
char_o: ; 4
|
||||||
|
.data 0b000000_000000_011000_100100 0b100100_100100_011000_000000 5
|
||||||
|
char_w: ; 5
|
||||||
|
.data 0b000000_000000_101010_101010 0b101010_101010_010100_000000 6
|
||||||
|
char_r: ; 6
|
||||||
|
.data 0b000000_000000_011000_100000 0b100000_100000_100000_000000 4
|
||||||
|
char_d: ; 7
|
||||||
|
.data 0b000000_000100_011100_100100 0b100100_100100_011100_000000 5
|
||||||
|
char_space: ; 8
|
||||||
|
.data 0 0 4
|
||||||
|
|
||||||
|
_start:
|
||||||
|
|
||||||
|
sw x0, 0b01_0100_000000(upper) ; gpu clear screen
|
||||||
|
li x6, 1 ; x position
|
||||||
|
li x7, 1 ; y position
|
||||||
|
li x0, string
|
||||||
|
loop:
|
||||||
|
sd x67, 0b01_0000_000000(upper) ; gpu move cursor
|
||||||
|
lw x1, 0(x0) ; x1 = current character
|
||||||
|
beqz x1, end
|
||||||
|
add x2, x1, x1
|
||||||
|
add x1, x2, x1 ; x1 *= 3
|
||||||
|
lw x2, characters(x1) ; upper 24 pixels
|
||||||
|
lw x3, characters_plus_1(x1) ; lower 24 pixels
|
||||||
|
sd x01, 0b01_0011_000000(upper) ; gpu print buffer to screen
|
||||||
|
sd x23, 0b01_0010_000000(upper) ; gpu print 6x8 char
|
||||||
|
lw x4, characters_plus_2(x1) ; width
|
||||||
|
add x6, x6, x4 ; x position += width
|
||||||
|
addi x0, x0, 1
|
||||||
|
b loop
|
||||||
|
|
||||||
|
end:
|
||||||
|
|
||||||
|
sd x01, 0b01_0011_000000(upper) ; gpu print buffer to screen
|
||||||
|
wfi
|
35
programs/parva/gpu_print_1
Normal file
35
programs/parva/gpu_print_1
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
font_data:
|
||||||
|
.data 0x8b18a9 0xf9a9d7 0xeba9a0 0xdeadbe 0xefdead 0xbeefde...
|
||||||
|
|
||||||
|
.export _start:
|
||||||
|
li x0, font_data
|
||||||
|
li x1, 16 # font data array length
|
||||||
|
add x3, x0, x1
|
||||||
|
|
||||||
|
loop_1:
|
||||||
|
lw x2, 0(x0)
|
||||||
|
gpu.sw x2, 0(x1)
|
||||||
|
addi x0, 1
|
||||||
|
blt x0, x3 loop_1
|
||||||
|
|
||||||
|
li x6, 0
|
||||||
|
loop_2:
|
||||||
|
kb.rw x0
|
||||||
|
beq x0, x6, loop_2
|
||||||
|
|
||||||
|
and x1, x0, 0x0f
|
||||||
|
li x4, 2 # x position
|
||||||
|
li x5, 2 # y position
|
||||||
|
|
||||||
|
# first char
|
||||||
|
srl x1, x0, 4
|
||||||
|
gpu.move x4, x5
|
||||||
|
gpu.pchar x1
|
||||||
|
|
||||||
|
# second char
|
||||||
|
addi x4, x4, 6
|
||||||
|
gpu.move x4, x5
|
||||||
|
gpu.pchar x1
|
||||||
|
|
||||||
|
gpu.swap
|
||||||
|
b loop_2
|
33
programs/parva/memtest_1.lbpasm
Normal file
33
programs/parva/memtest_1.lbpasm
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
li x0, program_end ; x0 = start
|
||||||
|
li x1, 0xff ; x1 = end
|
||||||
|
|
||||||
|
mv x2, x0
|
||||||
|
|
||||||
|
loop_write:
|
||||||
|
|
||||||
|
xori x3, x2, 0xaaa
|
||||||
|
sw x3, 0(x2)
|
||||||
|
addi x2, x2, 1
|
||||||
|
ble x2, x1, loop_write
|
||||||
|
|
||||||
|
mv x2, x0
|
||||||
|
|
||||||
|
loop_read:
|
||||||
|
|
||||||
|
xori x3, x2, 0xaaa
|
||||||
|
lw x4, 0(x2)
|
||||||
|
bne x3, x4, error
|
||||||
|
addi x2, x2, 1
|
||||||
|
ble x2, x1, loop_read
|
||||||
|
|
||||||
|
success:
|
||||||
|
|
||||||
|
li x0, 0
|
||||||
|
wfi
|
||||||
|
|
||||||
|
error:
|
||||||
|
|
||||||
|
mv x0, x2
|
||||||
|
wfi
|
||||||
|
|
||||||
|
program_end:
|
62
programs/parva/memtest_2.lbpasm
Normal file
62
programs/parva/memtest_2.lbpasm
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
li x0, program_end
|
||||||
|
li x1, 512
|
||||||
|
|
||||||
|
mv x2, x0
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
loop_write_word:
|
||||||
|
|
||||||
|
xori x3, x2, 0xaaa
|
||||||
|
sw x3, 0(x2)
|
||||||
|
addi x2, x2, 1
|
||||||
|
blt x2, x1, loop_write_word
|
||||||
|
|
||||||
|
mv x2, x0
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
loop_read_word:
|
||||||
|
|
||||||
|
xori x3, x2, 0xaaa
|
||||||
|
lw x4, 0(x2)
|
||||||
|
bne x3, x4, error
|
||||||
|
addi x2, x2, 1
|
||||||
|
blt x2, x1, loop_read_word
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
loop_write_double:
|
||||||
|
|
||||||
|
xori x6, x2, 0xaaa
|
||||||
|
xori x7, x2, 0x555
|
||||||
|
sd x67, 0(x2)
|
||||||
|
addi x2, x2, 2
|
||||||
|
blt x2, x1, loop_write_double
|
||||||
|
|
||||||
|
mv x2, x0
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
loop_read_double:
|
||||||
|
|
||||||
|
xori x6, x2, 0xaaa
|
||||||
|
xori x7, x2, 0x555
|
||||||
|
ld x45, 0(x2)
|
||||||
|
bne x4, x6, error
|
||||||
|
bne x5, x7, error
|
||||||
|
lw x4, 0(x2)
|
||||||
|
bne x4, x6, error
|
||||||
|
lw x5, 1(x2)
|
||||||
|
bne x5, x7, error
|
||||||
|
addi x2, x2, 2
|
||||||
|
blt x2, x1, loop_read_double
|
||||||
|
|
||||||
|
success:
|
||||||
|
|
||||||
|
li x0, 0
|
||||||
|
wfi
|
||||||
|
|
||||||
|
error:
|
||||||
|
|
||||||
|
mv x0, x2
|
||||||
|
wfi
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
program_end:
|
16
programs/parva/multiply_1.lbpasm
Normal file
16
programs/parva/multiply_1.lbpasm
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
li a0, 420
|
||||||
|
li a1, 69
|
||||||
|
|
||||||
|
multiply:
|
||||||
|
li t0, 0
|
||||||
|
loop:
|
||||||
|
andi t1, a1, 1
|
||||||
|
beqz t1, no_add
|
||||||
|
add t0, t0, a0
|
||||||
|
no_add:
|
||||||
|
slli a0, a0, 1
|
||||||
|
srli a1, a1, 1
|
||||||
|
bnez a1, loop
|
||||||
|
mv a0, t0
|
||||||
|
j ra
|
||||||
|
|
105
programs/parva/pythagorean_triples_1.lbpasm
Normal file
105
programs/parva/pythagorean_triples_1.lbpasm
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
; calculate all primitive pythogorean triples (a^2 + b^2 = c^2) where c <= 32
|
||||||
|
|
||||||
|
nop
|
||||||
|
b _start
|
||||||
|
|
||||||
|
results:
|
||||||
|
.repeat 0 15
|
||||||
|
|
||||||
|
table:
|
||||||
|
.repeat 0 33
|
||||||
|
|
||||||
|
result_index:
|
||||||
|
.data 0
|
||||||
|
|
||||||
|
_start:
|
||||||
|
|
||||||
|
li x7, 32 ; limit
|
||||||
|
li x0, 1 ; prime
|
||||||
|
li x2, 1 ; prime factor bit
|
||||||
|
slli x2, x2, 23
|
||||||
|
|
||||||
|
loop_prime:
|
||||||
|
|
||||||
|
addi x0, x0, 1
|
||||||
|
bgt x0, x7, squares
|
||||||
|
lw x4, table(x0)
|
||||||
|
srli x4, x4, 12
|
||||||
|
bnez x4, loop_prime
|
||||||
|
mv x3, x0 ; multiple
|
||||||
|
|
||||||
|
loop_index:
|
||||||
|
|
||||||
|
lw x4, table(x3) ; x4 = current prime factor bits
|
||||||
|
or x4, x4, x2 ; x4 |= add current prime factor bit
|
||||||
|
sw x4, table(x3)
|
||||||
|
add x3, x3, x0
|
||||||
|
ble x3, x7, loop_index
|
||||||
|
srli x2, x2, 1 ; update prime bit
|
||||||
|
b loop_prime
|
||||||
|
|
||||||
|
squares:
|
||||||
|
|
||||||
|
li x0, 0 ; n
|
||||||
|
li x1, 0 ; n^2
|
||||||
|
|
||||||
|
loop_squares:
|
||||||
|
|
||||||
|
lw x4, table(x0) ; 70
|
||||||
|
or x4, x4, x1
|
||||||
|
sw x4, table(x0)
|
||||||
|
add x1, x1, x0
|
||||||
|
add x1, x1, x0
|
||||||
|
addi x1, x1, 1
|
||||||
|
addi x0, x0, 1
|
||||||
|
ble x0, x7, loop_squares
|
||||||
|
|
||||||
|
li x0, 1 ; a = 1
|
||||||
|
|
||||||
|
iterate_a:
|
||||||
|
|
||||||
|
addi x0, x0, 1 ; 79
|
||||||
|
bgt x0, x7, end
|
||||||
|
lw x2, table(x0) ; a^2 (with prime bits)
|
||||||
|
mv x6, x0 ; b = a
|
||||||
|
|
||||||
|
iterate_b:
|
||||||
|
|
||||||
|
addi x6, x6, 1 ; 83
|
||||||
|
bgt x6, x7, iterate_a
|
||||||
|
lw x3, table(x6) ; b^2 (with prime bits)
|
||||||
|
and x4, x2, x3 ; check if a and b are coprime
|
||||||
|
srli x4, x4, 12
|
||||||
|
bnez x4, iterate_b ; 87
|
||||||
|
add x3, x2, x3
|
||||||
|
andi x3, x3, 0o77 ; a^2 + b^2
|
||||||
|
mv x5, x6 ; x5 = c = b
|
||||||
|
|
||||||
|
; x0 = a, x1 = sp, x2 = a^2, x3 = a^2+b^2, x4 = (unused)
|
||||||
|
; x5 = c, x6 = b, x7 = limit
|
||||||
|
|
||||||
|
iterate_c:
|
||||||
|
|
||||||
|
addi x5, x5, 1 ; c++ ; 92
|
||||||
|
bgt x5, x7, iterate_b
|
||||||
|
lw x4, table(x5) ; x4 = c^2|cp
|
||||||
|
andi x4, x4, 0o77 ; x4 = c^2
|
||||||
|
bgt x4, x3, iterate_b
|
||||||
|
bne x5, x3, iterate_c
|
||||||
|
|
||||||
|
; found triple
|
||||||
|
|
||||||
|
; x0 = a, x6 = b, x5 = c
|
||||||
|
|
||||||
|
lw x4, result_index
|
||||||
|
sw x0, 0(x4)
|
||||||
|
sw x6, 1(x4)
|
||||||
|
sw x5, 2(x4)
|
||||||
|
addi x4, x4, 3
|
||||||
|
sw x4, result_index
|
||||||
|
|
||||||
|
b iterate_a
|
||||||
|
|
||||||
|
end:
|
||||||
|
|
||||||
|
wfi
|
273
programs/parva/pythagorean_triples_2.lbpasm
Normal file
273
programs/parva/pythagorean_triples_2.lbpasm
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
; calculate all primitive pythogorean triples (a^2 + b^2 = c^2) where c <= 32
|
||||||
|
|
||||||
|
.eq limit 32
|
||||||
|
|
||||||
|
li sp, stack
|
||||||
|
|
||||||
|
li x0, 1
|
||||||
|
sw x0, cursor_x
|
||||||
|
sw x0, cursor_y
|
||||||
|
|
||||||
|
sw x0, gpu_clear_screen(upper)
|
||||||
|
|
||||||
|
# main stack frame:
|
||||||
|
# sp-(limit + 2): table
|
||||||
|
# sp-2: a
|
||||||
|
# sp-1: a^2
|
||||||
|
main:
|
||||||
|
li x7, limit # limit
|
||||||
|
mv x6, sp # x6 = table base
|
||||||
|
add sp, sp, x7
|
||||||
|
addi sp, sp, 2 # reserve rest of stack frame
|
||||||
|
|
||||||
|
# --- zero-fill table ---
|
||||||
|
|
||||||
|
mv x5, x6
|
||||||
|
li x2, 0
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
main__clear_table:
|
||||||
|
sw x2, 0(x5)
|
||||||
|
addi x5, x5, 1
|
||||||
|
ble x5, sp, main__clear_table
|
||||||
|
|
||||||
|
# --- calculate primes factors ---
|
||||||
|
|
||||||
|
li x0, 1 # x0 = prime
|
||||||
|
li x2, 1 # x2 = prime factor bit
|
||||||
|
|
||||||
|
main__primes:
|
||||||
|
|
||||||
|
addi x0, x0, 1
|
||||||
|
bgt x0, x7, main__primes_end
|
||||||
|
add x4, x6, x0
|
||||||
|
lw x4, 0(x4)
|
||||||
|
bnez x4, main__primes
|
||||||
|
|
||||||
|
mv x3, x0 # x3 = multiple
|
||||||
|
|
||||||
|
main__multiples:
|
||||||
|
|
||||||
|
add x5, x6, x3
|
||||||
|
lw x4, 0(x5) # x4 = current prime factor bits
|
||||||
|
or x4, x4, x2 # x4 |= add current prime factor bit
|
||||||
|
sw x4, 0(x5)
|
||||||
|
add x3, x3, x0
|
||||||
|
ble x3, x7, main__multiples
|
||||||
|
|
||||||
|
slli x2, x2, 1 # update prime bit
|
||||||
|
b main__primes
|
||||||
|
|
||||||
|
main__primes_end:
|
||||||
|
|
||||||
|
# --- find pythagorean triples ---
|
||||||
|
|
||||||
|
li x0, 1 # a = 1
|
||||||
|
|
||||||
|
main__iterate_a:
|
||||||
|
|
||||||
|
lw x0, -2(sp)
|
||||||
|
addi x0, x0, 1 # x0 = a
|
||||||
|
sw x0, -2(sp)
|
||||||
|
bge x0, x7, main__end
|
||||||
|
mulu x2, x0, x0 # x2 = a^2
|
||||||
|
sw x2, -1(sp)
|
||||||
|
add x3, x6, x0
|
||||||
|
lw x3, 0(x3) # x3 = a prime factors
|
||||||
|
|
||||||
|
mv x4, x0 # x4 = b
|
||||||
|
|
||||||
|
main__iterate_b:
|
||||||
|
|
||||||
|
addi x4, x4, 1
|
||||||
|
bgt x4, x7, main__iterate_a
|
||||||
|
add x5, x6, x4
|
||||||
|
lw x5, 0(x5) # x5 = b prime factors
|
||||||
|
and x5, x5, x3
|
||||||
|
bnez x5, main__iterate_b # check if a and b are coprime
|
||||||
|
mulu x5, x4, x4
|
||||||
|
lw x0, -1(sp) # x0 = a^2
|
||||||
|
add x5, x5, x0 # x5 = a^2 + b^2
|
||||||
|
|
||||||
|
mv x0, x4 # x0 = c
|
||||||
|
|
||||||
|
addi x2, x0, 1
|
||||||
|
mulu x2, x2, x2
|
||||||
|
bgt x2, x5, main__iterate_a
|
||||||
|
|
||||||
|
main__iterate_c:
|
||||||
|
|
||||||
|
addi x0, x0, 1 ; c += 1
|
||||||
|
mulu x2, x0, x0 # x2 = c^2
|
||||||
|
bgt x2, x5, main__iterate_b
|
||||||
|
bne x2, x5, main__iterate_c
|
||||||
|
|
||||||
|
# found triple
|
||||||
|
|
||||||
|
push x6
|
||||||
|
push x7
|
||||||
|
push x0
|
||||||
|
push x4
|
||||||
|
lw a0, -6(sp)
|
||||||
|
call print_number
|
||||||
|
|
||||||
|
lw x6, cursor_x
|
||||||
|
addi x6, x6, 4
|
||||||
|
sw x6, cursor_x
|
||||||
|
|
||||||
|
pop a0
|
||||||
|
call print_number
|
||||||
|
|
||||||
|
lw x6, cursor_x
|
||||||
|
addi x6, x6, 4
|
||||||
|
sw x6, cursor_x
|
||||||
|
|
||||||
|
pop a0
|
||||||
|
call print_number
|
||||||
|
|
||||||
|
# newline
|
||||||
|
li x6, 1
|
||||||
|
sw x6, cursor_x
|
||||||
|
lw x7, cursor_y
|
||||||
|
addi x7, x7, 6
|
||||||
|
sw x7, cursor_y
|
||||||
|
|
||||||
|
pop x7
|
||||||
|
pop x6
|
||||||
|
|
||||||
|
b main__iterate_a
|
||||||
|
|
||||||
|
main__end:
|
||||||
|
|
||||||
|
wfi
|
||||||
|
# end main()
|
||||||
|
|
||||||
|
.eq gpu_clear_screen 0b01_0100_000000
|
||||||
|
.eq gpu_move_cursor 0b01_0000_000000
|
||||||
|
.eq gpu_print_char 0b01_0010_000000
|
||||||
|
.eq gpu_show_buffer 0b01_0011_000000
|
||||||
|
|
||||||
|
cursor_x:
|
||||||
|
.data 1
|
||||||
|
cursor_y:
|
||||||
|
.data 1
|
||||||
|
|
||||||
|
# void print_number(*void return: ra, int number: a0)
|
||||||
|
print_number:
|
||||||
|
push ra
|
||||||
|
mv x0, a0
|
||||||
|
li x2, 0
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
# https://ridiculousfish.com/blog/posts/labor-of-division-episode-i.html
|
||||||
|
print_number__bin_to_dec_loop:
|
||||||
|
lw x4, print_number__ten_powers(x2)
|
||||||
|
|
||||||
|
beqz x4, print_number__end_bin_to_dec_loop
|
||||||
|
slli x5, x2, 1
|
||||||
|
|
||||||
|
lw x3, print_number__ten_divisors(x5)
|
||||||
|
|
||||||
|
mulhu x6, x0, x3
|
||||||
|
sub x7, x0, x6
|
||||||
|
|
||||||
|
srli x7, x7, 1
|
||||||
|
lw x5, (print_number__ten_divisors + 1)(x5)
|
||||||
|
|
||||||
|
add x3, x7, x6
|
||||||
|
srl x3, x3, x5 # x3 = digit of result
|
||||||
|
|
||||||
|
sw x3, print_number__result_string(x2)
|
||||||
|
mulu x3, x3, x4
|
||||||
|
|
||||||
|
sub x0, x0, x3
|
||||||
|
addi x2, x2, 1
|
||||||
|
|
||||||
|
b print_number__bin_to_dec_loop
|
||||||
|
print_number__end_bin_to_dec_loop:
|
||||||
|
|
||||||
|
sw x0, print_number__result_string(x2)
|
||||||
|
li x0, -1
|
||||||
|
lw x6, cursor_x
|
||||||
|
lw x7, cursor_y
|
||||||
|
print_number__skip_zeroes:
|
||||||
|
addi x0, x0, 1
|
||||||
|
lw x4, print_number__result_string(x0)
|
||||||
|
beqz x4, print_number__skip_zeroes
|
||||||
|
print_number__print_loop:
|
||||||
|
sd x67, gpu_move_cursor(upper)
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop # give time for the cursor to move
|
||||||
|
lw x4, print_number__result_string(x0)
|
||||||
|
bltz x4, print_number__end_print_loop
|
||||||
|
slli x4, x4, 1
|
||||||
|
lw x2, print_number__char_pixels(x4)
|
||||||
|
lw x3, (print_number__char_pixels + 1)(x4)
|
||||||
|
addi x6, x6, 4
|
||||||
|
addi x0, x0, 1
|
||||||
|
sd x23, gpu_print_char(upper)
|
||||||
|
b print_number__print_loop
|
||||||
|
print_number__end_print_loop:
|
||||||
|
sd x01, gpu_show_buffer(upper) # gpu show buffer
|
||||||
|
sw x6, cursor_x
|
||||||
|
pop ra
|
||||||
|
j ra
|
||||||
|
|
||||||
|
print_number__ten_powers:
|
||||||
|
.data 10000000
|
||||||
|
.data 1000000
|
||||||
|
.data 100000
|
||||||
|
.data 10000
|
||||||
|
.data 1000
|
||||||
|
.data 100
|
||||||
|
.data 10
|
||||||
|
.data 0
|
||||||
|
|
||||||
|
print_number__ten_divisors:
|
||||||
|
.data 0xad7f2a 23 # 10000000
|
||||||
|
.data 0x0c6f7b 19 # 1000000
|
||||||
|
.data 0x4f8b59 16 # 100000
|
||||||
|
.data 0xa36e2f 13 # 10000
|
||||||
|
.data 0x0624de 9 # 1000
|
||||||
|
.data 0x47ae15 6 # 100
|
||||||
|
.data 0x99999a 3 # 10
|
||||||
|
|
||||||
|
print_number__char_pixels:
|
||||||
|
.data 0b111000_101000_101000_101000 # 0
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b001000_001000_001000_001000 # 1
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_111000_100000 # 2
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_111000_001000 # 3
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b101000_101000_111000_001000 # 4
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_100000_111000_001000 # 5
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_100000_111000_101000 # 6
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_001000_001000_010000 # 7
|
||||||
|
.data 0b010000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_101000_111000_101000 # 8
|
||||||
|
.data 0b111000_000000_000000_000000
|
||||||
|
|
||||||
|
.data 0b111000_101000_111000_001000 # 9
|
||||||
|
.data 0b001000_000000_000000_000000
|
||||||
|
print_number__result_string:
|
||||||
|
.repeat 0 8
|
||||||
|
.data -1
|
||||||
|
# end print_number()
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
stack:
|
||||||
|
.data 0
|
197
programs/parva/tetris_1.lbpasm
Normal file
197
programs/parva/tetris_1.lbpasm
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
.arch parva_0_1
|
||||||
|
|
||||||
|
# 1(sp): score
|
||||||
|
# 2(sp): swap_block
|
||||||
|
|
||||||
|
# x4: block lines 0/1
|
||||||
|
# x5: block lines 2/3
|
||||||
|
# x6: field lines 0/1
|
||||||
|
# x7: field lines 2/3
|
||||||
|
|
||||||
|
.include consts
|
||||||
|
.include sys
|
||||||
|
|
||||||
|
.string_encoding sys-6
|
||||||
|
|
||||||
|
|
||||||
|
.data.ro
|
||||||
|
|
||||||
|
srs_kick_table:
|
||||||
|
# XY, 0 = 0, 1 = +1, 2 = +2, 7 = -1, 6 = -2
|
||||||
|
# I block
|
||||||
|
0o60_10_67_12
|
||||||
|
0o70_20_72_27
|
||||||
|
# TODO
|
||||||
|
# J, L, T, S, Z blocks
|
||||||
|
0o70_71_06_76
|
||||||
|
0o10_17_02_12
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
block_table:
|
||||||
|
# I 0
|
||||||
|
0b_0_0000_000000_0_0_1111_000000_0
|
||||||
|
0b_0_1111_000000_0_0_0000_000000_0
|
||||||
|
# I 1
|
||||||
|
0b_0_0010_000000_0_0_0010_000000_0
|
||||||
|
0b_0_0010_000000_0_0_0010_000000_0
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
hi_score_filename: .cstring "tet_hiscr.dat"
|
||||||
|
hi_score_file_error_message: .cstring .line_break 10 "could not open or create hi score file"
|
||||||
|
score_string: .cstring "score"
|
||||||
|
game_over_string: .cstring "game over"
|
||||||
|
.scores_button_string: .cstring "scores"
|
||||||
|
.start_button_string: .cstring "start"
|
||||||
|
.next_string: .cstring "next"
|
||||||
|
|
||||||
|
.data.rw
|
||||||
|
|
||||||
|
.align const.cache_line_size
|
||||||
|
swap_block: 0
|
||||||
|
score: 0
|
||||||
|
next_air_drop_time: 0
|
||||||
|
next_floor_drop_time: 0
|
||||||
|
stack: .repeat 0, 20
|
||||||
|
|
||||||
|
hi_scores:
|
||||||
|
.repeat 0, 20
|
||||||
|
|
||||||
|
.eq score_string_x 30
|
||||||
|
.eq score_string_y 10
|
||||||
|
|
||||||
|
|
||||||
|
.text
|
||||||
|
|
||||||
|
_start:
|
||||||
|
|
||||||
|
push ra
|
||||||
|
|
||||||
|
load_hi_scores:
|
||||||
|
|
||||||
|
li a0, hi_score_filename
|
||||||
|
li a1, hi_scores
|
||||||
|
li x0, 20
|
||||||
|
call sys.read_file
|
||||||
|
li x0, -1
|
||||||
|
beq a0, x0, create_hi_score_file
|
||||||
|
|
||||||
|
create_hi_score_file:
|
||||||
|
|
||||||
|
li a0, hi_score_filename
|
||||||
|
call sys.open_file
|
||||||
|
|
||||||
|
bltz a0, error_hi_score_file
|
||||||
|
|
||||||
|
wait:
|
||||||
|
|
||||||
|
wait_loop:
|
||||||
|
|
||||||
|
stub.kb.rdchar x0
|
||||||
|
|
||||||
|
beqz, x0, wait
|
||||||
|
li x1, const.char.left_arrow
|
||||||
|
beq x0, x1, move_left
|
||||||
|
li x1, const.char.right_arrow
|
||||||
|
beq x0, x1, move_right
|
||||||
|
li x1, const.char.up_arrow
|
||||||
|
|
||||||
|
beq x0, x1, rotate
|
||||||
|
li x1, const.char.down_arrow
|
||||||
|
beq x0, x1, slow_drop
|
||||||
|
li x1, const.char.space
|
||||||
|
beq x0, x1, hard_drop
|
||||||
|
li x1, const.char.backspace
|
||||||
|
|
||||||
|
beq x0, x1, swap
|
||||||
|
b wait_loop
|
||||||
|
|
||||||
|
rotate:
|
||||||
|
|
||||||
|
# x2 = current pos / block / rotation, [0 * 8, 4 bits for position x, 0 * 7, 3 bits for block type, 2 bits for rotation]
|
||||||
|
|
||||||
|
addi x3, x2, 1
|
||||||
|
andi x3, x3, 0b00011
|
||||||
|
andi x2, x2, 0b11100
|
||||||
|
or x2, x2, x3
|
||||||
|
ll x01 block_table(x2)
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
move_left:
|
||||||
|
|
||||||
|
slli x0, x4, 1
|
||||||
|
and x0, x0, x6
|
||||||
|
bneqz x0, fail
|
||||||
|
slli x0, x5, 1
|
||||||
|
and x0, x0, x7
|
||||||
|
bneqz x0, fail
|
||||||
|
|
||||||
|
slli x4, x4, 1
|
||||||
|
slli x5, x5, 1
|
||||||
|
b move_successful
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
move_right:
|
||||||
|
|
||||||
|
srli x0, x4, 1
|
||||||
|
and x0, x0, x6
|
||||||
|
bneqz x0, fail
|
||||||
|
srli x0, x5, 1
|
||||||
|
and x0, x0, x7
|
||||||
|
bneqz x0, fail
|
||||||
|
|
||||||
|
srli x4, x4, 1
|
||||||
|
srli x5, x5, 1
|
||||||
|
b move_successful
|
||||||
|
|
||||||
|
move_failed:
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
move_successful:
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
# decimal addition
|
||||||
|
# input: x0 = score change
|
||||||
|
.align 2
|
||||||
|
add_score:
|
||||||
|
|
||||||
|
lw x1, score
|
||||||
|
add x0, x0, x1
|
||||||
|
li x1, 0o1166
|
||||||
|
and x2, x0, 0o77
|
||||||
|
li x3, 10
|
||||||
|
blt x2, x3, 2
|
||||||
|
|
||||||
|
add x0, x0, x1
|
||||||
|
slli x1, x1, 6
|
||||||
|
srli x2, x0, 6
|
||||||
|
and x2, x0, 0o77
|
||||||
|
blt x2, x3, end_score_change
|
||||||
|
add x0, x0, x1
|
||||||
|
|
||||||
|
slli x1, x1, 6
|
||||||
|
srli x2, x0, 6
|
||||||
|
and x2, x0, 0o77
|
||||||
|
blt x2, x3, end_score_change
|
||||||
|
add x0, x0, x1
|
||||||
|
slli x1, x1, 6
|
||||||
|
|
||||||
|
srli x2 x0, 6
|
||||||
|
and x2, x0, 0o77
|
||||||
|
blt x2, x3, end_score_change
|
||||||
|
add x0, x0, x1
|
||||||
|
|
||||||
|
.align 2
|
||||||
|
end_score_change:
|
||||||
|
|
||||||
|
sw x0, score
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
quit:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pop ra
|
30
programs/v8/test
Normal file
30
programs/v8/test
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
LDB #$F0
|
||||||
|
JNC bankNoCarry
|
||||||
|
NOT B
|
||||||
|
bankNoCarry:
|
||||||
|
LDA $00
|
||||||
|
AND B,A
|
||||||
|
OR D,A
|
||||||
|
STA $00
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Memory copy: A=start, B=offset, length=C, destroys D
|
||||||
|
copyLoop:
|
||||||
|
LDD $00,A
|
||||||
|
ADC B,A
|
||||||
|
STD $00,A
|
||||||
|
DEC C
|
||||||
|
JNZ copyLoop
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Memory copy to screen: A=start, destroys C, D, screen address register
|
||||||
|
LDD #$80
|
||||||
|
LDC D
|
||||||
|
STD $03
|
||||||
|
screenCopyLoop:
|
||||||
|
|
||||||
|
LDD $00,A
|
||||||
|
STD $02
|
||||||
|
DEC C
|
||||||
|
JNZ screenCopyLoop
|
||||||
|
RET
|
Loading…
Add table
Add a link
Reference in a new issue