Accelerator engineering
Systolic and custom accelerator design: array sizing, quantisation and requantise datapaths, host interfaces, and self-test. Delivered with the scaling numbers, not a promise about them.
Systolic NPU RISC-V Linux
The NPU is a parametrised 2-D output-stationary systolic array (N = 2…16) with a verified O(N²) scaling law, a power-on self-test, and a host interface that needs no extra pins. It ran 131/131 INT8 matmuls on a 10,320-LE Cyclone IV across requantisation scales, and a full RV32E SoC drove it on its native bus in 7/7 self-checking vectors. Alongside it: Linux 6.11 on a soft-core CVA6, and unmodified Zephyr SMP on a €30 board.
StatusThe NPU and the CVA6 Linux platform are validated on hardware; the €30 board boots autonomously from its own flash. Current work: merging the two mature platform branches into gen1, then bringing the accelerator into the SoC fabric on the HBM path.
A 2-D systolic array, parametrised in N. INT8 operands flow into N² multiply-accumulate cells; INT32 partial sums are requantised back to INT8 through a pipelined stage, then land in a register file that is memory-mapped to the host. Output-stationary, one weight per cell, and the whole thing scales by changing a parameter.
Two things make it worth taking seriously rather than admiring: the scaling is measured and law-like, and the design self-tests on power-up before it hands the bus to anything else.
| N = 2 | 8 wide MACs · 251 cells |
|---|---|
| N = 4 | 32 wide MACs · 720 cells |
| N = 8 | 128 wide MACs · 2,528 cells |
| N = 16 | 512 wide MACs · 9,488 cells |
The array itself uses exactly N² wide INT8 multipliers — 4, 16, 64, 256 for N = 2, 4, 8, 16. The full design reports 2·N² because the requantise stage adds one scaling multiplier per output. Both the multiplier count and the cell count grow as O(N²). Reproduce any size with syn/yosys/bench_npu.sh 2 4 8 16; functional correctness at scale is checked by make -C sim npu and npu-big against a software reference.
One N×N output tile completes in 3N − 2 cycles in steady state (from the front-end skew schedule), performing up to N² MACs per cycle with every cell active. A 16×16 array therefore does 256 INT8 MACs/cycle at peak. The analytic model is checked against the finite-state machine in mm_frontend.v, not asserted.
| NPU, bare | 131 / 131 INT8 matmuls PASS |
|---|---|
| Across | requantisation scales |
| Full SoC | RV32E core + native NPU bus · 7 / 7 vectors PASS |
| Read over | JTAG — power-on BIST owns the bus first, then releases it |
| Device | EP4CE10E22C8 · 10,320 LE · 46 DSP · C8 speed grade |
| Host link | Virtual-JTAG — reuses the programming cable, no extra pins |
The self-checking vectors reproduce to 7/7 PASS, and the ABI between the compiler and the hardware was found wrong only on the board — an rv32i/RV32E mismatch plus a linker RAM-size error that no simulation run had reason to question. The raw Quartus map, fitter, static-timing and flow reports behind this summary are sent on request: these are the traces, not screenshots of them.
| FPGA flow | Quartus Prime Lite (free) · Yosys for technology-independent sizing |
|---|---|
| ASIC flow | OpenLane / OpenROAD → SkyWater 130 nm |
| ASIC synthesis | 37,002 standard cells at N = 2 — synthesis, not a tape-out |
| Device ceiling measured | N = 8 is the practical fit on the KU3P; N = 16 overflows its 1,368 DSPs |
No paid licence is required at any step, which is the point: an accelerator you can only build with an expensive toolchain is not an open platform. The ASIC figure is a synthesis result and is labelled as one — the tape-out path is documented and versioned, but nothing has been fabricated.
The substrate the NPU plugs into: two RISC-V platforms, both validated on hardware, at two different scales. One runs Linux out of DDR4; the other runs real SMP firmware on a board that costs less than a meal.
A complete 64-bit RISC-V SoC — CPU, DDR4 controller, interrupt and timer blocks, a UART and a memory-mapped accelerator slot — boots a real kernel to an interactive shell on a Xilinx Kintex UltraScale+ XCKU5P. Soft-core: there is no hardened CPU in the device.
| FPGA | XCKU5P-FFVB676-1-i · Kintex UltraScale+ |
|---|---|
| CPU core | CVA6, RV64IMAC, Sv39 MMU · 1 hart |
| Interconnect | AXI4, with a native ATOP read-modify-write engine in the RAM controller |
| DDR4 | 1 GiB MT40A512M16 · 2400 MT/s · ui_clk 300 MHz (4:1) |
| Boot chain | OpenSBI 1.8.1 → Linux 6.11.11 → BusyBox, static initramfs |
| Accelerator slot | Mapped at 0x10000000 and reachable from Linux |
| Tools | Vivado 2025.2 |
| Boot | complete, to a BusyBox ~ # promptstandalone: from its own flash, no host in the loop |
|---|---|
| Flash boot, power-on to shell | ~85 s against a < 3 s target: ~9 s fabric configuration, ~36 s loader (the software inflate dominates), ~33 s kernel |
| Usable RAM | 992 / 1022 MiB · 97 % |
| sched_clock | 64-bit @ 50 MHz · 20 ns resolution |
| Tick timer | 250 Hz · 4 ms |
| Kernel image | 11,097,600 B · 10.6 MiB |
| Paths meeting timing | 15/09 build: 99.852 % · 1,228 of 829,505 violated 19/09 build: 100 % · 0 of 206,109 violated 22/09 build: 100 % · 0 of 205,576 violated |
| Setup WNS | 15/09 build: −0.221 ns · TNS −81.3 ns 19/09 build: +0.184 ns · TNS 0.000 ns 22/09 build: +0.219 ns · TNS 0.000 ns · all specified constraints met |
| Hold WHS | 19/09 build: +0.011 ns · 0 violations of 205,599 22/09 build: +0.012 ns · 0 violations of 205,066 |
| LUT utilisation | 15/09 build: 161,688 / 216,960 · 74.5 % 19/09 build: 96,231 / 216,960 · 44.35 % 22/09 build: 93,941 / 216,960 · 43.3 % |
| DSP48E2 | 467 / 1,824 · 25.6 % |
Three routed builds are reported separately, and they are not compared. The 19/09 and 22/09 builds report every constraint met and zero failing endpoints, but their endpoint counts are a quarter of the 15/09 build's, so the runs describe different extents of the design. A single figure would hide that, and an earlier figure on this page could not be traced back to a routed report.
"Constraints met" is not a signoff. The flow's acceptance gate is WNS > +5 ns and CLUT < 75 %: occupancy passes comfortably, timing does not. +0.184 ns is an order of magnitude under the gate, as it was before those fixes, so this is a validation, not a signoff. The 19/09 hold margin (+0.011 ns) is inside the re-run noise band — re-runs of identical RTL here move hold by tens of picoseconds — so it is not margin.
And the unclosed run is stated on purpose. The 15/09 build's WNS is −0.221 ns, with 1,228 endpoints violated. A page that only publishes its good numbers is not evidence.
The traces. The flashed bitstream is ku5p_soc_top.bit, built 19/09 on an xcku5p-ffvb676 with Vivado 2025.2: sha256 ed122006c80b760de50b06f6803e4cc3b1e3d0316684d361bfd9a60b5cfe5e63, HEAD e959e04. The routed timing and utilisation reports behind every figure above are sent on request: these are the traces, not screenshots of them.
A second KU5P design meets timing. The PCIe standalone top routes with 0 of 24,943 endpoints violated, WNS +0.994 ns, 23/09. It is a different design from the SoC above, and it is reported as such.
A complete SoC on a Colorlight i9+ v6.1 (Artix-7 XC7A50T, 8 MB SDRAM, 25 MHz): two Ibex RV32IMA cores, a 64-bit AXI4 crossbar, an SDRAM controller, a CLINT, a 16550 console, a RISC-V-spec interrupt controller, a cross-core LR/SC exclusivity monitor, and free slots for an accelerator and a DMA. Above it runs Zephyr RTOS v4.1.0 in SMP — unmodified kernel. The board boots from its own SD card with no host attached.
| Cross-core atomics — LR/SC | 20,000 / 20,000 exact |
|---|---|
| Cross-core atomics — AMO | 20,000 / 20,000 exact |
| Interrupt-driven console | 400 / 400-byte burst, 0 FIFO overruns |
| Throughput — interrupt | 11,697 B/s |
| Throughput — polled | 136 B/s · ×86 slower |
| SMP stress, 6 threads | PASS · no lost IPIs |
| Timing — WNS | +9.691 ns · gate +5 ns |
| Timing — WHS | +0.042 ns |
| LUT utilisation | 56.6 % · gate 75 % |
| Commits on the branch | 272 · 494 files · +67,470 / −641 |
| Card identified | SDHC · OCR C0FF8000 CCS=1 · MBR 55 AA |
|---|---|
| SD stack | disk init ret=0 · 7,864,320 sectors · 3.75 GiB |
| FAT32 | mounted · write + read-back 1,000/1,000 B · checksum 2726712C |
| FSBL, bare-metal | 2,874 B · SD init with no OS · PASS |
| Boot ROM | 4,130 B · 50 % of 8 KiB BRAM · all gates pass |
| Autonomous boot | flash written · real power-cycle · FPGA configures from its own flash |
| Configuration flash | 16 MiB Macronix MX25L12833 · backed up before writing |
| Validation before jump | magic + length + checksum |
| Dual-hart contract | hart 1 released via MSIP[1], after validation |
| Gates validated | 7 · none skipped |
No FATFS in the FSBL and no OS: the payload is read at a fixed LBA by design, and the checksum is mandatory before the jump. The configuration flash stays rewritable over JTAG even after a failed boot, so the board cannot be lost — which is what turns flashing it into a routine step instead of a risk.
a&b where the architecture requires a&~b. Linux's clear_tsk_need_resched() uses that operation; it silently did nothing, the reschedule flag stayed set, and schedule() livelocked. One operator, three layers up, a hung kernel — and nothing in simulation had any reason to question it.release callback in the SPI driver, called on the nominal path — it jumped to address 0. A hardware crash wearing the costume of a filesystem bug.Why the console is polled here. The RISC-V interrupt controller on this platform does not implement the SiFive register layout, so its device-tree node was removed rather than papered over — the kernel would have faulted on the first interrupt. It is a diagnosed incompatibility with a named cause, deferred deliberately, not an unfinished feature.
The platform targets the buyers that closed silicon excludes or overcharges: European research groups, integrators under GDPR and AI Act pressure, and anyone who needs a datapath they can actually read.
Two things that rarely meet are combined here. A reconfigurable systolic accelerator, proven on hardware and reproducible from a free toolchain. And a software-recompilable RISC-V core with an open, auditable instruction path — already proven up to a Linux userspace. Underneath both sits HBM-class bandwidth that most competing small accelerators simply do not have.
The design posture is deliberate about what this is not:
What it wins on is the combination those alternatives cannot offer: an auditable open RTL path, a reconfigurable datapath, HBM-class bandwidth in a 75 W half-height card, and a provenance that stands up to a procurement filter written as "no third-country-controlled supplier".
| Device | EP4CE10E22C8 · Cyclone IV E · 10,320 LE |
|---|---|
| Hard blocks | 46 DSP · 414 Kb M9K |
| Toolchain | Quartus Prime 25.1 Lite — free edition |
| Host link | Virtual-JTAG over the USB-Blaster programming cable |
| In the fabric | NPU + register file + power-on BIST + reset synchroniser |
| FPGA | XCKU5P-FFVB676-1-i · Kintex UltraScale+ |
|---|---|
| CPU | CVA6 RV64IMAC · Sv39 · 1 hart · soft-core |
| Memory | 1 GiB DDR4 (MT40A512M16) · 2400 MT/s |
| Clocks | SoC 50 MHz · ui_clk 300 MHz |
| Runtime | OpenSBI 1.8.1 → Linux 6.11.11 → BusyBox |
| Device | Artix-7 XC7A50T |
|---|---|
| Memory | 8 MB SDRAM |
| Clock | 25 MHz |
| Cores | 2× Ibex RV32IMA · 64-bit AXI4 crossbar |
| Runtime | Zephyr RTOS v4.1.0, SMP, unmodified kernel |
| Cost | €30–50 module + JTAG dongle |
The scale-up target, chosen on price per gigabyte-per-second rather than peak spec: an HBM2 card is what lets a 16×16 array run without starving. Now on the bench, with the shell and the PCIe path under bring-up. No result is published here until it has been measured on the card.
| Card | AMD Alveo U50 (A-U50-P00G-PQ-G) |
|---|---|
| Memory | 8 GB HBM2 · 316 GB/s peak |
| Logic | 872 K LUTs · 5,952 DSP slices |
| On-chip | 51.8 Mb BRAM + 180 Mb UltraRAM |
| Format | Half-height, half-length, single slot, passive · 75 W |
Portability. AMD has issued the end-of-life notice for the U50 (XCN26004): last-time-buy 30 October 2026, last-time-ship 2 April 2027. That closes the new-and-warranted channel only — the used market is unaffected. The design is kept portable at RTL level to the U55C (16 GB, 460 GB/s) and V80 (32 GB, 820 GB/s).
Working notes, dated when the work lands — or labelled by the gate it closes. Threads still running are marked open.
131/131 INT8 matmuls pass on an EP4CE10 across requantisation scales, read back over JTAG. The same day, a full RV32E SoC boots from on-chip memory, drives the NPU on its native bus, and self-checks 7/7 vectors — an ABI mismatch and a linker RAM-size error surfaced only on the board.
A complete BlackParrot SoC closed timing on an XCKU3P. Kept as a reference point for what a manycore-oriented core costs on mid-range fabric.
An out-of-context synthesis of XiangShan's MinimalConfig reported 74 % of an XCKU3P's LUT6s. The real figure — a post-optimisation hierarchical report, on a larger device — was 520,681 LUTs against 216,960 available: 240 %. Stacking every optimistic lever (memories into BRAM, issue queues halved, predictors dropped, FPU and L3 removed) still lands near 291 K, above the 217 K budget, and the irreducible out-of-order core alone is ~350 K. MinimalConfig does not fit at any practical configuration. The port was tried on four platforms before the call was made to stop — and the estimate was wrong by roughly 3×, which only building it revealed.
Boot hang localised to vfs_caches_init / mnt_init — the point where the kernel first needs working memory mapping. Useful precisely because it was wrong: it moved the target to a board with real RAM.
Four verticals scored on sales friction and willingness to pay. Conclusion: lead with academic and R&D prototyping, keep edge AI and defence behind it, do not attack the data centre.
Every FPGA with on-package HBM2/HBM2e compared on bandwidth, LUT count, price and lifecycle status. Alveo U50 selected as the scale-up reference.
11 MB takes about 17 minutes at 115,200 baud — tolerable for bring-up, unusable for iteration. The same SD-boot approach now proven on the Ibex platform is the fix.
A 4,130-byte bare-metal FSBL lives in BRAM and boots the payload itself: BRAM → SD card → validate → jump. The host programmed the FPGA and streamed nothing.
The 16 MiB configuration flash was backed up first — full image, hash taken — then written, then verified by a real power-cycle with nothing else attached. That closes the chain end to end: RTL → SD card → filesystem → second stage → Boot ROM → flash. Seven gates, none skipped.
gen1, then the accelerator into the fabricThe two mature platform branches integrate into a single trunk, and from there the NPU moves from a proven standalone design into the memory-mapped slot already waiting for it.
Systolic and custom accelerator design: array sizing, quantisation and requantise datapaths, host interfaces, and self-test. Delivered with the scaling numbers, not a promise about them.
RISC-V integration, AXI interconnect, DDR and memory-controller bring-up, Linux and BSP work, FPGA flows down to a bootable board. Including the bring-up nobody else wants to do.
For labs and academic groups: design support, a reproducible build-and-signoff flow, and measured results you can cite — from synthesis to silicon.
Email [email protected]
I am one engineer. If the scope is real, I will tell you what I can deliver and what I cannot — that conversation takes less time than a proposal.