HLS code generation

The second group derives the C++ Vitis HLS sources — the kernel, the testbench, and the per-schema utility headers — from the same Python definitions the golden model uses.

Step Produces What it does
gen_include include_dir Generates include/*.h — one header per DataSchema class plus the streamutils and <elem>_array_utils helpers
gen_kernel poly_hpp, poly_cpp, poly_evaluate_impl HlsCodegenStep(comp_class=PolyAccelComponent): emits gen/poly.hpp + gen/poly.cpp from PolyAccelComponent.on_start; touches the sticky poly_evaluate_impl.tpp impl only if absent
gen_tb poly_tb HlsCodegenStep(comp_class=PolyTBHls, is_testbench=True): emits gen/poly_tb.cpp from PolyTBHls.main

Symmetry: kernel and testbench are the same step

The most surprising thing about this group is that the kernel and testbench are produced by the same step typeHlsCodegenStep — just with is_testbench=True on the testbench instance:

dag.add(HlsCodegenStep(
    name="gen_kernel",
    comp_class=PolyAccelComponent,
    source_artifact="poly_source",
    output_dir="gen",
    impl_dir=".",
))

dag.add(HlsCodegenStep(
    name="gen_tb",
    comp_class=PolyTBHls,
    source_artifact="poly_source",
    output_dir="gen",
    is_testbench=True,
))

The kernel-side codegen reads PolyAccelComponent.on_start (a SimPy coroutine) and emits a Vitis HLS C++ free function with the matching signature and AXI-Lite + AXI-Stream interface pragmas. Hooks marked @synthesizable (like evaluate) get a forward declaration in the header and a sticky impl-file stub at impl_dir/. The impl stub is written only if absent — your hand-written body survives subsequent runs.

The testbench-side codegen reads PolyTBHls.main() (a straight-line Python program) and emits int main(int argc, char** argv) with all the same stream / regmap / file-IO patterns lowered to streamutils::* / <elem>_array_utils::* calls.

What gets emitted

gen/
├── poly.hpp            # generated, always rewritten
├── poly.cpp            # generated, always rewritten
└── poly_tb.cpp         # generated, always rewritten

poly_evaluate_impl.tpp  # sticky hand-written hook (Horner evaluation)

include/
├── poly_cmd_hdr.h       poly_cmd_hdr_tb.h
├── poly_resp_hdr.h      poly_resp_hdr_tb.h
├── coeff_array.h        coeff_array_tb.h
├── float32_array_utils.h  float32_array_utils_tb.h
└── streamutils_hls.h    streamutils_tb.h

The hand-written hook

PolyAccelComponent.evaluate is marked @synthesizable — the codegen emits a forward declaration in poly.hpp and a stub at poly_evaluate_impl.tpp. This is where the actual Horner-method polynomial body lives:

// poly_evaluate_impl.tpp
namespace poly_impl {
PolyError evaluate(PolyCmdHdr cmd_hdr,
                   hls::stream<...> & s_in,
                   hls::stream<...> & m_out,
                   float coeffs[4]) {
    // hand-written Horner loop ...
}
}

The file is .gitignored-aware: re-runs of gen_kernel will not overwrite it once it exists, so future codegen-driven refactors of PolyAccelComponent.on_start do not stomp the hand-tuned compute body.

Run just this group

python -m examples.stream_inband.poly_build --through gen_tb

Produces every gen/*.cpp/.hpp and include/*.h the Vitis steps in Group 3 consume.


Next: C-sim functional verification →


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