P8X — Hand-Built 8-Bit TTL CPU¶
A from-scratch 8-bit CPU built from ~130 74HCT logic chips on an 8-slot DIN41612 backplane. Fully microcoded; the microcode ROM images burned to the EPROMs are the same images the emulator interprets.
The machine now exists twice: as the TTL card set, and as an FPGA
implementation of the same microarchitecture that boots the same
unmodified monitor, OS and toolchain. Both run the microcode from
microcode/genucode.py, and the C emulator is the golden reference for both.
The FPGA build also carries a PGC-class graphics engine (480x272 RGB565 panel, hardware 3D transforms, command lists, a fabric-parsed graphics language modeled on the Matrox PG-640A) — see the theory of operation and the graphics programmer's guide.
New to the abbreviations and signal names? See GLOSSARY.md.
Who made it: I designed P8X, and it runs on an FPGA. Claude, Anthropic's AI, has been directly involved in its design, coding and documentation. — Ken Rother
Architecture¶
- 8-bit data bus, 16-bit address bus
- 4 × 16-bit pointer registers (74169 up/down counters): P0 = PC, P1/P2 = general-purpose, P3 = stack pointer (empty-descending). The address bus is always driven by one of these — no separate MAR.
- Registers: A, B (ALU operands), T/T2 (hidden microcode temporaries), FLAGS (C, Z, N, V)
- ALU: 2 × 74181 + 74182 carry-lookahead, with a post-ALU shifter
- Microcoded control: 4 × 28C64 EEPROMs; ROM address = IR | step<<8 | cond<<12; 143 opcodes defined in
microcode/genucode.py(256 encodings available) - Memory map (rev E):
$0000–$17FFROM (6 KB; shrunk from 8 KB 2026-09-14),$1800–$FEFFRAM (2× 62256; $1800–$1FFF is a scratch island),$FF00–$FFFFI/O (every data address is single-sourced ingenerators/gen_memmap.py→memmap.inc/.h/.py; commands pull the scratch/graphics/TPA-base symbols via//#use mem)
Cards¶
The TTL machine is six core cards plus a PS/2 input card on the backplane, and a
bus test card for bring-up. Every board is designed and routed in KiCad (4-layer;
the plug-in cards are 280 × 140 mm) with orderable Gerbers in hardware/<board>/kicad/;
none has been fabricated yet. See hardware/KICAD-BOARDS.md
and hardware/RECONCILIATION.md (build readiness).
| Card | Function |
|---|---|
| Control / Microcode | Clock, reset, sequencer, microcode EPROMs, IR, condition mux, front-panel |
| Register Bank | P0–P3 16-bit pointer registers, address bus drivers |
| ALU | A, B, T, T2 registers; 74181 ALU; shifter; FLAGS |
| Memory | 28C256 EEPROM (6 KB ROM window) + 2× 62256 SRAM, address decode (rev F) |
| I/O | Switches, LEDs, bus monitor, two 6850 ACIAs (RS-232, two DB9s) |
| CF-IDE | Two CompactFlash drives in 8-bit True IDE mode, at $FF10–$FF17 and $FF18–$FF1F |
| PS/2 | Keyboard + mouse ports at $FF58–$FF5F; an ATmega1284P behind a latch bridge |
| Bus test | Bring-up tool: a Raspberry Pi Pico drives the bus one microcycle at a time over USB |
The cards plug into a passive 8-slot backplane over a 96-pin DIN 41612 bus (rev C2).
The Eagle CAD of the first board generation is frozen (rev E) in each board's
eagle-deprecated/ directory.
Toolchain¶
| Tool | Location | Purpose |
|---|---|---|
microcode/genucode.py |
microcode/ |
Microcode generator → u0–u3.bin EPROM images |
assembler/p8xasm.py |
assembler/ |
Two-pass assembler, shares opcode table with genucode.py |
emulator/p8xemu.c |
emulator/ |
Cycle-accurate emulator, interprets the same u0–u3.bin images |
firmware/p8xmon.asm |
firmware/ |
ROM monitor (E/D/I/F/B/G/? commands) + BIOS jump table at $0100 |
os/p8xos.asm |
os/ |
P8X/OS, RAM-resident disk OS booted from CF (guide) |
tools/p8xfs.py |
tools/ |
Host-side P8XFS disk-image tool (create/boot/put/get/ls) |
basic/p8xbasic.asm |
basic/ |
BASIC interpreter — disk-bootable or run-from-OS builds (guide) |
apps/p8xedit.asm, apps/p8xasm.asm, apps/p8xcc.asm |
apps/ |
On-target toolchain: line editor + native two-pass assembler + native C compiler (cc), as /bin programs (guide) |
compiler/p8cc.py |
compiler/ |
C cross-compiler (subset) → P8X asm → RUNnable .bin (guide) |
generators/gen_p8xopc.py |
generators/ |
Opcode table for the native assembler, generated from genucode.OPC |
generators/gen_eagle.py |
generators/ |
The canonical board netlists (CARDS, busnet()); its Eagle output is frozen at rev E |
generators/gen_kicad.py, build.sh |
generators/ |
KiCad boards from those netlists: placement, Freerouting, Gerbers, and the ERC/DRC readiness check |
Generators are canon. Never hand-edit board files (.kicad_pcb, and the frozen Eagle .sch/.brd) or ROM binaries — they are build artifacts. Edit the generator and regenerate. See generators/README.md for what each script does and how to run it.
Quick Start¶
# Build the emulator and regenerate microcode images
cd emulator && make
# Run the smoke tests (message print, JSR/RTS round-trip, branch countdown)
make test
# Rebuild a KiCad board: placement -> Freerouting -> Gerbers + renders -> the
# ERC/gate-sim/DRC/keepout/fab readiness check (needs KiCad 10 and a Freerouting
# jar; `all` builds every board). The netlists come from generators/gen_eagle.py.
cd ..
sh generators/build.sh memory-card
sh generators/check_card.sh memory-card # the readiness check alone
# Reference PDFs (run from anywhere):
cd hardware
python3 ../generators/gen_bus_pdf.py # bus definition PDF (hardware/backplane/)
python3 ../microcode/gen_progguide.py # programmer's guide (-> docs/)
EEPROM / programmer images¶
Both build paths emit Intel HEX alongside the raw .bin, for loading into an
EEPROM programmer:
- Microcode —
microcode/genucode.pywritesu0–u3.bin(what the emulator and tests load); the matching Intel HEX for the four 28C64 control-store EPROMs is produced intorom/bymake rom(see below). - Program ROM — the assembled monitor + BIOS for the 28C256 at
$0000(about 4.9 KB of the 6 KB window; BASIC is no longer ROM-resident).make rombuilds it intorom/. - Any other binary —
python3 tools/bin2hex.py in.bin out.hex [base](e.g. a monitor built directly withp8xasm.py).
For a ready-to-burn set at fixed paths, run cd emulator && make rom (or
sh tools/build_rom.sh). It refreshes the four control-store EPROMs in
microcode/ and writes the program ROM to rom/p8x-prog-rom.{bin,hex}. Both
are committed; see rom/README.md for the chip map.
Documentation¶
The documents below are also built into the project website, p8x.cottageworker.com (website/, MkDocs; published on every push to main).
| Document | Description |
|---|---|
| hardware/backplane/p8x-bus-definition.md | Authoritative 96-pin bus pinout, signal descriptions, DOE/DLD encoding, microcode word layout |
| hardware/backplane/p8x-backplane-design.md | PCB stackup, termination analysis, BOM |
| docs/p8x-card-standards.md | Design rules that apply to every plug-in card |
| docs/p8x-system-design.md | System and card-by-card architecture reference |
| hardware/cf-card/p8x-cf-os-design.md | CF-IDE hardware + P8X/OS design |
| hardware/cf-card/p8xfs-v2-hierarchical.md | P8XFS v2 hierarchical filesystem spec |
| docs/p8x-programmers-guide.pdf | Generated instruction set reference |
| basic/p8x-basic-guide.md | P8X BASIC language reference (statements, expressions, graphics, examples) |
| fpga/README.md | FPGA build: milestones, getting started, both paths |
| fpga/docs/architecture.md | FPGA module hierarchy, peripheral map, graphics, co-sim spec |
| fpga/rtl/README.md | What is shared vs sim-only, and the cen / sc_en contracts |
| fpga/sim/README.md | How the co-sim trace-diff works, and the graphics frame diff |
| BACKLOG.md | Live work only: NEXT / IDEAS / VERIFY / WONT-DO |
| BACKLOG-DONE.md | Completed work + the project log |
Per-card guides¶
Each board has its own directory under hardware/ holding everything about it —
the KiCad board and its Gerbers, placement PDF and renders (kicad/), a README
explaining how the circuit works chip by chip, any board-specific design docs, and
the frozen Eagle files (eagle-deprecated/):
| Card | Directory |
|---|---|
| Control / Microcode | hardware/control-card/ |
| Register Bank | hardware/regbank-card/ |
| ALU | hardware/alu-card/ |
| Memory | hardware/memory-card/ |
| I/O | hardware/io-card/ |
| CF-IDE | hardware/cf-card/ |
| PS/2 | hardware/ps2-card/ |
| Bus test | hardware/bustest-card/ |
| Backplane | hardware/backplane/ |
FPGA implementation¶
A standalone FPGA build on a Sipeed Tang Nano 20K (Gowin GW2AR-18) runs the whole machine — CPU, memory, ACIA console, microSD disk, and a 4.3" 480x272 graphics panel — from one chip and a USB cable. It is a parallel track to the TTL build, not a replacement: same horizontal microcode word, same sequencer, same pointer/address model, so the monitor, OS, BASIC, C compiler and assembler run unmodified.
| Milestone | State |
|---|---|
| 0 First light (UART echo + heartbeat) | done |
| 1 CPU core in simulation, all 88 opcodes of the time | done |
| 2 ACIA + driven console in simulation | done |
| 3 Core on real hardware, full 64K map | done |
| 4 microSD disk — P8X/OS boots from card | done |
| 5 Clock-up + IRQ | next |
| 6 Graphics: 480x272 panel + drawing engine | done |
As built: 9 MHz effective (27 MHz fabric, three phases per microcycle). The CPU-only build is 40/46 block RAMs at ~48 MHz Fmax; adding the graphics panel takes it to 44/46 and 13288/20736 LUT4, with Fmax measured at 38.8 MHz — still four times the clock it runs at. P8X is programmed into the board's flash, so it comes up standalone on power.
Graphics (build.sh lcd) adds the panel's native 480×272 framebuffer in
RGB565 direct colour — a pixel IS its colour, 65,536 of them — living in
the Tang Nano's in-package SDRAM behind a streaming controller, with a drawing
engine driven by BASIC in one window-space coordinate system (y up, the
PGC's own): COLOR r,g,b (or one packed value), CLS, PIXELW, LINE,
BOX, CIRCLE (a second radius gives an ellipse), IMAGE (draws a
P8I picture file — tools/p8img.py converts anything into one), the
PIXELR(x,y) and RGB(r,g,b) functions — plus the full PGC graphics
language as native statements (MOVE/DRAW/POLY/RECT, matrices, 3D,
command lists; see man basic). Text is the PGC's own stroke TEXT,
drawn card-side from the font the OS streams from /FONT.GL at boot
(GTEXT and its software rasterizer retired 2026-09-01). The drawing
statements emit that language; the engine lives in the device, so a
filled box costs the same handful of instructions as an empty one.
There is one geometry and no modes or palette to manage.
The same device is modelled in p8xemu, and the two are byte-compared frame
by frame.
3D (stages 7–8, fpga/tang-nano-20k/sdram/STAGE*.md): a wireframe
pipeline available three ways from C — all in software (//#use gfx +
//#use g3d), accelerated by the MDU (a memory-mapped hardware
multiply-divide unit at $FF30), or fully in fabric via the geometry
engine ($FF40): edge lists in SDRAM, an S7.8 matrix, one command to
transform/clip/project/draw, and page-flipped double buffering. The same
program picks the fastest fitted path at runtime; cube on the shipped disk
demonstrates all of them (man cube, man g3d).
Verification is the point. Every milestone is "make the RTL match the
emulator": the same program runs on both and their per-cycle architectural state
is diffed, so a divergence is a bug with an exact microcycle and signal rather
than a mystery. fpga/sim/isa_test.asm drives the original 88 opcodes through that diff;
the 55 added since are microcode only (no new hardware), and the RTL runs them from
the same images.
fpga/sim/run.sh 60000 isa_test.asm # co-sim, the original 88 opcodes
fpga/sim/console.sh "" os/run-disk.img # interactive console on the RTL
fpga/tang-nano-20k/build.sh cpu load # build + program the board
Status¶
- Emulator working: 143 opcodes, ACIA on stdin/stdout, CF-IDE disk model (
-c <img>), interactive I/O card (switches-s, LED trace-L), verified against microcode images - Assembler working: two-pass, full expression support, shares opcode table with microcode generator
- KiCad boards designed and routed for the six CPU cards, the PS/2 card, the bus test card and the backplane (9 boards, 0 unconnected, Gerbers in each
kicad/); none fabricated yet. The Eagle files of the first generation are frozen ineagle-deprecated/. (The standalone LED test card was a CAD-workflow trial, never built — deprecated and moved tohardware/deprecated/led-card/; its I/O address$FF0Cis now free.) - ROM monitor boots in the emulator; its filesystem hooks (
I/F/B) run end to end against a CF image (make test-cf) - P8X/OS v1.0 — full shell over flat and hierarchical (P8XFS v2) volumes. Built-in commands:
cd/mkdir/rmdir/load/run/save/del/path/pack/fsck/format/mount/umount/help/exit/man/sh/make/bootload(makebuilds a target from a CWDMakefile;bootload fileinstalls a freshly-built OS image into the boot sectors so the nextexit+Bruns it — the on-target OS-update step, closing the self-hosting loop) (the minimal-kernel split moved the pure-viewer/memory commands to/bin, includingdump/dep— onlypack/fsckremain resident because they mutate/scan the filesystem). Dual CompactFlash — a second card is mounted at/d1in one unified namespace (drive 0 is the root), so ordinary paths reach it with drive-unaware commands:cd /d1,cat /d1/NOTES,grep x /d1/SRC/*.C, cross-mountcp /d1/A /B. A single mount redirect inFRESOLVE/RV_STARTdoes the routing;cp -r /d1/dir /dirrecursively copies a subtree across the mount (card provisioning), creating directories via theSYS_MKDIRsyscall. Userland commands in/bin(written in C, run by bare name via implicit RUN + a/binsearch PATH, or explicitrun):dir/pwd/tree/cat/wc/grep/cp/mv/head/tail/more/sort/uniq/sed/awk/find/diff/cmp/vi/touch/man/dump/dep/examine/disasm(dir README.TXTlists a file /dir R*a glob,dir -R,cp -r, a VT100viscreen editor,man <cmd>reading/man,awk '{print $2}'field processing,examine= interactive memory examine/modify like the monitor'sE, etc.) — see os/commands/. Path resolution + CWD-path prompt; I/O redirection (</>) and two-stage pipes (a | b); line editing (backspace/DEL, Ctrl-D EOF, up/down-arrow command history — an 8-line RAM ring — and Tab autocomplete of commands/paths with common-prefix fill + a match list on the second Tab);packcompacts the directory tree andfsckchecks integrity on-target; host-sidep8xfs.pybuilds (--v2), navigates, andfscks images (make test-os) - BASIC builds two ways from one source: disk-bootable (
B) and a run-from-OS TPA program (run BASIC.bin) —make test-basic(ROM-resident BASIC was removed to reclaim ROM space; the old standalone$0000build was retired in 2026-08 when BASIC's console I/O moved onto the BIOS, which needs the monitor resident) - On-target toolchain: EDIT (line editor) + ASM (native two-pass assembler) + CC (a from-scratch C compiler written in asm) as
/binprograms — edit → compile (cc x.c >x.asm) → assemble → run a program entirely on the machine; ASM output is byte-identical to the host assembler across the whole opcode table (make test-os, see apps/). Rebuild any command on-target — every command's source rides along under/src/commands/{c,asm}(plus sharedlib_*helpers), each dir carrying a realMakefile(target: deps+ TAB recipe, withall/clean/installtargets). Themakebuilt-in reads the CWDMakefile, resolves prerequisites depth-first (shared deps built once), and drivescc→asm(orasmalone):cd /src/commands/c && make pwdrebuilds one command,make allthe whole dir intobin/,make installpublishes them over/bin,make cleanremoves the build outputs — see os/commands/. (P8X shell scripts end in.sh;makeuses proper Makefiles.) - Native C compiler — Milestone B achieved.
apps/p8xcc.asm(/bin/cc) is a from-scratch, single-pass C compiler written directly in assembly, small enough to compile C entirely on the machine (front and back end) where the optimizingp8cc.ccodegen — ~82 KB, larger than the whole 64 KB address space — never could. Through v0.28 it covers: functions, direct and mutual recursion, pointers + pass-by-reference,int/chararrays with[]and decay, structs (./->), globals, the full operator set (+ - * / % << >> & ^ | && || ?:,++/--/+=/-=, comparisons, unary- ! * &), hex/char/string literals with escapes,//+/* */comments, a recursive//#usepreprocessor (splices/lib/lib_*.c) plus object-like//#definemacros (e.g.//#use abinames the BIOS/OS addresses so a command writesbios(FOPEN, RDBUF, 0)), and theputchar/puts/getchar/peek/poke/argstr/biosbuiltins. It compiles real OS command source:pwd.c→cc→asm→ runs correctly on-target. Known gaps are listed under "cc — KNOWN LIMITATIONS" inBACKLOG-DONE.md(with the Milestone A/B record). - Host C compiler —
compiler/p8cc.py(the primary build tool: every/bincommand is compiled with it) pluscompiler/p8cc.c, the same compiler rewritten in its own subset that self-compiles ("small C in small C", Milestone A). Full subset incl.struct/union, global initializers, and the operators above (make test-c, host-vs-self differentialc_selfhost_test, see compiler/). - BIOS file API: byte streams (
FOPEN/FGETB,FWOPEN/FPUTB/FCLOSE), path resolution into subdirectories (FRESOLVE), name formatting (FNORM), and directory iteration (FOPENDIR/FNEXT) — the assembler rides on the streams and self-hosts (make test-cf) - FPGA (Tang Nano 20K): the same microarchitecture in Verilog, verified against the emulator cycle-for-cycle across the original 88 opcodes, then run on real hardware — monitor over USB serial, full 64K map, and P8X/OS booting from a microSD with the whole
/bintoolchain. The board wrote its own disk:fpga/tang-nano-20k/tools/imgload.asmstreams a P8XFS image over the console and writes it withCFWRITE, so no host root or card reader is needed. See fpga/ - Next: multi-stage pipes (
a | b | c); open-by-name as one syscall (SYS_OPEN); the IRQ-controller hardware card; hardware bring-up (DIN 41612 footprint check against the physical connectors, order the backplane first); FPGA milestone 5 (clock-up + IRQ). Current state in docs/p8x-status.md, the working list in BACKLOG.md