np2hw
NumPy → streaming hardware (Verilog) compiler for image and 2-D pipelines.
Write streaming image-processing code as ordinary NumPy. np2hw traces it (like JAX/TVM)
into a streaming line-based IR and emits synthesizable Verilog — with line buffers,
shift registers, edge handling, config registers, and ZeroAsic-stack interfaces
generated for you. The same NumPy function is the spec, the hardware, and the
validation reference.
# my_isp.py — a complete model file
import numpy as np
from np2hw import Param, Params
PARAMS = Params([Param("gain", np.uint8, default=16)]) # a config register
def model(img, p):
x = np.pad(img.astype(np.uint16), 1, mode="edge") # 3x3, replicate edges
blur = (x[:-2,:-2] + 2*x[:-2,1:-1] + x[:-2,2:]
+ 2*x[1:-1,:-2] + 4*x[1:-1,1:-1] + 2*x[1:-1,2:]
+ x[2:,:-2] + 2*x[2:,1:-1] + x[2:,2:]) // 16
return ((p.gain * blur) // 16).clip(0, 255).astype(np.uint8)
Save that as my_isp.py and run it — or use a shipped model from
examples/isp/:
np2hw run examples/isp/gain.py in.png out.png --param gain=24 # NumPy reference
np2hw run examples/isp/gain.py in.png out.png --param gain=24 --backend sim --sim cxxrtl # the generated RTL
np2hw view examples/isp/gain.py --source camera # live, with a gain slider
Why
For image processing and DSP, NumPy is the natural language — but mainstream HLS
targets C/C++ (Bambu, Vitis HLS, Catapult), and the Python accelerator DSLs
(HeteroCL, Allo, PyLog) use their own APIs, not plain NumPy, and target CPU/GPU/ML
rather than streaming ISP RTL. np2hw traces standard, unmodified NumPy into
streaming image-processing RTL, leaning on a hardware pattern library drawn
from production ISP experience. See design/07-prior-art.md
for the competitive map.
The engine traces general streaming NumPy — stencils, pointwise, edge handling,
mux (np.where), and phase-sliced planes (out[py::2, px::2] = ... with a
register-valued phase, which is how a per-CFA-colour operation is written).
Generated cores can be wired together with compose(), a composed module nests
inside another, and control_wrap() puts an AXI-Lite register file in front of
either. The hardest exerciser is a full ISP
built on np2hw end to end — models, netlist, control plane and verification.
(Next on the tracing roadmap: reductions, @/matmul, convolve — the
authoritative list of what traces today is
docs/supported-ops.md.)
Install
uv venv
uv pip install -e ".[media]" # core + image/video IO (np2hw run/view)
uv pip install -e ".[media,camera]" # + webcam for `view --source camera` (opencv-python)
uv pip install -e ".[media,switchboard]" # + run through the ZeroAsic Switchboard stack
uv installs the Python package and its extras. RTL simulation uses external
tools installed separately — Icarus (iverilog), Yosys (yosys), and Verilator
(verilator), e.g. from Homebrew, your package manager, or the OSS CAD Suite. A
non-uv setup (PYTHONPATH=src python3 …) also works — see
docs/install.md.
Documentation
| Doc | Covers |
|---|---|
| docs/install.md | Install, uv, optional extras, system tools |
| docs/cli.md | np2hw run / np2hw view — full reference |
| docs/writing-models.md | The model-file convention; which NumPy is traceable; gotchas |
| docs/supported-ops.md | Exact list of traced operators/methods/np.* functions (and what isn't yet) |
| docs/parameters.md | Config registers: Param, Params namespace, defaults, matrix kernels, bool/bypass, live control |
| docs/streaming-and-bitwidths.md | Streaming model, line buffers, edge handling, dtype/bitwidth semantics |
| docs/clock-crossings.md | The cdc_fifo crossing: what a timing and CDC flow must be told, and the reset contract |
| docs/memories.md | The memory seam: line buffers as a bindable module, the macro contract, sync_memory |
| docs/framing-and-resolution.md | SOF/EOF framing, continuous frames, parametric & dynamic resolution |
| docs/ipxact.md | IP-XACT: packaging a component, and the stream's own bus definition |
| docs/interfaces.md | AXI4-Stream Video, Switchboard, AXI-Lite/UMI register files, control_top |
| docs/backends.md | numpy / iverilog / cxxrtl / switchboard (verilator, icarus) |
| docs/view.md | Live viewer: camera/screen sources, sliders, FPS |
| docs/zeroasic.md | Switchboard stack, Logik/FPGA, Platypus, cloud emulation |
| docs/api.md | Python API: to_ir, generate, adapter generators |
Design rationale (the "why" behind each mechanism) lives in
design/ (01–07).
What it can do (built and validated)
- Trace NumPy → Verilog: slicing/arithmetic/
astype/clip/np.pad/np.where, flattened to a weighted tap map and lowered to a line IR (line buffers + shift registers, automatic delay-line counting, hash-consed sharing). - Faithful dtypes:
uint8wraps unless widened withastype; signed via NumPy types;.clip/.saturate/.truncatefor narrowing. The oracle is the same function on real NumPy dtypes. - Config registers:
Param(scalar/matrix/bool) with resetdefault=, aParamsnamespace for many-register ISPs, programmable kernels,np.wherebypass. - Edges: same-size output via replicate/zero padding (top/bottom flush during blanking, left/right replicate).
- Framing: honors AXI-Video
TUSER(SOF); EOF-driven height-agnostic framing; continuous multi-frame. - Resolution: WIDTH/HEIGHT as Verilog parameters (per-synthesis), or full
runtime resolution (
MAX_WIDTHbuffers +active_widthregister + VSYNC). - Interfaces: AXI4-Stream Video, Switchboard (SB + packed gearbox), AXI-Lite and
UMI register files (with frame-synced shadow registers), and a
control_topthat wires geometry/config registers to the core. - Control plane: registers at addresses the CALLER allocated (
Reg), including read-only identity words and SLVERR on an unmapped access, andcontrol_wrap(), which puts that register file in front of any self-describing module — a single core or a whole composed design. - Run anywhere: NumPy reference, Icarus, cxxrtl (fast compiled C++), and the real
Switchboard stack (Verilator/Icarus +
PySbTx/PySbRxqueues) — every path validated bit-exact against the NumPy reference. - Tooling: a generic CLI (
runfile-to-file,viewlive camera/screen with sliders + FPS); models are plain.pyfiles (no built-in names). - FuseSoC: ships as a generator (
np2hw.core, commandnp2hw-fusesoc) -- a consuming core names a model file and geometry, and the Verilog core is traced during that project's own build, byte-identical on every run. - Stream plumbing for the boundaries a traced core sits between:
skid(a registered boundary, so one core's combinational depth stops being the next one's),tee(one stream, two consumers, never stalling -- a consumer that falls behind tears its own branch and says so),cdc_fifo(the one crossing every design here contains: a recovered clock meeting the board's, owned so its discipline is checked rather than trusted), andboundary_report/combinational_outputs, which say where a composition still hands its depth onward. - A video path either side of the datapath:
video_indecodes a link and aligns sample depth on the way in;scanoutturns a pixel stream into a timed raster, with placement as a live register taken in vertical blanking;framebuffer_readreads a frame that cannot be out of phase, and says why it is idle rather than sitting silent. - The display cable as a control plane:
ddc_slave, an I2C slave on the DDC pins, so parameters reach a design over the video cable already attached to it.
Status
The compiler, interfaces, register files, simulation backends and CLI are implemented and validated (NumPy == RTL, bit-exact, across the example suite). Pointwise expression DAGs cover LUT-with-gather and matrix arithmetic with register coefficients, so multi-channel packed streams (a CCM's shape) trace today. Next: reductions, >1 pixel/clock, and the FPGA bitstream flow via Logik — see docs/zeroasic.md for the path to FPGA/Platypus.
What it generates, in hardware
revela is an open image signal processor whose blocks are NumPy functions, compiled to Verilog by np2hw. It runs on a PYNQ-Z2: a Raspberry Pi camera's raw Bayer arrives over HDMI (bayerlink), and black level, white balance, an adaptive Hamilton-Adams demosaic, a colour matrix and gamma all execute in fabric at 1920x1080, 10-bit, 148.5 MHz on one clock. Every block is checked bit-exact against the NumPy it came from before it is allowed to emit.
That is the claim this compiler exists to make, on a board anyone can buy: the mathematical model IS the hardware.
Funding
Developed independently; recurring support via github.com/sponsors/lanserge, or write first: s.rabykin@gmail.com. Sponsorable capability targets -- each a generic NumPy-level feature that unblocks everyone downstream, ISP or otherwise:
- Reductions (#1) -- per-zone accumulate/min/max over a frame, which is what any statistics or metering block needs.
- Buffered forks (#2) -- one stream feeding consumers that accept at different rates, with the elasticity stated and verified.
- Status registers (#3) -- live core state (sticky flags, counters) readable back through the generated register file, alongside the config it already carries.
- Multi-stream tracing (#4) -- blocks that merge or split streams, which today refuse to trace.
- Bypass-aware power gating (#5)
-- for every register-driven mux
(
np.where(enable, ...)), compute the exclusive logic cone it disables -- exact graph reachability on the traced IR, not a netlist heuristic -- and emit operand isolation plus flop clock-enables for it, with the savings proven by toggle counts in simulation, not asserted. - Portable C emitter (#6) -- the same pipeline as a plain C function, bit-exact against the NumPy that generated the Verilog: a golden model that runs beside the hardware, from the one source of truth.
- Measured device profiles (#8) -- np2hw prices a pipeline before it emits it, from a handful of per-device constants, and a wrong constant is a failed build or a wasted register. Those constants are currently FITTED to a few observed paths on one part. Attribute a vendor timing report back to the traced nodes it came from -- the generator's own wire names survive synthesis, so a path is not "7.5 ns somewhere" but "these nodes, priced at 5.6" -- and solve each term from its own evidence rather than curve-fitting slack, which would put the model inside its own feedback loop. Fitted at a stated conservative quantile, with the device's utilisation recorded beside it, and emitted as a Device subclass the user owns: a new part becomes calibration data, not a patch to np2hw. What it unblocks: refusals you can trust on silicon nobody here has, which is the precondition for every claim about how fast a generated pipeline runs.
- Register context banks (#7) -- N shadow copies of a core's full configuration, swapped atomically at frame boundaries: one pipeline serving several streams, context isolation proven bit-exact, not asserted.
Scope is agreed in writing before work starts; sponsored work lands in the open tree immediately, MIT like everything else -- sponsorship buys ordering and named credit, not exclusivity.
Author
Serge Rabyking — 12 years at Apical (acquired by Arm) on production ISP/image-processing IP; patent inventor (WO2016063023A1 / US10063787B2). More at serge.rabyking.com.
Metadata
Release files for np2hw 0.6.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| np2hw-0.6.1.tar.gz | 182.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| np2hw-0.6.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 368.4 kB
Release files / np2hw-0.6.1.tar.gz
| Download URL | np2hw-0.6.1.tar.gz |
|---|---|
| Size | 182.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
be9527eb9a0dcd0bd16094b5cc64de3703a7710828457628fa9dc09aa6bcd6f5
|
|
BLAKE2b-256 checksum How to use checksums |
28d8268fe03e2544dfbdf8587d1900fb14e4ee1d431def3d1b047d90edb1c9c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / np2hw-0.6.1-py3-none-any.whl
| Download URL | np2hw-0.6.1-py3-none-any.whl |
|---|---|
| Size | 186.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f79eb83642a9a45660a13dfee7479456f996c58ae807040252429e7e809b9193
|
|
BLAKE2b-256 checksum How to use checksums |
1ad743631f1a3041d9315a23f1dcfc15570f81d3caa4c39068a81440797955dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency log