Templating
This page is the C++ realization of component parameterization; the concept — what HwParam and
HwConst are, and when to use each — is Hardware Components: Parameterization.
Here we cover how each lowers into generated code.
How HwParam lowers
A HwParam value reaches codegen as an HwParamValue — an
int that also carries its .param_name. That dual nature lets the generator emit either a literal
value or a template-parameter name, depending on where it lands:
- Top kernel signature → literal width. Top-level kernels are emitted concrete (no
template <int …>block) so Vitis HLS can attach the AXI interfaces. The port widths inkernel_signature(comp)come from_stream_template_arg, which always emits the literal integer from the endpoint’sbitwidth(the variant’sHwParamValue). So aStreamIFMasterdeclared without_bw = 64becomeshls::stream<streamutils::axi4s_word<64>>&in the top. .tpphook → template parameter. A hand-written hook is emitted as a templated function (hook_signature(method, template_params=…)gives its stream args names likeWORD_BW), so the hook stub is written to a.tpprather than a.cpp—HlsCodegenStepselects the extension per hook. Emitting it as.tppkeeps the template definition visible through the generated header’s include path while the impl file stays sticky across rebuilds.
The decision between the two is HwParamValue.param_name: SynthContext.cpp_param(name) returns the
template-parameter name for a HwParam field, or repr(value) for a plain literal.
How HwConst lowers
A HwConst is a class-level structural constant intended to
emit as a C++ static constexpr T name = value;. This emission is currently deferred (a follow-up
phase): discover_hw_const(cls) already surfaces the fields, but the generator does not yet write the
static constexpr lines. In the meantime a HwConst shapes generated structure indirectly through the
Python values it feeds (e.g. a static array extent that sizes a buffer).
param_supports — emitting variant kernels
param_supports turns one component class into multiple
concrete kernel tops. Each variant key maps to a dict of HwParam overrides; codegen emits
<cpp_kernel_name>_<key> for each, alongside the default <cpp_kernel_name>.
class MyKernel(HwComponent):
cpp_kernel_name = "my_kernel"
in_bw: HwParam[int] = 32
param_supports = {
"bw64": {"in_bw": 64},
"bw128": {"in_bw": 128},
}
emits my_kernel, my_kernel_bw64, and my_kernel_bw128 — three concrete tops with fixed widths.
The mechanism: _iter_variants(comp_class) first validates with
validate_param_supports, then yields the default variant
(suffix "") followed by one instance per param_supports entry — each built through the normal
__init__ with the overrides applied (no immutability bypass). For each, kernel_signature(comp,
variant_suffix=key) names the top <base>_<key> and fills its ports with that instance’s concrete
HwParamValue widths. This is the path to take when hardware integration needs concrete top
signatures rather than a single templated top.
API
HwParamValue— int +.param_name; drives template-name-vs-literal.kernel_signature(comp, variant_suffix="")— the concrete top signature; appends_<suffix>for variants.param_supports/validate_param_supports— the variant map and its validation.HlsCodegenStep— selects.cppvs.tppper hook.
Quick reference
- Top kernels are concrete —
HwParambecomes a literal width in the signature. - Hand-written hooks are templated —
HwParambecomes a.tpptemplate parameter. HwConst→static constexpris deferred; the value still shapes structure via Python.param_supports→ one concrete<kernel>_<key>top per variant (default always emitted).- The concept and the
HwParamvsHwConstframing are in Parameterization.