Memory command queue — the ring-dequeue hook
This is the advanced case: a hook that is not a datapath at all but the
synthesizable half of a transport interface. The
AXI-MM command queue has a Python transactional model
(AXIMMQueue.write / get); its synthesizable side is a hand-written ring-dequeue
hook that is what get lowers to inside a kernel. Where the
block / stream / complex patterns are
hooks you write for your datapath, this one implements a reusable interface
once, and every kernel that dequeues a command gets it for free.
This page is that hook —
waveflow/build/aximm_queue_impl.tpp’s
queue_get — and how the extractor lowers a get call onto it. It is merged and
Vitis-verified: the VMAC generated top calls it
and cosims bit-exact.
It is a stmt-class hook. The general .tpp contract (the bit-exact Python sibling,
the namespace, #pragma HLS INLINE) is Writing a hook; the complex
datapath that consumes the dequeued command is complex.md. This page
is the queue-specific dequeue.
What lowers to it
In a synthesizable run loop, the consumer reads one typed command per iteration:
cmd = yield from self.cmd_queue.get(self.Cmd) # self.cmd_queue: AXIMMQueue
AXIMMQueue.get is decorated @synthesizable(stmt_class=AXIMMQueueGetStmt). The
single-element typed form (get(schema_type)) is the synthesizable one: the extractor
emits an AXIMMQueueGetStmt — an IR node (a SynthCallStmt) whose inputs[0] is
the element schema class and whose outputs[0] is the bound command variable. This is
the exact parallel of StreamGetStmt (a stream get) and the register-map get
stmt; the queue is just a different transport.
Codegen lowers that stmt in
waveflow/build/hwgen.py (_emit_aximm_queue_get)
to a declaration of the command struct plus a call to the queue_get hook,
passing the layout constants from the queue’s AXIMMQueueLayout as template
arguments. So the generated top contains no hand-rolled ring code — only a call into
this hook.
The hook contract
template <typename CmdT, int MEM_BW, int BASE, int CAP, int EW>
void queue_get(ap_uint<MEM_BW>* gmem, CmdT& out);
| param | meaning |
|---|---|
CmdT |
the generated command struct (e.g. VmacCmd) |
MEM_BW |
memory word width in bits (the gmem word) |
BASE |
ring base byte address (word-aligned) |
CAP |
ring capacity in slots (power of two) |
EW |
words per slot (== CmdT.nwords_per_inst(MEM_BW)) |
Two static_asserts enforce the synthesis preconditions: CAP is a power of two
(CAP >= 2 && (CAP & (CAP - 1)) == 0) and BASE is word-aligned. Both come for free
from AXIMMQueueLayout.
What it does
The hook is the single-producer / single-consumer dequeue, in five steps:
- Read
headonce. The consumer ownshead, so it is stable for the call — onem_axiread. - Poll until non-empty. While
tail == headthe ring is empty; the hook waits withpoll_until_ne(tail_word, head)— the synthesizable analogue of the Pythonpoll_untilwait — re-readingtailuntil the producer advances it. - Read one slot. Read
EWwords atslot(head)before advancing the pointer (SPSC ordering: consume the data, then publish that the slot is free). - Advance
head.head = (head + 1) & (CAP - 1)— the% capacitywrap as a bit-mask, which is exactly whyCAPmust be a power of two — and write it back so the producer sees the freed slot. - Deserialize. Turn the slot’s raw words into the typed command with the
generated
out.read_array<MEM_BW>(slot), yielding the populatedCmdT& out.
The command never crosses an s_axilite boundary — it is read straight from memory
and deserialized into a struct of scalars inside the kernel. That is what lets the
generated top be m_axi-only
and sidestep the nested-struct csynth pitfall.
See also
- AXI-MM Command Queue — the Python
AXIMMQueuemodel this hook is the synthesizable half of. - VMAC code generation — the generated top that
calls
queue_get, and the worked end-to-end example. - Writing a hook / Kernel transfer reference — the
general
.tppcontract and the in-kernel transfer calls. - Polling Overhead — the loosely-timed poll model behind the empty-ring wait.