mirror of
https://github.com/Asraelite/littlebigcomputer.git
synced 2025-07-18 00:26:50 +00:00
Init commit
This commit is contained in:
commit
c45ad79440
48 changed files with 6786 additions and 0 deletions
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
|
||||
\`\`\`
|
||||
`;
|
Loading…
Add table
Add a link
Reference in a new issue