Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Build all assembly artifacts (kernel, user programs, tests):
make all
  1. Run the VM, passing the kernel entrypoint and all user-space programs in ./user directory:
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 run to start the VM with the OS.

Workflow tips

  • Use conventional commits on master when you can.
  • Before pushing, run make ci to 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

RangeSizeDescription
0x00000xBFFF48 KiBRAM
0xC0000xDFFF8 KiBReserved MMIO (not wired yet)
0xE0000xEFFF4 KiBROM
0xF0000xF0FF256 BGPU registers
0xF1010xF1FF256 BKeyboard registers
0xF2000xF3FF512 BDisk registers and buffer
0xF4001 BRandom number generator
0xF4010xFFFF3071 BReserved 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 0x0000 returns the current mode. Other reads are currently unimplemented.

Keyboard (crates/mb8/src/dev/keyboard.rs)

  • Registers at 0xF101 (offsets relative to that base):
    • 0x00STATUS. Returns 1 when keys are queued, otherwise 0.
    • 0x01DATA. Reading pops the next key code from the queue; returns 0 when empty.
  • Writes are ignored.

Disk (crates/mb8/src/dev/disk.rs)

  • Registers at 0xF200 (offsets relative to that base):
    • 0x0000BLOCK number to operate on.
    • 0x0001CMD (0x00 no-op, 0x01 read, 0x02 write).
    • 0x00020x0102 — 256-byte disk buffer used for reads/writes.
  • CMD operations 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):
    • 0x00DATA. Reading returns the next random number in the sequence.
    • Writes to DATA are 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:IL form a 16-bit index pointer.
  • FPH:FPL hold the 16-bit frame pointer.
  • SPH:SPL hold the 16-bit stack pointer; PUSH/POP move it downward.
  • F is 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

  • D and S identify destination and source registers.
  • H and L identify registers containing the high and low bytes of an address.
  • I is an unsigned immediate bit.
  • O is a signed relative-offset bit.
  • X is 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, and rB are 8-bit registers.
  • rH:rL is a pair of 8-bit registers containing a 16-bit value.
  • imm8 and imm16 are hexadecimal immediate values.
  • off8 is the encoded 8-bit value of a signed relative offset.
  • label is 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) and lo(value) are compiler expressions that select the high and low bytes.
  • rel8(target) is the signed offset from the following instruction to target.
  • {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 namePreferred alias
R0A
R9IH
R10IL
R11FPH
R12FPL
R13SPH
R14SPL
R15F

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 bits
  • char - 8 bits
  • pointer - 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 at 0xBFFF and grows downward.
  • Index register: IH:IL is 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: R2 mode byte (0x00 off, 0x01 TTY). Writes the GPU mode register at 0xF000.

  • 0x02 — SYS_WRITE
    Input: R2 character byte. Sends it to the GPU TTY data register at 0xF001.

  • 0x03 — SYS_WRITELN
    Input: R2:R3 address of a zero-terminated string. Streams characters to the TTY data register until 0x00.

  • 0x04 — SYS_WAIT_FOR_KEY
    Blocks until the keyboard status register (0xF101) is non-zero. No outputs.

  • 0x05 — SYS_READ_KEY
    Output: R1 key code popped from the keyboard data register (0xF102). Returns 0 if the queue was empty.

  • 0x06 — SYS_DISK_SET_BLOCK
    Input: R2 block index. Stores it in the disk block register at 0xF200 for later operations.

  • 0x07 — SYS_DISK_READ_BLOCK
    Uses the previously selected block and copies it into the disk buffer window (0xF2020xF302).

  • 0x08 — SYS_DISK_WRITE_BLOCK
    Flushes the current disk buffer window into the previously selected block.

  • 0x09 — SYS_FS_LIST
    Input: R2:R3 destination buffer. Copies the directory block (block 0) from disk into RAM via MEMCPY.

  • 0x0A — SYS_FS_FIND
    Input: R2:R3 filename pointer.
    Output: R1 status (0 success, 1 not found), R2 block index, R3 file size.

  • 0x0B — SYS_FS_READ
    Input: R2:R3 filename pointer, R4:R5 destination buffer.
    Output: R1 status (0 success, 1 not found). On success it loads the file into the buffer using the disk buffer window.

  • 0x0C — SYS_FS_WRITE
    Input: R2:R3 filename pointer. Currently unimplemented.

  • 0x0D — SYS_FS_DELETE
    Input: R2:R3 filename pointer. Currently unimplemented.

  • 0x0E — SYS_EXEC
    Input: R2:R3 filename pointer. Loads the file into RAM at 0x1000 (user entry) and jumps to it.
    Output: R1 status (0 success, 1 not found).

  • 0x0F — SYS_EXIT
    No inputs. Returns control to the kernel entrypoint at 0xE000 (used by user programs to quit).

  • 0x10 — SYS_RAND
    Output: R1 random 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.