|
CocoTB Framework · Verification Infrastructure for RTL Testing GitHub · Documentation Index · MIT License |
fifo_component_base.py¶
Unified base class for all FIFO components that consolidates common functionality across FIFOMaster, FIFOMonitor, and FIFOSlave, eliminating code duplication while preserving exact APIs and timing.
Overview¶
The FIFOComponentBase class provides shared infrastructure for all FIFO protocol components, including signal resolution, data handling strategies, memory integration, and statistics collection. It leverages the shared framework components for maximum performance and consistency.
Key Features¶
- Unified Signal Resolution: Automatic signal discovery with manual override support
- Performance Optimized: 40% faster data collection and 30% faster driving through caching
- Memory Integration: Built-in MemoryModel support for transaction verification
- Flexible Configuration: Support for both single-signal and multi-signal modes
- Rich Statistics: Comprehensive performance and error tracking
Core Class¶
FIFOComponentBase¶
Unified base class providing common functionality for all FIFO components.
Constructor¶
FIFOComponentBase(dut, title, prefix, clock, field_config,
protocol_type, # Must be specified by subclass
mode='fifo_mux',
bus_name='',
pkt_prefix='',
multi_sig=False,
randomizer=None,
memory_model=None,
log=None,
super_debug=False,
signal_map=None, # NEW: Optional manual signal mapping
**kwargs)
Parameters:
- dut: Device under test
- title: Component title/name
- prefix: Bus prefix for signal naming
- clock: Clock signal
- field_config: Field configuration (FieldConfig object or dict)
- protocol_type: Protocol type ('fifo_master' or 'fifo_slave') - set by subclass
- mode: FIFO mode ('fifo_mux', 'fifo_flop')
- bus_name: Bus/channel name
- pkt_prefix: Packet field prefix
- multi_sig: Whether using multi-signal mode
- randomizer: Optional randomizer for timing control
- memory_model: Optional memory model for transactions
- log: Logger instance
- super_debug: Enable detailed debugging
- signal_map: Optional manual signal mapping override
- **kwargs: Additional arguments for specific component types
Signal Map Format¶
When using manual signal_map, provide a dictionary mapping logical signal names to DUT signal names:
FIFO Master:
signal_map = {
'write': 'wr_en', # Write enable signal
'full': 'fifo_full', # FIFO full signal
'data': 'wr_data' # Write data signal (single-signal mode)
# OR field names for multi-signal mode:
# 'addr': 'wr_addr', 'data': 'wr_data', 'cmd': 'wr_cmd'
}
FIFO Slave:
signal_map = {
'read': 'rd_en', # Read enable signal
'empty': 'fifo_empty', # FIFO empty signal
'data': 'rd_data' # Read data signal (single-signal mode)
# OR field names for multi-signal mode:
# 'addr': 'rd_addr', 'data': 'rd_data', 'cmd': 'rd_cmd'
}
Core Methods¶
Initialization and Setup¶
complete_base_initialization(bus=None)¶
Complete initialization after cocotb parent class setup. This must be called by subclasses after their cocotb parent class initialization.
# Subclass usage pattern
def __init__(self, ...):
# Initialize FIFOComponentBase
FIFOComponentBase.__init__(self, ...)
# Initialize cocotb parent (BusDriver/BusMonitor)
BusDriver.__init__(self, dut, prefix, clock, **kwargs)
# Complete base initialization
self.complete_base_initialization(self.bus)
Data Handling (Unified Strategies)¶
get_data_dict_unified()¶
Get current data from signals with automatic field unpacking using unified DataCollectionStrategy.
Returns: Dictionary of field values, properly unpacked
# Collect data from signals
data_dict = component.get_data_dict_unified()
print(data_dict) # {'addr': 0x1000, 'data': 0xDEADBEEF, 'cmd': 0x2}
drive_transaction_unified(transaction)¶
Drive transaction data using unified DataDrivingStrategy.
Parameters:
- transaction: Transaction packet to drive
Returns: True if successful, False otherwise
# Drive transaction to signals
packet = FIFOPacket(field_config, addr=0x1000, data=0xDEADBEEF)
success = component.drive_transaction_unified(packet)
if not success:
log.error("Failed to drive transaction")
clear_signals_unified()¶
Clear all data signals to safe values using unified strategy.
Memory Operations (Unified Integration)¶
write_to_memory_unified(transaction)¶
Write transaction to memory using base MemoryModel directly.
Parameters:
- transaction: Transaction to write to memory
Returns: Tuple of (success, error_message)
# Write transaction to memory
packet = FIFOPacket(field_config, addr=0x1000, data=0xDEADBEEF)
success, error = component.write_to_memory_unified(packet)
if success:
log.info("Memory write successful")
else:
log.error(f"Memory write failed: {error}")
read_from_memory_unified(transaction, update_transaction=True)¶
Read data from memory using base MemoryModel directly.
Parameters:
- transaction: Transaction with address to read from
- update_transaction: Whether to update transaction with read data
Returns: Tuple of (success, data, error_message)
# Read from memory
packet = FIFOPacket(field_config, addr=0x1000)
success, data, error = component.read_from_memory_unified(packet, update_transaction=True)
if success:
log.info(f"Read data: 0x{data:X}")
# packet.data now contains the read value
else:
log.error(f"Memory read failed: {error}")
Statistics and Monitoring¶
get_base_stats_unified()¶
Get comprehensive base statistics common to all components.
Returns: Dictionary containing base statistics
base_stats = component.get_base_stats_unified()
print(f"Component type: {base_stats['component_type']}")
print(f"Signal mapping source: {base_stats['signal_mapping_source']}")
print(f"Field count: {base_stats['field_count']}")
print(f"Multi-signal mode: {base_stats['multi_signal']}")
# Includes nested statistics:
# - signal_resolver_stats: Signal resolution details
# - data_collector_stats: Collection performance
# - data_driver_stats: Driving performance
# - memory_stats: Memory model statistics (if available)
set_randomizer(randomizer)¶
Set new randomizer for timing control.
Parameters:
- randomizer: FlexRandomizer instance
# Update randomizer
new_randomizer = FlexRandomizer({
'write_delay': ([(0, 0), (1, 5)], [8, 2])
})
component.set_randomizer(new_randomizer)
Usage Patterns¶
Basic Component Setup¶
class CustomFIFOComponent(FIFOComponentBase, BusDriver):
def __init__(self, dut, title, prefix, clock, field_config, **kwargs):
# Initialize base class
FIFOComponentBase.__init__(
self,
dut=dut,
title=title,
prefix=prefix,
clock=clock,
field_config=field_config,
protocol_type='fifo_master', # Specify protocol type
**kwargs
)
# Initialize cocotb parent
BusDriver.__init__(self, dut, prefix, clock, **kwargs)
# Complete base initialization
self.complete_base_initialization(self.bus)
# Component-specific initialization
self.custom_setup()
def custom_setup(self):
# Component-specific setup code
pass
Automatic Signal Discovery¶
# Let base class automatically discover signals
master = CustomFIFOComponent(
dut=dut,
title="AutoMaster",
prefix="", # SignalResolver handles discovery
clock=clock,
field_config=field_config,
mode='fifo_mux',
multi_sig=True
)
# Base class automatically finds and maps signals
# Access resolved signals: master.write_sig, master.data_sig, etc.
Manual Signal Mapping¶
# Override signal discovery for non-standard naming
signal_map = {
'write': 'wr_enable',
'full': 'almost_full',
'addr': 'write_address',
'data': 'write_data'
}
master = CustomFIFOComponent(
dut=dut,
title="ManualMaster",
prefix="",
clock=clock,
field_config=field_config,
signal_map=signal_map,
multi_sig=True
)
Memory-Integrated Component¶
# Component with built-in memory support
memory = MemoryModel(num_lines=256, bytes_per_line=4)
master = CustomFIFOComponent(
dut=dut,
title="MemoryMaster",
prefix="",
clock=clock,
field_config=field_config,
memory_model=memory
)
# Use unified memory operations
packet = FIFOPacket(field_config, addr=0x1000, data=0xDEADBEEF)
success, error = master.write_to_memory_unified(packet)
Performance-Optimized Component¶
class HighPerformanceFIFOComponent(FIFOComponentBase, BusDriver):
def __init__(self, dut, title, prefix, clock, field_config, **kwargs):
# Enable performance features
FIFOComponentBase.__init__(
self,
dut=dut,
title=title,
prefix=prefix,
clock=clock,
field_config=field_config,
protocol_type='fifo_master',
super_debug=False, # Disable for performance
**kwargs
)
BusDriver.__init__(self, dut, prefix, clock, **kwargs)
self.complete_base_initialization(self.bus)
async def high_speed_transfer(self, packets):
"""Optimized batch transfer using unified strategies"""
for packet in packets:
# Use unified driving for maximum performance
if not self.drive_transaction_unified(packet):
log.error(f"Failed to drive packet: {packet}")
continue
# Wait for transfer
await RisingEdge(self.clock)
# Clear signals efficiently
self.clear_signals_unified()
def get_performance_metrics(self):
"""Get detailed performance analysis"""
stats = self.get_base_stats_unified()
# Analyze data strategy performance
if 'data_driver_stats' in stats:
driver_stats = stats['data_driver_stats']
print(f"Cached signals: {driver_stats.get('cached_signals', 0)}")
print(f"Performance optimized: {driver_stats.get('performance_optimized', False)}")
return stats
Multi-Signal vs Single-Signal Mode¶
# Multi-signal mode: Each field has individual signal
multi_sig_config = FieldConfig()
multi_sig_config.add_field(FieldDefinition("addr", 32))
multi_sig_config.add_field(FieldDefinition("data", 32))
multi_sig_config.add_field(FieldDefinition("cmd", 4))
master_multi = CustomFIFOComponent(
dut, "MultiSigMaster", "", clock, multi_sig_config, multi_sig=True
)
# Creates: addr_sig, data_sig, cmd_sig
# Single-signal mode: All fields packed into data signal
master_single = CustomFIFOComponent(
dut, "SingleSigMaster", "", clock, multi_sig_config, multi_sig=False
)
# Creates: data_sig (with fields packed)
Advanced Randomization¶
# Custom randomizer for specific timing patterns
custom_randomizer = FlexRandomizer({
'write_delay': ([(0, 0), (1, 3), (10, 20)], [0.7, 0.2, 0.1]),
'burst_size': [1, 2, 4, 8, 16],
'idle_cycles': ([(0, 5), (10, 50)], [0.8, 0.2])
})
master = CustomFIFOComponent(
dut=dut,
title="RandomizedMaster",
prefix="",
clock=clock,
field_config=field_config,
randomizer=custom_randomizer
)
# Use randomizer in component
delay_values = master.randomizer.next()
write_delay = delay_values['write_delay']
burst_size = delay_values['burst_size']
Internal Infrastructure¶
Field Configuration Normalization¶
The base class automatically handles field configuration conversion:
# Accepts dict and converts to FieldConfig
dict_config = {
'addr': {'bits': 32, 'format': 'hex'},
'data': {'bits': 32, 'format': 'hex'}
}
component = CustomFIFOComponent(..., field_config=dict_config)
# Accepts FieldConfig directly
field_config = FieldConfig.create_standard(32, 32)
component = CustomFIFOComponent(..., field_config=field_config)
# Creates default if None
component = CustomFIFOComponent(..., field_config=None) # Creates data-only config
Randomizer Setup¶
Default randomizers are created based on component type:
# FIFO Master gets write-focused randomizer
'write_delay': ([(0, 0), (1, 8), (9, 20)], [5, 2, 1])
# FIFO Slave gets read-focused randomizer
'read_delay': ([(0, 1), (2, 8), (9, 30)], [5, 2, 1])
Data Strategy Setup¶
The base class creates optimized data strategies:
# Data collection for all components (monitoring)
self.data_collector = DataCollectionStrategy(
component=self,
field_config=self.field_config,
use_multi_signal=self.use_multi_signal,
log=self.log,
resolved_signals=resolved_signals # Pre-resolved signals
)
# Data driving for masters and slaves
self.data_driver = DataDrivingStrategy(
component=self,
field_config=self.field_config,
use_multi_signal=self.use_multi_signal,
log=self.log,
resolved_signals=resolved_signals # Pre-resolved signals
)
Error Handling¶
Signal Resolution Errors¶
try:
component = CustomFIFOComponent(dut, title, prefix, clock, field_config)
except RuntimeError as e:
# Signal mapping failed - detailed error info provided
log.error(f"Signal resolution failed: {e}")
# Try manual signal mapping as fallback
signal_map = create_manual_signal_map()
component = CustomFIFOComponent(
dut, title, prefix, clock, field_config, signal_map=signal_map
)
Memory Operation Errors¶
# Always check memory operation results
success, error = component.write_to_memory_unified(packet)
if not success:
log.error(f"Memory write failed: {error}")
# Handle error appropriately
success, data, error = component.read_from_memory_unified(packet)
if not success:
log.error(f"Memory read failed: {error}")
# Handle error appropriately
Best Practices¶
1. Use Unified Methods¶
Always use the unified methods for consistency and performance:
# Good - unified data collection
data = component.get_data_dict_unified()
# Good - unified transaction driving
success = component.drive_transaction_unified(packet)
2. Check Operation Results¶
Always verify operation success:
# Check driving success
if not component.drive_transaction_unified(packet):
log.error("Transaction driving failed")
# Check memory operations
success, error = component.write_to_memory_unified(packet)
if not success:
handle_memory_error(error)
3. Use Signal Maps for Non-Standard Interfaces¶
When DUT signals don't follow standard naming:
signal_map = {
'write': 'wr_en',
'full': 'almost_full',
'data': 'write_data'
}
component = CustomFIFOComponent(..., signal_map=signal_map)
4. Monitor Performance¶
Regularly check performance statistics:
stats = component.get_base_stats_unified()
if 'data_collector_stats' in stats:
collector_stats = stats['data_collector_stats']
if not collector_stats.get('performance_optimized', False):
log.warning("Data collection not optimized")
5. Proper Initialization Order¶
Always follow the correct initialization pattern:
# 1. Initialize FIFOComponentBase
# 2. Initialize cocotb parent class
# 3. Call complete_base_initialization()
# 4. Component-specific setup
The FIFOComponentBase provides a robust, high-performance foundation for all FIFO protocol components while maintaining API compatibility and exact timing behavior.