Skip to main content

np2hw

CI PyPI License: MIT

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: uint8 wraps unless widened with astype; signed via NumPy types; .clip/.saturate/.truncate for narrowing. The oracle is the same function on real NumPy dtypes.
  • Config registers: Param (scalar/matrix/bool) with reset default=, a Params namespace for many-register ISPs, programmable kernels, np.where bypass.
  • 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_WIDTH buffers + active_width register + VSYNC).
  • Interfaces: AXI4-Stream Video, Switchboard (SB + packed gearbox), AXI-Lite and UMI register files (with frame-synced shadow registers), and a control_top that 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, and control_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/PySbRx queues) — every path validated bit-exact against the NumPy reference.
  • Tooling: a generic CLI (run file-to-file, view live camera/screen with sliders + FPS); models are plain .py files (no built-in names).
  • FuseSoC: ships as a generator (np2hw.core, command np2hw-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), and boundary_report / combinational_outputs, which say where a composition still hands its depth onward.
  • A video path either side of the datapath: video_in decodes a link and aligns sample depth on the way in; scanout turns a pixel stream into a timed raster, with placement as a live register taken in vertical blanking; framebuffer_read reads 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)

Source distribution for np2hw 0.6.1
File Size Uploaded
np2hw-0.6.1.tar.gz 182.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for np2hw 0.6.1
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.6.1 This release

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page