FFN accelerator¶
A streaming, tiled, quantized FFN engine in Amaranth that runs the BERT
feed-forward sublayer, requant2(GELU(requant1(x·W1))·W2), entirely in
silicon. This engine owns the whole computation: it tiles, accumulates over
K, requantizes, and applies GELU on-chip, through the existing 2 KB Wishbone
MMIO window, no SoC changes.
x (int8) ─▶ MatMul-1 ─▶ Requant ─▶ GELU LUT ─▶ MatMul-2 ─▶ Requant ─▶ y (int8)
x·W1 int32→int8 int8→int8 ·W2 int32→int8
└─ tiled output-stationary MAC grid (tile×tile INT8 MACs) ─┘
Source: examples/ffn_accel/ in the manhattan-reasoning-gym repo. The
BERT feed-forward example is a real application built on top
of this same design.
What's real about it¶
| Feature | How |
|---|---|
| Parallel MAC grid | tile×tile output-stationary INT8→INT32 MACs, one DSP each |
| Hardware tiling | An FSM sweeps M×N output tiles, accumulating over K from packed BRAM |
| Proper quantization | Per-tensor int8 + gemmlowp-style fixed-point requant: (acc·mult + bias) >> shift |
| GELU | Exact int8→int8 lookup ROM, runtime-loadable per model |
| Native chaining | Each engine emits results in the layout the next consumes, GELU is a 1-pass streaming copy, no transpose |
| Pipelined | Address-ahead-of-data K-loop, 1-cycle requant/GELU stages |
The design is one self-contained file (the orchestrator uploads only that), and it's bit-exact against a NumPy golden model, the golden model performs the identical integer ops, so a passing simulation test is a proof of correctness, not an approximation.
ECP5-85 fit (real yosys synth_ecp5)¶
The whole dual-engine slave at tile=4:
| LUT4 | carry (CCU2C) | DSP (MULT18X18) | BRAM (DP16KD) | FF | |
|---|---|---|---|---|---|
| FFNSlave | 3,437 | 840 | 40 | 6 | 1,505 |
| Budget (LFE5UM5G-85F) | 84k | – | 156 | 208 | – |
Fits with huge headroom next to the VexRiscv+LiteEth SoC (see
Hardware & SoC architecture for what that SoC
alone costs and how much of the chip is left over before this design is even
added). The parallel MAC grid scales as tile² DSPs, 12×12 (144 MACs) is the
max all-DSP fit; 16×16 (256 MACs) needs on-fabric multipliers.
Run it¶
mrg login # or set MRG_API_KEY
mrg run examples/ffn_accel/client_sdk.py # builds the bitstream, programs a board, runs
The client builds a quantization plan host-side, streams the int8 operands + requant constants + GELU table through the MMIO window, runs the pipeline, reads the int8 output back, and asserts it's bit-exact against the golden model.
Covers the MAC grid, requant, the tiling engine, the full FFN, and the complete Wishbone MMIO data path, each bit-exact against the golden model.
The honest limitation (why the demo dims are small)¶
A full BERT-base FFN has 4.7 MB of weights, 10× the ECP5-85's 468 KB of
BRAM, so a real deployment must stream weights from off-chip, and on this
board the only host link is 100 Mbit Ethernet through a softcore, which caps
a full layer at roughly half a second of weight transfer (I/O-bound, not
compute-bound). The engine is architected for that streaming model (operands
flow in tile by tile); the on-board demo keeps dimensions small (8→32→8)
because operands load through the 2 KB MMIO window one word at a time. The
silicon is the real thing; feeding it at full BERT scale is a data-path
project (DMA + a faster link), not a compute one.