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 gatherY[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-running FreeRunMod stages — 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 / MemWStream as 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_axi bus 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_none hls::task top, 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, the latency + 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.

Table of contents