Endpoint interfaces
Most of the time you do not need this page. Declare an endpoint in Python and the mapping to Vitis ports is handled for you — codegen derives the kernel’s whole argument list and every
#pragma HLS INTERFACEfrom your endpoints, and you never write one by hand.You need it when you look inside the generated kernel. Chiefly: writing a custom hook, because the C++ arguments your hook receives are these ports — you cannot write the body without knowing that a stream endpoint arrives as an
hls::stream<axi4s_word<bw>>&and anm_aximaster as a plain pointer. Also when reading a synthesized block’s port list to integrate or debug it.
Concept
This page is organized per kind — stream, m_axi, regmap, bram. Where those kinds come from, and the fact that the XSI testbench reads the same vocabulary to pick its BFM models, is Endpoint kinds.
A generated kernel’s argument list and its #pragma HLS INTERFACE block are derived directly from
the component’s declared endpoints by
kernel_signature(comp). Each endpoint type maps to a specific
Vitis port realization, emitted in a canonical order — streams, then regmap fields, then m_axi
masters.
| Endpoint (Python) | Kernel argument | Interface pragma |
|---|---|---|
StreamIFMaster / StreamIFSlave |
hls::stream<streamutils::axi4s_word<bw>>& <name> |
#pragma HLS INTERFACE axis port=<name> |
VitisRegMapMMIFSlave (per field) |
<cpp_type>& <field> (or <elem>[<count>] for a raw array field) |
#pragma HLS INTERFACE s_axilite port=<field> bundle=control |
m_axi master (e.g. MMIFMaster / DirectMMIF master) |
ap_uint<bw>* <name> |
#pragma HLS INTERFACE m_axi port=<name> offset=slave bundle=gmem depth=<name>_depth — plus s_axilite port=<name> bundle=control when the component has a regmap (see below) |
Stream endpoints → axis
Every stream endpoint becomes an hls::stream reference of AXI4-Stream words, with an axis
interface pragma. The word bitwidth is the endpoint’s concrete bitwidth (or the variant’s
HwParamValue). The generated poly kernel:
void poly(
hls::stream<streamutils::axi4s_word<32>>& s_in,
hls::stream<streamutils::axi4s_word<32>>& m_out,
...
Master and slave streams both realize as hls::stream<...>& — the direction is a property of how the
body reads or writes them, not of the port type.
Regmap slave → s_axilite + ap_ctrl
A VitisRegMapMMIFSlave does not become one port — it expands
to one s_axilite port per user-declared register field, each on bundle=control. The
generated simp_fun kernel turns the x / a / b / y register fields into four scalar
references:
void simp_fun(
ap_int<32>& x,
ap_int<32>& a,
ap_int<32>& b,
ap_int<32>& y
);
The ap_start / ap_done control bits are not emitted as data ports; they are the
control protocol. When a regmap drives the component, the kernel’s return port also binds
s_axilite ... bundle=control, giving the host the ap_start/ap_done handshake that launches the
regmap-launched on_start body.
m_axi master → m_axi pointer
A memory-mapped master endpoint becomes an ap_uint<bw>* pointer with an m_axi pragma
(offset=slave bundle=gmem), the burst region bounded by a generated <name>_depth header constant.
The generated hist kernel:
void hist(
hls::stream<streamutils::axi4s_word<32>>& s_in,
hls::stream<streamutils::axi4s_word<32>>& m_out,
ap_uint<32>* m_mem
);
(Note the canonical order: the two streams precede the m_mem master.) Reading and writing through
this pointer inside the body — the lane/slice transactions — is Custom Hooks
material.
offset=slave needs a home for the pointer
offset=slave means the pointer’s base address is not a port — it arrives in an AXI-Lite register
that the host writes before launching. But bundle=gmem names the m_axi bundle; it says nothing
about where that offset register lives. So when the component has a regmap, codegen also emits
#pragma HLS INTERFACE s_axilite port=m_mem bundle=control
binding the offset register into the same control slave as ap_start/ap_done. Without it Vitis
silently invents a second AXI-Lite bundle for the offset alone, and the kernel exposes two address
spaces (s_axi_control and an auto-named s_axi_control_r) — one block, two slaves, for no reason.
A component with an m_axi but no regmap still has this problem. There is no
controlbundle for the offset to join, so Vitis auto-creates one andap_startstays on raw pins — meaning the block needs two different masters: one to write the base address over AXI-Lite, another to pulse a wire.block_scaleis in this state today.histwas, until it gained a regmap and becameHostActivated. This is a good reason to give a memory-mapped kernel a regmap even when it has no scalar arguments to put in one.
The control protocol on return
ap_ctrl_hs is the protocol — ap_start / ap_done / ap_idle / ap_ready. Which pragma binds
the return port decides only how those signals are reached, and follows from the endpoint mix (in
kernel_signature):
| Endpoint mix | return binds |
What the RTL exposes |
|---|---|---|
| a regmap slave is present | s_axilite ... bundle=control |
ap_start is not a port — Vitis generates a <kernel>_control_s_axi adapter that drives it from register 0x00 |
| else m_axi masters present | ap_ctrl_hs |
ap_start/ap_done/ap_idle/ap_ready are top-level pins, driven by whatever instantiates the block |
| else (stream-only) | s_axilite ... bundle=control |
as the first row |
The first row is worth dwelling on, because it explains a name: s_axilite port=return does not give
you a different kernel — it gives you the same pin-driven core plus a generated AXI-Lite→pin adapter.
That adapter is a real, separate RTL module, and on_start is its callback — which is why on_start
exists exactly when a regmap does.
Neither row is “better”: a host-launched accelerator (XRT) needs the AXI-Lite control registers, while a block launched by another block inside an IPI system wants the raw pins. See Hardware modules and Flows.
ap_ctrl_none— free-running, no handshake at all — is a third protocol that nothing generates yet (thefree_running_kerneltarget). Note a component whose entry isrun_procrather thanon_startis not free-running; it isap_ctrl_hson raw pins, per the middle row.
How a slave endpoint’s handler binds
A slave endpoint carries the Python handler that becomes (part of) the synthesized body:
- A
VitisRegMapMMIFSlaveis constructed withon_start=self.on_start(seeexamples/regmap/simp_fun.py). That handler is exactly the methodextract_kernellowers as the kernel body — the regmap slave both adds thes_axiliteports and designates theap_start-triggered entry point. - For stream / m_axi endpoints there is no separate handler method: the free-running
run_procbody reads and writes them directly, and those per-port read/write calls are lowered tohls::stream/m_axitransactions. The transaction methods themselves (read_stream_lane,read_array_lane, …) are documented in Custom Hooks: Kernel transfer reference.
API
kernel_signature(comp, variant_suffix="")— builds the concrete top-function signature +#pragma HLS INTERFACElines from the endpoints.StreamIFMaster/StreamIFSlave— stream endpoints →axisports.VitisRegMapMMIFSlave— regmap slave →s_axilitefield ports + control protocol; carries theon_starthandler.MMIFMaster/DirectMMIF— memory-mapped master →m_axipointer.
Quick reference
- Argument order is canonical: streams → regmap fields → m_axi masters.
- Stream endpoint ⇒
hls::stream<axi4s_word<bw>>&+axis. - Regmap slave ⇒ one
s_axiliteport per field +ap_start/ap_donecontrol; itson_startis the kernel body. - m_axi master ⇒
ap_uint<bw>*+m_axi offset=slave bundle=gmem depth=<name>_depth. returnprotocol:s_axilitewhen a regmap (or stream-only) drives control,ap_ctrl_hsfor m_axi-only kernels.