Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EML CPU

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.


ISA

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.


Memory layout

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

Build

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 clean

Usage

Assembler

build/asm input.asm -o output.bin

Emulator

build/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.

C compiler

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.c

Full 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.bin

Runtime library

src/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.


Known limitations

  • __floor is 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).

About

Using EML to shift complexity from silicon to software (dumb idea)

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages