Overview
MB8 is an 8-bit microcomputer in the spirit of the ZX Spectrum and Commodore 64, initially inspired by CHIP-8. It ships with a tiny CP/M-like operating system layer and a minimal assembly-first toolchain.
What’s inside
- 8-bit CPU with a compact ISA and pseudo-instructions for convenience.
- Memory-mapped devices (RAM, ROM, GPU TTY, keyboard, disk) wired through a simple bus.
- A small kernel plus user-space programs, all written in assembly.
Running the project
- Build all assembly artifacts (kernel, user programs, tests):
make all
- Run the VM, passing the kernel entrypoint and all user-space programs in
./userdirectory:
make run
The kernel image is loaded at 0xE000, user programs are passed as extra binaries, and the OS provides basic CP/M-like services via syscalls.
Contributing
Thanks for checking out the project and wanting to help!
Run the project locally
- Install Rust (stable toolchain is fine).
- Run
make runto start the VM with the OS.
Workflow tips
- Use conventional commits on
masterwhen you can. - Before pushing, run
make cito check the code.
What we need
- New features described in issues; if you have ideas, please open an issue first.
- Bug reports.
- Bug fixes.
- Tests (see coverage on Codecov).
- Improvements to existing code (opening an issue first is recommended).
Final note
These are guidelines, not strict rules. If anything is unclear, let’s talk.
Memory model
The MB8 VM exposes a single 64 KiB address space. All reads and writes go through the bus (crates/mb8/src/dev/bus.rs), which forwards them to RAM, ROM, or an MMIO device based on the address range.
Layout
| Range | Size | Description |
|---|---|---|
0x0000 – 0xBFFF | 48 KiB | RAM |
0xC000 – 0xDFFF | 8 KiB | Reserved MMIO (not wired yet) |
0xE000 – 0xEFFF | 4 KiB | ROM |
0xF000 – 0xF0FF | 256 B | GPU registers |
0xF101 – 0xF1FF | 256 B | Keyboard registers |
0xF200 – 0xF3FF | 512 B | Disk registers and buffer |
0xF400 | 1 B | Random number generator |
0xF401 – 0xFFFF | 3071 B | Reserved MMIO (not wired yet) |
The bus rejects the reserved regions with unimplemented!().
Bus
- CPU memory accesses always call into the bus, which in turn calls the matching device
read/write. - Devices own their buffers; the bus itself does not store data.
RAM (crates/mb8/src/dev/ram.rs)
- Plain byte-addressable memory. Writes update the backing array; reads return what was last written.
RAM_SIZE = 0xC000. The stack grows downward (STACK_TOP = 0xBFFF,STACK_BOTTOM = 0xBF00).
ROM (crates/mb8/src/dev/rom.rs)
- Backing store for program code (
ROM_SIZE = 0x1000). - The device currently accepts writes from the bus, but programs should not rely on mutating ROM; this may be blocked in the future. ROM is meant to hold the kernel/boot image.
GPU (crates/mb8/src/dev/gpu.rs)
- Registers live at
0xF000(offsets relative to that base):0x0000— mode register.0x00= off,0x01= TTY.0x0001— TTY data register. When mode is TTY, each write pushes a character to the screen and advances the cursor.
- Reading
0x0000returns the current mode. Other reads are currently unimplemented.
Keyboard (crates/mb8/src/dev/keyboard.rs)
- Registers at
0xF101(offsets relative to that base):0x00—STATUS. Returns1when keys are queued, otherwise0.0x01—DATA. Reading pops the next key code from the queue; returns0when empty.
- Writes are ignored.
Disk (crates/mb8/src/dev/disk.rs)
- Registers at
0xF200(offsets relative to that base):0x0000—BLOCKnumber to operate on.0x0001—CMD(0x00no-op,0x01read,0x02write).0x0002–0x0102— 256-byte disk buffer used for reads/writes.
CMDoperations move data between the internal image and the buffer; buffer reads/writes go directly to the 256-byte window.
Random Number Generator (crates/mb8/src/dev/rand.rs)
- Registers at
0xF400(offsets relative to that base):0x00—DATA. Reading returns the next random number in the sequence.- Writes to
DATAare ignored.
Register set
16 registers, each 8 bits wide. Some are paired into 16-bit pointers through aliases.
| register | alias | mask | description | size |
|---|---|---|---|---|
| R0 | A | 0x00 | Accumulator / general-purpose | 8 bits |
| R1 | - | 0x01 | General-purpose | 8 bits |
| R2 | - | 0x02 | General-purpose | 8 bits |
| R3 | - | 0x03 | General-purpose | 8 bits |
| R4 | - | 0x04 | General-purpose | 8 bits |
| R5 | - | 0x05 | General-purpose | 8 bits |
| R6 | - | 0x06 | General-purpose | 8 bits |
| R7 | - | 0x07 | General-purpose | 8 bits |
| R8 | - | 0x08 | General-purpose | 8 bits |
| R9 | IH | 0x09 | Index register high byte | 8 bits |
| R10 | IL | 0x0A | Index register low byte | 8 bits |
| R11 | FPH | 0x0B | Frame pointer high byte | 8 bits |
| R12 | FPL | 0x0C | Frame pointer low byte | 8 bits |
| R13 | SPH | 0x0D | Stack pointer high byte | 8 bits |
| R14 | SPL | 0x0E | Stack pointer low byte | 8 bits |
| R15 | F | 0x0F | Flags register (Z/N/C) | 8 bits |
Notes:
IH:ILform a 16-bit index pointer.FPH:FPLhold the 16-bit frame pointer.SPH:SPLhold the 16-bit stack pointer; PUSH/POP move it downward.Fis overwritten by arithmetic/logic/shift ops. Jumps read it, other ops leave it untouched.- Context switches keep register sets separate for each VM context.
Flags
These live in the F register and are rewritten by arithmetic/logic/shift instructions:
| flag | mask | description | set by |
|---|---|---|---|
| Z | 0x01 | Result is zero. | ADD, SUB, AND, OR, XOR, SHL, SHR (and pseudo-instructions that expand to them) |
| N | 0x02 | Copies bit 7 (sign) of the 8-bit result. | ADD, SUB, AND, OR, XOR, SHL, SHR |
| C | 0x04 | Set when an 8-bit result wraps: carry on ADD/SHL/SHR, borrow on SUB. | ADD, SUB, SHL, SHR |
Notes:
- Instructions not listed leave flags unchanged.
- Pseudo-instructions (
INC,DEC, shifts) inherit flag behavior from the underlying ops. - Flags 0x08, 0x10, 0x20, 0x40, 0x80 are reserved for future use.
Instruction format
Every opcode is 16 bits wide (0xABCD):
A— instruction group.B— sub-opcode or register nibble.C— usually a register or the upper 4 bits of an address.D— usually a register or the lower 4 bits of an address.
Example, ADD R0, R1 encodes as 0x1101:
0001 0001 0000 0001
Jump/load/store instructions treat XXX in 0xYXXX as a 12-bit address, covering the full 4 KiB memory bank.
Instruction set
MB8 instructions are 16 bits wide. This page describes the opcodes decoded and executed by the processor. Textual forms accepted by the assembler are documented separately in the assembler instruction reference.
Encoding notation
DandSidentify destination and source registers.HandLidentify registers containing the high and low bytes of an address.Iis an unsigned immediate bit.Ois a signed relative-offset bit.Xis ignored by the decoder.- Register fields use the four-bit values from the register set.
- Unless stated otherwise, an instruction leaves the flags unchanged.
Control instructions
NOP
Encoding: 0000 0000 XXXX XXXX
Hex pattern: 0x00XX
Operation: none.
Behavior: Advances execution without changing processor state.
HALT
Encoding: 0000 0001 XXXX XXXX
Hex pattern: 0x01XX
Operation: halted ← true.
Behavior: Stops the VM. The low byte is ignored by the current processor implementation.
SYS
Encoding: 0000 0010 XXXX XXXX
Hex pattern: 0x02XX
Operation: reserved system operation.
Behavior: The opcode is decoded by the processor, but its VM operation is currently
unimplemented.
Register-register instructions
MOV
Encoding: 0001 0000 DDDD SSSS
Hex pattern: 0x10DS
Fields: D is the destination register; S is the source register.
R[D] ← R[S]
Copies one register to another.
ADD
Encoding: 0001 0001 DDDD SSSS
Hex pattern: 0x11DS
Fields: D is the destination register; S is the source register.
R[D] ← R[D] + R[S]
The 8-bit result wraps on overflow.
Flags: replaces Z, N, and C; C indicates unsigned overflow.
SUB
Encoding: 0001 0010 DDDD SSSS
Hex pattern: 0x12DS
Fields: D is the destination register; S is the source register.
R[D] ← R[D] - R[S]
The 8-bit result wraps on underflow.
Flags: replaces Z, N, and C; C indicates unsigned underflow.
AND
Encoding: 0001 0011 DDDD SSSS
Hex pattern: 0x13DS
Fields: D is the destination register; S is the source register.
R[D] ← R[D] AND R[S]
Flags: replaces Z and N; clears C.
OR
Encoding: 0001 0100 DDDD SSSS
Hex pattern: 0x14DS
Fields: D is the destination register; S is the source register.
R[D] ← R[D] OR R[S]
Flags: replaces Z and N; clears C.
XOR
Encoding: 0001 0101 DDDD SSSS
Hex pattern: 0x15DS
Fields: D is the destination register; S is the source register.
R[D] ← R[D] XOR R[S]
Flags: replaces Z and N; clears C.
SHR
Encoding: 0001 0110 DDDD SSSS
Hex pattern: 0x16DS
Fields: D is the value register; S contains the shift count.
R[D] ← R[D] >> R[S]
Performs repeated logical right shifts.
Flags: replaces Z, N, and C; C contains the last bit shifted out, or is clear when the
shift count is zero.
SHL
Encoding: 0001 0111 DDDD SSSS
Hex pattern: 0x17DS
Fields: D is the value register; S contains the shift count.
R[D] ← R[D] << R[S]
Performs repeated 8-bit left shifts.
Flags: replaces Z, N, and C; C contains the last bit shifted out, or is clear when the
shift count is zero.
CMP
Encoding: 0001 1000 DDDD SSSS
Hex pattern: 0x18DS
Fields: D and S identify the registers to compare.
result ← R[D] - R[S]
Computes a subtraction for its flags without modifying either operand.
Flags: replaces Z, N, and C; C indicates unsigned underflow.
Immediate instructions
LDI
Encoding: 0010 DDDD IIII IIII
Hex pattern: 0x2DII
Fields: D is the destination register; I is an unsigned 8-bit value.
R[D] ← I
Loads an immediate byte into a register.
Jump instructions
Relative offsets are signed 8-bit values. The VM applies them to the program counter value for the next instruction.
JMP
Encoding: 0011 0000 HHHH LLLL
Hex pattern: 0x30HL
Fields: H and L identify registers containing the high and low address bytes.
PC ← (R[H] << 8) OR R[L]
Performs an absolute jump.
JR
Encoding: 0011 0001 OOOO OOOO
Hex pattern: 0x31OO
PC ← PC + sign_extend(O)
Performs an unconditional relative jump with 16-bit wrapping.
JZR
Encoding: 0011 0010 OOOO OOOO
Hex pattern: 0x32OO
if Z = 1: PC ← PC + sign_extend(O)
Performs a relative jump when the zero flag is set.
JNZR
Encoding: 0011 0011 OOOO OOOO
Hex pattern: 0x33OO
if Z = 0: PC ← PC + sign_extend(O)
Performs a relative jump when the zero flag is clear.
JCR
Encoding: 0011 0100 OOOO OOOO
Hex pattern: 0x34OO
if C = 1: PC ← PC + sign_extend(O)
Performs a relative jump when the carry flag is set.
JNCR
Encoding: 0011 0101 OOOO OOOO
Hex pattern: 0x35OO
if C = 0: PC ← PC + sign_extend(O)
Performs a relative jump when the carry flag is clear.
Stack instructions
The stack pointer is the 16-bit value in SPH:SPL. Stack operations halt the VM when they cross
the implemented stack bounds.
CALL
Encoding: 0100 0000 HHHH LLLL
Hex pattern: 0x40HL
Fields: H and L identify registers containing the high and low target-address bytes.
push16(PC)
PC ← (R[H] << 8) OR R[L]
Pushes the return address and transfers control to an absolute address.
RET
Encoding: 0100 0001 XXXX XXXX
Hex pattern: 0x41XX
PC ← pop16()
Restores a return address from the stack.
PUSH
Encoding: 0100 0010 SSSS XXXX
Hex pattern: 0x42SX
Fields: S is the source register.
MEM[SP] ← R[S]
SP ← SP - 1
Pushes one byte onto the descending stack.
POP
Encoding: 0100 0011 DDDD XXXX
Hex pattern: 0x43DX
Fields: D is the destination register.
SP ← SP + 1
R[D] ← MEM[SP]
Pops one byte from the descending stack.
Memory instructions
LD
Encoding: 0101 DDDD HHHH LLLL
Hex pattern: 0x5DHL
Fields: D is the destination; H and L identify the address registers.
R[D] ← MEM[(R[H] << 8) OR R[L]]
Reads one byte through the memory bus.
ST
Encoding: 0110 SSSS HHHH LLLL
Hex pattern: 0x6SHL
Fields: S is the source; H and L identify the address registers.
MEM[(R[H] << 8) OR R[L]] ← R[S]
Writes one byte through the memory bus.
Assembler syntax
MB8 assembly is compiled by the Rust asm crate in this workspace.
Writing a program for the VM
User images start at 0x1000, while kernel and kernel-test images start at 0xE000. Declare that
base once at the top of each root source:
.origin 0x1000
start:
LDI R1, 0x03
HALT
.addr 0x2000
The final .addr pads the image to its 4 KiB boundary.
Instructions and operands
Instructions and registers are conventionally uppercase. Separate operands with commas and write register pairs with a colon:
LDI R2:R3, MESSAGE
LD R1, [R2:R3]
ST [0xF001], R1
CALL [0xE500]
The assembler provides the ISA and pseudo-instructions directly. Pseudo-instructions include
INC, DEC, INC16, MUL, MEMCPY, STRCMP, immediate jumps, and absolute calls, loads,
stores, and jumps; no rule-file include is needed.
Directives
.origin <address>sets the image base and may appear once in the root program..addr <address>pads with zero bytes up to an absolute address..data <byte>, ...emits bytes..ascii "text"emits a string;\n,\0,\\, and\"escapes are supported..include "path.asm"inserts another source relative to the containing file..const @NAME, <value>defines a constant; refer to it as@NAME.
Labels beginning with _ are local to the preceding non-local label in the same source:
.const @SYS_WRITE, 0x02
write:
LDI R1, @SYS_WRITE
_loop:
JR [_loop]
Numeric literals use hexadecimal notation. Character immediates should be written as their byte
value, such as 0x0A for newline.
Building and running
Assemble one source directly:
cargo run --quiet -p asm -- user/sh.asm -o user/sh.bin
Use make, make kernel, make user for the repository images, and make run
to launch the VM.
Assembler instruction reference
This page lists every instruction form accepted by the MB8 assembler. Mnemonics are ordered alphabetically. Forms within a mnemonic are ordered as core, immediate, label, and other pseudo forms.
Conventions
rD,rS,rA, andrBare 8-bit registers.rH:rLis a pair of 8-bit registers containing a 16-bit value.imm8andimm16are hexadecimal immediate values.off8is the encoded 8-bit value of a signed relative offset.labelis a label defined in the assembled program.- An immediate operand may also be supplied through a constant such as
@NAME. - Square brackets denote an address or a memory operand and are part of the syntax.
hi(value)andlo(value)are compiler expressions that select the high and low bytes.rel8(target)is the signed offset from the following instruction totarget.{id}is the unique source-instruction index used in compiler-generated labels.- Registers read and written list actual accesses made by the emitted instructions, including temporary accesses. Scratch registers state whether those temporary values are preserved.
Mnemonics
ADD · AND · CALL · CMP · DEC · HALT · INC · INC16 · JCR · JMP · JNCR · JNZR · JR · JZR · LD · LDI · MEMCPY · MOV · MUL · NOP · NOT · OR · POP · PUSH · RET · SHL · SHR · ST · STRCMP · SUB · SWAP · SYS · XOR · ZERO
ADD
ADD rD, rS
Kind: Core.
Operands: rD is the destination and left operand; rS is the right operand.
Compiles to:
ADD rD, rS
Operation: Adds rS to rD, stores the wrapped 8-bit result in rD, and updates arithmetic
flags.
Registers read: rD, rS.
Registers written: rD, F.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and C on
unsigned overflow.
AND
AND rD, rS
Kind: Core.
Operands: rD is the destination and left operand; rS is the right operand.
Compiles to:
AND rD, rS
Operation: Stores the bitwise AND of rD and rS in rD.
Registers read: rD, rS.
Registers written: rD, F.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and clears C.
CALL
CALL [rH:rL]
Kind: Core.
Operands: rH:rL contains the absolute destination address.
Compiles to:
CALL [rH:rL]
Operation: Pushes the address of the following instruction and sets PC to the address in
rH:rL.
Registers read: rH, rL, PC, SPH, SPL.
Registers written: PC, SPH, SPL.
Scratch registers: None.
Stack: Pushes a two-byte return address; net stack-pointer change is -2 bytes.
Flags: Reads: None; writes: None.
CALL [imm16]
Kind: Pseudo.
Operands: imm16 is the absolute destination address.
Compiles to:
LDI IH, hi(imm16)
LDI IL, lo(imm16)
CALL [IH:IL]
Operation: Loads imm16 into IH:IL, pushes the address of the following instruction, and
sets PC to imm16.
Registers read: PC, SPH, SPL, IH, IL.
Registers written: IH, IL, PC, SPH, SPL.
Scratch registers: IH, IL are clobbered.
Stack: Pushes a two-byte return address; net stack-pointer change is -2 bytes.
Flags: Reads: None; writes: None.
CALL [label]
Kind: Pseudo.
Operands: label identifies the absolute destination address.
Compiles to:
LDI IH, hi(label)
LDI IL, lo(label)
CALL [IH:IL]
Operation: Loads the address of label into IH:IL, pushes the address of the following
instruction, and transfers control to label.
Registers read: PC, SPH, SPL, IH, IL.
Registers written: IH, IL, PC, SPH, SPL.
Scratch registers: IH, IL are clobbered.
Stack: Pushes a two-byte return address; net stack-pointer change is -2 bytes.
Flags: Reads: None; writes: None.
CMP
CMP rD, rS
Kind: Core.
Operands: rD is the left operand; rS is the right operand.
Compiles to:
CMP rD, rS
Operation: Computes rD - rS for flags without modifying either operand.
Registers read: rD, rS.
Registers written: F.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: Z when the difference is zero, N from difference bit 7, and
C on unsigned underflow.
CMP rD, imm8
Kind: Pseudo.
Operands: rD is the left operand; imm8 is the right operand.
Compiles to:
PUSH A
LDI A, imm8
CMP rD, A
POP A
Operation: Compares rD with imm8 while preserving the original value of A.
Registers read: rD, A, SPH, SPL.
Registers written: A, F, SPH, SPL.
Scratch registers: A is temporarily modified and restored.
Stack: Uses one temporary byte; maximum depth is 1 byte and net stack-pointer change is 0.
Flags: Reads: None; writes: Z when rD == imm8, N from difference bit 7, and C when
rD < imm8.
DEC
DEC rD
Kind: Pseudo.
Operands: rD is the register to decrement.
Compiles to:
PUSH A
LDI A, 0x01
SUB rD, A
POP A
Operation: Subtracts one from rD with 8-bit wrapping while preserving A.
Registers read: rD, A, SPH, SPL.
Registers written: rD, A, F, SPH, SPL.
Scratch registers: A is temporarily modified and restored.
Stack: Uses one temporary byte; maximum depth is 1 byte and net stack-pointer change is 0.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and C when
the decrement underflows.
HALT
HALT
Kind: Core.
Operands: None.
Compiles to:
HALT
Operation: Stops VM execution with an encoded low byte of 0x00.
Registers read: None.
Registers written: None.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
HALT imm8
Kind: Core encoded form.
Operands: imm8 is placed in the low byte of the instruction.
Compiles to:
HALT imm8
Operation: Stops VM execution. The current processor ignores the encoded low byte after
decoding.
Registers read: None.
Registers written: None.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
INC
INC rD
Kind: Pseudo.
Operands: rD is the register to increment.
Compiles to:
PUSH A
LDI A, 0x01
ADD rD, A
POP A
Operation: Adds one to rD with 8-bit wrapping while preserving A.
Registers read: rD, A, SPH, SPL.
Registers written: rD, A, F, SPH, SPL.
Scratch registers: A is temporarily modified and restored.
Stack: Uses one temporary byte; maximum depth is 1 byte and net stack-pointer change is 0.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and C when
the increment overflows.
INC16
INC16 rH:rL
Kind: Pseudo.
Operands: rH:rL contains the 16-bit value to increment.
Compiles to:
PUSH A
LDI A, 0xFF
CMP rL, A
POP A
JZR rel8(__mb8_inc16_hi_{id})
PUSH A
LDI A, 0x01
ADD rL, A
POP A
JR rel8(__mb8_inc16_end_{id})
__mb8_inc16_hi_{id}:
LDI rL, 0x00
PUSH A
LDI A, 0x01
ADD rH, A
POP A
__mb8_inc16_end_{id}:
NOP
Operation: Increments rH:rL; increments only rL unless it was 0xFF, in which case it
sets rL to zero and increments rH. A is preserved.
Registers read: rH, rL, A, F, PC, SPH, SPL.
Registers written: rH conditionally, rL, A, F, PC, SPH, SPL.
Scratch registers: A is temporarily modified and restored.
Stack: Uses one temporary byte at a time; maximum depth is 1 byte and net stack-pointer change
is 0.
Flags: Reads: Z for the generated branch; writes: Z, N, and C from the byte incremented
by the final ADD.
JCR
JCR off8
Kind: Core.
Operands: off8 is the encoded signed offset from the following instruction.
Compiles to:
JCR off8
Operation: Adds the signed offset to PC when C is set; otherwise continues at the following
instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: C; writes: None.
JCR [imm16]
Kind: Pseudo.
Operands: imm16 is an absolute target address converted to a relative offset.
Compiles to:
JCR rel8(imm16)
Operation: Branches to imm16 when C is set. The target must fit a signed 8-bit offset from
the following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: C; writes: None.
JCR [label]
Kind: Pseudo.
Operands: label is converted to a relative offset.
Compiles to:
JCR rel8(label)
Operation: Branches to label when C is set. The target must fit a signed 8-bit offset from
the following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: C; writes: None.
JMP
JMP [rH:rL]
Kind: Core.
Operands: rH:rL contains the absolute destination address.
Compiles to:
JMP [rH:rL]
Operation: Sets PC to the 16-bit address in rH:rL.
Registers read: rH, rL.
Registers written: PC.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
JMP [imm16]
Kind: Pseudo.
Operands: imm16 is the absolute destination address.
Compiles to:
LDI IH, hi(imm16)
LDI IL, lo(imm16)
JMP [IH:IL]
Operation: Loads imm16 into IH:IL and transfers control to it.
Registers read: IH, IL.
Registers written: IH, IL, PC.
Scratch registers: IH, IL are clobbered.
Stack: None.
Flags: Reads: None; writes: None.
JMP [label]
Kind: Pseudo.
Operands: label identifies the absolute destination address.
Compiles to:
LDI IH, hi(label)
LDI IL, lo(label)
JMP [IH:IL]
Operation: Loads the address of label into IH:IL and transfers control to it.
Registers read: IH, IL.
Registers written: IH, IL, PC.
Scratch registers: IH, IL are clobbered.
Stack: None.
Flags: Reads: None; writes: None.
JNCR
JNCR off8
Kind: Core.
Operands: off8 is the encoded signed offset from the following instruction.
Compiles to:
JNCR off8
Operation: Adds the signed offset to PC when C is clear; otherwise continues at the
following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: C; writes: None.
JNCR [imm16]
Kind: Pseudo.
Operands: imm16 is an absolute target address converted to a relative offset.
Compiles to:
JNCR rel8(imm16)
Operation: Branches to imm16 when C is clear. The target must fit a signed 8-bit offset
from the following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: C; writes: None.
JNCR [label]
Kind: Pseudo.
Operands: label is converted to a relative offset.
Compiles to:
JNCR rel8(label)
Operation: Branches to label when C is clear. The target must fit a signed 8-bit offset
from the following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: C; writes: None.
JNZR
JNZR off8
Kind: Core.
Operands: off8 is the encoded signed offset from the following instruction.
Compiles to:
JNZR off8
Operation: Adds the signed offset to PC when Z is clear; otherwise continues at the
following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: Z; writes: None.
JNZR [imm16]
Kind: Pseudo.
Operands: imm16 is an absolute target address converted to a relative offset.
Compiles to:
JNZR rel8(imm16)
Operation: Branches to imm16 when Z is clear. The target must fit a signed 8-bit offset
from the following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: Z; writes: None.
JNZR [label]
Kind: Pseudo.
Operands: label is converted to a relative offset.
Compiles to:
JNZR rel8(label)
Operation: Branches to label when Z is clear. The target must fit a signed 8-bit offset
from the following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: Z; writes: None.
JR
JR off8
Kind: Core.
Operands: off8 is the encoded signed offset from the following instruction.
Compiles to:
JR off8
Operation: Unconditionally adds the signed offset to PC.
Registers read: PC.
Registers written: PC.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
JR [imm16]
Kind: Pseudo.
Operands: imm16 is an absolute target address converted to a relative offset.
Compiles to:
JR rel8(imm16)
Operation: Branches to imm16. The target must fit a signed 8-bit offset from the following
instruction.
Registers read: PC.
Registers written: PC.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
JR [label]
Kind: Pseudo.
Operands: label is converted to a relative offset.
Compiles to:
JR rel8(label)
Operation: Branches to label. The target must fit a signed 8-bit offset from the following
instruction.
Registers read: PC.
Registers written: PC.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
JZR
JZR off8
Kind: Core.
Operands: off8 is the encoded signed offset from the following instruction.
Compiles to:
JZR off8
Operation: Adds the signed offset to PC when Z is set; otherwise continues at the following
instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: Z; writes: None.
JZR [imm16]
Kind: Pseudo.
Operands: imm16 is an absolute target address converted to a relative offset.
Compiles to:
JZR rel8(imm16)
Operation: Branches to imm16 when Z is set. The target must fit a signed 8-bit offset from
the following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: Z; writes: None.
JZR [label]
Kind: Pseudo.
Operands: label is converted to a relative offset.
Compiles to:
JZR rel8(label)
Operation: Branches to label when Z is set. The target must fit a signed 8-bit offset from
the following instruction.
Registers read: F, PC.
Registers written: PC when the branch is taken.
Scratch registers: None.
Stack: None.
Flags: Reads: Z; writes: None.
LD
LD rD, [rH:rL]
Kind: Core.
Operands: rD is the destination; rH:rL contains the source address.
Compiles to:
LD rD, [rH:rL]
Operation: Reads one byte from memory at rH:rL into rD.
Registers read: rH, rL.
Registers written: rD.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
LD rD, [imm16]
Kind: Pseudo.
Operands: rD is the destination; imm16 is the source address.
Compiles to:
LDI IH, hi(imm16)
LDI IL, lo(imm16)
LD rD, [IH:IL]
Operation: Loads the address into IH:IL and reads one byte from it into rD.
Registers read: IH, IL.
Registers written: IH, IL, rD.
Scratch registers: IH, IL are clobbered.
Stack: None.
Flags: Reads: None; writes: None.
LD rD, [label]
Kind: Pseudo.
Operands: rD is the destination; label identifies the source address.
Compiles to:
LDI IH, hi(label)
LDI IL, lo(label)
LD rD, [IH:IL]
Operation: Loads the address of label into IH:IL and reads one byte from it into rD.
Registers read: IH, IL.
Registers written: IH, IL, rD.
Scratch registers: IH, IL are clobbered.
Stack: None.
Flags: Reads: None; writes: None.
LD rD, [rH:rL - imm8]
Kind: Pseudo.
Operands: rD is the destination; rH:rL is the base address; imm8 is subtracted from it.
Compiles to:
LDI A, imm8
SUB rL, A
JNCR rel8(__mb8_ld_no_borrow_{id})
PUSH A
LDI A, 0x01
SUB rH, A
POP A
__mb8_ld_no_borrow_{id}:
LD rD, [rH:rL]
Operation: Subtracts imm8 from rH:rL, leaves the adjusted address in the pair, and reads
one byte from that address into rD.
Registers read: rH, rL, A, F, PC, SPH, SPL.
Registers written: rH conditionally, rL, rD, A, F, PC conditionally, SPH, SPL.
Scratch registers: A is clobbered with imm8.
Stack: The borrow path uses one temporary byte; maximum depth is 1 byte and net stack-pointer
change is 0.
Flags: Reads: C for the generated branch; writes: Z, N, and C from subtracting
imm8 from rL, or from decrementing rH when a borrow occurs.
LDI
LDI rD, imm8
Kind: Core.
Operands: rD is the destination; imm8 is the byte to load.
Compiles to:
LDI rD, imm8
Operation: Stores imm8 in rD.
Registers read: None.
Registers written: rD.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
LDI rH:rL, imm16
Kind: Pseudo.
Operands: rH:rL is the destination pair; imm16 is the 16-bit value.
Compiles to:
LDI rH, hi(imm16)
LDI rL, lo(imm16)
Operation: Stores the high byte of imm16 in rH and the low byte in rL.
Registers read: None.
Registers written: rH, rL.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
LDI rH:rL, label
Kind: Pseudo.
Operands: rH:rL is the destination pair; label supplies its 16-bit address.
Compiles to:
LDI rH, hi(label)
LDI rL, lo(label)
Operation: Stores the high and low bytes of the address of label in rH:rL.
Registers read: None.
Registers written: rH, rL.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
MEMCPY
MEMCPY [dstH:dstL], [srcH:srcL], len
Kind: Pseudo.
Operands: dstH:dstL is the destination pointer, srcH:srcL is the source pointer, and len
is a register containing the last zero-based byte index.
Compiles to:
PUSH A
LDI A, 0x00
__mb8_memcpy_loop_{id}:
PUSH A
LD A, [srcH:srcL]
ST [dstH:dstL], A
POP A
CMP A, len
JZR rel8(__mb8_memcpy_end_{id})
PUSH R7
LDI R7, 0x01
ADD A, R7
POP R7
PUSH A
LDI A, 0xFF
CMP srcL, A
POP A
JZR rel8(__mb8_memcpy_src_{id}_hi)
PUSH A
LDI A, 0x01
ADD srcL, A
POP A
JR rel8(__mb8_memcpy_src_{id}_end)
__mb8_memcpy_src_{id}_hi:
LDI srcL, 0x00
PUSH A
LDI A, 0x01
ADD srcH, A
POP A
__mb8_memcpy_src_{id}_end:
NOP
PUSH A
LDI A, 0xFF
CMP dstL, A
POP A
JZR rel8(__mb8_memcpy_dst_{id}_hi)
PUSH A
LDI A, 0x01
ADD dstL, A
POP A
JR rel8(__mb8_memcpy_dst_{id}_end)
__mb8_memcpy_dst_{id}_hi:
LDI dstL, 0x00
PUSH A
LDI A, 0x01
ADD dstH, A
POP A
__mb8_memcpy_dst_{id}_end:
NOP
JR rel8(__mb8_memcpy_loop_{id})
__mb8_memcpy_end_{id}:
POP A
Operation: Copies len + 1 bytes. The source and destination pointers advance after every
byte except the last; A and R7 are restored.
Registers read: dstH, dstL, srcH, srcL, len, A, R7, F, PC, SPH, SPL.
Registers written: dstH, dstL, srcH, srcL, A, R7, F, PC, SPH, SPL.
Scratch registers: A and R7 are temporarily modified and restored.
Stack: Keeps the original A on the stack for the loop and uses one additional temporary byte;
maximum depth is 2 bytes and net stack-pointer change is 0.
Flags: Reads: Z and C in generated branches; writes: final Z = 1, N = 0, and C = 0
from the terminating equality comparison of the byte index with len.
MOV
MOV rD, rS
Kind: Core.
Operands: rD is the destination; rS is the source.
Compiles to:
MOV rD, rS
Operation: Copies the byte in rS to rD.
Registers read: rS.
Registers written: rD.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
MUL
MUL rD, rA, rB
Kind: Pseudo.
Operands: rD receives the product; rA is the repeated addend; rB is the iteration count.
Compiles to:
LDI rD, 0x00
PUSH rB
__mb8_mul_iter_{id}:
ADD rD, rA
PUSH A
LDI A, 0x01
SUB rB, A
POP A
PUSH A
LDI A, 0x00
CMP rB, A
POP A
JNZR rel8(__mb8_mul_iter_{id})
POP rB
Operation: Repeatedly adds rA to rD while decrementing rB to zero. The 8-bit result wraps;
the original values of rB and A are restored. An initial rB of zero performs 256 iterations.
Registers read: rA, rB, A, F, PC, SPH, SPL.
Registers written: rD, rB, A, F, PC, SPH, SPL.
Scratch registers: A and rB are temporarily modified and restored.
Stack: Keeps the original rB on the stack and uses one additional temporary byte; maximum
depth is 2 bytes and net stack-pointer change is 0.
Flags: Reads: Z for the generated loop branch; writes: final Z = 1, N = 0, and C = 0
from comparing the decremented rB with zero.
NOP
NOP
Kind: Core.
Operands: None.
Compiles to:
NOP
Operation: Advances to the following instruction without changing processor state.
Registers read: None.
Registers written: None.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
NOT
NOT rD
Kind: Pseudo.
Operands: rD is the register to invert.
Compiles to:
PUSH A
LDI A, 0xFF
XOR rD, A
POP A
Operation: Inverts every bit of rD while preserving A.
Registers read: rD, A, SPH, SPL.
Registers written: rD, A, F, SPH, SPL.
Scratch registers: A is temporarily modified and restored.
Stack: Uses one temporary byte; maximum depth is 1 byte and net stack-pointer change is 0.
Flags: Reads: None; writes: Z when the inverted result is zero, N from result bit 7, and
clears C.
OR
OR rD, rS
Kind: Core.
Operands: rD is the destination and left operand; rS is the right operand.
Compiles to:
OR rD, rS
Operation: Stores the bitwise OR of rD and rS in rD.
Registers read: rD, rS.
Registers written: rD, F.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and clears C.
POP
POP rD
Kind: Core.
Operands: rD is the destination register.
Compiles to:
POP rD
Operation: Increments the stack pointer and loads the byte at the new address into rD; halts
the VM on stack underflow.
Registers read: SPH, SPL.
Registers written: rD, SPH, SPL.
Scratch registers: None.
Stack: Pops one byte; net stack-pointer change is +1 byte.
Flags: Reads: None; writes: None.
PUSH
PUSH rS
Kind: Core.
Operands: rS is the source register.
Compiles to:
PUSH rS
Operation: Writes rS at the current stack pointer and decrements it; halts the VM on stack
overflow.
Registers read: rS, SPH, SPL.
Registers written: SPH, SPL.
Scratch registers: None.
Stack: Pushes one byte; net stack-pointer change is -1 byte.
Flags: Reads: None; writes: None.
RET
RET
Kind: Core.
Operands: None.
Compiles to:
RET
Operation: Pops a two-byte return address into PC; halts the VM on stack underflow.
Registers read: SPH, SPL.
Registers written: PC, SPH, SPL.
Scratch registers: None.
Stack: Pops two bytes; net stack-pointer change is +2 bytes.
Flags: Reads: None; writes: None.
SHL
SHL rD, rS
Kind: Core.
Operands: rD is the value and destination; rS contains the shift count.
Compiles to:
SHL rD, rS
Operation: Repeatedly shifts rD left by the count in rS, discarding high bits.
Registers read: rD, rS.
Registers written: rD, F.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and C from
the last high bit shifted out; C is clear for a zero shift count.
SHL rD, imm8
Kind: Pseudo.
Operands: rD is the value and destination; imm8 is the shift count.
Compiles to:
PUSH A
LDI A, imm8
SHL rD, A
POP A
Operation: Shifts rD left by imm8, preserving the original value of A.
Registers read: rD, A, SPH, SPL.
Registers written: rD, A, F, SPH, SPL.
Scratch registers: A is temporarily modified and restored.
Stack: Uses one temporary byte; maximum depth is 1 byte and net stack-pointer change is 0.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and C from
the last high bit shifted out; C is clear for a zero shift count.
SHR
SHR rD, rS
Kind: Core.
Operands: rD is the value and destination; rS contains the shift count.
Compiles to:
SHR rD, rS
Operation: Repeatedly shifts rD right logically by the count in rS, inserting zero bits.
Registers read: rD, rS.
Registers written: rD, F.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and C from
the last low bit shifted out; C is clear for a zero shift count.
SHR rD, imm8
Kind: Pseudo.
Operands: rD is the value and destination; imm8 is the shift count.
Compiles to:
PUSH A
LDI A, imm8
SHR rD, A
POP A
Operation: Shifts rD right logically by imm8, preserving the original value of A.
Registers read: rD, A, SPH, SPL.
Registers written: rD, A, F, SPH, SPL.
Scratch registers: A is temporarily modified and restored.
Stack: Uses one temporary byte; maximum depth is 1 byte and net stack-pointer change is 0.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and C from
the last low bit shifted out; C is clear for a zero shift count.
ST
ST [rH:rL], rS
Kind: Core.
Operands: rH:rL contains the destination address; rS contains the byte to store.
Compiles to:
ST [rH:rL], rS
Operation: Writes rS to memory at the address in rH:rL.
Registers read: rH, rL, rS.
Registers written: None.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
ST [imm16], rS
Kind: Pseudo.
Operands: imm16 is the destination address; rS contains the byte to store.
Compiles to:
LDI IH, hi(imm16)
LDI IL, lo(imm16)
ST [IH:IL], rS
Operation: Loads imm16 into IH:IL and writes rS to that address.
Registers read: IH, IL, rS.
Registers written: IH, IL.
Scratch registers: IH, IL are clobbered.
Stack: None.
Flags: Reads: None; writes: None.
ST [label], rS
Kind: Pseudo.
Operands: label identifies the destination address; rS contains the byte to store.
Compiles to:
LDI IH, hi(label)
LDI IL, lo(label)
ST [IH:IL], rS
Operation: Loads the address of label into IH:IL and writes rS to that address.
Registers read: IH, IL, rS.
Registers written: IH, IL.
Scratch registers: IH, IL are clobbered.
Stack: None.
Flags: Reads: None; writes: None.
STRCMP
STRCMP result, temp, srcH, srcL, dstH, dstL
Kind: Pseudo.
Operands: srcH:srcL and dstH:dstL point to zero-terminated strings; result receives the
comparison result; temp receives bytes from the second string.
Compiles to:
__mb8_strcmp_loop_{id}:
LD result, [srcH:srcL]
LD temp, [dstH:dstL]
CMP result, temp
JNZR rel8(__mb8_strcmp_error_{id})
PUSH A
LDI A, 0x00
CMP temp, A
POP A
JZR rel8(__mb8_strcmp_success_{id})
PUSH A
LDI A, 0xFF
CMP srcL, A
POP A
JZR rel8(__mb8_strcmp_src_{id}_hi)
PUSH A
LDI A, 0x01
ADD srcL, A
POP A
JR rel8(__mb8_strcmp_src_{id}_end)
__mb8_strcmp_src_{id}_hi:
LDI srcL, 0x00
PUSH A
LDI A, 0x01
ADD srcH, A
POP A
__mb8_strcmp_src_{id}_end:
NOP
PUSH A
LDI A, 0xFF
CMP dstL, A
POP A
JZR rel8(__mb8_strcmp_dst_{id}_hi)
PUSH A
LDI A, 0x01
ADD dstL, A
POP A
JR rel8(__mb8_strcmp_dst_{id}_end)
__mb8_strcmp_dst_{id}_hi:
LDI dstL, 0x00
PUSH A
LDI A, 0x01
ADD dstH, A
POP A
__mb8_strcmp_dst_{id}_end:
NOP
LDI IH, hi(__mb8_strcmp_loop_{id})
LDI IL, lo(__mb8_strcmp_loop_{id})
JMP [IH:IL]
__mb8_strcmp_error_{id}:
LDI result, 0x01
JR rel8(__mb8_strcmp_end_{id})
__mb8_strcmp_success_{id}:
LDI result, 0x00
__mb8_strcmp_end_{id}:
Operation: Compares the strings byte by byte. Writes 0 to result when both reach the same
zero terminator and 1 on the first mismatch. Address pairs advance after equal nonzero bytes;
temp retains the last byte read from the second string.
Registers read: srcH, srcL, dstH, dstL, result, temp, A, IH, IL, F, PC,
SPH, SPL.
Registers written: srcH, srcL, dstH, dstL, result, temp, A, IH, IL, F,
PC, SPH, SPL.
Scratch registers: A is temporarily modified and restored; IH and IL are clobbered when
the loop repeats.
Stack: Uses one temporary byte at a time; maximum depth is 1 byte and net stack-pointer change
is 0.
Flags: Reads: Z and C in generated branches; writes: on mismatch, retains Z = 0 with N
and C from result - temp; on equality, ends with Z = 1, N = 0, and C = 0 from comparing
the zero terminator with zero.
SUB
SUB rD, rS
Kind: Core.
Operands: rD is the destination and left operand; rS is the right operand.
Compiles to:
SUB rD, rS
Operation: Subtracts rS from rD, stores the wrapped 8-bit result in rD, and updates
arithmetic flags.
Registers read: rD, rS.
Registers written: rD, F.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and C on
unsigned underflow.
SWAP
SWAP rA, rB
Kind: Pseudo.
Operands: rA and rB are the registers to exchange.
Compiles to:
PUSH rA
MOV rA, rB
POP rB
Operation: Exchanges the values in rA and rB.
Registers read: rA, rB, SPH, SPL.
Registers written: rA, rB, SPH, SPL.
Scratch registers: None.
Stack: Uses one temporary byte; maximum depth is 1 byte and net stack-pointer change is 0.
Flags: Reads: None; writes: None.
SYS
SYS
Kind: Core.
Operands: None.
Compiles to:
SYS
Operation: Executes the reserved system opcode. Its VM operation is currently unimplemented.
Registers read: None.
Registers written: None.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
XOR
XOR rD, rS
Kind: Core.
Operands: rD is the destination and left operand; rS is the right operand.
Compiles to:
XOR rD, rS
Operation: Stores the bitwise XOR of rD and rS in rD.
Registers read: rD, rS.
Registers written: rD, F.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: Z when the result is zero, N from result bit 7, and clears C.
ZERO
ZERO rD
Kind: Pseudo.
Operands: rD is the register to clear.
Compiles to:
LDI rD, 0x00
Operation: Stores zero in rD.
Registers read: None.
Registers written: rD.
Scratch registers: None.
Stack: None.
Flags: Reads: None; writes: None.
Assembler diagnostics
This page lists every diagnostic currently emitted by the MB8 assembler. The diagnostic code in the command-line output links directly to the corresponding section below.
A0100
Lex Error
The assembler found a character, malformed hexadecimal number, string escape, or unterminated string that cannot be converted into a token.
Example
LDI R1, 42
Decimal literals are not part of the assembly syntax.
How to fix
Use a hexadecimal literal and make sure strings contain only supported escapes.
LDI R1, 0x2A
A0101
Parse Error
The tokens are valid individually, but they do not form a valid instruction, label, or directive. This commonly means that punctuation or an operand is missing.
Example
LDI R1 0x2A
How to fix
Follow the required instruction form, including the comma between operands.
LDI R1, 0x2A
A0200
Include Error
The assembler could not read or expand a file named by an .include directive. The diagnostic
message contains the underlying file or include error.
Example
.include "missing.asm"
How to fix
Correct the path, create the included file, or fix its permissions. Include paths are resolved relative to the file containing the directive.
A0300
Unsupported Instruction Form
The mnemonic and operands do not match any core instruction or pseudo-instruction known to the assembler.
Example
MOV R1, 0x01
How to fix
Use a supported operand form. For example, load an immediate value with LDI.
LDI R1, 0x01
A0301
Duplicate Label
The same label is defined more than once in its scope. The diagnostic marks both definitions.
Example
loop:
NOP
loop:
HALT
How to fix
Rename or remove one definition, and update any references to the renamed label.
loop:
NOP
done:
HALT
A0302
Unknown Label
An instruction refers to a label that is not defined in the applicable source scope.
Example
JMP missing
How to fix
Define the label or correct its spelling.
JMP done
done:
HALT
A0303
Unexpected Directive After Include Expansion
An .include or .const directive reached an internal assembler stage where it should already
have been expanded or removed. This indicates an assembler bug, not an error that assembly source
is normally expected to cause.
How to fix
Please report the bug and include the source files, assembler version, and full diagnostic output.
A0304
Duplicate Origin Directive
An assembled program contains more than one .origin directive. The diagnostic marks the first
and duplicate directives.
Example
.origin 0x1000
.origin 0x2000
How to fix
Keep a single .origin directive in the root source.
.origin 0x1000
A0305
Address Overflow
Emitting an instruction or data would advance the current address beyond 0xFFFF, the largest
address representable by the assembler.
Example
.origin 0xFFFF
NOP
How to fix
Move the origin to a lower address or reduce the amount of emitted code or data.
.origin 0xFFFD
NOP
A0306
Value Out of Range
An immediate value does not fit the operand width required by the selected instruction form.
Example
LDI R1, 0x0100
How to fix
Use a value within the stated range or an instruction form that accepts the wider value.
LDI R1, 0xFF
A0307
Relative Jump Out of Range
The target of a relative jump is too far from the jump instruction to fit in a signed 8-bit offset.
Example
start:
JR [far]
.addr 0x0200
far:
HALT
How to fix
Move the target closer or use an absolute jump.
JMP far
A0308
Scratch Register Conflict
A pseudo-instruction needs a scratch register that is also used as one of its operands. Expanding the instruction would overwrite that operand.
Example
MUL A, R1, R2
How to fix
Use a different operand register, or replace the pseudo-instruction with explicit core instructions that preserve the value.
MUL R3, R1, R2
A0309
Invalid Address Directive
An .addr directive targets an address below the current address. The assembler can pad forward,
but it cannot move backward or overwrite bytes already emitted.
Example
.origin 0x1000
NOP
.addr 0x1001
How to fix
Choose a target at or above the current address.
.origin 0x1000
NOP
.addr 0x1002
A0310
Duplicate Constant
The same constant name is defined more than once. The diagnostic marks both definitions.
Example
.const @COLOR, 0x01
.const @COLOR, 0x02
How to fix
Keep one definition or give the constants distinct names.
.const @FOREGROUND, 0x01
.const @BACKGROUND, 0x02
A0311
Unknown Constant
An operand refers to a constant that has not been defined.
Example
LDI R1, @COLOR
How to fix
Define the constant or correct its spelling.
.const @COLOR, 0x01
LDI R1, @COLOR
A0312
Register Alias Preferred
A physical register name was used even though the register has a semantic alias. Aliases make the purpose of special registers clearer:
| Physical name | Preferred alias |
|---|---|
R0 | A |
R9 | IH |
R10 | IL |
R11 | FPH |
R12 | FPL |
R13 | SPH |
R14 | SPL |
R15 | F |
Example
LDI R0, 0x01
How to fix
Use the preferred alias instead of the physical register name.
LDI A, 0x01
Data model
Types
int- 16 bitschar- 8 bitspointer- 16 bits
Registers
- Callee saved:
R0 | A,R2,R4,R5,R8,R9 | IH,R10 | IL. - Caller saved:
R1,R3,R6,R7,R11 | FPH,R12 | FPL. - System:
R13 | SPH,R14 | SPL,R15 | F.
Calling rules:
- Function arguments: first three in
R1,R2,R3; the rest on the stack. - Return value: always in
R0(A). - Frame pointer: 16-bit value in
FPH:FPL. - Stack pointer: 16-bit value in
SPH:SPL; stack starts at0xBFFFand grows downward. - Index register:
IH:ILis a free 16-bit index pair for addressing.
System Calls
System calls live at 0xE500 (kernel/syscalls.asm). To invoke one, load the call ID into R1 and CALL 0xE500. Inputs and outputs travel through the registers listed below; all other registers are caller-saved. R0/A is reserved as scratch for pseudo-instructions and is not part of the syscall ABI.
-
0x01 — SYS_GPU_MODE
Input:R2mode byte (0x00off,0x01TTY). Writes the GPU mode register at0xF000. -
0x02 — SYS_WRITE
Input:R2character byte. Sends it to the GPU TTY data register at0xF001. -
0x03 — SYS_WRITELN
Input:R2:R3address of a zero-terminated string. Streams characters to the TTY data register until0x00. -
0x04 — SYS_WAIT_FOR_KEY
Blocks until the keyboard status register (0xF101) is non-zero. No outputs. -
0x05 — SYS_READ_KEY
Output:R1key code popped from the keyboard data register (0xF102). Returns0if the queue was empty. -
0x06 — SYS_DISK_SET_BLOCK
Input:R2block index. Stores it in the disk block register at0xF200for later operations. -
0x07 — SYS_DISK_READ_BLOCK
Uses the previously selected block and copies it into the disk buffer window (0xF202–0xF302). -
0x08 — SYS_DISK_WRITE_BLOCK
Flushes the current disk buffer window into the previously selected block. -
0x09 — SYS_FS_LIST
Input:R2:R3destination buffer. Copies the directory block (block0) from disk into RAM viaMEMCPY. -
0x0A — SYS_FS_FIND
Input:R2:R3filename pointer.
Output:R1status (0success,1not found),R2block index,R3file size. -
0x0B — SYS_FS_READ
Input:R2:R3filename pointer,R4:R5destination buffer.
Output:R1status (0success,1not found). On success it loads the file into the buffer using the disk buffer window. -
0x0C — SYS_FS_WRITE
Input:R2:R3filename pointer. Currently unimplemented. -
0x0D — SYS_FS_DELETE
Input:R2:R3filename pointer. Currently unimplemented. -
0x0E — SYS_EXEC
Input:R2:R3filename pointer. Loads the file into RAM at0x1000(user entry) and jumps to it.
Output:R1status (0success,1not found). -
0x0F — SYS_EXIT
No inputs. Returns control to the kernel entrypoint at0xE000(used by user programs to quit). -
0x10 — SYS_RAND
Output:R1random byte.
Examples
User-space programs now live under user/. A good starting point is the shell at user/sh.asm: the kernel loads it into RAM at 0x1000 and jumps to it after boot. Build it with make user and run via cargo run -- run ./kernel/main.bin ./user/sh.bin ./user/hw.bin ./user/ls.bin ./user/exit.bin ./user/help.bin.