|
CocoTB Framework · Verification Infrastructure for RTL Testing GitHub · Documentation Index · MIT License |
apb_sequence.py¶
APBSequence is a small dataclass with a big job: describe a traffic pattern — which operations, which addresses, what data, what delays — then deal out ready-to-send APBPacket objects one at a time.
Overview¶
What a sequence gives you:
- Pattern definition as data — plain lists of operations, addresses, data values, strobes, and delays
- Sequential or random selection from those lists
- Automatic cycling — short lists wrap around instead of running dry
- Direct APBPacket generation through a single next() call
- Per-transaction pacing via inter-cycle delay values
Key Features¶
- Dataclass-based configuration, so sequences are easy to build programmatically
- Iterator management with wrap-around — a 16-entry address list can feed a thousand transactions
- Optional random selection when you want coverage rather than order
- A
verify_dataflag for read-checking flows - One call per packet —
next()assembles the fields for you
Core Class¶
APBSequence¶
It's a dataclass, so building a sequence is just naming your lists:
Constructor¶
@dataclass
class APBSequence:
name: str = "basic"
pwrite_seq: List[bool] = field(default_factory=list)
addr_seq: List[int] = field(default_factory=list)
data_seq: List[int] = field(default_factory=list)
strb_seq: List[int] = field(default_factory=list)
pprot_seq: List[int] = field(default_factory=list)
inter_cycle_delays: List[int] = field(default_factory=list)
master_randomizer: Optional[Dict] = None
slave_randomizer: Optional[Tuple] = None
other_randomizer: Optional[Tuple] = None
use_random_selection: bool = False
verify_data: bool = True
data_width: int = 32
transaction_count: int = 0
Parameters:
- name: Sequence identifier for logging and debugging
- pwrite_seq: List of write/read operations (True=Write, False=Read)
- addr_seq: List of addresses to cycle through
- data_seq: List of data values for write operations
- strb_seq: List of strobe patterns for write operations
- pprot_seq: List of protection attribute values
- inter_cycle_delays: List of delay values between transactions
- master_randomizer: Randomizer configuration for master timing
- slave_randomizer: Randomizer configuration for slave behavior
- other_randomizer: Additional randomizer configuration
- use_random_selection: If True, randomly select from sequences instead of cycling
- verify_data: Enable data verification for read operations
- data_width: Data width for packet generation
- transaction_count: Current transaction counter
# Create basic alternating read/write sequence
sequence = APBSequence(
name="alternating_test",
pwrite_seq=[True, False, True, False], # Write, Read, Write, Read
addr_seq=[0x100, 0x104, 0x108, 0x10C], # Sequential addresses
data_seq=[0x11111111, 0x22222222, 0x33333333, 0x44444444],
strb_seq=[0xF, 0xF, 0xF, 0xF], # Full strobes
inter_cycle_delays=[2, 1, 2, 1] # Variable delays
)
# Create random selection sequence
random_sequence = APBSequence(
name="random_test",
pwrite_seq=[True, False], # Mix of reads and writes
addr_seq=list(range(0x1000, 0x2000, 4)), # Large address range
data_seq=[0xAAAAAAAA, 0x55555555, 0xFFFFFFFF, 0x00000000],
strb_seq=[0xF, 0x3, 0xC, 0x5], # Various strobe patterns
use_random_selection=True # Random selection mode
)
Methods¶
reset_iterators()¶
Rewind every list to the start. Call it between test phases that reuse a sequence.
next_pwrite() -> bool¶
Get the next operation: True for write, False for read.
Returns: True for write, False for read
is_write = sequence.next_pwrite()
if is_write:
print("Next operation: WRITE")
else:
print("Next operation: READ")
next_addr() -> int¶
Get the next address in the rotation.
Returns: Address value
next_data() -> int¶
Get the next write-data value.
Returns: Data value for write operations
next_strb() -> int¶
Get the next strobe-mask value.
Returns: Strobe mask value
next_pprot() -> int¶
Get the next protection-attribute value.
Returns: Protection attribute value
next_delay() -> int¶
Get the next inter-cycle delay.
Returns: Delay in clock cycles
next() -> APBPacket¶
Pull the next element from each list and assemble a ready-to-send APBPacket.
Returns: APBPacket ready for transmission
has_more_transactions() -> bool¶
False once the operation list is drained — the natural loop condition.
Returns: True if more transactions can be generated
Usage Patterns¶
Basic Sequential Sequence¶
The bread-and-butter pattern: write a register, read it back, move on.
from CocoTBFramework.components.apb.apb_sequence import APBSequence
# Create a simple register test sequence
register_sequence = APBSequence(
name="register_test",
pwrite_seq=[True, False] * 10, # 10 write-read pairs
addr_seq=[0x1000 + i*4 for i in range(10)], # Sequential addresses
data_seq=[0xA0000000 + i for i in range(10)], # Pattern data
strb_seq=[0xF] * 10, # Always full strobes
inter_cycle_delays=[1] * 19 # Single cycle delays
)
# Generate all packets
packets = []
while register_sequence.has_more_transactions():
packet = register_sequence.next()
packets.append(packet)
print(f"Generated {len(packets)} packets")
Burst Write/Read Sequence¶
Sixteen writes into a block, then sixteen reads back out. Lists that run short simply wrap.
# Create burst write followed by burst read
burst_sequence = APBSequence(
name="burst_test",
# First 16 writes, then 16 reads
pwrite_seq=[True] * 16 + [False] * 16,
addr_seq=[0x2000 + i*4 for i in range(16)], # Will cycle for reads
data_seq=[0xB0000000 + i for i in range(16)], # Burst data pattern
strb_seq=[0xF], # Single strobe pattern
inter_cycle_delays=[0], # Back-to-back transfers
verify_data=True
)
# Generate burst sequence
burst_packets = []
for i in range(32): # 16 writes + 16 reads
packet = burst_sequence.next()
burst_packets.append(packet)
print(f"Burst {i}: {packet.formatted(compact=True)}")
Random Access Pattern¶
Flip use_random_selection and the same lists become a coverage engine instead of a script.
# Create random access sequence
random_sequence = APBSequence(
name="random_access",
pwrite_seq=[True, False], # Equal read/write probability
addr_seq=[0x1000, 0x1004, 0x1008, 0x100C, 0x2000, 0x2004, 0x3000],
data_seq=[0x11111111, 0x22222222, 0x33333333, 0x44444444, 0xAAAAAAAA, 0x55555555],
strb_seq=[0xF, 0x3, 0xC, 0x5, 0xA], # Various strobe patterns
inter_cycle_delays=[0, 1, 2, 5, 10], # Variable delays
use_random_selection=True # Enable random selection
)
# Generate random packets
random_packets = []
for i in range(50):
packet = random_sequence.next()
random_packets.append(packet)
Strobe Pattern Testing¶
Same address, same data, eight different strobe masks:
# Create sequence testing different strobe patterns
strobe_sequence = APBSequence(
name="strobe_test",
pwrite_seq=[True] * 8, # All writes
addr_seq=[0x4000], # Same address
data_seq=[0x12345678], # Same data
strb_seq=[0x1, 0x2, 0x4, 0x8, 0x3, 0xC, 0x5, 0xF], # All strobe combinations
inter_cycle_delays=[2] * 7
)
# Generate strobe test packets
strobe_packets = []
while strobe_sequence.has_more_transactions():
packet = strobe_sequence.next()
strobe_packets.append(packet)
print(f"Strobe test: data=0x{packet.pwdata:X}, strb=0b{packet.pstrb:04b}")
Performance Testing Sequence¶
Zero delays and a thousand writes — the fastest stimulus this format can describe.
# Create high-performance back-to-back sequence
perf_sequence = APBSequence(
name="performance_test",
pwrite_seq=[True] * 1000, # 1000 consecutive writes
addr_seq=[0x8000 + i*4 for i in range(100)], # 100 unique addresses (cycles)
data_seq=[i for i in range(1000)], # Sequential data
strb_seq=[0xF], # Always full strobes
inter_cycle_delays=[0], # No delays - maximum performance
)
# Generate performance test
start_time = time.time()
perf_packets = []
while perf_sequence.has_more_transactions():
packet = perf_sequence.next()
perf_packets.append(packet)
print(f"Generated {len(perf_packets)} packets in {time.time() - start_time:.3f}s")
Multi-Phase Test Sequence¶
Initialize, pattern, verify — one sequence, three phases, built by concatenating lists.
def create_multi_phase_sequence():
"""Create a sequence with multiple test phases"""
# Phase 1: Initialization writes
init_writes = [True] * 8
init_addrs = [0x1000 + i*4 for i in range(8)]
init_data = [0] * 8 # Initialize to zero
# Phase 2: Pattern writes
pattern_writes = [True] * 16
pattern_addrs = [0x1000 + i*4 for i in range(8)] # Will cycle
pattern_data = [0xA5A5A5A5, 0x5A5A5A5A] * 8 # Alternating pattern
# Phase 3: Verification reads
verify_reads = [False] * 8
verify_addrs = [0x1000 + i*4 for i in range(8)]
# Combine phases
combined_sequence = APBSequence(
name="multi_phase_test",
pwrite_seq=init_writes + pattern_writes + verify_reads,
addr_seq=init_addrs + pattern_addrs + verify_addrs,
data_seq=init_data + pattern_data + [0] * 8, # Reads don't need data
strb_seq=[0xF] * 32,
inter_cycle_delays=[1] * 31,
verify_data=True
)
return combined_sequence
multi_phase = create_multi_phase_sequence()
Error Injection Sequence¶
Bad addresses and a zero strobe mixed into otherwise normal traffic, so the DUT's error handling gets exercised along the way.
# Create sequence with error conditions
error_sequence = APBSequence(
name="error_injection",
pwrite_seq=[True, True, False, True, False],
addr_seq=[0x100, 0xDEAD, 0x100, 0xBEEF, 0xBEEF], # Some invalid addresses
data_seq=[0x12345678, 0xBAADF00D, 0, 0xDEADBEEF, 0],
strb_seq=[0xF, 0x0, 0xF, 0xF, 0xF], # Invalid strobe pattern
pprot_seq=[0, 7, 0, 3, 0], # Various protection levels
inter_cycle_delays=[1, 5, 1, 2, 1]
)
# Generate error test packets
error_packets = []
while error_sequence.has_more_transactions():
packet = error_sequence.next()
error_packets.append(packet)
# Check for potential error conditions
if packet.paddr > 0x8000:
print(f"Warning: High address 0x{packet.paddr:X} may cause error")
if packet.direction == 'WRITE' and packet.pstrb == 0:
print(f"Warning: Write with no strobes may cause error")
Timing-Critical Sequence¶
Per-transaction delays let you script the gaps, not just the transfers.
# Create sequence with specific timing requirements
timing_sequence = APBSequence(
name="timing_critical",
pwrite_seq=[True, False, True, False, True, False],
addr_seq=[0x5000, 0x5000, 0x5004, 0x5004, 0x5008, 0x5008],
data_seq=[0x1, 0x0, 0x2, 0x0, 0x3, 0x0],
strb_seq=[0xF, 0xF, 0xF, 0xF, 0xF, 0xF],
inter_cycle_delays=[0, 10, 0, 15, 0, 20] # Specific timing requirements
)
# Process with timing awareness
timing_packets = []
while timing_sequence.has_more_transactions():
packet = timing_sequence.next()
delay = timing_sequence.next_delay()
timing_packets.append((packet, delay))
print(f"Packet: {packet.formatted(compact=True)}, Delay: {delay}")
Integration with Test Framework¶
Testbench Integration¶
The canonical loop: pull a packet, send it, honor the delay, repeat.
import cocotb
from cocotb.triggers import RisingEdge, Timer
from CocoTBFramework.components.apb.apb_components import APBMaster
from CocoTBFramework.components.apb.apb_sequence import APBSequence
@cocotb.test()
async def sequence_driven_test(dut):
# Create master
master = APBMaster(dut, "SeqMaster", "apb_", dut.clk)
# Create test sequence
test_sequence = APBSequence(
name="testbench_sequence",
pwrite_seq=[True, False] * 5,
addr_seq=[0x1000 + i*4 for i in range(5)],
data_seq=[0x10000000 + i for i in range(5)],
strb_seq=[0xF] * 5,
inter_cycle_delays=[1, 2, 1, 2, 1, 2, 1, 2, 1]
)
# Execute sequence
while test_sequence.has_more_transactions():
packet = test_sequence.next()
delay = test_sequence.next_delay()
# Send packet
await master.send(packet)
print(f"Sent: {packet.formatted(compact=True)}")
# Apply inter-cycle delay
if delay > 0:
for _ in range(delay):
await RisingEdge(dut.clk)
# Wait for completion
while master.transfer_busy:
await RisingEdge(dut.clk)
Loop-Based Sequence Execution¶
A reusable runner, both for finite sequences and for "run this N times" style tests:
async def execute_sequence_loop(master, sequence, cycles=None):
"""Execute a sequence with optional cycling"""
cycle_count = 0
max_cycles = cycles or float('inf')
while cycle_count < max_cycles:
if not sequence.has_more_transactions():
if cycles is None:
break # Finite sequence completed
else:
sequence.reset_iterators() # Restart for cycling
packet = sequence.next()
delay = sequence.next_delay()
await master.send(packet)
if delay > 0:
await Timer(delay, units='ns')
cycle_count += 1
if cycle_count % 100 == 0:
print(f"Executed {cycle_count} transactions")
# Execute sequence 5 times
await execute_sequence_loop(master, test_sequence, cycles=5)
Sequence Composition¶
Build the phases separately, then stitch them into one sequence for the full test:
def compose_sequences(*sequences):
"""Compose multiple sequences into one"""
combined = APBSequence(name="composed_sequence")
for seq in sequences:
# Reset to ensure we get all transactions
seq.reset_iterators()
while seq.has_more_transactions():
packet = seq.next()
# Extract packet fields and add to combined sequences
combined.pwrite_seq.append(packet.pwrite == 1)
combined.addr_seq.append(packet.paddr)
combined.data_seq.append(packet.pwdata if packet.pwrite else 0)
combined.strb_seq.append(packet.pstrb)
combined.pprot_seq.append(packet.pprot)
combined.inter_cycle_delays.append(1) # Default delay
# Reinitialize iterators
combined.reset_iterators()
return combined
# Compose multiple test phases
init_seq = create_initialization_sequence()
test_seq = create_functional_test_sequence()
cleanup_seq = create_cleanup_sequence()
full_test = compose_sequences(init_seq, test_seq, cleanup_seq)
Statistical Analysis¶
Know what your generator actually generates before you trust the coverage report:
def analyze_sequence(sequence):
"""Analyze sequence characteristics"""
sequence.reset_iterators()
stats = {
'total_transactions': 0,
'write_count': 0,
'read_count': 0,
'unique_addresses': set(),
'strobe_patterns': set(),
'total_delay': 0
}
while sequence.has_more_transactions():
packet = sequence.next()
delay = sequence.next_delay()
stats['total_transactions'] += 1
if packet.direction == 'WRITE':
stats['write_count'] += 1
else:
stats['read_count'] += 1
stats['unique_addresses'].add(packet.paddr)
stats['strobe_patterns'].add(packet.pstrb)
stats['total_delay'] += delay
# Convert sets to counts
stats['unique_addresses'] = len(stats['unique_addresses'])
stats['strobe_patterns'] = len(stats['strobe_patterns'])
# Calculate ratios
if stats['total_transactions'] > 0:
stats['write_ratio'] = stats['write_count'] / stats['total_transactions']
stats['average_delay'] = stats['total_delay'] / stats['total_transactions']
return stats
# Analyze test sequence
analysis = analyze_sequence(test_sequence)
print(f"Sequence analysis: {analysis}")
Best Practices¶
1. Use Descriptive Names¶
The name ends up in logs and debug output — future you wants register_bank_initialization, not seq2.
sequence = APBSequence(
name="register_bank_initialization", # Clear, descriptive name
# ... rest of configuration
)
2. Validate Sequence Configuration¶
A sequence with write operations but no data list fails later and weirder than a ValueError at build time:
def validate_sequence(sequence):
"""Validate sequence configuration"""
if not sequence.pwrite_seq:
raise ValueError("Empty pwrite_seq")
if not sequence.addr_seq:
raise ValueError("Empty addr_seq")
# Check for write operations without data
if any(sequence.pwrite_seq) and not sequence.data_seq:
raise ValueError("Write operations require data_seq")
return True
validate_sequence(my_sequence)
3. Handle Sequence Completion¶
Two idioms, both fine — pick one and stay consistent:
# Always check for more transactions
while sequence.has_more_transactions():
packet = sequence.next()
# Process packet
# Or use explicit transaction counting
for i in range(len(sequence.pwrite_seq)):
packet = sequence.next()
# Process packet
4. Use Reset for Repeatable Tests¶
Sequences carry iterator state. Reset between tests, or your second run starts where the first one stopped.
# Reset before each test run
sequence.reset_iterators()
# Run test
run_sequence_test(sequence)
# Reset for next test
sequence.reset_iterators()
run_different_test(sequence)
5. Combine Sequential and Random Modes¶
Sequential for bring-up, random for soak — same object, one flag.
# Create base sequence
base_sequence = APBSequence(
pwrite_seq=[True, False] * 10,
addr_seq=list(range(0x1000, 0x1100, 4)),
data_seq=list(range(100)),
use_random_selection=False # Sequential first
)
# Run sequential test
run_sequence_test(base_sequence)
# Switch to random mode for stress testing
base_sequence.use_random_selection = True
run_stress_test(base_sequence)
For anything beyond lists of values — weighted random fields, say — pair sequences with the APBTransaction generator from apb_packet.py.