spec v0.12.0
Architecture specification
Complete enough that a second, independent emulator could be built from this page alone. Everything here is what the playground actually executes — and the word's width is a design choice, so the whole reference reshapes with the selector below.
Number system
A trit holds one of three values, written - for -1, 0 for 0, and + for +1. Trits are written most significant first, so the digit at position i from the right carries weight 3i.
- Zero is all-zero trits and has exactly one representation.
- Negation is trit-wise inversion; there is no sign trit and no two's-complement rule.
- The sign of a value is the sign of its most significant non-zero trit.
- Arithmetic that leaves the word range wraps within 729 states and sets the overflow flag.
| Decimal | Balanced ternary word |
|---|---|
| 1 | 00000+ |
| -1 | 00000- |
| 8 | 000+0- |
| -8 | 000-0+ |
| 363 | +++++0 |
| 364 | ++++++ |
| -364 | ------ |
word dial
Machine word
Registers
8 general-purpose registers plus three special registers. R0 is hardwired to zero: writes to it are discarded.
SIGN is a single trit rather than a set of boolean condition bits: arithmetic sets it to the sign of the result, and CMP sets it to the sign of the difference. Conditional branches read it directly — JLT on -, JEQ on 0, JGT on +.
Flags contract (v0.2): a result-producing instruction (MOV, LOAD, POP, and every arithmetic/logic op) sets SIGN to the sign of the stored value and OVF to whether the raw mathematical result wrapped. CMP sets SIGN from the unwrapped difference — comparisons are exact across the full word range — and leaves OVF untouched. Everything else (STORE, PUSH, branches, CALL/RET, NOP, HALT, IN/OUT) leaves both flags untouched.
Memory
Input/output is a single buffered channel: IN Rd consumes the next value from the input buffer (an IN on an empty buffer halts with an error), and OUT Rs appends the register value to the output stream. The buffer holds whole words; the input buffer is configuration and survives a reset, the output stream is cleared with the machine.
Instruction encoding
Fixed width: every instruction is exactly 3 words.
| Word | Contents |
|---|---|
| PC + 0 | opcode |
| PC + 1 | operand A — register number, immediate, or address |
| PC + 2 | operand B — register number, immediate, or address |
The opcode fixes the operand kinds, so ADD Rd, Rs and ADD Rd, value are separate opcodes. Unused operand words are 0. An opcode with no definition raises an invalid-instruction error and halts the machine.
Division is round-to-nearest with ties away from zero (quotient digits are the nearest trit), and rem satisfies a = div(a, b) * b + rem(a, b) with |rem| ≤ |b|/2. SHL shifts one trit place left (×3, wrapping sets OVF); SHR shifts one place right (round-to-nearest of a/3 — a dropped least-significant trit is never a tie). LAND and LOR apply min and max to the trits of the two words independently.
Assembly syntax
A .word line emits raw words (literals or labels) into the program image and may sit anywhere in the source, but execution never flows across a data region — place tables after HALT, or jump over them.
The assembler reports unknown instructions, invalid registers, malformed literals, wrong operand counts, invalid operand types, duplicate labels, unresolved labels, and out-of-range values — each with its source line.
Instruction set
Click any row to open it in the live experiment bench below — an ELI5, the constraints, and one instruction actually running on the machine with values you can change. Need it on paper? the one-page cheat sheet prints the whole table.
Machine control
| Syntax | Opcode | Encoded | Effect |
|---|---|---|---|
| NOP | 0 | No state change. PC += 3. | |
| HALT | 1 | Sets HALT flag; execution stops. |
Data movement
| Syntax | Opcode | Encoded | Effect |
|---|---|---|---|
| MOV Rd, Rs | 2 | Rd <- Rs. Updates SIGN. | |
| MOV Rd, value | 3 | Rd <- immediate. Updates SIGN. |
Memory access
| Syntax | Opcode | Encoded | Effect |
|---|---|---|---|
| LOAD Rd, [addr] | 4 | Rd <- mem[addr]. Updates SIGN. | |
| LOAD Rd, [Rs] | 5 | Rd <- mem[Rs]. Updates SIGN. | |
| STORE [addr], Rs | 6 | mem[addr] <- Rs. | |
| STORE [Rd], Rs | 7 | mem[Rd] <- Rs. |
Arithmetic
| Syntax | Opcode | Encoded | Effect |
|---|---|---|---|
| ADD Rd, Rs | 8 | Rd <- Rd + Rs. Updates SIGN, OVF. | |
| ADD Rd, value | 9 | Rd <- Rd + immediate. Updates SIGN, OVF. | |
| SUB Rd, Rs | 10 | Rd <- Rd - Rs. Updates SIGN, OVF. | |
| SUB Rd, value | 11 | Rd <- Rd - immediate. Updates SIGN, OVF. | |
| MUL Rd, Rs | 12 | Rd <- Rd * Rs. Updates SIGN, OVF. | |
| MUL Rd, value | 13 | Rd <- Rd * immediate. Updates SIGN, OVF. | |
| NEG Rd | 14 | Rd <- -Rd (trit-wise inversion). Updates SIGN. | |
| DIV Rd, Rs | 26 | Rd <- round(Rd / Rs), ties away from zero. Updates SIGN, OVF. | |
| DIV Rd, value | 27 | Rd <- round(Rd / immediate), ties away from zero. Updates SIGN, OVF. | |
| REM Rd, Rs | 28 | Rd <- Rd - DIV(Rd, Rs) * Rs. Updates SIGN, OVF. | |
| REM Rd, value | 29 | Rd <- Rd - DIV(Rd, imm) * imm. Updates SIGN, OVF. | |
| SHL Rd, Rs | 30 | Rd <- Rs * 3 (shift one trit place left). Updates SIGN, OVF. | |
| SHR Rd, Rs | 31 | Rd <- round(Rs / 3) (shift one trit place right). Updates SIGN, OVF. |
Comparison
| Syntax | Opcode | Encoded | Effect |
|---|---|---|---|
| CMP Rd, Rs | 15 | SIGN <- sign(Rd - Rs), raw difference. OVF untouched. | |
| CMP Rd, value | 16 | SIGN <- sign(Rd - immediate), raw difference. OVF untouched. |
Control flow
| Syntax | Opcode | Encoded | Effect |
|---|---|---|---|
| JMP label | 17 | PC <- addr. | |
| JEQ label | 18 | PC <- addr if SIGN = 0. | |
| JNE label | 19 | PC <- addr if SIGN /= 0. | |
| JLT label | 20 | PC <- addr if SIGN = -. | |
| JGT label | 21 | PC <- addr if SIGN = +. | |
| CALL label | 22 | Push return address, PC <- addr. | |
| RET | 23 | Pop return address into PC. |
Stack
| Syntax | Opcode | Encoded | Effect |
|---|---|---|---|
| PUSH Rs | 24 | mem[SP] <- Rs, SP -= 1. | |
| POP Rd | 25 | SP += 1, Rd <- mem[SP]. |
Trit-wise logic
| Syntax | Opcode | Encoded | Effect |
|---|---|---|---|
| LAND Rd, Rs | 32 | Rd <- trit-wise min(Rd, Rs). Updates SIGN, OVF. | |
| LOR Rd, Rs | 33 | Rd <- trit-wise max(Rd, Rs). Updates SIGN, OVF. |
Input / output
| Syntax | Opcode | Encoded | Effect |
|---|---|---|---|
| IN Rd | 34 | Rd <- next input value. Errors if the input buffer is empty. | |
| OUT Rs | 35 | Output <- Rd. |