Mini NPU¶
A systolic array inference accelerator in Verilog on a Tang Nano 9K. Runs INT8 MNIST inference on weights trained and exported by seatorch. The portfolio centerpiece — the hardware half of "no PyTorch, no black boxes."
Status: not started. Begins after seatorch's milestone 10 (weight export) ships — the NPU needs real quantized weights to load, not synthetic test data, or you'll build the wrong export format twice.
Board: Sipeed Tang Nano 9K (Gowin GW1NR-9, ~8640 LUTs, ~20 DSP blocks, BRAM, PSRAM, ~₹2000). Toolchain: Yosys, nextpnr, Gowin programmer, Icarus Verilog, GTKWave.
Goal¶
MNIST image → ESP32 → UART → FPGA systolic array → predicted digit, benchmarked against
CPU inference, with LUT/BRAM/DSP usage reported.
Architecture (final)¶
ESP32 (host)
│ SPI/UART
Tang Nano 9K
┌─────────────────────────┐
│ UART Controller │
│ BRAM (input/weight/out) │
│ Controller FSM │
│ Systolic Array (PEs) │
│ Accumulator │
│ Activation (ReLU) │
│ Output Buffer │
└─────────────────────────┘
Controller FSM: IDLE → LOAD → COMPUTE → WRITE → DONE
Each PE per cycle: out += weight * activation — weights flow horizontally, activations
flow vertically, partial sums flow through. This is the TPU-style dataflow at toy scale.
Milestones (risk-ordered — small working pieces, not the full architecture before any results)¶
| # | Milestone | What it proves |
|---|---|---|
| 1 | HDLBits complete (gates, FSMs, counters) | digital design fundamentals |
| 2 | Single MAC unit, verified (2×3+4=10 in simulation) |
arithmetic in RTL |
| 3 | Matrix multiplier (small, fixed-size, behavioral) | dataflow before structure |
| 4 | 2×2 systolic array of PEs | the real architecture, smallest viable size |
| 5 | Controller FSM + BRAM interfaces wired to the 2×2 array | microarchitecture integration |
| 6 | UART module, hand-written (not IP-generated) | serial protocol from scratch |
| 7 | Weight loading over UART into BRAM | host↔FPGA bridge working end to end |
| 8 | Tiny network inference on the 2×2 array (toy weights, not yet MNIST) | full pipeline proof before scaling size or model |
| 9 | Scale to 4×4 systolic array, if LUTs/DSPs allow | area/throughput tradeoff, real resource constraints |
| 10 | seatorch INT8 export → .hex import into BRAM |
quantization pipeline connected end to end |
| 11 | Full MNIST inference: image → ESP32 → UART → FPGA → prediction | system integration, the actual headline result |
| 12 | Benchmark vs CPU, LUT/BRAM/DSP usage report | performance methodology, what goes in the writeup |
Risk note (kept from earlier planning): milestone 4 is where most RTL projects stall. If it's taking more than 3 weeks, stop and debug method — wrong abstraction, not just a bug — rather than pushing through. Getting a 2×2 array working is more valuable than a perfect one.
Ship checklist¶
- Verilog source + C driver + UART bridge, one repo
- Benchmark numbers: latency vs CPU, resource usage (LUT/BRAM/DSP)
- Writeup: "What a small systolic array teaches you about why real NPU compilers tile the way they do" — specific numbers from milestone 12, real tradeoffs, not "I built an FPGA project"
- Show HN, r/FPGA, r/ECE
Extensions (after milestone 12, not before)¶
- Parameterized array size — 4×4 vs 8×8 area/speed comparison
- Tiny ISA:
LOAD / MATMUL / RELU / STORE - Convolution support for small CNNs
Decision log¶
- Tang Nano 9K chosen over Tang Nano 1K — 1K's LUT/DSP budget too constrained for a systolic array of any useful size.
- Fill in as you go: 2×2 vs going straight to 4×4, BRAM layout, UART baud rate / framing.
Explicitly not in scope (v1)¶
Float support (INT8 only — float is "expensive" in hardware, that tradeoff is part of the writeup), multi-model support, anything beyond a single fixed network topology per build.