|
CocoTB Framework · Verification Infrastructure for RTL Testing GitHub · Documentation Index · MIT License |
apb_scoreboard.py¶
Two scoreboards live here. APBScoreboard handles the single-slave case; APBCrossbarScoreboard handles systems where an address decoder steers each transaction to one of several slaves. There's also an APB→GAXI transformer kept here for backward compatibility.
Overview¶
- Single-Slave Verification: straightforward expected-vs-actual checking for one APB slave
- Multi-Slave Support: address-based routing to a per-slave scoreboard
- Direction-Aware Comparison: reads and writes compared on the fields that matter to each
- Enhanced Error Reporting: mismatches logged field by field
- Protocol Transformation: APB → GAXI conversion support
Classes¶
APBScoreboard¶
The single-slave workhorse. Queueing and reporting come from BaseScoreboard; what this class adds is APB-aware comparison and mismatch logging that actually tells you which field broke.
class APBScoreboard(BaseScoreboard):
def __init__(self, name, addr_width=32, data_width=32, log=None)
Parameters:
- name: scoreboard name; shows up in reports
- addr_width: address bus width in bits (default: 32)
- data_width: data bus width in bits (default: 32)
- log: logger for mismatch detail
Key Attributes:
- addr_width: address bus width
- data_width: data bus width
- strb_width: strobe width (data_width // 8)
- master_transactions: transactions tracked by master ID
Core Methods¶
Transaction Comparison¶
_compare_transactions(expected, actual)¶
Comparison the simple way: type-check both packets, then lean on APBPacket.__eq__, which already knows which fields matter.
Parameters:
- expected: expected APB transaction (APBPacket)
- actual: actual APB transaction (APBPacket)
Returns:
- bool: True on match, False otherwise
Comparison Logic:
- Both transactions must be APBPacket instances
- Uses APBPacket's built-in __eq__ method
- Direction, address, data, and control fields are all covered by that equality
# Automatic comparison when both transactions available
scoreboard.add_expected(expected_apb_packet)
scoreboard.add_actual(actual_apb_packet) # Triggers comparison
_log_mismatch(expected, actual)¶
When a comparison fails, this walks the packet field by field so the log names the difference—direction, address, data, strobe—instead of just announcing that one exists.
Parameters:
- expected: expected APB transaction
- actual: actual APB transaction
Detailed Logging: - Direction mismatch detection - Address field comparison - Data field analysis for both read and write - Strobe field validation - Enable signal checking
# Example mismatch log output:
# APB Cycle mismatch:
# Expected: WRITE addr=0x1000 data=0xDEADBEEF strb=0xF
# Actual: WRITE addr=0x1000 data=0xBEEFDEAD strb=0xF
# Data mismatch: expected=0xDEADBEEF, actual=0xBEEFDEAD
Multi-Slave Support¶
APBCrossbarScoreboard¶
One APBScoreboard per slave under the hood, with an address map in front that routes each transaction where it belongs.
class APBCrossbarScoreboard:
def __init__(self, name, num_slaves, addr_width=32, data_width=32, log=None)
Parameters:
- name: scoreboard name
- num_slaves: how many slaves sit behind the crossbar
- addr_width: address bus width (default: 32)
- data_width: data bus width (default: 32)
- log: logger instance
Architecture:
- An individual APBScoreboard for each slave
- Address-based routing, automatic once configured
- Address ranges you can redefine per slave
- One combined report across all of them
Address Mapping¶
set_address_map(addr_map)¶
Give each slave its address range.
Parameters:
- addr_map: list of (base_addr, end_addr) tuples, one per slave
Default Mapping: - Slave 0: 0x0000 - 0x0FFC - Slave 1: 0x1000 - 0x1FFC - Slave N: N0x1000 - (N0x1000 + 0xFFC)
# Custom address mapping
scoreboard = APBCrossbarScoreboard("MultiSlave", num_slaves=3)
addr_map = [
(0x0000, 0x7FFF), # Slave 0: 32KB
(0x8000, 0xBFFF), # Slave 1: 16KB
(0xC000, 0xFFFF), # Slave 2: 16KB
]
scoreboard.set_address_map(addr_map)
get_slave_idx(addr)¶
Which slave owns this address?
Parameters:
- addr: the address to route
Returns:
- int: slave index
Routing Logic:
1. Check the configured map for a range that contains the address
2. If nothing claims it, fall back to modulo routing: addr // 0x1000 % num_slaves—a misconfigured map degrades to something predictable instead of dropping the transaction
Transaction Management¶
add_master_transaction(transaction, master_id)¶
Add a master-side transaction; the scoreboard routes it.
Parameters:
- transaction: the APB transaction to route
- master_id: master identifier for tracking
Behavior:
- Resolves the target slave with get_slave_idx()
- Forwards the transaction to that slave's scoreboard
- Keeps per-master tracking up to date
# Automatic routing based on address
scoreboard.add_master_transaction(transaction, master_id=0)
# Transaction routed to correct slave based on address
add_slave_transaction(transaction, slave_idx)¶
Add a transaction that came from a specific slave.
Parameters:
- transaction: APB transaction from the slave
- slave_idx: slave index (0 to num_slaves-1)
Error Handling: - Out-of-range slave indices are logged, not silently accepted
Reporting¶
report()¶
One line per slave, plus the overall verdict.
Returns:
- str: combined report from all slave scoreboards
Report Format:
APB Multi-Slave Scoreboard Report (MultiSlave):
Slave 0: PASS
Slave 1: FAIL (0.95)
Slave 2: PASS
Overall: FAIL
Protocol Transformation¶
APBtoGAXITransformer¶
APB-to-GAXI conversion.
Compatibility subclass. This class now derives from the canonical
scoreboards.apb_gaxi_transformer.APBtoGAXITransformer, keeping the constructor and list-returningtransform()documented below while inheritingapb_to_gaxi()/gaxi_to_apb(). Both import paths work; reach forapb_gaxi_transformerin new code. See APB-GAXI Transformer.
class APBtoGAXITransformer(ProtocolTransformer):
def __init__(self, gaxi_field_config, packet_class, log=None)
Parameters:
- gaxi_field_config: GAXI field configuration
- packet_class: GAXI packet class to instantiate
- log: logger instance
Transformation Logic¶
transform(apb_cycle)¶
Convert one APB transaction into GAXI packet(s).
Parameters:
- apb_cycle: the APB packet to transform
Returns:
- List[GAXIPacket]: the transformed packets
Field Mapping:
- apb.paddr → gaxi.addr
- apb.pwdata/prdata → gaxi.data (direction-dependent)
- apb.pstrb → gaxi.strb (writes only)
# Create transformer
transformer = APBtoGAXITransformer(gaxi_field_config, GAXIPacket, log=logger)
# Use with scoreboard
gaxi_scoreboard = GAXIScoreboard("Bridge", gaxi_field_config, log=logger)
gaxi_scoreboard.set_transformer(transformer)
# APB transactions automatically converted for GAXI comparison
gaxi_scoreboard.add_expected(apb_transaction) # Transformed to GAXI
gaxi_scoreboard.add_actual(gaxi_packet) # Direct comparison
Usage Examples¶
Basic Single-Slave Verification¶
The minimal loop: build expected and actual packets, hand them over, read the verdict.
from CocoTBFramework.scoreboards.apb_scoreboard import APBScoreboard
from CocoTBFramework.components.apb.apb_packet import APBPacket
# Create scoreboard
scoreboard = APBScoreboard("APB_Slave", addr_width=32, data_width=32, log=logger)
# Create test transactions
expected = APBPacket()
expected.direction = 'WRITE'
expected.paddr = 0x1000
expected.pwdata = 0xDEADBEEF
expected.pstrb = 0xF
actual = APBPacket()
actual.direction = 'WRITE'
actual.paddr = 0x1000
actual.pwdata = 0xDEADBEEF
actual.pstrb = 0xF
# Verify transactions
scoreboard.add_expected(expected)
scoreboard.add_actual(actual)
# Check results
error_count = scoreboard.report()
pass_rate = scoreboard.result()
print(f"Verification: {'PASS' if error_count == 0 else 'FAIL'} ({pass_rate:.2%})")
Multi-Slave System Verification¶
Four peripherals, four address ranges, and routing you don't have to think about after setup.
from CocoTBFramework.scoreboards.apb_scoreboard import APBCrossbarScoreboard
# Create multi-slave scoreboard
scoreboard = APBCrossbarScoreboard("APB_System", num_slaves=4, log=logger)
# Configure custom address mapping
addr_map = [
(0x0000, 0x0FFF), # Peripheral 0: GPIO
(0x1000, 0x1FFF), # Peripheral 1: UART
(0x2000, 0x2FFF), # Peripheral 2: SPI
(0x3000, 0x3FFF), # Peripheral 3: I2C
]
scoreboard.set_address_map(addr_map)
# Add transactions - automatically routed
gpio_transaction = create_apb4_transaction(addr=0x0100, data=0xFF) # → Slave 0
uart_transaction = create_apb4_transaction(addr=0x1004, data=0x55) # → Slave 1
scoreboard.add_master_transaction(gpio_transaction, master_id=0)
scoreboard.add_master_transaction(uart_transaction, master_id=0)
# Add expected slave responses
scoreboard.add_slave_transaction(gpio_response, slave_idx=0)
scoreboard.add_slave_transaction(uart_response, slave_idx=1)
# Generate comprehensive report
print(scoreboard.report())
Cross-Protocol Bridge Verification¶
When the DUT converts APB to GAXI, install the transformer and compare in the GAXI domain.
from CocoTBFramework.scoreboards.apb_scoreboard import APBtoGAXITransformer
from CocoTBFramework.scoreboards.gaxi_scoreboard import GAXIScoreboard
# Create transformer and target scoreboard
transformer = APBtoGAXITransformer(gaxi_field_config, GAXIPacket, log=logger)
bridge_scoreboard = GAXIScoreboard("APB_GAXI_Bridge", gaxi_field_config, log=logger)
bridge_scoreboard.set_transformer(transformer)
# Verify APB input produces correct GAXI output
apb_input = create_apb4_write(addr=0x2000, data=0x12345678)
gaxi_output = monitor_gaxi_transaction()
bridge_scoreboard.add_expected(apb_input) # Automatically transformed
bridge_scoreboard.add_actual(gaxi_output) # Direct GAXI comparison
# Analysis
errors = bridge_scoreboard.report()
if errors == 0:
print("Bridge verification passed")
else:
print(f"Bridge verification failed: {errors} errors")
Enhanced Error Analysis¶
Subclass and override _log_mismatch when you want arithmetic on the mismatch—address offsets and XOR masks beat eyeballing hex dumps.
# Custom scoreboard with detailed error reporting
class DetailedAPBScoreboard(APBScoreboard):
def _log_mismatch(self, expected, actual):
super()._log_mismatch(expected, actual)
# Additional analysis
if self.log:
if expected.paddr != actual.paddr:
addr_diff = actual.paddr - expected.paddr
self.log.error(f" Address offset: 0x{addr_diff:X}")
if hasattr(expected, 'pwdata') and hasattr(actual, 'pwdata'):
if expected.pwdata != actual.pwdata:
xor_result = expected.pwdata ^ actual.pwdata
self.log.error(f" Data XOR: 0x{xor_result:08X}")
# Usage with enhanced reporting
detailed_scoreboard = DetailedAPBScoreboard("Detailed", log=logger)
Best Practices¶
Address Mapping Configuration¶
- Keep slave ranges clear and non-overlapping
- Power-of-2 boundaries make the decode logic—and your debugging—easier
- Write the map down in the test configuration so the bench and the DUT can't drift apart
Transaction Tracking¶
- Meaningful master IDs pay off when you're correlating transactions later
clear()between test phases- Watch queue sizes in long runs
Error Analysis¶
- Leave detailed logging on while debugging mismatches
- The field-by-field output is your first stop, not the waveform
- Keep mismatch pairs around for post-test analysis
Performance Optimization¶
- Size queues for the traffic you actually expect
- Use batch operations when pushing large transaction sets
- Retire completed transactions periodically
Integration Points¶
Monitor Integration¶
# Connect APB monitor to scoreboard
def on_apb_transaction(packet):
scoreboard.add_actual(packet)
apb_monitor.add_callback(on_apb_transaction)
Test Sequence Integration¶
# Generate expected transactions from test sequence
sequence = APBSequence("test_pattern")
for packet in sequence.generate():
scoreboard.add_expected(packet)
Single slave or a whole crossbar, it's the same comparison machinery underneath—with the transformer hook for when the DUT turns out to be a bridge.