Add compiler

This commit is contained in:
Asraelite 2024-05-17 18:37:51 +02:00
parent 476972f85a
commit 3f3125ef43
31 changed files with 2625 additions and 3 deletions

2
.gitignore vendored
View file

@ -5,3 +5,5 @@ package-lock.json
assembler/**/*.js
!assembler/ldt/**
c/
target/
Cargo.lock

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"editor.rulers": [100]
}

View file

@ -10,8 +10,10 @@
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script type="module" src="main.js"></script>
<style>
<title>LittleBigComputer assembler</title>
<style>
</style>
</head>
@ -24,6 +26,7 @@
<select id="config-target-arch-select">
<option value="v8">V8</option>
<option value="parva_0_1">Parva 0.1</option>
<option value="bitzzy">Bitzzy</option>
<option value="lodestar">Lodestar</option>
</select>
<button type="button" id="config-target-arch-docs-button">Show docs</button>

View file

@ -2,13 +2,14 @@ import * as bitUtils from './bit_utils.js';
import targetV8 from './targets/v8.js';
import targetParva_0_1 from './targets/parva_0_1.js';
import targetBitzzy from './targets/bitzzy.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 = 30;
const EMULATOR_SPEED = 70;
window.addEventListener('load', init);
@ -194,6 +195,8 @@ function getArch(name: string): ArchSpecification {
return targetV8;
} else if (name === 'parva_0_1') {
return targetParva_0_1;
} else if (name === 'bitzzy') {
return targetBitzzy;
} else if (name === 'lodestar') {
return targetLodestar as any;
} else {

792
assembler/targets/bitzzy.ts Normal file
View file

@ -0,0 +1,792 @@
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;
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 = 'x|y|z|X|Y|Z|YX|yx';
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, true));
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, dynamic: boolean = false): ParsedLinePart {
if (isNumber(numberOrLabelText)) {
return toBitString(toNumber(numberOrLabelText), bits, false, 8);
} else {
return {
addressRelative: false,
sourceAddress: this.currentAddress,
bitCount: dynamic ? 16 : 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 i3 = (...parts: Array<ParsedLinePart>) => {
this.instruction(parts, 1, source, 3);
};
const i4 = (...parts: Array<ParsedLinePart>) => {
this.instruction(parts, 1, source, 4);
};
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 addr = bindablePattern(VALUE_RE);
const registerAndA = bindablePattern(REGISTER_RE, '', `${so},${so}A`);
const valueAndA = bindablePattern(VALUE_RE, '', `${so},${so}A`);
const x = '[xX]';
const y = '[yY]';
const z = '[zZ]';
const yx = '[yY][xX]';
match('hlt', () => i('00000000'));
match('rti', () => i('00000010'));
match('eni', () => i('00000011'));
match('dsi', () => i('00000100'));
match('nop', () => i('00000101'));
match('rem', s, x, () => i('00001000'));
match('rem', s, y, () => i('00001001'));
match('rem', s, z, () => i('00001010'));
match('clr', () => i('00001011'));
match('jmp', s, addr(a), ({ a }) => { i3('00010000', this.value(a, 16)) });
match('jmp', s, addr(a), sep, x, ({ a }) => { i3('00010001', this.value(a, 16)) });
match('jmp', s, addr(a), sep, y, ({ a }) => { i3('00010010', this.value(a, 16)) });
match('jmp', s, addr(a), sep, z, ({ a }) => { i3('00010011', this.value(a, 16)) });
match('jsr', s, addr(a), ({ a }) => { i3('00010100', this.value(a, 16)) });
match('jsr', s, addr(a), sep, x, ({ a }) => { i3('00010101', this.value(a, 16)) });
match('jsr', s, addr(a), sep, y, ({ a }) => { i3('00010110', this.value(a, 16)) });
match('jsr', s, addr(a), sep, z, ({ a }) => { i3('00010111', this.value(a, 16)) });
match('swp', s, x, sep, y, () => { i('00011000') });
match('swp', s, y, sep, x, () => pseudo('SWP X, Y'));
match('swp', s, x, sep, z, () => { i('00011001') });
match('swp', s, z, sep, x, () => pseudo('SWP X, Z'));
match('swp', s, y, sep, z, () => { i('00011011') });
match('swp', s, z, sep, y, () => pseudo('SWP Y, Z'));
match('jmpez', s, x, sep, addr(a), ({ a }) => { i3('00100000', this.value(a, 16)) });
match('jmpez', s, z, sep, addr(a), ({ a }) => { i3('00100001', this.value(a, 16)) });
match('jmpez', s, register(r), sep, addr(a), () => { throw new Error('JMPEZ can only be used with registers X and Z') });
match('jmpgt', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100010', this.value(a, 16)) });
match('jmpgt', s, register(a), sep, register(b), sep, addr(r), () => { throw new Error('JMPGT can only be used with registers X and Y') });
match('jmpeq', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100011', this.value(a, 16)) });
match('jmpeq', s, y, sep, x, sep, addr(a), ({ a }) => pseudo(`JMPEQ X, Y, ${a}`));
match('jmpeq', s, x, sep, z, sep, addr(a), ({ a }) => { i3('11110010', this.value(a, 16)) });
match('jmpeq', s, z, sep, x, sep, addr(a), ({ a }) => pseudo(`JMPEQ X, Z, ${a}`));
match('jmpeq', s, y, sep, z, sep, addr(a), ({ a }) => { i3('11110011', this.value(a, 16)) });
match('jmpeq', s, z, sep, y, sep, addr(a), ({ a }) => pseudo(`JMPEQ Y, Z, ${a}`));
match('jmprez', s, addr(a), ({ a }) => { i3('11110000', this.value(a, 16)) });
match('jmprnz', s, addr(a), ({ a }) => { i3('11110001', this.value(a, 16)) });
match('jsrez', s, x, sep, addr(a), ({ a }) => { i3('00100100', this.value(a, 16)) });
match('jsrez', s, z, sep, addr(a), ({ a }) => { i3('00100101', this.value(a, 16)) });
match('jsrez', s, register(r), sep, addr(a), () => { throw new Error('JSREZ can only be used with registers X and Z') });
match('jsrgt', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100110', this.value(a, 16)) });
match('jsrgt', s, register(a), sep, register(b), sep, () => { throw new Error('JSRGT can only be used with registers X and Y') });
match('jsreq', s, x, sep, y, sep, addr(a),
({ a }) => { i3('00100111', this.value(a, 16)) });
match('jsreq', s, y, sep, x, sep, addr(a), ({ a }) => pseudo(`JSREQ X, Y, ${a}`));
match('jsreq', s, register(a), sep, register(b), sep, addr(r), () => { throw new Error('JSREQ can only be used with registers X and Y') });
match('ret', () => i('00000001'));
match('retrez', () => i('11110100'));
match('retrnz', () => i('11110101'));
match('djnz', s, x, sep, addr(a), ({ a }) => i3('11100000', this.value(a, 16)));
match('djnz', s, y, sep, addr(a), ({ a }) => i3('11100001', this.value(a, 16)));
match('djnz', s, z, sep, addr(a), ({ a }) => i3('11100010', this.value(a, 16)));
match('lod', s, x, sep, immediate(a), ({ a }) => i2('01111100', this.value(a, 8)));
match('lod', s, y, sep, immediate(a), ({ a }) => i2('01111101', this.value(a, 8)));
match('lod', s, z, sep, immediate(a), ({ a }) => i2('01111110', this.value(a, 8)));
match('lod', s, y, sep, x, () => i('00101000'));
match('lod', s, z, sep, x, () => i('00101001'));
match('lod', s, x, sep, y, () => i('00101010'));
match('lod', s, z, sep, y, () => i('00101011'));
match('lod', s, x, sep, z, () => i('00101100'));
match('lod', s, y, sep, z, () => i('00101101'));
match('lod', s, x, sep, addr(a), ({ a }) => { i3('10100000', this.value(a, 16)) });
match('lod', s, y, sep, addr(a), ({ a }) => { i3('10110000', this.value(a, 16)) });
match('lod', s, z, sep, addr(a), ({ a }) => { i3('11000000', this.value(a, 16)) });
match('lod', s, x, sep, addr(a), sep, y,
({ a }) => { i3('10100001', this.value(a, 16)) });
match('lod', s, x, sep, addr(a), sep, z,
({ a }) => { i3('10100010', this.value(a, 16)) });
match('lod', s, y, sep, addr(a), sep, x,
({ a }) => { i3('10110001', this.value(a, 16)) });
match('lod', s, y, sep, addr(a), sep, z,
({ a }) => { i3('10110010', this.value(a, 16)) });
match('lod', s, z, sep, addr(a), sep, x,
({ a }) => { i3('11000001', this.value(a, 16)) });
match('lod', s, z, sep, addr(a), sep, y,
({ a }) => { i3('11000010', this.value(a, 16)) });
match('str', s, x, sep, addr(a), ({ a }) => { i3('10100100', this.value(a, 16)) });
match('str', s, y, sep, addr(a), ({ a }) => { i3('10110100', this.value(a, 16)) });
match('str', s, z, sep, addr(a), ({ a }) => { i3('11000100', this.value(a, 16)) });
match('str', s, x, sep, addr(a), sep, y,
({ a }) => { i3('10100101', this.value(a, 16)) });
match('str', s, x, sep, addr(a), sep, z,
({ a }) => { i3('10100110', this.value(a, 16)) });
match('str', s, y, sep, addr(a), sep, x,
({ a }) => { i3('10110101', this.value(a, 16)) });
match('str', s, y, sep, addr(a), sep, z,
({ a }) => { i3('10110110', this.value(a, 16)) });
match('str', s, z, sep, addr(a), sep, x,
({ a }) => { i3('11000101', this.value(a, 16)) });
match('str', s, z, sep, addr(a), sep, y,
({ a }) => { i3('11000110', this.value(a, 16)) });
match('str', s, immediate(a), sep, addr(b), sep, x,
({ a, b }) => { i4('11010000', this.value(b, 16), this.value(a, 8)) });
match('str', s, immediate(a), sep, addr(b), sep, y,
({ a, b }) => { i4('11010001', this.value(b, 16), this.value(a, 8)) });
match('str', s, immediate(a), sep, addr(b), sep, z,
({ a, b }) => { i4('11010010', this.value(b, 16), this.value(a, 8)) });
match('str', s, immediate(a), sep, addr(b),
({ a, b }) => { i4('11010011', this.value(b, 16), this.value(a, 8)) });
match('str', s, immediate(a), sep, addr(b), sep, yx,
({ a, b }) => { i4('11010100', this.value(b, 16), this.value(a, 8)) });
match('lod', s, z, sep, addr(a), sep, yx,
({ a }) => { i3('11000011', this.value(a, 16)) });
match('str', s, z, sep, addr(a), sep, yx,
({ a }) => { i3('11000111', this.value(a, 16)) });
match('jmp', s, addr(a), ({ a }) => { i3('00010000', this.value(a, 16)) });
match('inc', s, x, () => i('00110000'));
match('inc', s, y, () => i('00110001'));
match('inc', s, z, () => i('00110010'));
match('inc', s, addr(a), ({ a }) => i3('00110011', this.value(a, 16)));
match('dec', s, x, () => i('00110100'));
match('dec', s, y, () => i('00110101'));
match('dec', s, z, () => i('00110110'));
match('dec', s, addr(a), ({ a }) => i3('00110111', this.value(a, 16)));
match('add', s, x, sep, immediate(a), ({ a }) => i2('01000000', this.value(a, 8)));
match('add', s, y, sep, immediate(a), ({ a }) => i2('01010000', this.value(a, 8)));
match('add', s, z, sep, immediate(a), ({ a }) => i2('01100000', this.value(a, 8)));
// TODO: Check where result is stored and maybe remove the duplicate encodings if needed
match('add', s, x, sep, y, () => i('01000100'));
match('add', s, y, sep, x, () => i('01000100'));
match('add', s, x, sep, z, () => i('01000101'));
match('add', s, z, sep, x, () => i('01000101'));
match('add', s, y, sep, z, () => i('01010101'));
match('add', s, z, sep, y, () => i('01010101'));
match('sub', s, x, sep, immediate(a), ({ a }) => i2('01000001', this.value(a, 8)));
match('sub', s, y, sep, immediate(a), ({ a }) => i2('01010001', this.value(a, 8)));
match('sub', s, z, sep, immediate(a), ({ a }) => i2('01100001', this.value(a, 8)));
match('sub', s, x, sep, y, () => i('01000110'));
match('sub', s, x, sep, z, () => i('01000111'));
match('sub', s, y, sep, x, () => i('01010110'));
match('sub', s, y, sep, z, () => i('01010111'));
match('sub', s, z, sep, x, () => i('01100110'));
match('sub', s, z, sep, y, () => i('01100111'));
match('mul', s, x, sep, immediate(a), ({ a }) => i2('01000010', this.value(a, 8)));
match('mul', s, y, sep, immediate(a), ({ a }) => i2('01010010', this.value(a, 8)));
match('mul', s, z, sep, immediate(a), ({ a }) => i2('01100010', this.value(a, 8)));
match('mul', s, x, sep, y, () => i('01001000'));
match('mul', s, y, sep, x, () => i('01001000'));
match('mul', s, x, sep, z, () => i('01001001'));
match('mul', s, z, sep, x, () => i('01001001'));
match('mul', s, y, sep, z, () => i('01011001'));
match('mul', s, z, sep, y, () => i('01011001'));
match('div', s, x, sep, immediate(a), ({ a }) => i2('01000011', this.value(a, 8)));
match('div', s, y, sep, immediate(a), ({ a }) => i2('01010011', this.value(a, 8)));
match('div', s, z, sep, immediate(a), ({ a }) => i2('01100011', this.value(a, 8)));
match('div', s, x, sep, y, () => i('01001010'));
match('div', s, x, sep, z, () => i('01001011'));
match('div', s, y, sep, x, () => i('01011010'));
match('div', s, y, sep, z, () => i('01011011'));
match('div', s, z, sep, x, () => i('01101010'));
match('div', s, z, sep, y, () => i('01101011'));
match('mod', s, x, sep, immediate(a), ({ a }) => i2('10011100', this.value(a, 8)));
match('mod', s, y, sep, immediate(a), ({ a }) => i2('10011101', this.value(a, 8)));
match('mod', s, z, sep, immediate(a), ({ a }) => i2('10011110', this.value(a, 8)));
match('mod', s, x, sep, y, () => i('01001100'));
match('mod', s, x, sep, z, () => i('01001101'));
match('mod', s, y, sep, x, () => i('01011100'));
match('mod', s, y, sep, z, () => i('01011101'));
match('mod', s, z, sep, x, () => i('01101100'));
match('mod', s, z, sep, y, () => i('01101101'));
match('lsl', s, x, () => i('01110000'));
match('lsl', s, y, () => i('01110001'));
match('lsl', s, z, () => i('01110010'));
match('lsr', s, x, () => i('01110100'));
match('lsr', s, y, () => i('01110101'));
match('lsr', s, z, () => i('01110110'));
match('not', s, x, () => i('01111000'));
match('not', s, y, () => i('01111001'));
match('not', s, z, () => i('01111010'));
match('and', s, x, sep, immediate(a), ({ a }) => i2('10010000', this.value(a, 8)));
match('and', s, y, sep, immediate(a), ({ a }) => i2('10010001', this.value(a, 8)));
match('and', s, z, sep, immediate(a), ({ a }) => i2('10010010', this.value(a, 8)));
match('and', s, x, sep, y, () => i('10000000'));
match('and', s, y, sep, x, () => i('10000000'));
match('and', s, x, sep, z, () => i('10000001'));
match('and', s, z, sep, x, () => i('10000001'));
match('and', s, y, sep, z, () => i('10000010'));
match('and', s, z, sep, y, () => i('10000010'));
match('xor', s, x, sep, immediate(a), ({ a }) => i2('10011000', this.value(a, 8)));
match('xor', s, y, sep, immediate(a), ({ a }) => i2('10011001', this.value(a, 8)));
match('xor', s, z, sep, immediate(a), ({ a }) => i2('10011010', this.value(a, 8)));
match('xor', s, x, sep, y, () => i('10000100'));
match('xor', s, y, sep, x, () => i('10000100'));
match('xor', s, x, sep, z, () => i('10000101'));
match('xor', s, z, sep, x, () => i('10000101'));
match('xor', s, y, sep, z, () => i('10000110'));
match('xor', s, z, sep, y, () => i('10000110'));
match('or', s, x, sep, immediate(a), ({ a }) => i2('10010100', this.value(a, 8)));
match('or', s, y, sep, immediate(a), ({ a }) => i2('10010101', this.value(a, 8)));
match('or', s, z, sep, immediate(a), ({ a }) => i2('10010110', this.value(a, 8)));
match('or', s, x, sep, y, () => i('10001000'));
match('or', s, y, sep, x, () => i('10001000'));
match('or', s, x, sep, z, () => i('10001001'));
match('or', s, z, sep, x, () => i('10001001'));
match('or', s, y, sep, z, () => i('10001010'));
match('or', s, z, sep, y, () => i('10001010'));
// 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, littleEndian: boolean = true): string {
if (typeof part === 'string') {
return part;
} else if (isNumber(part.value)) {
return toBitString(toNumber(part.value), part.bitCount, part.addressRelative, littleEndian ? 8 : null);
} 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, littleEndian ? 8 : null);
}
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,
value: base,
}, false), 2,);
const offsetValue = parseInt(this.resolveParsedLinePart({
...part,
addressRelative: false,
value: offset,
}, false), 2) * (operator === '+' ? 1 : -1);
return toBitString(baseValue + offsetValue, part.bitCount, part.addressRelative, littleEndian ? 8 : null);
}
if (this.constants.has(part.value)) {
const value = this.constants.get(part.value);
return toBitString(value, part.bitCount, false, littleEndian ? 8 : null);
} 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() {
}
printState(): string {
return 'Emulator not implemented';
}
}
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([xyz]|yx)\b/i,
instruction: /^[a-zA-Z0-9\.]+/,
other: /\S/,
});
const archSpecification: ArchSpecification = {
documentation: '',
syntaxHighlighting,
assemble,
maxWordsPerInstruction: 4,
wordSize: 8,
emulator: new V8Emulator(),
};
export default archSpecification;
archSpecification.documentation = `
# Bitzzy
An 8-bit CPU by Mazzetip with 16-bit addressing.
## Registers
There are three registers: X, Y, and Z. Z is the accumulator.
## Assembler syntax
Instructions and registers are case-insensitive. For example, \`INC X\` and \`inc x\` are both valid.
Labels and constants are case-sensitive. For example, \`myLabel:\` and \`mylabel:\` are different labels.
Comments begin with a semicolon. For example, \`; this is a comment\`.
Labels can be offset by a value by placing them in parentheses. For example, \`(myLabel + 2)\` is two bytes after \`myLabel\`.
&lt;reg&gt; indicates a register, i.e. X, Y, or Z. For example, the instruction \`INC <reg>\` can be used as \`INC X\`, \`INC Y\`, or \`INC Z\`.
When an instruction takes two registers, they must not be the same. E.g. \`SWP x, y\` is okay but \`SWP x, x\` is illegal.
&lt;imm&gt; indicates an 8-bit immediate value. This must be prefixed with '#' and can be signed or unsigned. For example, \`LOD X, #\$ff\` loads the value -1 into register X.
&lt;addr&gt; indicates a 16-bit address. For example, \`JMP \$fa08\` jumps to address 0xfa08.
When registers X and Y are used together to form a 16-bit address, they are written as YX. Y forms the upper 8 bits while X forms the lower 8 bits.
## Instructions
HLT: Halt the CPU.
RTI: Return from interrupt.
ENI: Enable interrupts.
DSI: Disable interrupts.
NOP: No operation.
REM &lt;reg&gt;: Set register &lt;reg&gt; to the remainder (the carry/borrow flag).
CLR: Clear remainder.
JMP &lt;addr&gt;: Jump to the address &lt;addr&gt;.
JMP &lt;addr&gt;, &lt;reg&gt;: Jump to the address &lt;addr&gt; + register &lt;reg&gt;.
JMPEZ X, &lt;addr&gt;: Jump to the address &lt;addr&gt; if register X is zero.
JMPEZ Z, &lt;addr&gt;: Jump to the address &lt;addr&gt; if register Z is zero.
JMPGT X, Z, &lt;addr&gt;: Jump to the address &lt;addr&gt; if X is greater than Y.
JMPEQ &lt;reg-a&gt;, &lt;reg-b&gt;, &lt;addr&gt;: Jump to the address &lt;addr&gt; if register &lt;reg-a&gt; is equal to register &lt;reg-b&gt;.
JMPRNZ &lt;addr&gt;: Jump to the address &lt;addr&gt; if the remainder is not zero.
JMPREZ &lt;addr&gt;: Jump to the address &lt;addr&gt; if the remainder is zero.
JSR &lt;addr&gt;: Jump to the address &lt;addr&gt; and push the return address onto the stack.
JSR &lt;addr&gt;, &lt;reg&gt;: Jump to the address &lt;addr&gt; + register &lt;reg&gt; and push the return address onto the stack.
JSREZ X, &lt;addr&gt;: Jump to the address &lt;addr&gt; if register X is zero and push the return address onto the stack.
JSREZ Z, &lt;addr&gt;: Jump to the address &lt;addr&gt; if register Z is zero and push the return address onto the stack.
JSRGT X, Z, &lt;addr&gt;: Jump to the address &lt;addr&gt; if X is greater than Y and push the return address onto the stack.
JSREQ &lt;reg-a&gt;, &lt;reg-b&gt;, &lt;addr&gt;: Jump to the address &lt;addr&gt; if register &lt;reg-a&gt; is equal to register &lt;reg-b&gt; and push the return address onto the stack.
DJNZ &lt;reg&gt;, &lt;addr&gt;: Decrement register &lt;reg&gt; and jump to the address &lt;addr&gt; if it is now not zero.
RET: Return from subroutine.
RETRNZ: Return from subroutine if the remainder is not zero.
RETREZ: Jump to the address &lt;addr&gt; if the remainder is zero.
INC &lt;reg&gt;: Increment register &lt;reg&gt;.
INC &lt;addr&gt;: Increment value at address &lt;addr&gt;.
DEC &lt;reg&gt;: Decrement register &lt;reg&gt;.
DEC &lt;addr&gt;: Decrement value at address &lt;addr&gt;.
ADD &lt;reg-a&gt;, &lt;reg-b&gt;: Add register &lt;reg-b&gt; to register &lt;reg-a&gt; and store the result in Z.
ADD &lt;reg&gt;, &lt;imm&gt;: Add the immediate value &lt;imm&gt; to register &lt;reg&gt; and store the result in register &lt;reg&gt;.
SUB &lt;reg-a&gt;, &lt;reg-b&gt;: Subtract register &lt;reg-b&gt; from register &lt;reg-a&gt; and store the result in Z.
SUB &lt;reg&gt;, &lt;imm&gt;: Subtract the immediate value &lt;imm&gt; from register &lt;reg&gt; and store the result in register &lt;reg&gt;.
LSL &lt;reg&gt;: Logical shift left register &lt;reg&gt; by one bit.
LSR &lt;reg&gt;: Logical shift right register &lt;reg&gt; by one bit.
AND &lt;reg-a&gt;, &lt;reg-b&gt;: Bitwise AND register &lt;reg-a&gt; with register &lt;reg-b&gt; and store the result in Z.
AND &lt;reg&gt;, &lt;imm&gt;: Bitwise AND register &lt;reg&gt; with the immediate value &lt;imm&gt; and store the result in register &lt;reg&gt;.
OR &lt;reg-a&gt;, &lt;reg-b&gt;: Bitwise OR register &lt;reg-a&gt; with register &lt;reg-b&gt; and store the result in Z.
OR &lt;reg&gt;, &lt;imm&gt;: Bitwise OR register &lt;reg&gt; with the immediate value &lt;imm&gt; and store the result in register &lt;reg&gt;.
XOR &lt;reg-a&gt;, &lt;reg-b&gt;: Bitwise XOR register &lt;reg-a&gt; with register &lt;reg-b&gt; and store the result in Z.
XOR &lt;reg&gt;, &lt;imm&gt;: Bitwise XOR register &lt;reg&gt; with the immediate value &lt;imm&gt; and store the result in register &lt;reg&gt;.
LOD &lt;reg&gt;, &lt;imm&gt;: Load the immediate value &lt;imm&gt; into register &lt;reg&gt;.
LOD &lt;reg-a&gt;, &lt;reg-b&gt;: Load the value in register &lt;reg-b&gt; into register &lt;reg-a&gt;.
LOD &lt;reg&gt;, &lt;addr&gt;: Load the value at address &lt;addr&gt; into register &lt;reg&gt;.
LOD &lt;reg-a&gt;, &lt;addr&gt;, &lt;reg-b&gt;: Load the value at address &lt;addr&gt; + register &lt;reg-b&gt; into register &lt;reg-a&gt;.
LOD Z, &lt;addr&gt;, YX: Load the value at address &lt;addr&gt; + the combined value of registers X and Y ($YYXX) into register Z.
SWP &lt;reg-a&gt;, &lt;reg-b&gt;: Swap the values in registers &lt;reg-a&gt; and &lt;reg-b&gt;.
STR &lt;reg&gt;, &lt;addr&gt;: Store the value in register &lt;reg&gt; at address &lt;addr&gt;.
STR &lt;reg-a&gt;, &lt;addr&gt;, &lt;reg-b&gt;: Store the value in register &lt;reg-a&gt; at address &lt;addr&gt; + register &lt;reg-b&gt;.
STR &lt;imm&gt;, &lt;addr&gt;: Store the immediate value &lt;imm&gt; to address &lt;addr&gt;.
STR &lt;imm&gt;, &lt;addr&gt;, &lt;reg&gt;: Store the immediate value &lt;imm&gt; to address &lt;addr&gt; + register &lt;reg&gt;.
STR Z, &lt;addr&gt;, YX: Store the value in register Z at address &lt;addr&gt; + the combined value of registers X and Y ($YYXX).
MUL &lt;reg-a&gt;, &lt;reg-b&gt;: Multiply register &lt;reg-a&gt; by register &lt;reg-b&gt; and store the result in in Z.
MUL &lt;reg&gt;, &lt;imm&gt;: Multiply register &lt;reg&gt; by the immediate value &lt;imm&gt; and store the result in register &lt;reg&gt;.
DIV &lt;reg-a&gt;, &lt;reg-b&gt;: Divide register &lt;reg-a&gt; by register &lt;reg-b&gt; and store the result in in Z.
DIV &lt;reg&gt;, &lt;imm&gt;: Divide register &lt;reg&gt; by the immediate value &lt;imm&gt; and store the result in register &lt;reg&gt;.
MOD &lt;reg-a&gt;, &lt;reg-b&gt;: Divide register &lt;reg-a&gt; by register &lt;reg-b&gt; and store the remainder in in Z.
MOD &lt;reg&gt;, &lt;imm&gt;: Divide register &lt;reg&gt; by the immediate value &lt;imm&gt; and store the remainder in register &lt;reg&gt;.
## Assembler directives
\`<name>:\` : Define a label.
\`.data <imm>, <imm>, <imm>, ...\`: Store 8-bit values in memory. You can also use labels, in which case the 16-bit address of the label will be stored.
\`.repeat <imm>, <repetitions>\`: Repeat <imm> in memory multiple times.
\`.address <addr>\`: Set the current address to <addr>.
\`.align <imm>\`: Align the current address to the next multiple of <imm>.
\`.end\`: Ignore all following instructions.
\`.start\`: Resume assembling instructions.
\`.eq\ <name>, <imm>\`: Define a constant.
`;

View file

@ -633,7 +633,12 @@ class ParvaEmulator implements Emulator {
}
step() {
const instructionsFetched = (2 - (this.pc % 2)) + (this.previousFrameAccessedMemory ? 0 : 2);
this.previousFrameAccessedMemory = false;
for (let i = 0; i < instructionsFetched; i++) {
if (this.stepCore()) break;
}
}
stepCore(): boolean {
@ -724,6 +729,7 @@ class ParvaEmulator implements Emulator {
}
this.previousFrameAccessedMemory = true;
coreTerminates = true;
} else if (operationType === '11') {
// branching
const special = condition.startsWith('11');

View file

@ -1,4 +1,4 @@
export function toBitString(n: number, bits: number, signed: boolean = false): string {
export function toBitString(n: number, bits: number, signed: boolean = false, littleEndianByteSize: null | number = null): string {
const minimum = -(2 ** (bits - 1));
const maximum = signed ? (2 ** (bits - 1)) - 1 : 2 ** bits - 1;
@ -18,6 +18,15 @@ export function toBitString(n: number, bits: number, signed: boolean = false): s
if (n !== 0) {
throw new Error(`Internal error: ${n} not zero after conversion to ${bits}-bit ${signed ? 'signed' : 'unsigned'} integer`);
}
if (littleEndianByteSize !== null) {
const bytes = [];
for (let i = 0; i < result.length / littleEndianByteSize; i++) {
bytes.push(result.substring(i * littleEndianByteSize, (i + 1) * littleEndianByteSize));
}
result = bytes.reverse().join('');
}
return result;
}

9
compiler/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "compiler"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nom = "7.1.3"

View file

@ -0,0 +1,96 @@
# target parva_0_1
li sp, stack
fn tokenize(input) {
let current_token_ptr = input - 1;
let temp = -1;
push(temp); // mark the beginning of the token stack
loop {
current_token_ptr += 1;
let current_token = rw(current_token_ptr);
if current_token == 0 {
goto(tokenize_end);
}
if current_token == '0' {
goto(tokenize_symbol);
}
if current_token == '9' {
goto(tokenize_symbol);
}
let number_start_addr = sp
current_token_ptr
let token_value = current_token - '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__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:

View file

@ -0,0 +1,6 @@
x = (y + 3) + 5;
y += x;
z = foo;
fn test() {
poop = moop;
}

1
compiler/rustfmt.toml Normal file
View file

@ -0,0 +1 @@
hard_tabs = true

137
compiler/src/compiler.rs Normal file
View file

@ -0,0 +1,137 @@
use crate::{
parser::{Expression, Statement},
CompilationError,
};
struct Context {
temporary_counter: usize,
}
impl Context {
fn new() -> Self {
Self {
temporary_counter: 0,
}
}
fn new_temporary(&mut self) -> String {
let result = format!("__temp{}", self.temporary_counter);
self.temporary_counter += 1;
result
}
}
pub fn compile(ast: Statement) -> Result<String, CompilationError> {
let mut context = Context::new();
println!("initial: {:#?}\n", ast);
let ast = ast_pass_0(ast)?;
println!("pass 0: {:#?}\n", ast);
let ast = ast_pass_1(&mut context, vec![ast])?;
println!("pass 1: {:#?}\n", ast);
Ok(format!("{:?}\n", ast))
}
/// Pass 0
///
/// Rewrites compound assignments into simple assignments, e.g. `a += 1` to `a = a + 1`
fn ast_pass_0(ast: Statement) -> Result<Statement, CompilationError> {
let result = match ast {
Statement::Block(inner) => Statement::Block(
inner
.into_iter()
.map(ast_pass_0)
.collect::<Result<Vec<_>, _>>()?,
),
Statement::AddAssign(name, expr) => Statement::Assign(
name.clone(),
Expression::Add(Box::new(Expression::Identifier(name)), Box::new(expr)),
),
statement => statement,
};
Ok(result)
}
/// Pass 1
///
/// Expands nested expressions into simple expressions,
/// e.g. `a = (x + y) + z;` to `temp0 = x + y; a = temp0 + z;`
fn ast_pass_1(
context: &mut Context,
statements: Vec<Statement>,
) -> Result<Vec<Statement>, CompilationError> {
let mut statements_out = Vec::new();
for statement in statements {
match statement {
Statement::Block(inner) => {
statements_out.push(Statement::Block(ast_pass_1(context, inner)?));
}
Statement::Assign(name, expr) => {
let (mut expression_statements, expression) =
flatten_expression(context, expr.clone())?;
statements_out.extend(expression_statements);
statements_out.push(Statement::Assign(name.clone(), expression));
}
statement => statements_out.push(statement),
};
}
Ok(statements_out)
}
fn flatten_expression(
context: &mut Context,
expression: Expression,
) -> Result<(Vec<Statement>, Expression), CompilationError> {
let mut statements = Vec::new();
let result = match expression {
Expression::Identifier(name) => (vec![], Expression::Identifier(name)),
Expression::Number(name) => (vec![], Expression::Number(name)),
Expression::Add(left, right) => {
let (left_statements, left) = flatten_expression(context, *left)?;
statements.extend(left_statements);
let (right_statements, right) = flatten_expression(context, *right)?;
statements.extend(right_statements);
let temp_name = context.new_temporary();
let statement = Statement::Assign(
temp_name.clone(),
Expression::Add(Box::new(left), Box::new(right)),
);
statements.push(statement);
(statements, Expression::Identifier(temp_name))
}
expression => (vec![], expression),
};
Ok(result)
}
/// Pass 2
///
/// Convert to IR
fn ast_pass_2(
context: &mut Context,
statements: Vec<Statement>,
) -> Result<Vec<Statement>, CompilationError> {
let mut statements_out = Vec::new();
for statement in statements {
match statement {
Statement::Block(inner) => {
statements_out.push(Statement::Block(ast_pass_1(context, inner)?));
}
Statement::Assign(name, expr) => {
let (mut expression_statements, expression) =
flatten_expression(context, expr.clone())?;
statements_out.extend(expression_statements);
statements_out.push(Statement::Assign(name.clone(), expression));
}
statement => statements_out.push(statement),
};
}
Ok(statements_out)
}

View file

@ -0,0 +1,11 @@
pub struct Variable {
name: String,
}
pub enum PseudoInstruction {
Call(Variable),
Return,
Add(Variable, Variable, Variable),
Sub(Variable, Variable, Variable),
Li(Variable, i32),
}

186
compiler/src/lexer.rs Normal file
View file

@ -0,0 +1,186 @@
use crate::CompilationError;
#[derive(Debug, Clone)]
pub enum Token {
Identifier(String),
Character(char),
Number(i32),
Operator(Operator),
Colon,
AtSign,
OpenParenthesis,
CloseParenthesis,
OpenBrace,
CloseBrace,
Assignment(Assignment),
Comparison(Comparison),
Semicolon,
Keyword(Keyword),
}
#[derive(Debug, Copy, Clone)]
pub enum Operator {
Plus,
Minus,
Star,
Slash,
}
#[derive(Debug, Copy, Clone)]
pub enum Keyword {
Let,
Return,
}
#[derive(Debug, Copy, Clone)]
pub enum Assignment {
Assign,
AddAssign,
}
#[derive(Debug, Copy, Clone)]
pub enum Comparison {
Equals,
GreaterThan,
LessThan,
}
#[derive(Debug, Clone)]
pub struct LexerConfiguration {}
struct Lexer {
configuration: LexerConfiguration,
input: Vec<char>,
position: usize,
line: usize,
line_start_position: usize,
}
impl Lexer {
fn new(configuration: LexerConfiguration, input: &str) -> Self {
Self {
configuration: LexerConfiguration {},
input: input.chars().collect(),
position: 0,
line: 0,
line_start_position: 0,
}
}
fn next_token(&mut self) -> Result<Option<Token>, CompilationError> {
while self.position < self.input.len() {
let next = &self.input[self.position..];
self.position += 1;
type Tk = Token;
let token = match next {
['=', '=', ..] => Tk::Comparison(Comparison::Equals),
['>', ..] => Tk::Comparison(Comparison::GreaterThan),
['<', ..] => Tk::Comparison(Comparison::LessThan),
['=', ..] => Tk::Assignment(Assignment::Assign),
['+', '=', ..] => Tk::Assignment(Assignment::AddAssign),
['+', ..] => Tk::Operator(Operator::Plus),
['-', ..] => Tk::Operator(Operator::Minus),
['*', ..] => Tk::Operator(Operator::Star),
['/', ..] => Tk::Operator(Operator::Slash),
['(', ..] => Tk::OpenParenthesis,
[')', ..] => Tk::CloseParenthesis,
['a'..='z' | 'A'..='Z' | '_', ..] => {
let start = self.position - 1;
while self.position < self.input.len()
&& (self.input[self.position].is_alphanumeric()
|| self.input[self.position] == '_')
{
self.position += 1;
}
let identifier = self.input[start..self.position].iter().collect::<String>();
match identifier.as_str() {
"let" => Token::Keyword(Keyword::Let),
"return" => Token::Keyword(Keyword::Return),
_ => Token::Identifier(identifier),
}
}
['{', ..] => Tk::OpenBrace,
['}', ..] => Tk::CloseBrace,
[';', ..] => Tk::Semicolon,
[':', ..] => Tk::Colon,
['@', ..] => Tk::AtSign,
['\'', ..] => {
let start = self.position;
while self.position < self.input.len() && self.input[self.position] != '\'' {
self.position += 1;
}
if self.position >= self.input.len() {
return Err(CompilationError {
message: format!("Expected closing single quote"),
line: self.line,
column: start - self.line_start_position,
});
}
self.position += 1;
let character = self.input[start..self.position - 1]
.iter()
.collect::<String>()
.chars()
.next()
.unwrap();
Token::Character(character)
}
['0'..='9', ..] => {
let start = self.position - 1;
while self.position < self.input.len() && self.input[self.position].is_digit(10)
{
self.position += 1;
}
let number: i32 = self.input[start..self.position]
.iter()
.collect::<String>()
.parse::<i32>()
.map_err(|err| CompilationError {
message: format!("Expected closing single quote"),
line: self.line,
column: start - self.line_start_position,
})?;
Token::Number(number)
}
['\n', ..] => {
self.line += 1;
self.line_start_position = self.position;
continue;
}
[' ', '\t', ..] => continue,
_ => continue,
};
return Ok(Some(token));
}
Ok(None)
}
}
pub fn lex(input: &str, configuration: LexerConfiguration) -> Result<Vec<Token>, String> {
let mut lexer = Lexer::new(configuration, input);
let mut tokens = Vec::new();
loop {
let token = match lexer.next_token() {
Ok(Some(token)) => token,
Ok(None) => break,
Err(CompilationError {
message,
line,
column,
}) => {
return Err(format!(
"Parsing failed at line {}:{}: {}",
line + 1, column + 1, message
));
}
};
tokens.push(token);
}
Ok(tokens)
}

71
compiler/src/main.rs Normal file
View file

@ -0,0 +1,71 @@
#![allow(unused)]
/*
can only reorder instructions within a block
branch points delimit blocks
*/
use std::env;
use std::fs;
use std::io::{self, Read};
mod compiler;
mod parser;
#[derive(Debug)]
pub struct CompilationError {
pub message: String,
pub line: usize,
pub column: usize,
}
impl CompilationError {
pub fn new(message: String, line: usize, column: usize) -> Self {
Self {
message,
line,
column,
}
}
}
fn main() {
let args: Vec<String> = env::args().collect();
let input = if args.len() > 1 {
if ((args[1] == "-s") || (args[1] == "--source") && args.len() > 2) {
args[2].to_owned()
} else {
let filename = &args[1];
if filename == "--" {
let mut buffer = String::new();
io::stdin()
.read_to_string(&mut buffer)
.expect("Failed to read from stdin");
buffer
} else {
fs::read_to_string(filename).expect("Failed to read file")
}
}
} else {
panic!("Expected a filename or '--' as argument");
};
let parse_result = match parser::parse(&input) {
Ok(expr) => expr,
Err(err) => {
eprintln!("Error: {:?}", err);
std::process::exit(1);
}
};
let compile_result = match compiler::compile(parse_result) {
Ok(expr) => expr,
Err(err) => {
eprintln!("Error: {:?}", err);
std::process::exit(1);
}
};
println!("Compiled: {:?}", compile_result);
}

378
compiler/src/parser.rs Normal file
View file

@ -0,0 +1,378 @@
use crate::CompilationError;
mod asm;
use nom::{
branch::alt,
bytes::complete::{tag, take_while},
character::complete::{alpha1, alphanumeric1, char, one_of},
combinator::{all_consuming, complete, map, map_res, opt, recognize},
error::{dbg_dmp, ParseError},
multi::{many0, many1, separated_list0},
sequence::{delimited, preceded, terminated, tuple},
Finish, IResult,
};
#[derive(Debug, Clone)]
pub enum Statement {
Block(Vec<Statement>),
Label(String),
Assign(String, Expression),
AddAssign(String, Expression),
SubAssign(String, Expression),
MulAssign(String, Expression),
DivAssign(String, Expression),
FunctionDeclaration(String, Vec<String>, Box<Statement>),
SubroutineDeclaration(String, Box<Statement>),
}
#[derive(Debug, Copy, Clone)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Sll,
Srl,
Sra,
BitAnd,
BitOr,
BitXor,
Index,
}
#[derive(Debug, Clone)]
pub enum Expression {
Number(i64),
Identifier(String),
FunctionCall(String, Vec<Expression>),
Add(Box<Expression>, Box<Expression>),
Sub(Box<Expression>, Box<Expression>),
Mul(Box<Expression>, Box<Expression>),
Div(Box<Expression>, Box<Expression>),
Sll(Box<Expression>, Box<Expression>),
Srl(Box<Expression>, Box<Expression>),
Sra(Box<Expression>, Box<Expression>),
BitNeg(Box<Expression>, Box<Expression>),
BitAnd(Box<Expression>, Box<Expression>),
BitOr(Box<Expression>, Box<Expression>),
BitXor(Box<Expression>, Box<Expression>),
Deref(Box<Expression>),
Index(Box<Expression>, Box<Expression>),
}
pub fn parse(input: &str) -> Result<Statement, CompilationError> {
let parse_result = all_consuming(complete(program))(input).finish();
let ast = match parse_result {
Ok((_, ast)) => ast,
Err(err) => {
// let (line, column) = get_line_and_column(input);
let (line, column) = (0, 0); // TODO
return Err(CompilationError::new(
format!("Failed to parse input: {:?}", err),
line,
column,
));
}
};
Ok(ast)
}
fn expression(input: &str) -> IResult<&str, Expression> {
alt((
map_res(
tuple((
primitive_expression,
whitespace,
tag("+"),
whitespace,
expression,
)),
|(left, _, _, _, right)| {
Ok::<_, nom::error::Error<String>>(Expression::Add(Box::new(left), Box::new(right)))
},
),
map_res(
tuple((
primitive_expression,
whitespace,
tag("-"),
whitespace,
expression,
)),
|(left, _, _, _, right)| {
Ok::<_, nom::error::Error<String>>(Expression::Sub(Box::new(left), Box::new(right)))
},
),
map_res(
tuple((
primitive_expression,
whitespace,
tag("*"),
whitespace,
expression,
)),
|(left, _, _, _, right)| {
Ok::<_, nom::error::Error<String>>(Expression::Mul(Box::new(left), Box::new(right)))
},
),
map_res(
tuple((
primitive_expression,
whitespace,
tag("/"),
whitespace,
expression,
)),
|(left, _, _, _, right)| {
Ok::<_, nom::error::Error<String>>(Expression::Div(Box::new(left), Box::new(right)))
},
),
map_res(
tuple((
primitive_expression,
whitespace,
tag("<<"),
whitespace,
expression,
)),
|(left, _, _, _, right)| {
Ok::<_, nom::error::Error<String>>(Expression::Sll(Box::new(left), Box::new(right)))
},
),
map_res(
tuple((
primitive_expression,
whitespace,
tag(">>"),
whitespace,
expression,
)),
|(left, _, _, _, right)| {
Ok::<_, nom::error::Error<String>>(Expression::Srl(Box::new(left), Box::new(right)))
},
),
map_res(
tuple((
primitive_expression,
whitespace,
tag("&"),
whitespace,
expression,
)),
|(left, _, _, _, right)| {
Ok::<_, nom::error::Error<String>>(Expression::BitAnd(
Box::new(left),
Box::new(right),
))
},
),
map_res(
tuple((
primitive_expression,
whitespace,
tag("|"),
whitespace,
expression,
)),
|(left, _, _, _, right)| {
Ok::<_, nom::error::Error<String>>(Expression::BitOr(
Box::new(left),
Box::new(right),
))
},
),
map_res(
tuple((
primitive_expression,
whitespace,
tag("^"),
whitespace,
expression,
)),
|(left, _, _, _, right)| {
Ok::<_, nom::error::Error<String>>(Expression::BitXor(
Box::new(left),
Box::new(right),
))
},
),
map_res(
tuple((
identifier,
whitespace,
delimited(
tag("("),
separated_list0(tag(","), delimited(whitespace, expression, whitespace)),
tag(")"),
),
)),
|(name, _, arguments)| {
Ok::<_, nom::error::Error<String>>(Expression::FunctionCall(
name.to_string(),
arguments,
))
},
),
map_res(
tuple((
tag("*"),
whitespace,
expression,
)),
|(_, _, value)| {
Ok::<_, nom::error::Error<String>>(Expression::Deref(Box::new(value)))
},
),
primitive_expression,
))(input)
}
fn primitive_expression(input: &str) -> IResult<&str, Expression> {
alt((
variable,
number,
map_res(
tuple((tag("("), whitespace, expression, whitespace, tag(")"))),
|(_, _, expr, _, _)| Ok::<_, nom::error::Error<String>>(expr),
),
))(input)
}
fn statement(input: &str) -> IResult<&str, Statement> {
delimited(whitespace, alt((block, assignment, function)), whitespace)(input)
}
fn block(input: &str) -> IResult<&str, Statement> {
let (input, (_, _, statements, _, _)) =
tuple((tag("{"), whitespace, many0(statement), whitespace, tag("}")))(input)?;
Ok((input, Statement::Block(statements)))
}
fn program(input: &str) -> IResult<&str, Statement> {
let (input, (statements)) = many0(statement)(input)?;
Ok((input, Statement::Block(statements)))
}
fn assignment(input: &str) -> IResult<&str, Statement> {
let (input, (name, _, operator, _, expr, _)) = tuple((
identifier,
whitespace,
opt(one_of("+-/*")),
tag("="),
delimited(whitespace, expression, whitespace),
tag(";"),
))(input)?;
let name = name.to_string();
let statement = match operator {
Some('+') => Statement::AddAssign(name, expr),
Some('-') => Statement::SubAssign(name, expr),
Some('/') => Statement::SubAssign(name, expr),
Some('*') => Statement::SubAssign(name, expr),
None => Statement::Assign(name, expr),
_ => unreachable!(),
};
Ok((input, statement))
}
fn function(input: &str) -> IResult<&str, Statement> {
let (input, (_, name, params, _, body)) = tuple((
tag("fn"),
delimited(whitespace, identifier, whitespace),
delimited(tag("("), separated_list0(tag(","), identifier), tag(")")),
whitespace,
block,
))(input)?;
Ok((
input,
Statement::FunctionDeclaration(
name.to_string(),
params.into_iter().map(String::from).collect(),
Box::new(body),
),
))
}
fn variable(input: &str) -> IResult<&str, Expression> {
map(identifier, |name| Expression::Identifier(name.to_string()))(input)
}
fn identifier(input: &str) -> IResult<&str, &str> {
recognize(tuple((alt((tag("_"), alpha1)), many0(alphanumeric1))))(input)
}
fn number(input: &str) -> IResult<&str, Expression> {
let (input, number) = map(
alt((
hexadecimal_number,
octal_number,
binary_number,
decimal_number,
)),
|number| Expression::Number(number),
)(input)?;
Ok((input, number))
}
fn hexadecimal_number(input: &str) -> IResult<&str, i64> {
map_res(
preceded(
alt((tag("0x"), tag("0X"))),
recognize(many1(terminated(
one_of("0123456789abcdefABCDEF"),
many0(char('_')),
))),
),
|out: &str| i64::from_str_radix(&str::replace(&out, "_", ""), 16),
)(input)
}
fn octal_number(input: &str) -> IResult<&str, i64> {
map_res(
preceded(
alt((tag("0o"), tag("0O"))),
recognize(many1(terminated(one_of("01234567"), many0(char('_'))))),
),
|out: &str| i64::from_str_radix(&str::replace(&out, "_", ""), 8),
)(input)
}
fn binary_number(input: &str) -> IResult<&str, i64> {
map_res(
preceded(
alt((tag("0b"), tag("0B"))),
recognize(many1(terminated(one_of("01"), many0(char('_'))))),
),
|out: &str| i64::from_str_radix(&str::replace(&out, "_", ""), 2),
)(input)
}
fn decimal_number(input: &str) -> IResult<&str, i64> {
map_res(
recognize(many1(terminated(one_of("0123456789"), many0(char('_'))))),
|out: &str| i64::from_str_radix(&str::replace(&out, "_", ""), 10),
)(input)
}
fn whitespace(i: &str) -> IResult<&str, &str> {
recognize(many0(one_of(" \n\t")))(i)
}
// fn expect<'a, F, E, T>(parser: F, error_msg: E) -> impl Fn(&'a str) -> IResult<Option<T>, T>
// where
// F: Fn(&'a str) -> IResult<T, T>,
// E: ToString,
// {
// move |input| match parser(input) {
// Ok((remaining, out)) => Ok((remaining, Some(out))),
// Err(nom::Err::Error((input, _))) | Err(nom::Err::Failure((input, _))) => {
// let err = Error(input.to_range(), error_msg.to_string());
// input.extra.report_error(err); // Push error onto stack.
// Ok((input, None)) // Parsing failed, but keep going.
// }
// Err(err) => Err(err),
// }
// }

View file

@ -0,0 +1,20 @@
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{alphanumeric1, char, one_of},
combinator::{map, map_res, recognize},
multi::{many0, many1, separated_list0},
sequence::{preceded, terminated, tuple},
IResult,
};
pub enum Instruction {
Add(Operand, Operand, Operand),
Sub(Operand, Operand, Operand),
Beqz(Operand, Operand),
}
pub enum Operand {
Direct(u8),
Identifier(String),
}

7
doc/lbp/general_tips.md Normal file
View file

@ -0,0 +1,7 @@
https://web.archive.org/web/20160324052734/http://www.lbpcentral.com/forums/showthread.php?92215-Tips-and-Tricks
Logic is counted TWICE on the thermo when sackboy is in the level so if you have a complicated chip, it might be best to use a sackbot. (I set the controliinator for that sackbot to a color channel as nearest player seems to lag for me in beta)
How to adjust the camera WHILE creating or moving stuff around: Pause, go to settings and in the bottom you'll see an option called ''Create'' There you'll find ''Touch create mode'' Then you can set it to ''Camera'' and be able to zoom and move the camera around, no matter what you're doing!
Faster travel in the editor: Use Oddsock or Swoop while creating, they're much faster at moving and hovering around ( Of course you'll need to change character to test in-editor )

63
doc/lbp/logic_research.md Normal file
View file

@ -0,0 +1,63 @@
(when a gate has multiple inputs, unless specified otherwise, the speed applies to all of the inputs)
# General
For multiple inputs, dependencies are evaluated by the order they appear on the gate, top to bottom.
# Selector
Cycle input is slow. It does not evaluate its dependencies early.
Other inputs are fast and behave as normal gates.
# Microchip
(with one input and no outputs)
All inputs fast
The activate input is checked before and independently of the regular inputs.
Other inputs are then checked top-to-bottom
Activation before other, older gates? No
With inner microchips and no inputs/outputs -> tag propogration is instant [microchip_activation_0]
# Sequencer
(with one input and no outputs)
All inputs fast
# Sackbot trick
Converting analog to digital with a sackbot is instant. It can be done multiple times per frame and occurs during the same phase as logic gates.
A sackbot uses about 1/250th of a thermometer (~4000 units). It has 16 inputs that can be used (~250 thermometer units per analog-to-digital conversion).
The output can be on for two frames instead of the expected one.
# Physics
Activating a destroyer and a tag on an object in the same frame causes the tag sensor to not activate.
# Non-terminal components
Timer: fast
Counter: fast
Toggle: fast
Randomiser: fast
Wave generator: fast
# Terminal components
Emitter: slow
Tag: slow
Mover: slow

BIN
doc/other/bittzy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

0
doc/other/mazzetip_cpu Normal file
View file

164
doc/parva/assembly_0.2.md Normal file
View file

@ -0,0 +1,164 @@
# Registers
0000 | x0/zero | always 0
0001 | x1/ra | return address
0010 | x2/sp | stack pointer
0011 | x3/gp | global pointer
0100 | x4/tp | thread pointer
0101 | x5/t0 | temporary
0110 | x6/t1
0111 | x7/t2
1000 | x8/s0/fp | saved register, frame pointer
1001 | x9/s1
1010 | x10/a0 | function argument, return value
1011 | x11/a1
1100 | x12/a2
1101 | x13/a3
1110 | x14/a4
1111 | x15/a5
_idea_
A new syntax is needed for doubleword registers now that there can be two digits. Maybe only
allow the bottom 8 to represent doubleword registers, and shift s0/s1 to an earlier position to
compensate?
_idea_
Multiple calling conventions, one with many saved registers and another with few?
# ALU instructions (0)
000000 DDDD AAAA IIII IIIIII | addi xD, xA, I
000001 DDDD AAAA BBBB 000000 | add xD, xA, xB
010000 DDDD IIII IIII IIIIII | lui xD, I | load upper immediate (14 bits)
_idea_
011000 DDDD IIII IIII IIIIII | lli xD, I | load lower immediate (14 bits) (also change opcode of lui to match)
_idea_
Reserve two bits for specifying the immediate format, possibilities:
imm, imm << 12, (imm << 12) | (imm)
Maybe only allow this in a separate [opcode, d/a, format, imm] format where d and a are the same
_idea_
Fused mul/mulh and divu/remu (important now because of dedicated cores)
0XXXXR | X = operation, R = use register as second operand
0000 | add
0001 | sub/lui | sub if R=1, lui if R=0
0010 | sll
0011 | srl
0100 | sra
0101 | xor
0110 | or
0111 | and
1000 | mul
1001 | mulh
1010 | divu
1011 | remu
1100 | (unused)
1101 | (unused)
1110 | (unused)
1111 | (unused)
# Data instructions (10)
100000 AAAA DDDD IIIIIIIIII | lw xD, [xA + I] ; load word at [xA + I] into xD
100010 AAAA DDDD IIIIIIIIII | sw xD, [xA + I] ; store xD into address [xA + I]
100100 AAAA DDDD IIIIIIIIII | ld xDD, [xA + I] ; load double [xA + I] into xDD
100110 AAAA DDDD IIIIIIIIII | sd xDD, [xA + I] ; store xDD into address [xA + I]
100001 AAAA DDDD BBBB IIIIII | lw xD, [xA + xB + I] ; load word at [xA + xB + I] into xD
100011 AAAA DDDD BBBB IIIIII | sw xD, [xA + xB + I] ; store xD into address [xA + xB + I]
100101 AAAA DDDD BBBB IIIIII | ld xDD, [xA + xB + I] ; load double [xA + xB + I] into xDD
100111 AAAA DDDD BBBB IIIIII | sd xDD, [xA + xB + I] ; store xDD into address [xA + xB + I]
_pseudo-instructions_
lw/sw/ld/sd xD, I => lw/sw/ld/sd xD, I(zero)
_idea_
Data instructions relative to PC
Test if having dedicated instructions for this is worth it as opposed to auipc + add + lw
_idea_
Prefetch instruction. Set a cache line to load for the next frame, but do not stop execution.
Execution could maybe even be allowed to continue while fetching from main memory over multiple frames.
_idea_
Push/pop multiple, maybe using ld/sd, e.g. push s01, pop a01
_idea_
Push/pop aligned pseudo-instruction, e.g. `pusha x0, x1, x3, x5, x8` -> `pushd d0; push x3; push x5; push x8; addi sp, sp, -1`
Combines registers into doublewords when possible, and aligns `sp` to a multiple a of 2 if necessary.
# Branching instructions (11)
110CCC AAAA DDDD IIII IIIIII | bC xA, xD, I ; compare xA and xD for condition C and branch to [pc + I]
111000 AAAA IIII IIII IIIIII | j [xA + I] ; jump to [xA + I]
111110 AAAA DDDD BBBB IIIIII | jalr xD, [xA + xB + I] ; jump to [xA + xB + I] and set xD to pc + 1
_idea_
011CCC AAAA iiii iiII IIIIII | bC xA, i, I ; compare xA and immediate i for condition C and branch to [pc + I]
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 | (unused)
001 | bne, not equals
011 | bgeu, greater than or equal unsigned
101 | bge, greater than or equal
111 | (unused)
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
# Interrupts
Trigger interrupt input
Two (three?) input bits to select which interrupt vector to use.
Some maskable, some not maskable
Single line in memory holding all control values (interrupt vectors, interrupts enabled bit, pc, timers, cpuid)
Contains two value inputs, these can be read as special registers
Timer interrupts?
# I/O
Write to special memory line, send I/O out signal

23
doc/parva/design.md Normal file
View file

@ -0,0 +1,23 @@
# L2 data cache
Writeback scenarios:
1. Normal: write L1-0 to L2-0, L1-1 to L2-1
2. Swap: write L1-0 to L2-1, L1-1 to L2-0
3. Normal miss: write L1-0 to L2-1, L1-1 to L2-2
4. Swap miss: write L1-0 to L2-2, L1-1 to L2-1
On miss:
Check if requested line is in L2. If so, send a move signal to that position.
# L2 instruction cache
Same as data cache but with no writeback functionality
Must pre-emptively fetch the next line after the PC

63
doc/parva/ideas.md Normal file
View file

@ -0,0 +1,63 @@
# Instruction set
Load relative address: rd <- pc + imm
Load immediate with 14-bit value
Splat/merge: 24-bit value into two registers, 12 bits each, or four registers, 6 bits each.
Branch if least significant bit is set.
Branch, comparing to immediate. Can either be two immediates in the instruction, or make it a 48-bit instruction.
Branch if equal / not equal to zero for doubleword registers, e.g. `beqz x01, target`.
Multiply 12-bits
Single-frame binary to decimal by chaining together the multiply/divide parts of different ALUs.
String manipulation instructions:
Truncate after first null character, e.g. doubleword "abcde\0fg" -> "abcde\0\0\0"
Length of doubleword up to first null character, e.g. "abcde\0fg" -> 5
Left-align / right-align strings.
All cores can do 12-bit bitwise operations. 24-bit operations are done by two cores in sequence.
# Registers
Some registers can be read by any core, but only written to by a single core.
This could be a separate `mv` instruction, so generally the desination operand is only three bits.
# Memory
No direct write ability, writes are only performed by the CPU core.
3-tier memory: looping memory, L2 cache of several lines, L1 cache of e.g. two lines passed through the CPU pipeline.
If a loop memory writeback is still in progress and the eviction of another line is requested, the memory controller can select the least recently used line which isn't dirty and evict that instead.
# Branch prediction
Split instructions into groups of 3/4.
When a branch is taken, store the instruction group that is branched to along with the target address.
E.g. a branch to address 15, where at address 15 there are instructions A, B, C, will store [15, A, B, C] into the branch prediction part of the instruction pipeline.
Only every third core (or some other number) can perform branching. The branch prediction values only need to be passed to these cores.
Other cores can still check for branching, but not actually execute it.
Two predicted paths could be stored with prioritization. When the most recently taken one is taken, nothing happens. When the less recently taken one is taken, the two swap positions. When a new branch is taken, the less recently taken one is overwritten.
# Specialization
Only certain cores can perform certain actions, e.g. division, bitwise operations.
Some registers are fast-read, slow-write. They only implement writing logic in an instruction that can be executed by certain cores, maybe once per frame. All other cores can read from them.
B core: take branches
A core: bitwise arithmetic
M core: 24-bit multiplcation / division

View file

@ -0,0 +1,184 @@
$0:
hlt
ret
rti
eni
dsi
nop
rem x
rem y
rem z
clr
$1:
jmp 5
jmp 5, x
jmp 5, y
jmp 5, z
jsr 5
jsr 5
jsr 5, x
jsr 5, y
jsr 5, z
swp x, y
swp y,x
swp x,z
swp z,x
swp z,y
swp y,z
$2:
jmpez x, 5
jmpez z, 5
jmpgt x,y, 5
jmpeq x,y, 5
jsrez x, 5
jsrez z, 5
jsrgt x, y, 5
jsreq x, y, 5
lod y, x
lod z, x
lod y, x
lod y, z
lod x, z
lod y, z
$3:
inc x
inc y
inc z
inc 5
dec x
dec y
dec z
dec 5
sub x, #5
sub y, #5
sub z, #5
div x, #5
div y, #5
div z, #5
$4:
add x, #5
sub x, #5
mul x, #5
div x, #5
add x, y
add x, z
sub x, y
sub x,z
mul x,y
mul x,z
div x,y
div x,z
mod x,y
mod x,z
$5:
add y, #5
sub y, #5
mul y, #5
div y, #5
add y, z
sub y,x
sub y,z
mul y,z
div y,x
div y,z
mod y,x
mod y,z
$6:
add z, #5
sub z,#5
mul z,#5
div z,#5
sub z,x
sub z,y
div z,x
div z,y
mod z,x
mod z,y
$7:
lsl x
lsl y
lsl z
lsr x
lsr y
lsr z
not x
not y
not z
lod x, #5
lod y, #5
lod z, #5
$8:
and x,y
and x,z
and y,z
xor x,z
xor x,y
xor y,z
or x,y
or x,z
or y,z
$9:
and x, #5
and y, #5
and z, #5
or x, #5
or y, #5
or z, #5
xor x, #5
xor y, #5
xor z, #5
mod x, #5
mod y, #5
mod z, #5
$a:
lod x, 5
lod x, 5, y
lod x, 5, z
$b:
lod y, 5
lod y, 5, x
lod y, 5, z
$c:
lod z, 5
lod z, 5, x
lod z, 5, y
lod z, 5, yx
$d:
str x, 5
str x, 5, y
str x, 5, z
str #5, 3, x
str #5, 3, y
str #5, 3, z
str #5, 3
$e:
str y, 5
str y, 5, x
str y, 5, z
$f:
str z, 5
str z, 5, x
str z, 5, y
str z, 5, yx
str #5, 3, yx

View file

@ -0,0 +1,61 @@
# cat
.extern f_open, f_close, f_read, write
.string_encoding system
main: # fn (args: [cstr8]) -> int
let argv = %a0;
let argc = %a1;
# mv s0, a0
# push a1
let arg_index = 0;
arg_loop:
beq arg_index, argc, end;
let filename = lw [argv + arg_index];
%a0 = filename;
call f_open
let fd = %a0;
bltz fd, error_open;
%a0 = fd;
call f_read;
let file_contents = %a0;
arg_loop:
lw t0, 1(sp)
beq s1, t0, end
lw a0, 0(s0)
call f_open # a0 = file descriptor
bltz a0, error_open
push a0
call f_read # a0 = pointer to contents
bltz a0, error_read
mv a1, a0
li a0, 1 # stdout
call write
pop a0
call f_close
addi s1, s1, 1
b arg_loop
error_read:
pop zero
mv s0, a1
call f_close
mv a1, s0
error_open:
mv s0, a1
li a0, 1
li a1, error_message
call write
mv a1, s0
call write
b end
end:
li a0, 0
ret
error_message:
.string "\fr" # color red
.string "Error: \0"

View file

@ -0,0 +1,3 @@
# file

View file

@ -0,0 +1,87 @@
# x8: pointer to current char in input string
# I think supporting semi-fast unaligned reads is cheaper than having this alignment logic.
# andi x0, x8, 0b11
# li x1, 4
# sub x0, x1, x0
# add x0, x0, x0
# addi x0, x0, 1
# b x0
# lw x0, [x8 + 0]
# beq x0, x2, (split + 3)
# lw x0, [x8 + 1]
# beq x0, x2, (split + 2)
# lw x0, [x8 + 1]
# beq x0, x2, (split + 1)
# Frame 1
loop_0:
sw x8, [x9]
addi x9, x9, 1
li x2, ' '
lq x4:7, x8
b loop_1_no_align
loop_1:
andi x8, x8, 0b11
# Frame 2
loop_1_no_align:
vindexof.6 x0, x1, x4
bgez x0, split
addi x8, x8, 1
vindexof.6 x0, x1, x5
bgez x0, split
addi x8, x8, 1
vindexof.6 x0, x1, x6
bgez x0, split
addi x8, x8, 1
vindexof.6 x0, x1, x7
bgez x0, split
addi x8, x8, 1
b loop_1
# Frame 8
split:
slli x1, x0, 2
add x1, x1, x0
add x1, x1, x0
sll x1, x2, x1
lw x0, [x8]
xor x0, x0, x1
addi x8, x8, 1
# etc.
# Frame 15
hash:
li x0, 5381
lw x2, [x3]
loop_2:
slli x1, x0, 5
add x0, x0, x1
add x0, x0, x1
addi x3, x3, 1
lw x2, [x3]
andi x1, x2, 0b111111
bnez x1, loop_2

View file

@ -0,0 +1,45 @@
let input_string: *word;
let space: char;
li space, ' ';
let parts: quad;
lq parts, input_string;
b loop_1_no_align
loop_1:
andi input_string, input_string, 0b11
# Frame 2
loop_1_no_align:
vindexof.6 x0, x1, x4
bgez x0, split
addi x8, x8, 1
vindexof.6 x0, x1, x5
bgez x0, split
addi x8, x8, 1
vindexof.6 x0, x1, x6
bgez x0, split
addi x8, x8, 1
vindexof.6 x0, x1, x7
bgez x0, split
addi x8, x8, 1
b loop_1
# Frame 8
split:
@mul char_index, 6
sll x1, x2, x1
lw x0, [x8]
xor x0, x0, x1
addi x8, x8, 1

View file

@ -0,0 +1,189 @@
.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: .string "tet_hiscr.dat\0"
hi_score_file_error_message: .string .line_break 10 "could not open or create hi score file\0"
score_string: .string "score\0"
game_over_string: .string "game over\0"
.scores_button_string: .string "scores\0"
.start_button_string: .string "start\0"
.next_string: .string "next\0"
.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
beqi x0, const.char.left_arrow, move_left
beqi x0, const.char.right_arrow, move_right
beqi x0, const.char.up_arrow, rotate
beqi x0, const.char.space, hard_drop
beqi x0, const.char.backspace, 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
ld d0, [x2 + block_table]
.align 4
move_left:
slli x0, x4, 1
and x0, x0, x6
bnez x0, fail
slli x0, x5, 1
and x0, x0, x7
bnez x0, fail
slli x4, x4, 1
slli x5, x5, 1
b move_successful
.align 4
move_right:
srli x0, x4, 1
and x0, x0, x6
bnez x0, fail
srli x0, x5, 1
and x0, x0, x7
bnez 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
andi x2, x0, 0o77
blt x2, x3, end_score_change
add x0, x0, x1
slli x1, x1, 6
srli x2, x0, 6
andi x2, x0, 0o77
blt x2, x3, end_score_change
add x0, x0, x1
slli x1, x1, 6
srli x2 x0, 6
andi x2, x0, 0o77
blt x2, x3, end_score_change
add x0, x0, x1
.align 2
end_score_change:
sw x0, score
quit:
pop ra