Interleaver — a gather accelerator with a custom compute stage
This example builds on mem_copy. Where the data mover copies a buffer unchanged,
interleaver computes a gather — Y[i] = X[P[i]], reordering X under an index vector P — so it
adds the two things a pure data mover never needs: a real compute stage, and on-chip random
access (holding X in block RAM so the index-driven reads are single-cycle). It is the same
concurrent free-running flow: a composite of ap_ctrl_none
hls::tasks, wired by internal streams and stream-of-blocks, driven by a command stream and reporting on
a done stream.
mem_copy reuses only pre-calibrated infrastructure and so calibrates nothing itself. The interleaver
is the counterpart: its il_compute gather is the design’s own kernel, whose timing does not ship —
so this is the example where you fit a custom component’s timing, the half of the
calibration story mem_copy has none of. It still reuses the shipped
infrastructure — the m_axi bus law is loaded from the platform exactly as before — and layers its own
compute model on top.
Learning Objectives
In going through this example, you will learn to:
- Model a gather / permutation accelerator (
Y[i] = X[P[i]]) as a composite of free-runningFreeRunModstages — load, a custom compute, and store - Use a stream of blocks to give the compute stage
random access to a buffer (
X[P[i]]), and overlap the next job’s load with this job’s compute - Reuse the framework
MemRStream/MemWStreamas the read/write stages — framing two reads (P and X) with an in-band descriptor, which also paces the free-running pipeline (one job in flight, no deadlock) and returns a commit-timed done, with no custom mem adaptors and no separate token - Visualize the six-stage pipeline overlap on an activity diagram, straight from the loosely-timed simulation
- Reuse the platform’s shipped infra timing — the mem-stream adaptors and the
m_axibus law — then fit the custom compute stage’s own loop model from a size sweep (the direct method) and store it in the platform library so a build loads it with no re-fit
In this example
The pages build the design up from Python, parallel to mem_copy:
- Module overview — the gather, the six stages, the stream-of-blocks for random access, and the per-job token forwarding.
- Python — building the design in Python: the descriptor split, the two-reads forwarding chain, the stream-of-blocks, the hand-written leaves, and the composite.
- Testbench (Python) — the graph that surrounds the design and drives it in pysim, and
the
Y = X[P]golden check. - DUT codegen — how the graph becomes the
ap_ctrl_nonehls::tasktop, and why every task body is hand-written. - Testbench codegen — the generated C++ BFM harness that drives the RTL through XSI.
- RTL simulation — running the harness in
xsim: no deadlock, and bit-exact through real RTL. - The timing model — declaring
il_compute’s own loop model: where it plugs into the stage, thelatency + ii·(n − 1)formula, and what the parameters mean. - Timing in the pysim — what the loosely-timed pysim measures and the six-stage pipeline activity plot it produces (from the pysim → figure build DAG).
- RTL timing and the comparison — the RTL cadence measured from a trace, the generated RTL-vs-pysim table, and the ≈0.7% agreement (including the reader residual the interleaver forced into existence).
- Fitting the timing model — the capstone: measure
il_compute’s per-firing cost from a full-pipeline XSI run (the span, gated on no stall), fit the two parameters, and ship the fit to the platform library — a recipe for fitting a custom stage of your own.