Components¶
This document describes the component types supported by the HDLSim backend and how to use them effectively.
Component Types¶
Extern Components¶
Extern components wrap existing SystemVerilog modules for integration with Zuspec testbenches.
Key Features:
Inherit from
ExternprotocolReference pre-existing HDL modules
Provide source file information via
@annotation_filesetDefine ports as Signal fields
Implemented entirely in SystemVerilog
Example:
from zuspec.dataclasses import dataclass, Component, Signal, input, output
from zuspec.dataclasses.protocols import Extern, annotation_fileset
@dataclass
class UartModule(Component, Extern):
"""Wrapper for existing uart.sv module."""
# Port declarations
clock: Signal = input()
reset: Signal = input()
tx_data: Signal = input()
tx_valid: Signal = input()
tx_ready: Signal = output()
# Provide source files
@annotation_fileset(sources=["rtl/uart.sv", "rtl/uart_pkg.sv"])
def __post_init__(self):
pass
Usage Guidelines:
Port names and directions should match the HDL module
Use
@annotation_filesetto specify all required source filesExtern components can only connect to other SV-domain components
No Python-side implementation needed
XtorComponent Transactors¶
XtorComponent defines transactors that bridge the Python and SystemVerilog domains.
Key Features:
Inherit from
XtorComponent[ProtocolType]Protocol defines the transaction-level API
SystemVerilog implementation generated by zuspec-be-sv
Python proxy generated via PyHDL-IF
Access via
xtor_ifattribute in Python tests
Protocol Definition:
The protocol class defines the API methods available to Python tests:
class WishboneInitiatorIf:
"""Wishbone bus initiator protocol."""
async def write(self, addr: int, data: int) -> None:
"""Write to address."""
...
async def read(self, addr: int) -> int:
"""Read from address."""
...
async def wait_ack(self) -> None:
"""Wait for acknowledge."""
...
Transactor Definition:
@dataclass
class WishboneInitiator(XtorComponent[WishboneInitiatorIf]):
"""Wishbone bus initiator transactor."""
# Port declarations
clock: Signal = input()
reset: Signal = input()
# Wishbone signals
wb_adr: Signal = output()
wb_dat_o: Signal = output()
wb_dat_i: Signal = input()
wb_cyc: Signal = output()
wb_stb: Signal = output()
wb_we: Signal = output()
wb_ack: Signal = input()
Python Test Usage:
async def test_wishbone_write(tb):
"""Test Wishbone write transaction."""
# Access transactor via xtor_if
await tb.wb_init.xtor_if.write(addr=0x1000, data=0xDEADBEEF)
# Read back
value = await tb.wb_init.xtor_if.read(addr=0x1000)
assert value == 0xDEADBEEF
Usage Guidelines:
Protocol methods should be async for transaction-level operations
Use sync methods for simple queries/configurations
Transactor state is maintained in SystemVerilog
Complex protocol implementations benefit from Protocol-driven design
Python Components¶
Standard Zuspec components that remain in the Python domain.
Key Features:
Standard
@dataclassZuspec componentsRun entirely in pytest/Python
Cannot connect to signals (use TLM interfaces only)
Useful for test orchestration and high-level sequencing
Example:
@dataclass
class TestSequencer(Component):
"""High-level test sequence coordinator."""
def __post_init__(self):
self.test_count = 0
async def run_basic_sequence(self, xtor):
"""Run a basic test sequence."""
await xtor.write(0, 0x100)
await xtor.write(1, 0x200)
self.test_count += 1
def get_stats(self):
"""Return test statistics."""
return {"tests_run": self.test_count}
Usage Guidelines:
Use for test orchestration, sequence management
Can hold test state and statistics
Communicate with SV components via XtorComponent APIs
Cannot access signals directly
Component Bindings¶
Signal Connectivity¶
Use __bind__ to define signal connections between components:
@dataclass
class MyTestbench(Component):
dut: UartModule = inst()
tx_xtor: UartTxXtor = inst()
clkrst: ClkRstXtor = inst()
def __bind__(self):
"""Define signal bindings."""
return (
# Clock/reset connections
(self.clkrst.clock, self.dut.clock),
(self.clkrst.reset, self.dut.reset),
# Data connections
(self.tx_xtor.tx_data, self.dut.tx_data),
(self.tx_xtor.tx_valid, self.dut.tx_valid),
(self.dut.tx_ready, self.tx_xtor.tx_ready),
)
Binding Rules:
Only SV-domain components (Extern, XtorComponent) can have signal bindings
Each binding is a tuple:
(source_signal, dest_signal)Direction must match (output -> input)
Python components cannot participate in signal bindings
Profile and Checker¶
HDLTestbench Profile¶
The HDLTestbench profile enforces domain separation rules:
from zuspec.be.hdlsim.profile import HDLTestbenchProfile
from zuspec.be.hdlsim.checker import HDLTestbenchChecker
# Get profile instance
profile = HDLTestbenchProfile
# Get checker
checker = profile.get_checker()
# Check component
checker.check_component(MyTestbench)
# Report errors
if checker.has_errors():
for error in checker.get_errors():
print(f"Error: {error}")
Validation Rules¶
The checker validates:
Domain Separation: No signal connections between Python and SV domains
Component Classification: Correct use of Extern, XtorComponent, Component
Binding Validity: Signal direction and type compatibility
Protocol Compliance: XtorComponent correctly implements Protocol
Best Practices¶
Design Guidelines¶
Minimize Signal Crossing: Keep SV components together, use TLM at boundaries
Protocol First: Design transaction APIs before implementing transactors
Composition: Build complex testbenches from smaller, reusable components
Separation: Keep test logic (Python) separate from signal protocols (SV)
Performance Considerations¶
Transaction Granularity: Coarser transactions reduce Python/SV crossing overhead
Buffering: Use FIFOs/buffers at domain boundaries
Batch Operations: Group related operations in single API calls
Debugging Tips¶
Use profile checker early and often during development
Generate SV files and inspect them to understand what’s being created
Test transactors independently before integration
Use PyHDL-IF logging to trace Python/SV communication
Common Patterns¶
Clock/Reset Management¶
@dataclass
class ClkRstXtor(XtorComponent[ClkRstIf]):
clock: Signal = output()
reset: Signal = output()
# In test:
await tb.clkrst.reset_pulse(cycles=10)
await tb.clkrst.wait_cycles(100)
Bus Interfaces¶
@dataclass
class BusInitiator(XtorComponent[BusInitiatorIf]):
# Bus signals
...
# In test:
await tb.bus.write_burst(addr=base, data=[1,2,3,4])
result = await tb.bus.read_burst(addr=base, count=4)
Monitor Components¶
@dataclass
class ScoreboardMonitor(Component):
"""Python-domain monitor."""
def __post_init__(self):
self.transactions = []
async def monitor(self, xtor):
"""Monitor transactions."""
while True:
trans = await xtor.wait_transaction()
self.transactions.append(trans)