Examples

To demonstrate Waveflow, we have developed a set of examples that build progressively more advanced features. Each one is a complete, end-to-end design — a Python golden model, a simulation, a generated Vitis HLS kernel and testbench, and RTL verification — and each introduces one or two new concepts on top of the previous example. We will add many more over time.

The table of contents below is in reading order, and every entry’s summary is read from that example’s own front matter — so the list is generated rather than maintained here, and cannot fall out of step with what it lists.


Table of contents

  • Basic vector arithmetic - The front door for vectorization, and the smallest demonstration of the claim the rest of the framework rests on: one element-wise multiply-accumulate, computed over NumPy arrays with no per-element Python loop, in integer, float and fixed point — and asserted equal to the Vitis kernel bit for bit in all three. A data and schema example, so it comes before any module-to-module interface exists.
  • Register mapped simple function - The first end-to-end example: design, simulation, synthesis and RTL co-simulation of a standalone control-driven Vitis kernel, over the simplest AXI interface there is — an AXI-Lite register map. A host modelled as a SimObj drives the launch protocol (write the inputs, assert ap_start, poll ap_done, read the result), so a system simulation confirms the design works before any testbench exists, and the measured RTL cycles are compared back against the Python estimate.
  • Streaming polynomial - Control moves off the register map and onto the data stream. A polynomial accelerator packetizes a variable-length AXI4-Stream with TLAST, carries its command in-band as a header ahead of the samples rather than in registers, and runs as a persistent loop over back-to-back transactions that halts cleanly on an END command. Kernel and testbench are both generated from the one Python source, and the RTL cosim cycle count is checked against the pysim estimate.
  • Histogram with shared memory - The payload moves off the control plane and into memory. A histogram accelerator takes three buffer addresses in its command, reads samples and bin edges and writes counts back over a single AXI4 memory-mapped master, while a stream still carries the command and the status response. The first example to exercise multiple distinct buffers at independent addresses, two element types over one bundle, and bounds checks that select a typed error status.
  • Free-running memory copy - The worked example for the free-running flow, and the counterpart to the register-map one. Where a control-driven kernel is a single host-launched function, this is a composite of free-running hls::tasks copying words between memory regions — which Vitis cannot co-simulate, so it is verified by driving the real RTL cycle by cycle through an XSI BFM. It also reuses the MemRStream / MemWStream adaptors, whose timing models ship pre-fit, so the loosely-timed pysim reproduces the RTL with no calibration of its own.
  • Vector multiply resource modeling - Measuring and modelling what a design costs. A free-running vector multiplier swept across its two parameters, with the measurements handed to a VitisResourceModel that predicts LUT, FF, DSP and BRAM anywhere in the space. The arithmetic is trivial on purpose — the subject is how little you have to say to get a trustworthy model: describe what the design contains, and the library picks the features, prices the device geometry and fits whatever is left.
  • Composite kernel interleaver - A gather accelerator — Y[i] = X[P[i]], reordering one vector under an index vector. It builds on the data mover by adding the two things copying never needs: a real compute stage, and on-chip random access, holding X in block RAM so the index-driven reads are single-cycle. Same free-running composite flow, and this is where fitting a custom component's own timing model is taken up rather than inherited.
  • Block FIR with state - The first design in the tree whose firings are not independent. A block FIR has to carry two things from one firing to the next, with different lifetimes — the coefficients, loaded once, and the tail of the previous block — so both are storage the module itself owns and declares with add_state. In fixed point, as a four-module composite, RTL-gated and resource-modelled: the advanced counterpart to the vector multiply.
  • VMAC with an AXI-MM command queue - Control moved off the stream and into memory: the host appends commands to a ring buffer in shared memory and a free-running accelerator dequeues and executes them over a single m_axi master. The vehicle is VMAC, a complex fixed-point vector-MAC with three element-wise operations and an optional row reduce. Being revised — the command-queue interface it demonstrates is current, but the accelerator itself is due a rebuild on the interleaver's foundation, so read it for the queue rather than for VMAC.
  • A memory reached three ways - One true-dual-port memory that lives OUTSIDE the Vitis kernel, as hand-written Verilog joined by a generated wrapper, reached by three transactions over two free-running tasks: WRITE a payload in, COMPUTE over the words in place, READ them back. Command-driven, over DataList messages that generate the C++ headers the kernel compiles against; every transaction answers, because a write has no return path and a refused read is indistinguishable from a quiet stream. WRITE and COMPUTE share one port on one task, so what it costs to read a word you are about to write is a measurement in one waveform rather than an argument.
  • Playing a stored waveform - The worked example for RfShotTx: one transmitter, two command streams, and the same RTL answering both. A driver pushes in-band frames — a header then samples, TLAST at the end — the design loads them into a BRAM behind a lock, and a real Rfdc plays them out at the converter's grid. The finite stream plays three passes and goes quiet; the infinite one is preempted mid-play and switches waveform. Every verdict the protocol has is exercised across the two, and the playout is byte-identical between pysim and RTL.
  • Capturing without losing anything - The worked example for RfShotRx: a real ADC plays a ramp into a memory split into two regions, the capture fills one while the reader drains the other, and every window goes out as a frame with a header that says what was lost. The scenario IS the gate — a ramp makes a dropped block a visible step in the numbers rather than something a counter has to be believed about.
  • Measuring a delay with an address - The worked example for the RfShotTx / RfShotRx pair with absolute indexing on: one converter carrying both directions, a path with a bulk delay between them, and both buffers indexing absolutely. The channel delay is then a difference of memory addresses — read off a window header and one sample value, with no timestamps and no correlator. Covers why the reading aliases at one buffer, why an address cannot tell a path delay from an epoch offset, and why the two loads are spaced more than a pass apart.
  • RF loopback - The worked example for designs that talk to an RF data converter. A source plays sample blocks into an ADC, the samples cross into the fabric as AXI-Stream words, a trivial pass-through relays them, and a DAC turns them back into sample blocks at a sink — a loopback that is byte-identical end to end, two blocks later. Deliberately without DSP: the point is the converter boundary itself, and the loss counters that are the only evidence a sample grid was actually met.