Skip to content
RTL Design Sherpa CocoTB Framework · Verification Infrastructure for RTL Testing
GitHub · Documentation Index · MIT License

gaxi_sequence.py

A GAXISequence is an ordered list of transactions to play through a master — with delays between them, dependencies among them, and randomized field values if you want them. Build the sequence, generate the packets, hand them to a driver or the command handler. Field masking is the packet's job, not the sequence's, which keeps this class small.

Overview

The GAXISequence class provides: - Ordered transaction lists with per-transaction delays - Dependency tracking — transaction N can wait on transaction M - FlexRandomizer integration for constrained random field values - Pattern builders (walking ones/zeros, alternating bits, bursts) for the classic corner cases - Field masking left to the Packet class, where it belongs - Optimized initialization for large field configurations

Earlier versions of this class did their own field masking; that code is gone, because the packet already does it right. The APIs are unchanged.

Class

GAXISequence

class GAXISequence:
    def __init__(self, name="basic", field_config=None, packet_class=None, log=None)

Parameters: - name: Sequence name for identification - field_config: Field configuration (FieldConfig object or dictionary) - packet_class: Packet class to use (defaults to GAXIPacket) - log: Logger instance

Performance Note: Field configurations wider than about 50 bits get an optimized initialization path automatically — large configs used to make sequence construction noticeably slow, and this is the fix.

Core Methods

Randomization Setup

set_randomizer(constraints_dict)

Attach a FlexRandomizer for field value generation. Required before any of the add_randomized_* calls.

Parameters: - constraints_dict: Dictionary of field constraints for FlexRandomizer

Returns: Self for method chaining

sequence = GAXISequence("randomized_test", field_config)

# Set up randomizer with constraints
sequence.set_randomizer({
    'data': ([(0x0000, 0xFFFF), (0x10000, 0x1FFFF)], [0.7, 0.3]),
    'addr': ([(0, 1023)], [1.0])
})

Transaction Management

add_transaction(field_values=None, delay=0, depends_on=None)

Add a transaction to the sequence with automatic field masking.

Parameters: - field_values: Dictionary of field values (automatically masked by Packet) - delay: Delay after this transaction - depends_on: Index of transaction this depends on (None for no dependency)

Returns: Index of added transaction for dependency tracking

# Add basic transaction
index = sequence.add_transaction({
    'addr': 0x1000,
    'data': 0xDEADBEEF,
    'cmd': 0x2
}, delay=5)

# Add transaction with dependency
dependent_index = sequence.add_transaction({
    'addr': 0x2000,
    'data': 0xCAFEBABE
}, depends_on=index)

add_data_transaction(data, delay=0, depends_on=None)

The shorthand for "I only care about the data field."

Parameters: - data: Data value (automatically masked by Packet class) - delay: Delay after transaction - depends_on: Index of transaction this depends on

Returns: Index of added transaction

# Add data-only transactions
seq_index = sequence.add_data_transaction(0x12345678, delay=2)
next_index = sequence.add_data_transaction(0x87654321, depends_on=seq_index)

Pattern Generation

add_burst(count, start_data=0, data_step=1, delay=0, dependency_chain=False)

Add a run of transactions with incrementing data. Set dependency_chain=True and each one waits on its predecessor — useful when you want to force ordering through the DUT.

Parameters: - count: Number of transactions in burst - start_data: Starting data value - data_step: Step between data values - delay: Delay between transactions - dependency_chain: If True, each transaction depends on the previous

Returns: List of transaction indexes

# Add burst without dependencies
burst_indexes = sequence.add_burst(
    count=8,
    start_data=0x1000,
    data_step=0x100,
    delay=1
)

# Add burst with dependency chain
chain_indexes = sequence.add_burst(
    count=5,
    start_data=0x2000,
    data_step=4,
    dependency_chain=True  # Each depends on previous
)

add_pattern(pattern_name, data_width=32, delay=0)

The classic bit patterns. These catch stuck bits, shorted lines, and swap wiring — the stuff random data walks past.

Parameters: - pattern_name: Type of pattern ('walking_ones', 'walking_zeros', 'alternating') - data_width: Width of data field - delay: Delay between pattern transactions

Returns: List of transaction indexes

# Add walking ones pattern
ones_indexes = sequence.add_pattern('walking_ones', data_width=32, delay=1)

# Add walking zeros pattern
zeros_indexes = sequence.add_pattern('walking_zeros', data_width=16, delay=2)

# Add alternating bit patterns
alt_indexes = sequence.add_pattern('alternating', data_width=32, delay=0)

Randomized Transactions

add_randomized_transaction(delay=0, depends_on=None, field_overrides=None)

Add a transaction whose fields come from the randomizer. Pin specific fields with field_overrides when you need a randomized packet with one fixed value.

Parameters: - delay: Delay after transaction - depends_on: Index of transaction this depends on - field_overrides: Dictionary of field values to override random values

Returns: Index of added transaction

# Must set randomizer first
sequence.set_randomizer({
    'addr': ([(0x1000, 0x8000)], [1.0]),
    'data': ([(0, 0xFFFFFFFF)], [1.0])
})

# Add randomized transaction
rand_index = sequence.add_randomized_transaction(
    delay=3,
    field_overrides={'cmd': 0x2}  # Override command to WRITE
)

generate_packets_with_randomization(count)

Generate standalone randomized packets — not added to the sequence, just returned.

Parameters: - count: Number of packets to generate

Returns: List of packets with randomized field values

# Generate random test packets
random_packets = sequence.generate_packets_with_randomization(count=20)
for packet in random_packets:
    print(f"Random: {packet.formatted(compact=True)}")

Backward Compatibility Methods

Older tests used these names; they still work and do what they always did.

add_data_value(data, delay=0)

Add a transaction with a data value - backward compatibility.

index = sequence.add_data_value(0xDEADBEEF, delay=5)

add_data_value_with_dependency(data, delay=0, depends_on_index=None, dependency_type="after")

Add a data transaction that depends on completion of a previous transaction.

base_index = sequence.add_data_value(0x1000)
dep_index = sequence.add_data_value_with_dependency(0x2000, depends_on_index=base_index)

add_data_incrementing(count, data_start=0, data_step=1, delay=0)

Add transactions with incrementing data values.

Returns: Tuple of (self, list of indexes) for method chaining

seq, indexes = sequence.add_data_incrementing(
    count=10,
    data_start=0x1000,
    data_step=0x100,
    delay=2
)

Pattern Convenience Methods

add_walking_ones(data_width=32, delay=0)

Add transactions with walking ones pattern.

ones_indexes = sequence.add_walking_ones(data_width=32, delay=1)

add_walking_zeros(data_width=32, delay=0)

Add transactions with walking zeros pattern.

zeros_indexes = sequence.add_walking_zeros(data_width=16, delay=2)

add_alternating_bits(data_width=32, delay=0)

Add transactions with alternating bit patterns.

alt_indexes = sequence.add_alternating_bits(data_width=24, delay=1)

Dependency Management

add_burst_with_dependencies(count, data_start=0, data_step=1, delay=0, dependency_spacing=1)

A burst where each transaction waits on one earlier in the burst. dependency_spacing=1 chains on the immediate predecessor; larger spacing lets some parallelism through.

Parameters: - count: Number of transactions - data_start: Starting data value - data_step: Step size between data values - delay: Delay between transactions - dependency_spacing: How many transactions back to depend on

Returns: Tuple of (self, list of indexes)

# Each transaction depends on the previous one
seq, chain_indexes = sequence.add_burst_with_dependencies(
    count=5,
    data_start=0x1000,
    dependency_spacing=1
)

# Each transaction depends on two transactions back
seq, spaced_indexes = sequence.add_burst_with_dependencies(
    count=10,
    dependency_spacing=2
)

add_dependency_chain(count, data_start=0, data_step=1, delay=0)

Add a chain where each transaction depends on the previous one.

seq, chain_indexes = sequence.add_dependency_chain(
    count=8,
    data_start=0x2000,
    data_step=4
)

Packet Generation

generate_packets(count=None)

Turn the sequence into packets. Dependency information rides along as metadata on each packet.

Parameters: - count: Number of packets to generate (None for all)

Returns: List of generated packets with dependency metadata

# Generate all packets
all_packets = sequence.generate_packets()

# Generate first 10 packets
first_packets = sequence.generate_packets(count=10)

# Each packet includes dependency metadata
for packet in all_packets:
    if hasattr(packet, 'depends_on_index'):
        print(f"Packet {packet.sequence_index} depends on {packet.depends_on_index}")

Dependency Analysis

get_dependency_order()

An execution order that respects every dependency. If you're driving transactions yourself rather than going through the command handler, this is the order to use.

Returns: List of transaction indexes in dependency-resolved order

# Get execution order
execution_order = sequence.get_dependency_order()
print(f"Execute transactions in order: {execution_order}")

# Generate packets in dependency order
ordered_packets = []
all_packets = sequence.generate_packets()
for index in execution_order:
    ordered_packets.append(all_packets[index])

validate_dependencies()

Check the whole graph — dangling indexes, unresolvable waits. Cheap to run; do it before the simulation, not after a hang.

Returns: True if valid, raises ValueError if invalid

try:
    sequence.validate_dependencies()
    print("All dependencies are valid")
except ValueError as e:
    print(f"Dependency validation failed: {e}")

get_dependency_graph()

The raw dependency map, for when you want to see what you actually built.

Returns: Dictionary mapping transaction indexes to their dependencies

dep_graph = sequence.get_dependency_graph()
print(f"Dependencies: {dep_graph['dependencies']}")
print(f"Transaction count: {dep_graph['transaction_count']}")

Statistics and Information

get_stats()

Sequence shape at a glance: length, dependency count, whether randomization is in play.

Returns: Dictionary with comprehensive statistics

stats = sequence.get_stats()
print(f"Sequence length: {stats['sequence_length']}")
print(f"Dependencies: {stats['dependencies']}")
print(f"Dependency percentage: {stats.get('dependency_percentage', 0):.1f}%")
print(f"Uses randomization: {stats['uses_randomization']}")

reset()

Reset sequence to empty state.

sequence.reset()  # Clear all transactions and dependencies

__len__()

Return number of transactions in sequence.

transaction_count = len(sequence)

Usage Patterns

Basic Sequence Creation

from CocoTBFramework.components.gaxi.gaxi_sequence import GAXISequence
from CocoTBFramework.components.shared.field_config import FieldConfig

# Create field configuration
field_config = FieldConfig()
field_config.add_field(FieldDefinition("addr", 32, format="hex"))
field_config.add_field(FieldDefinition("data", 32, format="hex"))
field_config.add_field(FieldDefinition("cmd", 4, format="hex"))

# Create sequence
sequence = GAXISequence("basic_test", field_config)

# Add various transaction types
sequence.add_transaction({'addr': 0x1000, 'data': 0xDEADBEEF, 'cmd': 0x2})
sequence.add_data_transaction(0x12345678, delay=2)
sequence.add_burst(count=5, start_data=0x1000, data_step=0x100)

# Generate packets
packets = sequence.generate_packets()
print(f"Generated {len(packets)} packets")

Randomized Sequence

# Create sequence with randomization
sequence = GAXISequence("random_test", field_config)

# Set up randomizer
sequence.set_randomizer({
    'addr': ([(0x1000, 0x1FFF), (0x8000, 0x8FFF)], [0.7, 0.3]),
    'data': ([(0, 0xFFFF), (0x10000, 0xFFFFFFFF)], [0.5, 0.5]),
    'cmd': ([(0x1, 0x2)], [1.0])  # READ or WRITE only
})

# Add randomized transactions
for i in range(20):
    sequence.add_randomized_transaction(delay=random.randint(0, 5))

# Generate packets with random values
packets = sequence.generate_packets()

Pattern-Based Testing

# Create pattern sequence
pattern_sequence = GAXISequence("pattern_test", field_config)

# Add different test patterns
pattern_sequence.add_walking_ones(data_width=32, delay=1)
pattern_sequence.add_walking_zeros(data_width=32, delay=1)
pattern_sequence.add_alternating_bits(data_width=32, delay=2)

# Add custom patterns
custom_patterns = [0xAAAAAAAA, 0x55555555, 0xCCCCCCCC, 0x33333333]
for pattern in custom_patterns:
    pattern_sequence.add_data_transaction(pattern, delay=1)

packets = pattern_sequence.generate_packets()
print(f"Pattern sequence has {len(packets)} packets")

Dependency Chain Testing

# Create dependency sequence
dep_sequence = GAXISequence("dependency_test", field_config)

# Add initial transaction
base_index = dep_sequence.add_transaction({
    'addr': 0x1000,
    'data': 0x1,
    'cmd': 0x2  # WRITE
})

# Add chain of dependent transactions
for i in range(1, 10):
    dep_index = dep_sequence.add_transaction({
        'addr': 0x1000 + i*4,
        'data': i,
        'cmd': 0x2
    }, depends_on=base_index + i - 1)  # Depends on previous

# Validate dependencies
dep_sequence.validate_dependencies()

# Get execution order
order = dep_sequence.get_dependency_order()
print(f"Execution order: {order}")

# Generate packets in dependency order
packets = dep_sequence.generate_packets()

Complex Mixed Sequence

Phases of a real test, in one sequence: an init burst, pattern traffic that waits on it, randomized traffic, and a cleanup chain at the end.

def create_complex_test_sequence(field_config):
    """Create a complex sequence with mixed transaction types"""
    sequence = GAXISequence("complex_test", field_config)

    # Set up randomizer for some transactions
    sequence.set_randomizer({
        'addr': ([(0x1000, 0x8000)], [1.0]),
        'data': ([(0, 0xFFFFFFFF)], [1.0])
    })

    # Phase 1: Initialization burst
    init_indexes = sequence.add_burst(
        count=4,
        start_data=0x10000000,
        data_step=0x100,
        delay=0
    )

    # Phase 2: Pattern testing (depends on init)
    pattern_start = sequence.add_walking_ones(data_width=32, delay=1)
    for idx in pattern_start[:3]:  # First 3 patterns depend on init
        sequence.sequence_data[idx] = (
            sequence.sequence_data[idx][0],  # field_values
            sequence.sequence_data[idx][1],  # delay
            init_indexes[-1]  # depends_on last init transaction
        )

    # Phase 3: Random transactions
    random_indexes = []
    for i in range(10):
        rand_idx = sequence.add_randomized_transaction(delay=2)
        random_indexes.append(rand_idx)

    # Phase 4: Cleanup (depends on random completion)
    cleanup_indexes = sequence.add_burst(
        count=3,
        start_data=0x99999999,
        dependency_chain=True
    )

    return sequence

# Create and use complex sequence
complex_seq = create_complex_test_sequence(field_config)
packets = complex_seq.generate_packets()

# Analyze sequence
stats = complex_seq.get_stats()
print(f"Complex sequence statistics: {stats}")

Factory Methods

The classmethods below cover the common sequence shapes in one call:

create_burst_sequence(name, count, start_data=0, data_step=1, field_config=None, dependency_chain=False)

Create a burst sequence with incrementing data.

burst_seq = GAXISequence.create_burst_sequence(
    name="burst_test",
    count=16,
    start_data=0x1000,
    data_step=4,
    field_config=field_config,
    dependency_chain=True
)

create_pattern_sequence(name, pattern_name, data_width=32, field_config=None)

Create a sequence with test patterns.

pattern_seq = GAXISequence.create_pattern_sequence(
    name="walking_ones_test",
    pattern_name="walking_ones",
    data_width=32,
    field_config=field_config
)

create_randomized_sequence(name, constraints, count, field_config=None)

Create a sequence with randomized values.

random_seq = GAXISequence.create_randomized_sequence(
    name="random_test",
    constraints={
        'addr': ([(0x1000, 0x8000)], [1.0]),
        'data': ([(0, 0xFFFFFFFF)], [1.0])
    },
    count=50,
    field_config=field_config
)

create_dependency_chain(name="dependency_chain", count=5, data_start=0, data_step=1, delay=0)

Create a sequence with transactions forming a dependency chain.

chain_seq = GAXISequence.create_dependency_chain(
    name="chain_test",
    count=8,
    data_start=0x2000,
    data_step=8,
    delay=1
)

Performance Considerations

Large Field Configurations

For field configurations with >50 total bits, the class automatically uses optimized initialization:

# Automatically optimized for large configs
large_config = create_large_field_config()  # >50 bits
sequence = GAXISequence("large_test", large_config)  # Uses optimized mode

Memory Usage

Long sequences stay cheap — transactions are stored compactly until you generate packets:

# Efficient for large sequences
large_sequence = GAXISequence("large", field_config)
for i in range(10000):
    large_sequence.add_data_transaction(i)
# Memory usage remains reasonable

Error Handling

Dependency Validation

try:
    sequence.validate_dependencies()
except ValueError as e:
    print(f"Invalid dependencies: {e}")
    # Fix dependencies and retry

Randomizer Errors

Calling a randomized method without a randomizer raises immediately — set it up first.

try:
    sequence.add_randomized_transaction()
except ValueError as e:
    print(f"No randomizer set: {e}")
    # Set randomizer first
    sequence.set_randomizer(constraints)

Field Validation

Over-wide values are masked by the packet, not rejected — you get the low bits, which is almost always what a test meant.

# Field validation handled automatically by Packet class
sequence.add_transaction({'addr': 0x123456789})  # Automatically masked

Best Practices

1. Use Factory Methods for Common Patterns

# Prefer factory methods
burst_seq = GAXISequence.create_burst_sequence("test", 10, 0x1000)
# Over manual creation

2. Validate Dependencies Early

# Validate after adding dependent transactions
sequence.add_burst_with_dependencies(count=5, dependency_spacing=1)
sequence.validate_dependencies()  # Catch errors early

Random data outside the DUT's address map just tests your error handling. Sometimes that's the point. Usually it isn't.

# Set up realistic randomization constraints
sequence.set_randomizer({
    'addr': ([(0x1000, 0x7FFF)], [1.0]),  # Valid address range
    'data': ([(0, 0xFFFFFFFF)], [1.0])     # Full data range
})

4. Generate Packets in Dependency Order

# For dependency chains, use dependency order
order = sequence.get_dependency_order()
packets = sequence.generate_packets()
ordered_packets = [packets[i] for i in order]

5. Watch the Dependency Ratio

A sequence where everything waits on everything is a serial test wearing a parallel costume.

# Check sequence health
stats = sequence.get_stats()
if stats['dependency_percentage'] > 50:
    print("Warning: High dependency ratio may affect parallelism")

The factory classmethods cover the common shapes — burst, pattern, randomized, chain. Compose the add_* calls when you need something stranger, and validate the dependencies before you hand the sequence off.