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 ``Extern`` protocol * Reference pre-existing HDL modules * Provide source file information via ``@annotation_fileset`` * Define ports as Signal fields * Implemented entirely in SystemVerilog **Example**: .. code-block:: python 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_fileset`` to specify all required source files * Extern 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_if`` attribute in Python tests **Protocol Definition**: The protocol class defines the API methods available to Python tests: .. code-block:: python 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**: .. code-block:: python @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**: .. code-block:: python 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 ``@dataclass`` Zuspec components * Run entirely in pytest/Python * Cannot connect to signals (use TLM interfaces only) * Useful for test orchestration and high-level sequencing **Example**: .. code-block:: python @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: .. code-block:: python @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: .. code-block:: python 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: 1. **Domain Separation**: No signal connections between Python and SV domains 2. **Component Classification**: Correct use of Extern, XtorComponent, Component 3. **Binding Validity**: Signal direction and type compatibility 4. **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 ^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: python @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 ^^^^^^^^^^^^^^ .. code-block:: python @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 ^^^^^^^^^^^^^^^^^^ .. code-block:: python @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)