Flow steps
The sequential flow starts from a Python module description, simulates and refines it entirely in Python, then lowers the module and its testbench to C++ and lets Vitis do the rest — first functionally (C-simulation), then against the real RTL (co-simulation). Every step below is walked with its real code in the register-map example.
flowchart LR
subgraph py["Python (source of truth)"]
direction TB
HA["HostActivated<br/>SimpFun"]
TB["SeqTB<br/>SimpFunTBHls"]
end
HA --> PS["PySim<br/>(Simulation environment)"]
PS --> V["golden + test vectors<br/>+ timing prediction"]
HA -->|"HlsCodegenStep"| K["kernel C++<br/>simp_fun.hpp / .cpp"]
TB -->|"HlsCodegenStep<br/>(is_testbench)"| M["testbench C++<br/>simp_fun_tb.cpp (int main)"]
K --> CS["C-simulation"]
M --> CS
K -->|"csynth"| RTL["Kernel RTL<br/>(ap_ctrl_hs)"]
M --> CO["C/RTL co-simulation"]
RTL --> CO
V -.->|"check"| CS
V -.->|"check"| CO
The steps
1 · Module source. First, describe the hardware you want as a
HostActivated component — for the example, SimpFun. This Python class
is the design: its on_start is the behavior and its register map is the boundary. Everything
downstream is derived from it. (regmap example: Python model.)
2 · PySim. Before generating any Vitis or RTL code, Waveflow lets you simulate the module in
Python. A HostActivated is an ordinary SimObj, so you are free to construct
whatever simulation environment the application needs to evaluate and refine the design — for
simp_fun, a host SimObj drives the DUT concurrently over a real AXI-Lite link and checks it
computes relu(a·x + b). The simulation is run by constructing a Simulation and
handing it the objects. By convention this step is called PySim; it is also where you generate the
artifacts the later gates check against — test vectors, the expected outputs (the golden), and the
predicted timing. (regmap example: System simulation.)
3 · Testbench. Next, write the testbench that validates the design’s function and timing. It is
usually simpler than the full PySim — often it just reads PySim’s test vectors, injects them into the
device, and compares the outputs. In the sequential flow the testbench is a SeqTB: a sequential
program whose one essential step is running the kernel (a call). The same SeqTB.main() has two
lives — run in Python it produces the timed golden; lowered to C++ (next step) it becomes the testbench
Vitis executes. (regmap example: Sequential execution.)
4 · Codegen — one step, two modes. The same build step, HlsCodegenStep
(waveflow/build/hwcodegen_steps.py), lowers both:
| Input | Step | Output |
|---|---|---|
SimpFun |
HlsCodegenStep |
simp_fun.hpp + simp_fun.cpp (the kernel), plus a hand-written hook stub simp_fun_compute_impl.cpp |
SimpFunTBHls |
HlsCodegenStep(is_testbench=True) |
simp_fun_tb.cpp — a single int main(), no header |
The example’s BuildDag names these two instances gen_kernel and gen_tb. The kernel C++ is what
csynth turns into the ap_ctrl_hs kernel RTL; the testbench C++ is a straight-line program that
calls the kernel. (regmap example: Code generation.)
5 · C-simulation. Vitis compiles the testbench together with the kernel’s C++ and runs main() —
untimed, no hardware. The kernel call is literally a C++ function call. This is the functional check:
its outputs are compared against the Python golden (gate 1).
6 · C-synthesis. The kernel C++ is synthesized to RTL. Only the kernel is synthesized — the testbench stays C++.
7 · C/RTL co-simulation. Vitis re-runs the same main(), but now each kernel call drives the
synthesized RTL through the ap_start → wait-for-ap_done handshake. Because the DUT is a function,
Vitis generates that RTL harness for you. This is where the cycle count comes from, compared against
the Python timing prediction (gate 3).
One ordering constraint is worth noticing: csynth consumes the C-sim result — synthesis does not run until C-simulation has passed. There is no point measuring the timing of a kernel that computes the wrong answer.
The full
BuildDagforsimp_funhas more nodes — building the test vectors, a synthesis-report gate (II ≤ 1), and rendering the timing diagram — and it splits PySim in two: a concurrent host-drives-DUT system sim (SystemSimStep) and the single-processSeqTBgolden run (PySimStep). The three verification gates are walked in the regmap example’s C and RTL simulation page.
Source of truth: waveflow/build/hwcodegen_steps.py (HlsCodegenStep, its produces()),
waveflow/build/hwgen.py (kernel_files_to_str), examples/regmap/simp_fun_build.py (the DAG).