Examples
Waveflow’s examples are a teaching progression, and they come in two families. You learn to represent and compute on data first, then how to move that data between modules over real hardware protocols:
- Data & schema patterns — how a design’s numbers are typed, stored, and computed on, kept vectorized (numpy arrays end to end) so functional simulation is fast and bit-exact. Start here.
- Interface patterns — how a host and an accelerator exchange that data over the AXI-* protocols, one new interface concept per example.
The rationale is bottom-up: the interface examples carry schema-typed data across
their ports, so it pays to understand the data layer before the protocols that
transport it. Every directory is named for the pattern it teaches; the
computation keeps its own identity in the files inside (e.g. stream_inband/ holds
the polynomial accelerator in poly.py).
Family 1 — data & schema patterns
These teach the data schema and vectorization layers: typed fields, numpy-backed arrays, and the type-preserving operators, all proven bit-exact against Vitis.
| Example | Vehicle | What it teaches | Status |
|---|---|---|---|
basic_vec |
one MAC (a*b + c) |
vectorized golden vs vectorized Vitis, bit-exact for int / float / fixed — thevectorization front-door | available |
schemas/fixedpoint |
edge-value sweep | the rigorous fixed-point conformance harness (all QMode/OMode × widths) behind FixedField |
available (code) |
basic_vec is the teaching front-door (minimal, readable); schemas/fixedpoint is
the exhaustive proof. They share the same conformance machinery.
Family 2 — interface patterns
These move more of the host↔accelerator contract off the control plane and into shared structures at each step, one new interface concept at a time. They are the full five-stage flow below.
The general Waveflow flow
Python is the single source of truth. Every full example walks the same five stages, each derived from the one before:
- Python model — the golden numerical behavior, in numpy / PyTorch.
- Python simulation — the SimPy transactional simulation (Components + Interfaces): protocol-accurate, cycle-approximate.
- Code generation — a Vitis HLS C++ kernel and testbench emitted from that same Python model.
- C and RTL simulation — Vitis C-simulation, then C-synthesis followed by RTL co-simulation, each checked against the Python golden model.
- Timing extraction — cycle and burst measurements pulled from co-simulation and fed back into the Python timing model.
The Flow coverage column below cites these stage numbers.
The five interface patterns
The progression moves more of the host↔accelerator contract off the control
plane and into shared structures at each step: from all control in registers,
to control in-band on the data stream, to data in memory, to control
also in memory. pure_stream slots in early as the boundary-free streaming
base case.
| # | Pattern (directory) | Vehicle | New concept introduced | Flow coverage | Status |
|---|---|---|---|---|---|
| 1 | regmap |
simple function | register-mapped control (AXI4-Lite) | stages 1–5 | available |
| 2 | pure_stream |
moving-average filter | streaming dataflow — no packet boundary, no TLAST, no control | planned | reserved (not built yet) |
| 3 | stream_inband |
polynomial | packetization (TLAST) + in-band control on the stream | stages 1–5 | available |
| 4 | shared_mem |
histogram | data in memory (AXI-MM), control over a dedicated stream | stages 1–5 | available; codegen upgrade in progress |
| — | rowwise_fir |
per-row FIR filter | no new interface (reuses shared_mem’s AXI-stream control + AXI-MM data); turns inward to the internal load-compute-store dataflow + a cosim-calibrated timing model |
stages 1–5 + calibration | available |
| 5 | mmqueue |
complex vector MAC (VMAC) | control also in memory, via a descriptor queue | stages 1–5 | available |
The shared-memory (shared_mem) example is the reference for AXI-MM (m_axi)
codegen — multiple buffers and element types read/written over one bundle, with
the kernel and testbench generated from the Python component.
rowwise_fir is different in kind, which is why it sits before mmqueue
even though it adds no interface. Where the numbered steps each move more of the interface
contract into shared structures, rowwise FIR reuses shared_mem’s AXI-stream control (over
AXI-MM data) and instead studies the accelerator’s internal structure — the load-compute-store
dataflow — and how to give it a physical, cosim-calibrated timing model. It is the culmination
of the timing-model and calibration arc; mmqueue then
stays last as the climax of the interface progression (control in a memory queue).