Trinitite is a tiny operating system for Raspberry Pi 4/5, written in Forth with a C memory arena. It boots directly into a Forth/Nock REPL, with no userspace/kernel distinction. The kernel provides a minimal set of primitives for working with nouns, evaluating Nock formulas, and loading jammed atoms from QEMU's file-loader device. The REPL supports building and evaluating arbitrary Nock subject/formula pairs, either by hand or by loading pre-jammed pairs via PILL.
Trinitite is an experimental platform into which I jammed a lot of half-baked ideas, like live jet loading (as Forth words), a hash-based indirect atom arena, variable kernel shapes, and a Forth-based implementation of a Nock standard library. It is very much a dynamic work in progress.
makeCross-compile for CI (Ubuntu):
make CC=aarch64-linux-gnu-gcc LD=aarch64-linux-gnu-ld \
OBJCOPY=aarch64-linux-gnu-objcopymake run # boots into the Forth REPL
make debug # boots under GDB
make test # run regression suite (376 tests)Tests and benchmarks are available.
The kernel boots into a bare-metal Forth REPL. Nouns are 64-bit tagged words.
After Phase 5d, small integers are already valid direct atoms — direct(42) == 42 —
so no conversion is needed:
| Word | Stack effect | Description |
|---|---|---|
CONS |
( head tail -- cell ) |
allocate a cell |
CAR |
( cell -- head ) |
head of a cell |
CDR |
( cell -- tail ) |
tail of a cell |
ATOM? |
( noun -- flag ) |
-1 if atom, 0 if cell |
NOCK |
( subject formula -- result ) |
evaluate Nock |
JAM |
( noun -- atom ) |
serialize noun to atom |
CUE |
( atom -- noun ) |
deserialize atom to noun |
PILL |
( -- atom ) |
load jammed atom from QEMU file loader |
. |
( n -- ) |
print top of stack as 16-digit hex |
N. |
( noun -- ) |
print atom as decimal |
>NOUN |
( n -- noun ) |
clears bit 63; no-op for values < 2⁶³ |
NOUN> |
( noun -- n ) |
clears bit 63; no-op for values < 2⁶³ |
See the Forth documentation for more details on the Forth environment and available words.
NOCK consumes ( subject formula -- result ). Build nouns with plain integers
and CONS. Nock cells are right-associative: [a b c] = [a [b c]], so push
a, push b, push c, then CONS CONS.
\ *[42 [0 1]] = 42 (slot 1 of atom)
42 0 1 CONS NOCK .
\ *[42 [4 [0 1]]] = 43 (increment)
42 4 0 1 CONS CONS NOCK N.
\ *[0 [1 [1 2]]] = [1 2] (quote a cell), print head
0 1 1 2 CONS CONS NOCK CAR N.General rule: work right-to-left — innermost subterms first, then CONS
outward.
For formulas too large to type, jam [subject formula] externally and load it
via QEMU's file-loader device.
Pill file format (little-endian):
- bytes 0–7:
uint64_t= byte count of jam data that follows - bytes 8+: raw jam bytes (little-endian bignum)
Create a pill from a raw jam file:
import sys, struct
d = open('formula.jam', 'rb').read()
open('pill.bin', 'wb').write(struct.pack('<Q', len(d)) + d)Run with the pill loaded (loads at physical address 0x10000000):
make run-pill PILL=pill.bin
Decode and evaluate in the REPL:
PILL CUE \ decode the [subject formula] pair noun
DUP CAR SWAP CDR \ split: subject below, formula on top
NOCK \ evaluateOr define a helper word once per session:
: NOCK-PAIR DUP CAR SWAP CDR NOCK ;
PILL CUE NOCK-PAIR NOUN> .PILL returns atom 0 if no pill was loaded (QEMU zeroes RAM at startup).
| Item | Notes |
|---|---|
| Raspberry Pi 4 Model B | 2 GB or 4 GB both work |
| microSD card (≥ 1 GB) | FAT32 formatted |
| USB-to-3.3V serial adapter | CP2102, FT232, CH340, etc. — 3.3 V logic, not 5 V |
| 3× female-to-female jumper wires | |
| USB power supply for the Pi |
Format the card FAT32. Copy three RPi firmware files to the root:
bootcode.bin
start.elf
fixup.dat
Get them from the raspberrypi/firmware repo (just those three files — you do not need the kernel from that repo).
Create config.txt in the root:
arm_64bit=1
dtoverlay=disable-bt
enable_uart=1Copy the kernel:
make
cp kernel8.img /Volumes/<your-sd>/Eject the card and insert it into the Pi.
The serial console is on UART0 (PL011), exposed on the 40-pin header. Use 3.3 V logic only — 5 V will damage the Pi.
Pi GPIO 14 (TXD, pin 8) → RX on serial adapter
Pi GPIO 15 (RXD, pin 10) → TX on serial adapter
Pi GND (pin 9) → GND on serial adapter
Do not connect the adapter's VCC/3.3V/5V pin to the Pi. Power the Pi from its own USB-C port.
Pin 9 is one of eight GND pins on the header — if it is occupied, pins 6, 14, 20, 25, 30, 34, or 39 all work.
40-pin header (USB ports facing you, header at top-right):
left col right col
pin 1 (3V3) [ ][ ] (5V) pin 2
pin 3 (SDA) [ ][ ] (5V) pin 4
pin 5 (SCL) [ ][ ] (GND) pin 6
pin 7 (GPIO4) [ ][ ] (TXD) ← pin 8 → adapter RX
pin 9 (GND) ← [ ][ ] (RXD) ← pin 10 → adapter TX
↑
adapter GND
Find the device:
ls /dev/cu.usbserial-* /dev/cu.usbmodem* 2>/dev/nullConnect at 115200 8N1:
screen /dev/cu.usbserial-XXXX 115200Power on the Pi. Within a second or two you should see:
Trinitite v0.1 AArch64 Forth
>
That is the Forth REPL. Type Forth words and press Enter.
To quit screen: Ctrl-A \.
After editing source, rebuild and copy:
make
cp kernel8.img /Volumes/<your-sd>/
# power-cycle the PiOr with TFTP netboot:
make deploy TFTP_ROOT=/private/tftpboot
# reset the PiNo output at all — check wiring polarity (TX→RX, RX→TX), confirm 3.3 V adapter,
confirm dtoverlay=disable-bt and enable_uart=1 in config.txt.
Garbage characters — baud rate mismatch; set terminal to exactly 115200.
Hangs after a few lines — flow control issue; disable RTS/CTS in your terminal.
ok appears but input is ignored — check the TX wire (Pi RXD ← adapter TX).
First public release ("v0.1-alpha", not properly versioned yet) on ~2026.03.13.
© 2026 Sigilante, released under the MIT License
