A custom 32-bit CPU architecture built on two native instructions — EML and NAND — with a complete toolchain: emulator, assembler, and C compiler.
The key insight: EML Rd, Rs1, Rs2 computes Rd = exp(Rs1) - log(Rs2). Combined with NAND, all arithmetic (add, subtract, multiply, divide, comparison, shift) can be derived from first principles.
Instruction format (32-bit word):
[31:29] opcode [28:25] Rd [24:21] Rs1 [20:17] Rs2 [16:0] imm17
| Opcode | Mnemonic | Operation |
|---|---|---|
| 0 | EML Rd, Rs1, Rs2 |
Rd.f = exp(Rs1.f) − log(Rs2.f) |
| 1 | NAND Rd, Rs1, Rs2 |
Rd.i = ~(Rs1.i & Rs2.i) |
| 2 | LOAD Rd, Rs1, imm17 |
Rd = mem[round(Rs1.f) + imm17] |
| 3 | STORE Rd, Rs1, imm17 |
mem[round(Rs1.f) + imm17] = Rd |
| 4 | LI Rd, imm21 |
Rd = sign_extend(imm21) |
| 5 | BZ Rd, Rs1, imm17 |
if Rd.i == 0: PC = Rs1.i + imm17 |
| 7 | HALT |
halt execution |
Registers: R0–R15 (64-bit, interpreted as double or uint64_t per instruction). R0 hardwired to 0. R1 = return value / first arg. R14 = SP. R15 = LR.
The assembler also supports pseudo-instructions: MOV, CALL, RET, JMP.
| Range | Contents |
|---|---|
0x0000–0x000F |
Constant pool (0.0, 1.0, -1.0, 2.0, 0.5, e, ln2, floor magic, …) |
0x0010–0x001A |
Runtime function pointer table (__add, __sub, __mul, …) |
0x0200 |
Code load base |
0xFFFE |
Stack top |
Requires gcc and libm.
make # builds build/emu, build/asm, and build/cc
make test # assembles tests/asm/smoke.asm and runs it (expected exit: 42)
make cleanbuild/asm input.asm -o output.binbuild/emu [options] program.bin| Flag | Effect |
|---|---|
-d |
dump all register state after halt |
-t |
trace every instruction (PC + SP per cycle) |
-m ADDR |
print mem[ADDR] after halt (hex address) |
-n N |
stop after N cycles |
Exit code equals int(R1.f) at halt.
The C compiler lives in src/compiler/ and is built by make all as build/cc. To build it
manually (note ast.c is required):
gcc -Wall -Wextra -std=c11 -O2 -Isrc/compiler -o build/cc \
src/compiler/lexer.c src/compiler/ast.c src/compiler/parser.c \
src/compiler/codegen.c src/compiler/main.cFull C → binary pipeline (requires the stdlib):
build/cc input.c -o build/input.asm
# prepend startup + runtime, then assemble:
cat src/stdlib/startup.asm src/stdlib/runtime.asm build/input.asm > build/combined.asm
build/asm build/combined.asm -o build/input.bin
build/emu build/input.binsrc/stdlib/runtime.asm implements the full arithmetic suite from EML and NAND:
- Arithmetic:
__add,__sub,__mul,__div,__neg,__abs,__exp,__ln - Comparisons:
__lt,__le,__gt,__ge,__eq,__ne - Bitwise:
__band,__bor,__bxor,__bnot,__not - Shifts:
__shl,__shr - Other:
__floor,__mod
Calling convention: args in R1, R2; result in R1. Caller-saved: R1–R10, R15. Callee-saved: R11–R13, R14 (SP).
src/stdlib/startup.asm provides _start, which saves the emulator's halt sentinel, calls main, and returns.
Because every operation is software, each one costs many native instructions
(e.g. a * b is ~51). To measure the dynamic instruction cost of each routine,
run tools/measure_cycles.sh. See docs/measuring-instruction-cost.md
for the method and results.
__flooris only correct for integer inputs; negative non-integers are off by 1.- Branch and immediate ranges are limited: imm17 (signed 17-bit), imm21 (signed 21-bit).