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.

specified wordstates 729 · range ±364

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.
DecimalBalanced ternary word
100000+
-100000-
8000+0-
-8000-0+
363+++++0
364++++++
-364------

word dial

6 trits · set each switch to - 0 +
243
81
27
9
3
1
reads000----9 -3 -1 =-13

Machine word

trits per word6 (one "tryte")
distinct values729
integer range-364 .. 364
register width6 trits
memory unit1 word
instruction width3 words (18 trits)
overflowwrap-around, OVF flag set

Registers

8 general-purpose registers plus three special registers. R0 is hardwired to zero: writes to it are discarded.

R0constant 0, immutable
R1 .. R7general purpose, reset to 0
PCprogram counter, reset to 0, advances by 3
SPstack pointer, reset to 364, grows downward
FLAGSSIGN (-, 0, +), OVF, HALT

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

size729 words
addresses-364 .. 364 (balanced, like any word)
reset stateevery word is 0
program originaddress 0, growing upward
stackfrom 364 downward
out-of-range accessexecution error, machine halts
program imagefetching or branching outside it is an error
stack collisionpush, CALL, or RET into the image fails
data words.word emits raw words next to instructions

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.

WordContents
PC + 0opcode
PC + 1operand A — register number, immediate, or address
PC + 2operand 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

comment; to end of line
labelNAME: at the start of a line, case-insensitive
registerR0 .. R7 (names starting with R are reserved)
decimal literal42, -17
ternary literal+0- or 0t+0-
memory reference[100], [LABEL], [R2]
data directive.word 5, -3, TABLE
label as valueMOV R1, TABLE — the label's address

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

SyntaxOpcodeEncodedEffect
NOP0000000No state change. PC += 3.
HALT100000+Sets HALT flag; execution stops.

Data movement

SyntaxOpcodeEncodedEffect
MOV Rd, Rs20000+-Rd <- Rs. Updates SIGN.
MOV Rd, value30000+0Rd <- immediate. Updates SIGN.

Memory access

SyntaxOpcodeEncodedEffect
LOAD Rd, [addr]40000++Rd <- mem[addr]. Updates SIGN.
LOAD Rd, [Rs]5000+--Rd <- mem[Rs]. Updates SIGN.
STORE [addr], Rs6000+-0mem[addr] <- Rs.
STORE [Rd], Rs7000+-+mem[Rd] <- Rs.

Arithmetic

SyntaxOpcodeEncodedEffect
ADD Rd, Rs8000+0-Rd <- Rd + Rs. Updates SIGN, OVF.
ADD Rd, value9000+00Rd <- Rd + immediate. Updates SIGN, OVF.
SUB Rd, Rs10000+0+Rd <- Rd - Rs. Updates SIGN, OVF.
SUB Rd, value11000++-Rd <- Rd - immediate. Updates SIGN, OVF.
MUL Rd, Rs12000++0Rd <- Rd * Rs. Updates SIGN, OVF.
MUL Rd, value13000+++Rd <- Rd * immediate. Updates SIGN, OVF.
NEG Rd1400+---Rd <- -Rd (trit-wise inversion). Updates SIGN.
DIV Rd, Rs2600+00-Rd <- round(Rd / Rs), ties away from zero. Updates SIGN, OVF.
DIV Rd, value2700+000Rd <- round(Rd / immediate), ties away from zero. Updates SIGN, OVF.
REM Rd, Rs2800+00+Rd <- Rd - DIV(Rd, Rs) * Rs. Updates SIGN, OVF.
REM Rd, value2900+0+-Rd <- Rd - DIV(Rd, imm) * imm. Updates SIGN, OVF.
SHL Rd, Rs3000+0+0Rd <- Rs * 3 (shift one trit place left). Updates SIGN, OVF.
SHR Rd, Rs3100+0++Rd <- round(Rs / 3) (shift one trit place right). Updates SIGN, OVF.

Comparison

SyntaxOpcodeEncodedEffect
CMP Rd, Rs1500+--0SIGN <- sign(Rd - Rs), raw difference. OVF untouched.
CMP Rd, value1600+--+SIGN <- sign(Rd - immediate), raw difference. OVF untouched.

Control flow

SyntaxOpcodeEncodedEffect
JMP label1700+-0-PC <- addr.
JEQ label1800+-00PC <- addr if SIGN = 0.
JNE label1900+-0+PC <- addr if SIGN /= 0.
JLT label2000+-+-PC <- addr if SIGN = -.
JGT label2100+-+0PC <- addr if SIGN = +.
CALL label2200+-++Push return address, PC <- addr.
RET2300+0--Pop return address into PC.

Stack

SyntaxOpcodeEncodedEffect
PUSH Rs2400+0-0mem[SP] <- Rs, SP -= 1.
POP Rd2500+0-+SP += 1, Rd <- mem[SP].

Trit-wise logic

SyntaxOpcodeEncodedEffect
LAND Rd, Rs3200++--Rd <- trit-wise min(Rd, Rs). Updates SIGN, OVF.
LOR Rd, Rs3300++-0Rd <- trit-wise max(Rd, Rs). Updates SIGN, OVF.

Input / output

SyntaxOpcodeEncodedEffect
IN Rd3400++-+Rd <- next input value. Errors if the input buffer is empty.
OUT Rs3500++0-Output <- Rd.

select an instruction above to run it here — one yellow wire, the whole truth.