This directory contains Tomato's two-pass assembler, assembly test programs,
and TOMATO OS v3.0. Machine instructions come from
docs/isa/tomato.v1.csv; assembler-only
vocabulary comes from
docs/isa/tomato.v1.pseudo.csv.
Project map: root README · ISA · Tomato OS · FPGA core
Tomato OS · menu and Tetris · software sheet
| Path | What |
|---|---|
assembler.py |
Two-pass assembler; the ISA CSVs are its only opcode source |
os/ |
Tomato OS v3.0, its 14 entries, Envelop client, and bounded executor |
asm/ |
Small programs, each with a value a testbench asserts on |
python3 software/assembler.py software/asm/counter.s -o hardware/fpga/core/tb/mem/counter.mem --listFrom hardware/fpga/core: make asm builds every image, make burn packs them into the FPGA burn headers, make os runs the OS in simulation.
The assembler holds no opcode table of its own. It reads both CSVs at startup, so growing the machine's vocabulary is a data change, not a code change.
| File | Holds |
|---|---|
tomato.v1.csv |
The 512-row control ROM: 91 instructions plus NOP, 92 burned rows |
tomato.v1.pseudo.csv |
Pseudo-instructions that expand into already-burned instructions |
Check the second one against the first at any time:
python3 software/assembler.py --selftestThat assembles every pseudo twice — once as the pseudo, once as the instructions it claims to expand into — and fails if the two word images differ. make -C hardware/fpga/core test runs it as asm-check.
| Form | Example |
|---|---|
| RRR | ADD r4, r1, r2 |
| MOV | MOV r1, r2 (rd ← rB) |
| unary | ZERO r5 |
| ADDI/LW | ADDI r1, r0, 10 / LW r6, r5, 0 |
| SW | SW r1, r5, 0 (data, base, imm8) |
| branch | BNE loop / BEQ done (PC-relative) |
| jump | JMP loop (absolute word address) |
| system | NOP / HALT / RET |
Comments: ; or #. Labels: name:.
The CPU has only BEQ / BNE / BLT / BGE, and the compare that feeds them is a separate instruction, so plain intent used to read as two unrelated lines. These say it in one:
| Write | Get |
|---|---|
CALL puts |
JAL r16, puts — the link register |
BEQZ r22, done |
CMP r22, r0 + BEQ done |
BNEZ, BLTZ, BGEZ |
the same shape, other conditions |
BLE done |
BEQ done + BLT done |
BGT done |
skip the BGE when the compare was equal |
LI r5, 100 |
ADDI r5, r0, 100 — value, not address |
TST r6 |
CMP r6, r0 |
INC / DEC / NEG / CLR |
in-place arithmetic, spelled out |
BZ / BNZ / BMI / BPL |
the conditionals, read after a TST |
BRA / ASL |
JMP / LSL under their usual names |
LA rd, label loads an address, and is the one thing limited to the low 4 KB — it assembles to ADDI rd, r0, addr, so the label must fit an unsigned 12-bit immediate. Pointer tables have no such limit: a .word label holds the full 32-bit address, which is how Tomato OS keeps its prose above 0x1000.
| Directive | Effect |
|---|---|
.org addr |
Set the next word address (sparse image; holes are 0) |
.word n, n |
Emit raw 32-bit words — glyphs, tables, pointers |
.space n |
Reserve n zeroed words for something written at runtime |
.ascii "text" |
One word per character (memory is word-addressed) |
.asciz "text" |
The same, NUL-terminated |
The OS sources identify TOMATO OS v3.0 and declare 14 menu entries.
Desktop v1.2 is the workspace UI revision, not the OS version. The image links
tomato_os.s, the Tomato-side Envelop client and setup records, and the bounded
remote executor.
See os/README.md for the menu, source boundaries, and focused
checks. Envelop's user clients, backend, and nearby bridge live in the separate
Envelop project; their source or deployment status cannot be inferred from the
machine-side assembly here.
| Program | Check |
|---|---|
asm/counter.s |
r1 = disp = 10 |
asm/fib.s |
fib(8) → r1 = r6 = 21 |
asm/collatz.s |
n=27 → 111 steps in r1/disp |
asm/call.s |
JAL/RET → r1=99 r2=42 |
asm/bytes.s |
SB/LB signed byte |
asm/ecall.s |
trap + RET → r1=42 |
asm/softops.s |
ROR/LBU → r1=r6=129 |
asm/io.s |
IN/OUT keyboard echo |
asm/kb_mmio.s |
LW MMIO kb data/status |

