Blog
[the programs live in a ROM built from the spec; the wire carries requests, not .wasm binaries; one board, one night, six runs] A t27 spec in gHashTag/t27 (branch wasm-board, 351e3cdf4) became a Verilog core through t27c gen-verilog, passed through openXC7 onto an ALINX AX7203, and answered 134 of 134 requests exactly -- six runs in a row, 804 of 804. 21 opcodes of the WASM MVP set, a 14-program 101-byte ROM, i32.mul as a 32-clock shift-add microsequence; timing went from 18.57 to 70.41 MHz against a 68.7 bar at one percent of the LUTs -- every frequency here is a nextpnr estimate, not sign-off timing. The self-tested conformance harness caught a wire-offset bug that would have failed 76 checks against a perfectly working board.

Overnight on 12 October one t27 spec became a Verilog core, openXC7 placed it on an ALINX AX7203 (Artix-7 XC7A200T-2), and the chip answered every request exactly: 134 of 134, six runs in a row, 804 of 804 answers bit for bit. This post is the receipt trail -- what runs, what it took, and what it is not.
The bench session: flash, self-test, run
The core on the chip is fpga_wasm_board, generated by t27c gen-verilog from specs/fpga/wasm_board_ax7203.t27 in gHashTag/t27 (branch wasm-board, commit 351e3cdf4). It speaks UART at 1,144,744 baud, parses AA 55 frames, and interprets 21 opcodes of the WebAssembly MVP set. Fourteen programs -- 101 bytes of them -- sit in a ROM built from the same spec; the wire carries requests and answers, not binaries.
The bring-up top -- STARTUPE2 for the configuration clock, a three-flop UART synchroniser, the LEDs -- is hand-written Verilog (fpga/vivado/wasm_board_ax7203.v), under the owner-approved-foreign exception in that repository. Everything that computes is generated from the spec; everything that only brings the chip up is not.
The toolchain is openXC7: yosys with -flatten -abc9 -nocarry -nodsp -nosrl, then nextpnr (heap placer, router1, seed 1). The first cut was a nextpnr estimate of 18.57 MHz. Rewriting i32.mul into a 32-clock shift-add microsequence took the estimate to 70.41 MHz against a 68.7 MHz bar. The core costs 3,910 of 269,200 LUTs on the XC7A200T -- one percent -- and 1,127 flip-flops. The local build takes about 65 seconds without Docker, and the bitstream is 9,730,755 bytes.
Before any hardware run, the conformance harness in trinity-fpga checks a model of the board against a golden interpreter -- an independent WASM MVP interpreter with its own opcode table and a general signed-LEB128 decoder, sharing no code with the spec. Fifteen pin cross-checks tie the spec port list to the wires.
The harness self-test injects six faults -- a lie, a bad opcode, a sequence break, a dropped frame, a bit slip, the wrong program -- and proves every check fails on its own fault. That is what caught the real bug before the board did: X_OFF and Y_OFF in the spec are offsets into the frame body, but on the wire x sits at byte six, two sync bytes in. Read the offsets the wrong way and 76 of 134 checks fail -- against a board that is working perfectly. The self-test turned that into a fix made before the first flash.
openFPGALoader flashes the bitstream over the FT232H cable in about 15 seconds, with no sudo, and reports done. Then the board answered 134 of 134 requests exactly -- six consecutive runs, 804 of 804 answers, zero mismatches, zero lost frames, 416 answers per second at the pace of the link. The vector set is pinned by hash:
89527478cf813f99b9e76fce77f1b846b5f3ddbf22cb212f08b5ac0eec108f8f
| What | Result |
|---|---|
| Timing (nextpnr estimate) | estimate 70.41 MHz against a 68.7 bar; 18.57 before the i32.mul microsequence |
| Area on XC7A200T | 3,910 of 269,200 LUTs (1%); 1,127 FFs |
| Board runs | 6 x 134/134 exact = 804/804; 416 answers/s |
| Build | openXC7, local, ~65 s, no Docker; bitstream 9,730,755 bytes |
Software interpreters -- wasm3, WAMR, wasmi -- run WASM on CPUs and run it well; WASM in hardware is a much thinner file. A small academic set of hand-written WASM-on-FPGA designs appeared through 2024-2026, and eBPF offload on smart NICs plus the old Java-in-silicon line (picoJava, JOP) are the precedent that a bytecode ISA can be a hardware target at all. What is different here is not speed -- none is claimed against any of them -- but provenance and order of work: the computing core is generated from a spec, and the conformance harness, with its self-tested fault injection, was written and passing before the first bitstream touched the board.
Epic trinity-fpga#917 -- AI-generated FPGA hardware that executes WASM -- has its MVP closed on silicon. Next: widen the opcode set, load programs over the wire, and shrink the hand-written top toward zero.
Work with me
I work contract and part-time on hardware-AI, FPGA/RTL and ML systems — from specification and open toolchains to reproducible measurements.