Defining a component
The class
A hardware module is a subclass of HwComponent, which
extends Component (the base simulation object with named
endpoints and the SimPy lifecycle). Components are written as dataclasses; runtime configuration and
simulation-only state are plain fields, while synthesis knobs use HwParam and
HwConst.
A few class-level (ClassVar) settings steer the generated C++ but are inert in simulation:
cpp_kernel_name (override the kernel function name), cpp_namespace (the hook namespace), and
param_supports (emit variant kernels) — all covered in Component Code Generation.
Declaring endpoints
A component’s ports are interface endpoints, declared in __post_init__ and registered with
add_endpoint(...) (which records the endpoint under its name
and back-links it to the component). From
examples/regmap/simp_fun.py:
@dataclass
class SimpFunComponent(HwComponent):
cpp_kernel_name: ClassVar[str | None] = "simp_fun"
cpp_namespace: ClassVar[str | None] = "simp_fun_impl"
clk: Clock = field(default_factory=lambda: Clock(freq=100e6))
def __post_init__(self) -> None:
super().__post_init__()
self.regmap = VitisRegMap({
"x": RegField(Int32, RegAccess.RW, description="Input operand"),
"a": RegField(Int32, RegAccess.RW, description="Multiply coefficient"),
"b": RegField(Int32, RegAccess.RW, description="Bias term"),
"y": RegField(Int32, RegAccess.R, description="relu(a*x + b)"),
})
self.s_lite = VitisRegMapMMIFSlave(
name=f"{self.name}_s_lite", sim=self.sim, bitwidth=32,
regmap=self.regmap, on_start=self.on_start,
)
self.add_endpoint(self.s_lite)
simp_fun declares one endpoint — an AXI-Lite register map
(VitisRegMapMMIFSlave). A component with a datapath declares more:
examples/stream_inband/poly.py’s PolyAccelComponent
declares a stream input, a stream output, and a regmap, and registers all three:
def __post_init__(self) -> None:
super().__post_init__()
self.s_in = StreamIFSlave( name=f'{self.name}_s_in', sim=self.sim, bitwidth=self.in_bw)
self.m_out = StreamIFMaster(name=f'{self.name}_m_out', sim=self.sim, bitwidth=self.out_bw)
self.s_lite = VitisRegMapMMIFSlave(name=f'{self.name}_s_lite', sim=self.sim, ...)
for ep in (self.s_in, self.m_out, self.s_lite):
self.add_endpoint(ep)
Which endpoint types exist, and the master/slave role of each, is Endpoint methods; the endpoint classes and their construction live in Interfaces.
Behavior and compute
A component’s behavior is the methods on its endpoints, driven from a lifecycle
method — on_start for a regmap-launched component (like simp_fun) or run_proc for a free-running
one. Compute that must synthesize to hardware is marked
@synthesizable:
def on_start(self) -> ProcessGen[None]:
y = self.compute(self.regmap.get("x"), self.regmap.get("a"), self.regmap.get("b"))
self.regmap.set("y", y)
@synthesizable
def compute(self, x: Int32, a: Int32, b: Int32) -> Int32:
return Int32(relu_affine(int(x.val), int(a.val), int(b.val)))
Execution models
How you structure that behavior puts the component in one of two modes — and the mode follows from its endpoints:
- Free-running (
run_proc). The default: a long-lived lifecycle process that loops over the component’s stream /m_axiports, the way a streaming datapath runs continuously. You implementrun_proc. - Regmap-launched (
on_start). Invocation-style. A component that declares aVitisRegMapMMIFSlaveis launched by the host (which writesap_start); itson_startreads its inputs from the register fields, computes, writes the results, and returns once.simp_funabove is this mode;polyuses it as a control path while streaming sample data throughs_in/m_out.
The selection is automatic — you don’t set it explicitly: a component carrying a VitisRegMapMMIFSlave is
regmap-launched (you implement on_start); otherwise it is free-running (you implement run_proc). The C++
realization of each — the ap_start / ap_done handshake vs. ap_ctrl_hs, and how the kernel entry is
chosen — is Component Code Generation: Component structure.
The same class generates C++
This one class is also the source for the synthesizable kernel: the generator turns the component
into a top-level Vitis HLS function whose arguments are these same endpoints, and lowers the
@synthesizable methods into the kernel body. That dual view — same simp_fun component, as a
generated void simp_fun(ap_int<32>& x, …) — is
Component Code Generation: Component structure.
Quick reference
- Subclass
HwComponent(a dataclass); callsuper().__post_init__()first. - Declare each port in
__post_init__and register it withadd_endpoint(...). - Keep synthesis knobs in
HwParam/HwConst; everything else is a plain field. - Drive behavior from
on_start(regmap-launched) orrun_proc(free-running) — see Lifecycle. - Mark hardware compute
@synthesizable; the endpoint methods move the data.