Generated files
Emission is the last step: the resolved HwStmt tree becomes files. The output is
deterministic — the same component always produces byte-identical C++, which is what makes the
generated tree reviewable and lets tests assert on it.
Two lifecycles, and the difference matters
| File | Owner | Lifecycle |
|---|---|---|
<kernel>.hpp / <kernel>.cpp |
framework | rewritten from scratch every run |
<kernel>_tb.cpp |
framework | rewritten every run |
<kernel>_<hook>_impl.cpp (or .tpp) |
you | sticky — written once if absent, then never touched |
The sticky rule is the whole hook contract: codegen writes a stub the first time so you have a signature to fill in, then stays out of the way forever. Your edits survive every rebuild.
These two must not share a directory. output_dir is a build product — gen/ is .gitignored and
rewritten. impl_dir holds source you maintain and commit. Putting hooks in gen/ would leave your
hand-written C++ untracked and adjacent to files that get regenerated.
Generating in a build DAG
From examples/regmap/simp_fun_build.py:
dag.add(HlsCodegenStep(
name="gen_kernel",
comp_class=SimpFun,
source_artifact="simp_fun_source",
output_dir="gen", # framework-owned, .gitignored
impl_dir=".", # your sticky hook files, committed
))
dag.add(HlsCodegenStep(
name="gen_tb",
comp_class=SimpFunTBHls,
source_artifact="simp_fun_source",
output_dir="gen",
is_testbench=True, # emits one <kernel>_tb.cpp instead
))
source_artifact names the DAG artifact for the component’s .py — the dependency that makes codegen
re-run when you edit the Python.
Names
- The kernel top is a global function,
void simp_fun(...)— Vitis needs an unqualified entry point to attach interfaces to.cpp_kernel_name(comp_class)gives the name (CamelCase → snake_case, trailing_componentdropped); override withcpp_kernel_name. - Hooks are namespaced, defaulting to
<kernel>_impl— so the call site issimp_fun_impl::compute(...). Override withcpp_namespace; set it to""to emit hooks in the global namespace.
The default appends _impl rather than reusing the kernel name because a namespace and a function
cannot share a name in one scope — void square(...) beside namespace square {...} is ill-formed
C++.
The generated header shows both halves:
void simp_fun(
ap_int<32>& x,
ap_int<32>& a,
ap_int<32>& b,
ap_int<32>& y
);
namespace simp_fun_impl {
ap_int<32> compute(ap_int<32> x, ap_int<32> a, ap_int<32> b);
}
.cpp vs .tpp
A hook’s stub extension follows from whether it is templated: a hook whose signature carries a
template parameter (a HwParam-derived width) is emitted as .tpp so the
definition stays visible through the header’s include path; a concrete hook is a plain .cpp.
If a hook’s expected extension changes — it gains or loses a template parameter — the old file becomes stale, and codegen treats the mismatch as an error rather than silently leaving an orphan the compiler might still pick up.
API
kernel_files_to_str(comp_class, output_dir=".", impl_dir=None)—{filename: contents}for a kernel.tb_files_to_str(tb_class)— the same for a testbench.HlsCodegenStep— writes them in a DAG (comp_class,source_artifact,output_dir,impl_dir,is_testbench).cpp_kernel_name(comp_class)/resolved_namespace(comp_class)— the two names above.
Quick reference
- File set:
<kernel>.hpp,<kernel>.cpp, one_impl.{cpp,tpp}per hook; or one<kernel>_tb.cpp. output_dir(gen/) is rewritten every run;impl_dirfiles are sticky — written once, then yours.- Never point
impl_diratgen/: it is.gitignored. - Kernel tops are global; hooks default to the
<kernel>_implnamespace. .tppmarks a templated hook; a stale extension is an error, not a warning.