apb5_components.py¶
APB5 Protocol Components providing Monitor, Master, and Slave implementations with AMBA5 extension support. This module extends the APB4 protocol components with user-defined signals, wake-up support, and parity signal monitoring for comprehensive APB5 verification.
Overview¶
The apb5_components.py module provides three main classes that implement the APB5 protocol:
- APB5Monitor: Observes and logs APB5 transactions including user signals and wake-up
- APB5Slave: Responds to APB5 transactions with memory backing and randomized user signal responses
- APB5Master: Drives APB5 transactions with user signal and wake-up capture support
Key Features¶
- Full APB5 signal support with optional signal handling for all AMBA5 extensions
- User signal channels (PAUSER, PWUSER, PRUSER, PBUSER) with configurable widths
- Wake-up signal support via PWAKEUP
- Optional parity signal monitoring (PWDATAPARITY, PADDRPARITY, PCTRLPARITY, etc.)
- Memory model integration for realistic slave behavior
- Configurable timing randomization with user signal value randomization
- Transaction queuing and pipelining for performance testing
Constants and Mappings¶
Signal Definitions¶
# APB PWRITE mapping
pwrite = ['READ', 'WRITE']
# Required APB5 signals (APB4 base)
apb5_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 APB5 signals (APB4 + AMBA5 extensions)
apb5_optional_signals = [
"PPROT", # Protection control
"PSLVERR", # Slave error
"PSTRB", # Write strobes
"PAUSER", # Request user attributes
"PWUSER", # Write data user attributes
"PRUSER", # Read data user attributes
"PBUSER", # Response user attributes
"PWAKEUP", # Wake-up request
"PWDATAPARITY", # Write data parity
"PADDRPARITY", # Address parity
"PCTRLPARITY", # Control signal parity
"PRDATAPARITY", # Read data parity
"PREADYPARITY", # Ready parity
"PSLVERRPARITY", # Slave error parity
]
Core Classes¶
APB5Monitor¶
Bus monitor for observing and logging APB5 transactions without interfering with protocol operation. Captures all APB5 extension signals including user attributes and wake-up status.
Constructor¶
APB5Monitor(entity, title, prefix, clock, signals=None,
bus_width=32, addr_width=12,
auser_width=4, wuser_width=4, ruser_width=4, buser_width=4,
log=None, **kwargs)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
entity |
object | required | DUT entity to monitor |
title |
str | required | Monitor identifier for logging |
prefix |
str | required | Signal prefix for bus connection (trailing _ auto-stripped) |
clock |
signal | required | Clock signal for synchronization |
signals |
list | None | Custom signal list (default: all APB5 signals) |
bus_width |
int | 32 | Data bus width in bits |
addr_width |
int | 12 | Address bus width in bits |
auser_width |
int | 4 | PAUSER width in bits |
wuser_width |
int | 4 | PWUSER width in bits |
ruser_width |
int | 4 | PRUSER width in bits |
buser_width |
int | 4 | PBUSER width in bits |
log |
Logger | None | Logger instance (default: entity logger) |
# Create APB5 monitor
monitor = APB5Monitor(
entity=dut,
title="APB5_Monitor",
prefix="apb_",
clock=dut.clk,
bus_width=32,
addr_width=16,
auser_width=8,
ruser_width=8
)
Methods¶
is_signal_present(signal_name) -> bool¶
Check if an optional signal is present and connected on the bus.
if monitor.is_signal_present('PWAKEUP'):
# Handle wake-up signal monitoring
pass
if monitor.is_signal_present('PAUSER'):
# User signal is available
pass
print(transaction)¶
Print formatted transaction information to log at debug level.
Parameters:
- transaction: APB5Packet transaction to display
Transaction Detection¶
The monitor automatically detects valid APB5 transactions when:
- PSEL is asserted
- PENABLE is asserted
- PREADY is asserted
- All signals have resolvable values
On detection, the monitor captures all present APB5 extension signals (PAUSER, PWUSER, PRUSER, PBUSER, PWAKEUP) and creates an APB5Packet record. Sampling occurs on the falling clock edge with a 200ps settling delay.
APB5Slave¶
APB5 slave implementation with memory backing, configurable response behavior, and randomized user signal responses.
Constructor¶
APB5Slave(entity, title, prefix, clock, registers, signals=None,
bus_width=32, addr_width=12,
auser_width=4, wuser_width=4, ruser_width=4, buser_width=4,
randomizer=None, log=None, error_overflow=False,
wakeup_generator=None, **kwargs)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
entity |
object | required | DUT entity to connect to |
title |
str | required | Slave identifier for logging |
prefix |
str | required | Signal prefix for bus connection (trailing _ auto-stripped) |
clock |
signal | required | Clock signal for synchronization |
registers |
list[int] | required | Initial register values (byte array) |
signals |
list | None | Custom signal list (default: all APB5 signals) |
bus_width |
int | 32 | Data bus width in bits |
addr_width |
int | 12 | Address bus width in bits |
auser_width |
int | 4 | PAUSER width in bits |
wuser_width |
int | 4 | PWUSER width in bits |
ruser_width |
int | 4 | PRUSER width in bits |
buser_width |
int | 4 | PBUSER width in bits |
randomizer |
FlexRandomizer | None | Timing and user signal randomizer |
log |
Logger | None | Logger instance (default: entity logger) |
error_overflow |
bool | False | Generate errors on address overflow |
wakeup_generator |
callable | None | Callback to generate wake-up events |
# Create APB5 slave with 256 registers and error overflow detection
registers = [0] * 1024 # 256 32-bit registers
slave = APB5Slave(
entity=dut,
title="APB5_Slave",
prefix="apb_",
clock=dut.clk,
registers=registers,
bus_width=32,
addr_width=16,
ruser_width=8,
buser_width=8,
error_overflow=True
)
Default Randomizer¶
When no randomizer is provided, the slave uses a default configuration:
FlexRandomizer({
'ready': ([(0, 1), (2, 5), (6, 10)], [5, 2, 1]), # Mostly short delays
'error': ([(0, 0), (1, 1)], [10, 0]), # No errors by default
'pruser': ([(0, (1 << ruser_width) - 1)], [1]), # Random PRUSER values
'pbuser': ([(0, (1 << buser_width) - 1)], [1]), # Random PBUSER values
})
Methods¶
is_signal_present(signal_name) -> bool¶
Check if an optional signal is present on the bus.
print(transaction)¶
Print transaction for debug logging.
set_wakeup(value)¶
Set the PWAKEUP signal value. Only effective if PWAKEUP is present on the bus.
Parameters:
- value: Integer value to drive on PWAKEUP (0 or 1)
await slave.set_wakeup(1) # Assert wake-up
# ... perform operations ...
await slave.set_wakeup(0) # Deassert wake-up
Response Behavior¶
The slave provides configurable response timing and user signal generation:
- Ready Delay: Configurable cycles before asserting PREADY (via ready randomizer field)
- Error Injection: Random or deterministic error generation (via error randomizer field)
- User Signal Response: Randomized PRUSER and PBUSER values on each response
- Wake-up Generation: Optional wake-up generator callback
- Address Overflow: Configurable error on out-of-range addresses
Transaction Queue¶
The slave maintains a sentQ deque containing APB5Packet records of all processed transactions, accessible for post-transaction verification.
APB5Master¶
APB5 master implementation that drives transactions with user signal support and response capture.
Constructor¶
APB5Master(entity, title, prefix, clock,
bus_width=32, addr_width=12,
auser_width=4, wuser_width=4, ruser_width=4, buser_width=4,
log=None, **kwargs)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
entity |
object | required | DUT entity to drive |
title |
str | required | Master identifier for logging |
prefix |
str | required | Signal prefix for bus connection (trailing _ auto-stripped) |
clock |
signal | required | Clock signal for synchronization |
bus_width |
int | 32 | Data bus width in bits |
addr_width |
int | 12 | Address bus width in bits |
auser_width |
int | 4 | PAUSER width in bits |
wuser_width |
int | 4 | PWUSER width in bits |
ruser_width |
int | 4 | PRUSER width in bits |
buser_width |
int | 4 | PBUSER width in bits |
log |
Logger | None | Logger instance (default: entity logger) |
# Create APB5 master with 8-bit user signals
master = APB5Master(
entity=dut,
title="APB5_Master",
prefix="apb_",
clock=dut.clk,
bus_width=32,
addr_width=16,
auser_width=8,
wuser_width=8
)
Methods¶
is_signal_present(signal_name) -> bool¶
Check if a signal is present on the bus.
send(transaction)¶
Send an APB5 transaction. Drives all present APB5 signals including user attributes.
Parameters:
- transaction: APB5Packet to transmit
packet = APB5Packet(
pwrite=1, paddr=0x100,
pwdata=0xDEADBEEF,
pstrb=0xF,
pauser=0x5,
pwuser=0xA
)
await master.send(packet)
write(address, data, strb=None, pprot=0, pauser=0, pwuser=0) -> APB5Packet¶
Perform an APB5 write transaction with convenience parameters.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
address |
int | required | Target address |
data |
int | required | Write data value |
strb |
int | None | Write strobe mask (default: all bytes enabled) |
pprot |
int | 0 | Protection control value |
pauser |
int | 0 | Request user attribute value |
pwuser |
int | 0 | Write data user attribute value |
Returns: APB5Packet with completed transaction (including response fields)
# Simple write
result = await master.write(address=0x200, data=0x12345678)
# Write with user signals and partial strobe
result = await master.write(
address=0x300,
data=0xAABBCCDD,
strb=0x3,
pauser=0xF,
pwuser=0x5
)
read(address, pprot=0, pauser=0) -> APB5Packet¶
Perform an APB5 read transaction with convenience parameters.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
address |
int | required | Target address |
pprot |
int | 0 | Protection control value |
pauser |
int | 0 | Request user attribute value |
Returns: APB5Packet with completed transaction (prdata, pruser, pbuser populated)
# Simple read
result = await master.read(address=0x200)
read_data = result.fields['prdata']
ruser_val = result.fields['pruser']
# Read with user attribute
result = await master.read(address=0x300, pauser=0xA)
Transaction Pipeline¶
The master implements the standard APB transaction pipeline with APB5 extensions: 1. Setup Phase: Drive PSEL, PADDR, PWRITE, PWDATA, PSTRB, PPROT, PAUSER, PWUSER 2. Access Phase: Assert PENABLE 3. Wait for Ready: Poll PREADY on rising clock edges 4. Response Capture: Sample PRDATA, PSLVERR, PRUSER, PBUSER, PWAKEUP (with 200ps settling delay) 5. Deassert: Clear PSEL and PENABLE
Transaction Queue¶
The master maintains a sentQ deque containing APB5Packet records of all completed transactions.
Usage Patterns¶
Basic Monitor Setup¶
import cocotb
from cocotb.triggers import Timer
from CocoTBFramework.components.apb5 import APB5Monitor
@cocotb.test()
async def monitor_test(dut):
monitor = APB5Monitor(
entity=dut,
title="Protocol_Monitor",
prefix="apb_",
clock=dut.clk,
bus_width=32,
auser_width=8
)
# Add callback for transaction observation
def transaction_callback(packet):
print(f"Observed: {packet.formatted(compact=True)}")
print(f" PAUSER: 0x{packet.fields['pauser']:02X}")
print(f" PRUSER: 0x{packet.fields['pruser']:02X}")
monitor.add_callback(transaction_callback)
# Monitor runs automatically
await Timer(1000, units='ns')
Master-Slave Communication with User Signals¶
from cocotb.triggers import RisingEdge
from CocoTBFramework.components.apb5 import APB5Master, APB5Slave, APB5Packet
async def master_slave_test(dut):
master = APB5Master(dut, "Master", "m_apb_", dut.clk, auser_width=8)
slave = APB5Slave(
dut, "Slave", "s_apb_", dut.clk,
registers=[0] * 256,
ruser_width=8, buser_width=8
)
# Write with user attributes
write_pkt = APB5Packet(
pwrite=1, paddr=0x100,
pwdata=0x12345678,
pstrb=0xF,
pauser=0xAB,
pwuser=0xCD
)
await master.send(write_pkt)
# Read back - response will include randomized PRUSER and PBUSER
result = await master.read(address=0x100, pauser=0xAB)
print(f"Read data: 0x{result.fields['prdata']:08X}")
print(f"PRUSER: 0x{result.fields['pruser']:02X}")
print(f"PBUSER: 0x{result.fields['pbuser']:02X}")
Custom Slave Randomizer with User Signals¶
from CocoTBFramework.components.apb5 import create_apb5_slave, create_apb5_randomizer
async def custom_randomizer_test(dut):
# Create randomizer with specific user signal patterns
randomizer = create_apb5_randomizer(
ready_delay_weights=([(0, 0), (1, 3)], [4, 1]),
error_weights=([(0, 0), (1, 1)], [5, 1]),
ruser_width=8,
buser_width=8
)
slave = create_apb5_slave(
dut, "Custom_Slave", "apb_", dut.clk,
registers=[0] * 512,
ruser_width=8, buser_width=8,
randomizer=randomizer,
error_overflow=True
)
Integration with Framework¶
Memory Model Integration¶
The APB5Slave uses the shared MemoryModel for realistic memory behavior:
# Memory model provides:
# - Byte-level access control with strobe masks
# - Access tracking and coverage
# - Boundary checking with configurable overflow behavior
# - Preset register values
Packet Integration¶
Components work seamlessly with APB5Packet:
# Automatic field extraction including user signals
# Protocol compliance checking
# Transaction correlation via sentQ
# APB4 backward compatibility via to_apb4_packet()
Randomization Integration¶
Uses FlexRandomizer for comprehensive timing and user signal control:
# Configurable delay distributions for PREADY
# Error injection patterns via error field
# Randomized PRUSER and PBUSER values per response
# Full range based on configured user signal widths
Best Practices¶
1. Match User Signal Widths¶
# Ensure widths match between master, slave, and monitor
AUSER_W = 8
RUSER_W = 8
master = APB5Master(dut, "M", "apb_", dut.clk, auser_width=AUSER_W)
slave = APB5Slave(dut, "S", "apb_", dut.clk, registers=[0]*256,
auser_width=AUSER_W, ruser_width=RUSER_W)
monitor = APB5Monitor(dut, "Mon", "apb_", dut.clk,
auser_width=AUSER_W, ruser_width=RUSER_W)
2. Handle Optional Signals Gracefully¶
# Always check signal presence before accessing
if master.is_signal_present('PAUSER'):
# PAUSER is connected
pass
if slave.is_signal_present('PWAKEUP'):
await slave.set_wakeup(1)
3. Verify User Signal Round-Trip¶
# Check that user signals are captured correctly
await master.send(packet)
sent = master.sentQ[-1]
observed = monitor.recv_queue[-1] # If using callbacks
assert sent.fields['pauser'] == observed.fields['pauser']
The APB5 components provide a comprehensive foundation for APB5 protocol verification, extending the proven APB4 infrastructure with full AMBA5 extension support for user signals, wake-up, and parity checking.