|
CocoTB Framework · Verification Infrastructure for RTL Testing GitHub · Documentation Index · MIT License |
gaxi_factories.py¶
Factory functions for creating GAXI components without the constructor boilerplate. They pass your parameters straight through — what they add is sensible defaults (empty prefixes work for most DUTs), a standard field configuration when you don't supply one, and one-call creation of a whole master/slave/monitor/scoreboard environment.
Overview¶
Every factory here builds the components documented on their own pages, with the same parameters, in the same order, meaning the same things. Old testbenches keep working: legacy parameter names are still accepted, even the ones that are now ignored (they say so). The factories also create a shared MemoryModel for you when you don't supply one, which is what you want more often than not.
Key Features¶
- Sensible defaults — empty string prefixes cover most DUTs
- Same parameter handling in every factory
- Backward compatible — every old parameter still accepted, a few of them ignored and documented as such
- Memory model auto-created when you don't pass one
- Whole-system factories that return every component in a dict, ready to wire up
Field Configuration¶
get_default_field_config(data_width=32)¶
The standard GAXI field config: one data field of the given width. Most smoke tests never need more.
Parameters:
- data_width: Data width in bits (default: 32)
Returns: FieldConfig object for standard data field
# Get default 32-bit data configuration
config = get_default_field_config()
# Get 64-bit data configuration
config_64 = get_default_field_config(data_width=64)
Component Factories¶
create_gaxi_master()¶
Build a GAXIMaster with defaults filled in.
create_gaxi_master(dut, title, prefix, clock, field_config=None, packet_class=None,
randomizer=None, memory_model=None, memory_fields=None, log=None,
signal_map=None, optional_signal_map=None, field_mode=False,
multi_sig=False, mode='skid', bus_name='', pkt_prefix='',
**kwargs)
Parameters:
- dut: Device under test
- title: Component title
- prefix: Signal prefix
- clock: Clock signal
- field_config: Field configuration (default: standard data field)
- packet_class: Packet class produced by the component's pipeline (None = GAXIPacket). Wired through the _build_packet() hook
- randomizer: Timing randomizer (default: standard master constraints)
- memory_model: Memory model for transactions (optional)
- memory_fields: Field mapping for memory operations (unused - kept for compatibility)
- log: Logger instance (default: dut's logger)
- signal_map: Manual signal mapping dict (forwarded to the component; None = automatic signal discovery)
- optional_signal_map: Optional signal mapping (unused - kept for compatibility)
- field_mode: Field mode (unused - kept for compatibility)
- multi_sig: Whether using multi-signal mode
- mode: Operating mode ('skid', 'fifo_mux', 'fifo_flop')
- bus_name: Bus/channel name
- pkt_prefix: Packet field prefix
- **kwargs: Additional arguments
Returns: GAXIMaster instance
# Basic master creation
master = create_gaxi_master(
dut=dut,
title="TestMaster",
prefix="master_",
clock=dut.clk
)
# Advanced master with custom configuration
master = create_gaxi_master(
dut=dut,
title="AdvancedMaster",
prefix="",
clock=dut.clk,
field_config=custom_field_config,
memory_model=shared_memory,
multi_sig=True,
mode='fifo_flop',
log=custom_logger
)
create_gaxi_slave()¶
Build a GAXISlave with defaults filled in.
create_gaxi_slave(dut, title, prefix, clock, field_config=None, field_mode=False,
packet_class=None, randomizer=None, memory_model=None,
memory_fields=None, log=None, mode='skid', signal_map=None,
optional_signal_map=None, multi_sig=False, bus_name='',
pkt_prefix='', **kwargs)
Parameters: Same as create_gaxi_master() with slave-specific defaults
Returns: GAXISlave instance
# Basic slave creation
slave = create_gaxi_slave(
dut=dut,
title="TestSlave",
prefix="slave_",
clock=dut.clk
)
# Slave with shared memory model
slave = create_gaxi_slave(
dut=dut,
title="MemorySlave",
prefix="",
clock=dut.clk,
memory_model=shared_memory,
mode='skid'
)
create_gaxi_monitor()¶
Build a GAXIMonitor watching either side of the interface.
create_gaxi_monitor(dut, title, prefix, clock, field_config=None, is_slave=False,
log=None, mode='skid', bus_name='', pkt_prefix='',
multi_sig=False, signal_map=None, packet_class=None, **kwargs)
Parameters:
- dut: Device under test
- title: Component title
- prefix: Signal prefix
- clock: Clock signal
- field_config: Field configuration (default: standard data field)
- is_slave: If True, monitor slave side; if False, monitor master side
- log: Logger instance (default: dut's logger)
- mode: Operating mode ('skid', 'fifo_mux', 'fifo_flop')
- multi_sig: Whether using multi-signal mode
- bus_name: Bus/channel name
- pkt_prefix: Packet field prefix
- signal_map: Manual signal mapping dict (forwarded to the component; None = automatic signal discovery)
- packet_class: Packet class produced by the receive pipeline (None = GAXIPacket). Wired through the _build_packet() hook
- **kwargs: Additional arguments
Returns: GAXIMonitor instance
# Master-side monitor
master_monitor = create_gaxi_monitor(
dut=dut,
title="MasterMonitor",
prefix="",
clock=dut.clk,
is_slave=False
)
# Slave-side monitor
slave_monitor = create_gaxi_monitor(
dut=dut,
title="SlaveMonitor",
prefix="",
clock=dut.clk,
is_slave=True,
mode='fifo_flop'
)
Custom packet classes¶
Every component factory forwards packet_class into the component's
_build_packet()
hook, so the whole pipeline — receive path, create_packet(), and the
master transmit path — produces your class instead of GAXIPacket:
class MyPacket(GAXIPacket):
pass
slave = create_gaxi_slave(dut, "Slave", "", clock, packet_class=MyPacket)
monitor = create_gaxi_monitor(dut, "Mon", "", clock, packet_class=MyPacket)
# Or apply it to a whole component set at once
components = create_gaxi_components(dut, clock, packet_class=MyPacket)
pkt = monitor._recvQ.popleft()
assert isinstance(pkt, MyPacket) # protocol subclass survives the pipeline
packet_class covers classes constructible as MyPacket(field_config). If
your packet needs additional constructor arguments, subclass the component
and override _build_packet() instead — see the
component base docs.
Leave packet_class out and nothing changes: components produce plain
GAXIPacket instances, as they always have.
create_gaxi_scoreboard()¶
Build a GAXIScoreboard.
Parameters:
- name: Scoreboard name
- field_config: Field configuration (default: standard data field)
- log: Logger instance
Returns: GAXIScoreboard instance
# Basic scoreboard
scoreboard = create_gaxi_scoreboard("TestScoreboard")
# Scoreboard with custom configuration
scoreboard = create_gaxi_scoreboard(
name="CustomScoreboard",
field_config=custom_field_config,
log=custom_logger
)
System Creation Factories¶
create_gaxi_components()¶
Build the whole bench in one call: master, slave, a monitor for each side, a scoreboard, and a shared memory model.
create_gaxi_components(dut, clock, title_prefix="", field_config=None,
field_mode=False, packet_class=None, memory_model=None,
log=None, mode='skid', signal_map=None, optional_signal_map=None,
multi_sig=False, bus_name='', pkt_prefix='',
**kwargs)
Parameters:
- dut: Device under test
- clock: Clock signal
- title_prefix: Prefix for component titles
- field_config: Field configuration (default: standard data field)
- field_mode: Field mode (unused - kept for compatibility)
- packet_class: Packet class produced by the component's pipeline (None = GAXIPacket). Wired through the _build_packet() hook
- memory_model: Memory model for components (auto-created if None)
- log: Logger instance
- mode: Operating mode for slave/monitor
- signal_map: Manual signal mapping dict (forwarded to the component; None = automatic signal discovery)
- optional_signal_map: Optional signal mapping (unused - kept for compatibility)
- multi_sig: Whether using multi-signal mode
- bus_name: Bus/channel name
- pkt_prefix: Packet field prefix
- **kwargs: Additional configuration passed to all components
Returns: Dictionary containing all created components
# Create complete GAXI system
components = create_gaxi_components(
dut=dut,
clock=dut.clk,
title_prefix="GAXI_"
)
# Access components
master = components['master']
slave = components['slave']
master_monitor = components['master_monitor']
slave_monitor = components['slave_monitor']
scoreboard = components['scoreboard']
memory_model = components['memory_model']
create_gaxi_system()¶
Alias for create_gaxi_components() with a trimmed parameter list.
create_gaxi_system(dut, clock, title_prefix="", field_config=None,
memory_model=None, log=None, bus_name='', pkt_prefix='',
**kwargs)
Parameters: Simplified version of create_gaxi_components() parameters
Returns: Dictionary containing all created components and shared resources
# Create GAXI system with clean API
system = create_gaxi_system(
dut=dut,
clock=dut.clk,
title_prefix="System_",
field_config=custom_config
)
create_gaxi_test_environment()¶
The everything-included option: all components plus a few convenience functions (send_data, read_memory, write_memory, get_all_stats) so a simple test never touches the components directly.
Parameters:
- dut: Device under test
- clock: Clock signal
- bus_name: Bus/channel name
- pkt_prefix: Packet field prefix
- **kwargs: Configuration overrides including:
- title_prefix: Component title prefix (default: 'GAXI_')
- field_config: Field configuration
- data_width: Data width (default: 32)
- memory_size: Memory size in lines (default: 1024)
- log: Logger instance
Returns: Dictionary with complete test environment including convenience functions
# Create ready-to-use test environment
env = create_gaxi_test_environment(
dut=dut,
clock=dut.clk,
data_width=64,
memory_size=2048
)
# Use convenience functions
await env['send_data'](0xDEADBEEF)
data = env['read_memory'](0x1000)
env['write_memory'](0x1000, 0x12345678)
all_stats = env['get_all_stats']()
Usage Patterns¶
Basic Component Creation¶
@cocotb.test()
async def test_basic_gaxi(dut):
"""Basic GAXI test with factory-created components"""
# Create individual components
master = create_gaxi_master(
dut=dut,
title="TestMaster",
prefix="",
clock=dut.clk
)
slave = create_gaxi_slave(
dut=dut,
title="TestSlave",
prefix="",
clock=dut.clk
)
# Test basic functionality
await master.reset_bus()
await slave.reset_bus()
# Send test transaction
packet = master.create_packet(data=0xDEADBEEF)
await master.send(packet)
# Verify reception
await Timer(100, units='ns')
received = slave.get_observed_packets()
assert len(received) > 0
Complete System Creation¶
@cocotb.test()
async def test_complete_system(dut):
"""Test with complete GAXI system"""
# Create complete system
# (data_width/memory_size are create_gaxi_test_environment options;
# create_gaxi_system takes a field_config and memory_model instead)
system = create_gaxi_system(
dut=dut,
clock=dut.clk,
title_prefix="Test_",
log=log
)
# Extract components
master = system['master']
slave = system['slave']
master_monitor = system['master_monitor']
slave_monitor = system['slave_monitor']
scoreboard = system['scoreboard']
memory = system['memory_model']
# Connect monitors to scoreboard
master_monitor.add_callback(scoreboard.add_expected)
slave_monitor.add_callback(scoreboard.add_actual)
# Run test sequence
await run_test_sequence(master, slave)
# Check results
stats = {
'master': master.get_stats(),
'slave': slave.get_stats(),
'master_monitor': master_monitor.get_stats(),
'slave_monitor': slave_monitor.get_stats(),
'memory': memory.get_stats()
}
log.info(f"Test completed: {stats}")
Test Environment Creation¶
@cocotb.test()
async def test_with_environment(dut):
"""Test using complete test environment"""
# Create test environment with convenience functions
env = create_gaxi_test_environment(
dut=dut,
clock=dut.clk,
title_prefix="ENV_",
data_width=64,
memory_size=2048
)
# Use convenience functions for testing
test_data = [0xDEADBEEF, 0xCAFEBABE, 0x12345678, 0x87654321]
for i, data in enumerate(test_data):
# Send data using convenience function
await env['send_data'](data)
# Write to memory using convenience function
env['write_memory'](0x1000 + i*4, data)
# Read back and verify
for i, expected_data in enumerate(test_data):
addr = 0x1000 + i*4
read_data = env['read_memory'](addr)
# Convert bytearray to integer for comparison
if isinstance(read_data, bytearray):
read_value = int.from_bytes(read_data, byteorder='little')
else:
read_value = read_data
assert read_value == expected_data, f"Memory mismatch at 0x{addr:X}"
# Get comprehensive statistics
all_stats = env['get_all_stats']()
log.info(f"Environment test completed: {all_stats}")
Advanced Configuration¶
async def create_advanced_gaxi_system(dut, clock):
"""Create advanced GAXI system with custom configuration"""
# Create custom field configuration
field_config = FieldConfig()
field_config.add_field(FieldDefinition("addr", 32, format="hex"))
field_config.add_field(FieldDefinition("data", 64, format="hex"))
field_config.add_field(FieldDefinition("cmd", 4, format="hex", encoding={
0x0: "NOP", 0x1: "READ", 0x2: "WRITE", 0x3: "BURST"
}))
# Create shared memory model
memory = MemoryModel(
num_lines=4096,
bytes_per_line=8, # 64-bit data
log=dut._log
)
# Create components with advanced configuration
master = create_gaxi_master(
dut=dut,
title="AdvancedMaster",
prefix="m_",
clock=clock,
field_config=field_config,
memory_model=memory,
multi_sig=True,
mode='fifo_flop',
)
slave = create_gaxi_slave(
dut=dut,
title="AdvancedSlave",
prefix="s_",
clock=clock,
field_config=field_config,
memory_model=memory,
multi_sig=True,
mode='fifo_flop',
)
# Create monitors for both sides
master_monitor = create_gaxi_monitor(
dut=dut,
title="MasterSideMonitor",
prefix="m_",
clock=clock,
field_config=field_config,
is_slave=False,
multi_sig=True
)
slave_monitor = create_gaxi_monitor(
dut=dut,
title="SlaveSideMonitor",
prefix="s_",
clock=clock,
field_config=field_config,
is_slave=True,
multi_sig=True,
mode='fifo_flop'
)
# Create scoreboard
scoreboard = create_gaxi_scoreboard(
name="AdvancedScoreboard",
field_config=field_config,
log=dut._log
)
return {
'master': master,
'slave': slave,
'master_monitor': master_monitor,
'slave_monitor': slave_monitor,
'scoreboard': scoreboard,
'memory_model': memory,
'field_config': field_config
}
Multi-Instance Systems¶
Several masters, one slave, one shared memory — the factories make the wiring tedious rather than hard, so it helps to wrap it in a function you'll reuse.
async def create_multi_master_system(dut, clock):
"""Create system with multiple masters and one slave"""
# Shared resources
shared_memory = MemoryModel(num_lines=2048, bytes_per_line=4)
shared_config = get_default_field_config(data_width=32)
# Create multiple masters
masters = {}
for i in range(4):
masters[f'master_{i}'] = create_gaxi_master(
dut=dut,
title=f"Master{i}",
prefix=f"m{i}_",
clock=clock,
field_config=shared_config,
memory_model=shared_memory
)
# Create single slave
slave = create_gaxi_slave(
dut=dut,
title="SharedSlave",
prefix="s_",
clock=clock,
field_config=shared_config,
memory_model=shared_memory
)
# Create monitors for each master
monitors = {}
for i in range(4):
monitors[f'master_{i}_monitor'] = create_gaxi_monitor(
dut=dut,
title=f"Master{i}Monitor",
prefix=f"m{i}_",
clock=clock,
field_config=shared_config,
is_slave=False
)
# Slave monitor
monitors['slave_monitor'] = create_gaxi_monitor(
dut=dut,
title="SlaveMonitor",
prefix="s_",
clock=clock,
field_config=shared_config,
is_slave=True
)
# Scoreboard for transaction checking
scoreboard = create_gaxi_scoreboard(
name="MultiMasterScoreboard",
field_config=shared_config
)
return {
'masters': masters,
'slave': slave,
'monitors': monitors,
'scoreboard': scoreboard,
'shared_memory': shared_memory
}
Factory Pattern Integration¶
If your project spins up the same few configurations in every test, put a factory class in front of the factories. Boring, but it pays for itself by the third testbench.
class GAXITestFactory:
"""Factory class for creating GAXI test environments"""
def __init__(self, dut, clock):
self.dut = dut
self.clock = clock
self.default_config = get_default_field_config()
def create_basic_system(self, prefix=""):
"""Create basic GAXI system"""
return create_gaxi_system(
dut=self.dut,
clock=self.clock,
title_prefix=prefix
)
def create_performance_test_system(self, data_width=32, memory_size=4096):
"""Create system optimized for performance testing"""
return create_gaxi_test_environment(
dut=self.dut,
clock=self.clock,
title_prefix="PERF_",
data_width=data_width,
memory_size=memory_size
)
def create_protocol_test_system(self, multi_sig=True, mode='skid'):
"""Create system for protocol compliance testing"""
field_config = FieldConfig()
field_config.add_field(FieldDefinition("addr", 32))
field_config.add_field(FieldDefinition("data", 32))
field_config.add_field(FieldDefinition("prot", 3))
field_config.add_field(FieldDefinition("user", 4))
return create_gaxi_components(
dut=self.dut,
clock=self.clock,
title_prefix="PROT_",
field_config=field_config,
multi_sig=multi_sig,
mode=mode
)
def create_stress_test_system(self):
"""Create system for stress testing"""
return create_gaxi_test_environment(
dut=self.dut,
clock=self.clock,
title_prefix="STRESS_",
data_width=64,
memory_size=8192
)
# Usage
@cocotb.test()
async def test_with_factory(dut):
factory = GAXITestFactory(dut, dut.clk)
# Create different systems for different test phases
basic_system = factory.create_basic_system("BASIC_")
perf_system = factory.create_performance_test_system(64, 2048)
stress_system = factory.create_stress_test_system()
# Run tests with different systems...
Error Handling and Validation¶
Parameter Validation¶
The factories don't hide component validation from you — if a parameter is wrong, the component raises at construction, same as if you'd built it by hand.
# Factories automatically handle parameter validation
try:
master = create_gaxi_master(
dut=dut,
title="TestMaster",
prefix="",
clock=dut.clk,
field_config=invalid_config # Will be validated and corrected
)
except Exception as e:
log.error(f"Factory creation failed: {e}")
Default Fallbacks¶
Anything you don't pass gets the boring, usually-right default: standard field config, the DUT's logger, empty prefixes.
# Factories provide sensible defaults for all parameters
master = create_gaxi_master(dut, "Master", "", dut.clk)
# Uses: default field config, dut's logger, empty prefixes, etc.
Memory Model Auto-Creation¶
# Memory model is automatically created if not provided
system = create_gaxi_components(dut, dut.clk)
# Automatically creates MemoryModel(1024, 4) with proper configuration
Best Practices¶
1. Use Empty String Prefixes for Most Cases¶
2. Create Complete Systems for Full Testing¶
# Use create_gaxi_test_environment for comprehensive testing
env = create_gaxi_test_environment(dut, dut.clk)
# Includes all components plus convenience functions
3. Share Memory Models Across Components¶
One memory, many components — otherwise your master and slave disagree about what's stored where.
# Share memory for consistent state
memory = MemoryModel(1024, 4, log=log)
master = create_gaxi_master(dut, "Master", "", dut.clk, memory_model=memory)
slave = create_gaxi_slave(dut, "Slave", "", dut.clk, memory_model=memory)
4. Use Multi-Signal Mode for Complex Protocols¶
# Enable multi-signal mode for protocols with many fields
components = create_gaxi_components(dut, dut.clk, multi_sig=True)
5. Use Factory Classes for Complex Testbenches¶
# Create factory class for standardized test setups
factory = GAXITestFactory(dut, dut.clk)
system = factory.create_protocol_test_system()
Backward Compatibility¶
The factories keep full backward compatibility:
- Every parameter that used to exist still exists, in the same position, meaning the same thing
- Defaults improved only where old code didn't depend on them
- New options default to the old behavior
- Legacy names are still accepted (
field_mode,optional_signal_map) even when they're ignored;signal_mapis forwarded to the component
# Legacy code continues to work unchanged
components = create_gaxi_components(
dut, clock, title_prefix="Legacy_", field_mode=True,
signal_map=old_signal_map, optional_signal_map=old_optional_map
)
Use the factories unless you have a reason not to. Everything they do is documented on the component pages, and you can always do it by hand — but for the common case of "give me a working GAXI environment", one call beats five.