Code generation — the synthesizable top

VMAC’s synthesizable top is auto-extracted from the same run_proc the Python simulation runs. There is no hand-written top in the build path: the framework lowers the extracted run_proc IR to a free-running C++ kernel, and a Vitis cosim checks it bit-exact against the one golden.

The generated path is the real one

vmac_topgen.py generates the top from the extracted run_proc IR through the framework kernel_to_cpp path (kernel_signature + kernel_body_to_cpp). The result is a free-running, m_axi-only kernel that:

  1. dequeues a VmacCmd from the in-memory ring — the synthesizable AXIMMQueue.get lowered to the queue_get ring-dequeue hook;
  2. stops if the command is OpCode.end;
  3. otherwise runs vmac_compute (the hand-written datapath hook) and loops.

It is wired into the build as the topgen_cosim step, which builds a ring image in memory (head/tail/capacity + one command slot + an END slot via the producer-side VmacCmd.serialize), lays the A/B operands after the ring, invokes the generated top, and checks the Y region bit-exact against vmac_golden_mem.apply_golden (which runs the one execute golden — no second golden):

cd examples/vmac
python vmac_build.py --through topgen_cosim   # the auto-extracted top, bit-exact cosim (Vitis)

So the generated top and the synthesizable queue dequeue are done and validated, not aspirational.

Why the top has only m_axi + ap_ctrl

The generated top exposes exactly two interfaces: the m_axi gmem master and the ap_ctrl block-control handshake. There is no s_axilite command port. The command never crosses a register boundary — it is read from the ring in memory by the dequeue hook and deserialized inside the kernel.

This is deliberate (decision D6) and it sidesteps a csynth pitfall. Passing the nested VmacCmd struct in over s_axilite (or by value into the synthesizable path) mis-decomposes through HLS’s struct/array optimization at C-synthesis — loop bounds can fold to zero and the kernel gets dead-code-eliminated. That is exactly the gotcha the Custom Hooks: Complex guide documents, and it is why the old hand-rolled top had to unpack the command into scalar registers by hand. Reading the command from memory removes the boundary entirely: the deserialized fields are already plain scalars by the time they reach vmac_compute_core.

How the pieces fit

The generated top is the glue; the datapath is hand-written:

The generated top calls into both hooks; codegen emits the calls, you wrote the bodies.

Transitional note

The build still contains hand-rolled render_top / render_cosim_tb f-strings (the ones that unpack the command into s_axilite scalars). They are not the current top — they remain only because the calibration pipeline (vmac_cosim_sweep.py / vmac_cosim_stage3.pycalibration/vmac_calibration.json, the queue-sim baseline behind the timing study) still shares them. Migrating the calibration sweep onto the generated top and re-running the Vitis sweep is a follow-up; the generated path above is what produces the kernel.


This site uses Just the Docs, a documentation theme for Jekyll.