|
CocoTB Framework · Verification Infrastructure for RTL Testing GitHub · Documentation Index · MIT License |
apb_components.py¶
APB Protocol Components providing Monitor, Master, and Slave implementations for comprehensive APB verification. This module contains the core protocol-level components that handle signal-level communication and protocol compliance.
Overview¶
The apb_components.py module provides three main classes that implement the APB protocol:
- APBMonitor: Observes and logs APB transactions
- APBSlave: Responds to APB transactions with memory backing
- APBMaster: Drives APB transactions with configurable timing
Key Features¶
- Full APB signal support with optional signal handling
- Memory model integration for realistic slave behavior
- Configurable timing randomization for stress testing
- Comprehensive error injection and protocol validation
- Transaction queuing and pipelining for performance testing
Constants and Mappings¶
Signal Definitions¶
# APB PWRITE mapping
pwrite = ['READ', 'WRITE']
# Required APB signals
apb_signals = [
"PSEL", # Peripheral select
"PWRITE", # Write enable
"PENABLE", # Enable signal
"PADDR", # Address bus
"PWDATA", # Write data bus
"PRDATA", # Read data bus
"PREADY" # Ready signal
]
# Optional APB signals
apb_optional_signals = [
"PPROT", # Protection control
"PSLVERR", # Slave error
"PSTRB" # Write strobes
]
Core Classes¶
APBMonitor¶
Bus monitor for observing and logging APB transactions without interfering with protocol operation.
Constructor¶
APBMonitor(entity, title, prefix, clock, signals=None, bus_width=32, addr_width=12, log=None, **kwargs)
Parameters:
- entity: DUT entity to monitor
- title: Monitor identifier for logging
- prefix: Signal prefix for bus connection
- clock: Clock signal for synchronization
- signals: Custom signal list (default: standard APB signals)
- bus_width: Data bus width in bits (default: 32)
- addr_width: Address bus width in bits (default: 12)
- log: Logger instance (default: entity logger)
# Create APB monitor
monitor = APBMonitor(
entity=dut,
title="APB_Monitor",
prefix="apb_",
clock=dut.clk,
bus_width=32,
addr_width=16
)
Methods¶
is_signal_present(signal_name) -> bool¶
Check if an optional signal is present and connected.
print(transaction)¶
Print formatted transaction information to log.
Parameters:
- transaction: APBPacket transaction to display
Transaction Detection¶
The monitor automatically detects valid APB transactions when:
- PSEL is asserted
- PENABLE is asserted
- PREADY is asserted
- All signals have resolvable values
APBSlave¶
APB slave implementation with memory backing and configurable response behavior.
Constructor¶
APBSlave(entity, title, prefix, clock, registers, signals=None, bus_width=32, addr_width=12, randomizer=None, log=None, error_overflow=False, **kwargs)
Parameters:
- entity: DUT entity to connect to
- title: Slave identifier for logging
- prefix: Signal prefix for bus connection
- clock: Clock signal for synchronization
- registers: Initial register values or register count
- signals: Custom signal list (default: standard APB signals)
- bus_width: Data bus width in bits (default: 32)
- addr_width: Address bus width in bits (default: 12)
- randomizer: Timing randomizer (default: FlexRandomizer)
- log: Logger instance (default: entity logger)
- error_overflow: Generate errors on address overflow (default: False)
# Create APB slave with 256 registers
registers = [0] * 1024 # 256 32-bit registers
slave = APBSlave(
entity=dut,
title="APB_Slave",
prefix="apb_",
clock=dut.clk,
registers=registers,
bus_width=32,
addr_width=16,
error_overflow=True
)
Methods¶
set_randomizer(randomizer)¶
Update the timing randomizer for ready signal delays.
new_randomizer = FlexRandomizer({
'ready': ([[0, 2], [5, 10]], [8, 1]),
'error': ([[0, 0], [1, 1]], [20, 1])
})
slave.set_randomizer(new_randomizer)
dump_registers()¶
Display current register contents to log.
reset_bus()¶
Reset all bus outputs to default values.
reset_registers()¶
Reset all registers to their preset values.
Response Behavior¶
The slave provides configurable response timing: - Ready Delay: Configurable cycles before asserting PREADY - Error Injection: Random or deterministic error generation - Memory Expansion: Automatic memory expansion on overflow
APBMaster¶
APB master implementation that drives transactions with configurable timing and queuing.
Constructor¶
APBMaster(entity, title, prefix, clock, signals=None, bus_width=32, addr_width=12, randomizer=None, log=None, **kwargs)
Parameters:
- entity: DUT entity to drive
- title: Master identifier for logging
- prefix: Signal prefix for bus connection
- clock: Clock signal for synchronization
- signals: Custom signal list (default: standard APB signals)
- bus_width: Data bus width in bits (default: 32)
- addr_width: Address bus width in bits (default: 12)
- randomizer: Timing randomizer (default: FlexRandomizer)
- log: Logger instance (default: entity logger)
# Create APB master
master = APBMaster(
entity=dut,
title="APB_Master",
prefix="apb_",
clock=dut.clk,
bus_width=32,
addr_width=16
)
Methods¶
set_randomizer(randomizer)¶
Update the timing randomizer for transaction delays.
timing_randomizer = FlexRandomizer({
'psel': ([[0, 0], [1, 5]], [6, 1]), # Mostly immediate PSEL
'penable': ([[0, 0], [1, 2]], [4, 1]) # Minimal PENABLE delay
})
master.set_randomizer(timing_randomizer)
reset_bus()¶
Reset all bus outputs and clear transaction queue.
send(transaction)¶
Add transaction to transmission queue.
Parameters:
- transaction: APBPacket to transmit
busy_send(transaction)¶
Send transaction and wait for completion.
Transaction Pipeline¶
The master implements a transaction pipeline: 1. Queue Management: Transactions queued for transmission 2. Signal Setup: Configure address, data, and control signals 3. Protocol Phases: Handle PSEL and PENABLE timing 4. Completion: Wait for PREADY and capture response
Usage Patterns¶
Basic Monitor Setup¶
import cocotb
from cocotb.triggers import RisingEdge
from CocoTBFramework.components.apb.apb_components import APBMonitor
@cocotb.test()
async def monitor_test(dut):
# Create monitor
monitor = APBMonitor(
entity=dut,
title="Protocol_Monitor",
prefix="apb_",
clock=dut.clk,
bus_width=32
)
# Add callback for transaction observation
def transaction_callback(packet):
print(f"Observed: {packet.formatted(compact=True)}")
monitor.add_callback(transaction_callback)
# Monitor runs automatically
await Timer(1000, units='ns')
Master-Slave Communication¶
async def master_slave_test(dut):
# Create master and slave
master = APBMaster(dut, "Master", "m_apb_", dut.clk)
slave = APBSlave(dut, "Slave", "s_apb_", dut.clk, registers=256)
# Reset both components
await master.reset_bus()
await slave.reset_bus()
# Write operation
write_packet = APBPacket(
pwrite=1,
paddr=0x100,
pwdata=0x12345678,
pstrb=0xF
)
await master.send(write_packet)
# Read operation
read_packet = APBPacket(
pwrite=0,
paddr=0x100
)
await master.send(read_packet)
# Wait for completion
while master.transfer_busy:
await RisingEdge(dut.clk)
Advanced Timing Configuration¶
def setup_timing_profiles():
# Fast profile for performance testing
fast_profile = FlexRandomizer({
'psel': ([[0, 0]], [1]), # No PSEL delay
'penable': ([[0, 0]], [1]), # No PENABLE delay
'ready': ([[0, 0]], [1]), # Immediate ready
'error': ([[0, 0]], [1]) # No errors
})
# Stress profile for robustness testing
stress_profile = FlexRandomizer({
'psel': ([[0, 0], [1, 10]], [3, 1]), # Variable PSEL delay
'penable': ([[0, 1], [2, 5]], [2, 1]), # Variable PENABLE delay
'ready': ([[0, 5], [10, 20]], [4, 1]), # Variable ready delay
'error': ([[0, 0], [1, 1]], [10, 1]) # 10% error rate
})
return fast_profile, stress_profile
async def timing_test(dut):
fast_profile, stress_profile = setup_timing_profiles()
master = APBMaster(dut, "Master", "apb_", dut.clk)
slave = APBSlave(dut, "Slave", "apb_", dut.clk, registers=1024)
# Test with fast timing
master.set_randomizer(fast_profile)
slave.set_randomizer(fast_profile)
# Run fast test sequence
await run_test_sequence(master, fast_transactions)
# Switch to stress timing
master.set_randomizer(stress_profile)
slave.set_randomizer(stress_profile)
# Run stress test sequence
await run_test_sequence(master, stress_transactions)
Error Injection Testing¶
async def error_injection_test(dut):
# Create slave with error injection
error_randomizer = FlexRandomizer({
'ready': ([[1, 3], [5, 10]], [3, 1]),
'error': ([[0, 0], [1, 1]], [4, 1]) # 20% error rate
})
slave = APBSlave(
dut, "Error_Slave", "apb_", dut.clk,
registers=256,
randomizer=error_randomizer,
error_overflow=True
)
master = APBMaster(dut, "Master", "apb_", dut.clk)
# Test normal addresses
for addr in range(0, 64, 4):
packet = APBPacket(pwrite=1, paddr=addr, pwdata=addr*2)
await master.send(packet)
# Test overflow addresses (should generate errors)
for addr in range(0x1000, 0x1040, 4):
packet = APBPacket(pwrite=1, paddr=addr, pwdata=0xBAADF00D)
await master.send(packet)
Register Verification¶
async def register_verification(dut):
slave = APBSlave(dut, "Register_Slave", "apb_", dut.clk, registers=1024)
master = APBMaster(dut, "Master", "apb_", dut.clk)
# Test register read/write
test_patterns = [0x00000000, 0xFFFFFFFF, 0x55555555, 0xAAAAAAAA]
for i, pattern in enumerate(test_patterns):
addr = i * 4
# Write pattern
write_packet = APBPacket(pwrite=1, paddr=addr, pwdata=pattern)
await master.send(write_packet)
# Read back
read_packet = APBPacket(pwrite=0, paddr=addr)
await master.send(read_packet)
# Verify in slave memory
await Timer(100, units='ns')
slave.dump_registers()
Performance Testing¶
async def performance_test(dut):
# Configure for maximum performance
fast_randomizer = FlexRandomizer({
'psel': ([[0, 0]], [1]),
'penable': ([[0, 0]], [1]),
'ready': ([[0, 0]], [1]),
'error': ([[0, 0]], [1])
})
master = APBMaster(dut, "Perf_Master", "apb_", dut.clk, randomizer=fast_randomizer)
slave = APBSlave(dut, "Perf_Slave", "apb_", dut.clk, registers=1024, randomizer=fast_randomizer)
# Measure transaction throughput
start_time = get_sim_time('ns')
# Send 1000 back-to-back transactions
for i in range(1000):
packet = APBPacket(pwrite=1, paddr=(i*4) % 1024, pwdata=i)
await master.send(packet)
# Wait for completion
while master.transfer_busy:
await RisingEdge(dut.clk)
end_time = get_sim_time('ns')
duration = end_time - start_time
print(f"1000 transactions completed in {duration} ns")
print(f"Throughput: {1000 * 1e9 / duration:.2f} transactions/second")
Integration with Framework¶
Memory Model Integration¶
The APBSlave uses the shared MemoryModel for realistic memory behavior:
# Memory model provides:
# - Byte-level access control
# - Strobe mask support
# - Access tracking and coverage
# - Boundary checking
# - Memory expansion
Packet Integration¶
Components work seamlessly with APBPacket:
# Automatic field extraction and validation
# Protocol compliance checking
# Transaction correlation and tracking
Randomization Integration¶
Uses FlexRandomizer for comprehensive timing control:
Best Practices¶
1. Use Appropriate Randomization¶
# Development/debug: minimal delays
debug_randomizer = FlexRandomizer({
'psel': ([[0, 0]], [1]),
'penable': ([[0, 0]], [1]),
'ready': ([[0, 1]], [1])
})
# Stress testing: variable delays
stress_randomizer = FlexRandomizer({
'psel': ([[0, 0], [1, 10]], [7, 1]),
'penable': ([[0, 1], [2, 5]], [3, 1]),
'ready': ([[0, 5], [10, 25]], [5, 1])
})
2. Handle Optional Signals¶
# Always check signal presence
if slave.is_signal_present('PSLVERR'):
# Handle slave error
pass
if master.is_signal_present('PSTRB'):
# Use write strobes
packet.pstrb = 0xF
3. Reset Components Properly¶
4. Monitor Transaction Completion¶
# For performance-critical tests
await master.busy_send(packet)
# For pipelined operation
await master.send(packet)
while master.transfer_busy:
await RisingEdge(dut.clk)
5. Use Memory Dumps for Debug¶
# Regular memory verification
slave.dump_registers()
# After test completion
print(f"Slave processed {slave.count} transactions")
The APB components provide a comprehensive foundation for APB protocol verification, from basic functional testing to advanced stress testing and performance analysis.