|
CocoTB Framework · Verification Infrastructure for RTL Testing GitHub · Documentation Index · MIT License |
gaxi_packet.py¶
GAXIPacket is thin on purpose. Everything that makes a packet useful — field validation, masking, pack/unpack, formatting — lives in the base Packet class and works here unchanged. What this subclass adds is timing: a packet can carry a master randomizer and a slave randomizer, and when a driver asks, it hands back the number of cycles to wait before touching valid or ready.
Overview¶
The GAXIPacket class provides:
- All field operations inherited from base Packet, unmodified
- Per-packet master/slave randomizers for timing control
- Valid-delay and ready-delay generation via FlexRandomizer
- Delays generated once and cached per packet — reuse the packet and you reuse its delays (this is the part that surprises people)
All field management, masking, pack/unpack, and formatting functionality is inherited from the base Packet class without modification.
Class¶
GAXIPacket¶
class GAXIPacket(Packet):
def __init__(self, field_config=None, master_randomizer=None,
slave_randomizer=None, **kwargs)
Parameters:
- field_config: Field configuration (FieldConfig object or dict)
- master_randomizer: Optional randomizer for master interface timing
- slave_randomizer: Optional randomizer for slave interface timing
- **kwargs: Initial field values passed to parent Packet class
Inherited from Packet:
- All field access via attributes (e.g., packet.addr, packet.data)
- Automatic field validation and masking
- FIFO packing/unpacking support
- Rich formatting and comparison capabilities
- Thread-safe field caching
GAXI-Specific Methods¶
Randomizer Management¶
set_master_randomizer(randomizer)¶
Attach (or replace) the randomizer that produces valid delays.
Parameters:
- randomizer: FlexRandomizer instance for master timing
from CocoTBFramework.components.shared.flex_randomizer import FlexRandomizer
# Create master randomizer
master_randomizer = FlexRandomizer({
'valid_delay': ([(0, 0), (1, 5), (10, 20)], [0.6, 0.3, 0.1])
})
packet.set_master_randomizer(master_randomizer)
set_slave_randomizer(randomizer)¶
Attach (or replace) the randomizer that produces ready delays.
Parameters:
- randomizer: FlexRandomizer instance for slave timing
# Create slave randomizer
slave_randomizer = FlexRandomizer({
'ready_delay': ([(0, 1), (2, 8), (9, 30)], [0.5, 0.3, 0.2])
})
packet.set_slave_randomizer(slave_randomizer)
Delay Generation¶
get_master_delay()¶
The valid delay for this packet, in cycles. First call rolls the randomizer and caches the result; later calls return the cached value. Returns 0 if no master randomizer is set.
Returns: Delay in cycles (0 if no randomizer)
# Get valid delay for master
valid_delay = packet.get_master_delay()
print(f"Master should wait {valid_delay} cycles before asserting valid")
get_slave_delay()¶
The ready delay for this packet, in cycles. Same caching behavior as the master side.
Returns: Delay in cycles (0 if no randomizer)
# Get ready delay for slave
ready_delay = packet.get_slave_delay()
print(f"Slave should wait {ready_delay} cycles before asserting ready")
Usage Patterns¶
Basic Packet Creation¶
from CocoTBFramework.components.gaxi.gaxi_packet import GAXIPacket
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 packet with initial values
packet = GAXIPacket(
field_config=field_config,
addr=0x1000,
data=0xDEADBEEF,
cmd=0x2 # WRITE command
)
# Access fields directly (inherited from base Packet)
print(f"Address: 0x{packet.addr:X}")
print(f"Data: 0x{packet.data:X}")
print(f"Command: {packet.cmd}")
Packet with Timing Randomizers¶
from CocoTBFramework.components.shared.flex_randomizer import FlexRandomizer
# Create randomizers for timing
master_randomizer = FlexRandomizer({
'valid_delay': ([(0, 0), (1, 3), (5, 10)], [0.7, 0.2, 0.1])
})
slave_randomizer = FlexRandomizer({
'ready_delay': ([(0, 0), (1, 5)], [0.8, 0.2])
})
# Create packet with randomizers
packet = GAXIPacket(
field_config=field_config,
master_randomizer=master_randomizer,
slave_randomizer=slave_randomizer,
addr=0x2000,
data=0xCAFEBABE
)
# Get timing delays
valid_delay = packet.get_master_delay()
ready_delay = packet.get_slave_delay()
print(f"Master valid delay: {valid_delay} cycles")
print(f"Slave ready delay: {ready_delay} cycles")
Field Operations (Inherited)¶
Nothing here is GAXI-specific — it's the base Packet contract, shown because it's what you'll actually use most.
# All field operations inherited from base Packet class
# Field validation and masking (automatic)
packet.addr = 0x123456789 # Automatically masked to 32 bits -> 0x23456789
packet.data = 0xDEADBEEF # Valid 32-bit value
# FIFO packing/unpacking (inherited)
fifo_data = packet.pack_for_fifo()
print(f"FIFO data: {fifo_data}")
# Create new packet from FIFO data
new_packet = GAXIPacket(field_config)
new_packet.unpack_from_fifo(fifo_data)
# Formatting (inherited)
print(packet.formatted()) # Detailed format
print(packet.formatted(compact=True)) # Compact format
Transaction Timing Integration¶
How a driver might consume the packet's delay when pushing it onto the bus:
import cocotb
from cocotb.triggers import RisingEdge, Timer
class TimedGAXIMaster:
def __init__(self, dut, clock, field_config):
self.dut = dut
self.clock = clock
self.field_config = field_config
# Create timing randomizers
self.master_randomizer = FlexRandomizer({
'valid_delay': ([(0, 0), (1, 5)], [0.8, 0.2])
})
async def send_packet(self, **field_values):
"""Send packet with randomized timing"""
# Create packet with randomizer
packet = GAXIPacket(
field_config=self.field_config,
master_randomizer=self.master_randomizer,
**field_values
)
# Get timing delay
valid_delay = packet.get_master_delay()
# Apply delay before driving (delay is in clock cycles)
for _ in range(valid_delay):
await RisingEdge(self.clock)
# Drive packet fields
self.dut.addr.value = packet.addr
self.dut.data.value = packet.data
self.dut.valid.value = 1
# Wait for handshake
while self.dut.ready.value != 1:
await RisingEdge(self.clock)
await RisingEdge(self.clock)
self.dut.valid.value = 0
return packet
# Usage
master = TimedGAXIMaster(dut, clock, field_config)
packet = await master.send_packet(addr=0x1000, data=0x12345678, cmd=0x2)
Slave Timing Integration¶
And the mirror image on the receive side — delay before raising ready:
class TimedGAXISlave:
def __init__(self, dut, clock, field_config):
self.dut = dut
self.clock = clock
self.field_config = field_config
# Create slave timing randomizer
self.slave_randomizer = FlexRandomizer({
'ready_delay': ([(0, 0), (1, 3), (5, 10)], [0.6, 0.3, 0.1])
})
async def receive_packet(self):
"""Receive packet with randomized ready timing"""
# Wait for valid
while self.dut.valid.value != 1:
await RisingEdge(self.clock)
# Create packet for timing
packet = GAXIPacket(
field_config=self.field_config,
slave_randomizer=self.slave_randomizer
)
# Get ready delay
ready_delay = packet.get_slave_delay()
# Apply delay before asserting ready (delay is in clock cycles)
for _ in range(ready_delay):
await RisingEdge(self.clock)
# Assert ready and capture data
self.dut.ready.value = 1
await RisingEdge(self.clock)
# Capture packet data
packet.addr = int(self.dut.addr.value)
packet.data = int(self.dut.data.value)
if hasattr(self.dut, 'cmd'):
packet.cmd = int(self.dut.cmd.value)
# Deassert ready
self.dut.ready.value = 0
return packet
# Usage
slave = TimedGAXISlave(dut, clock, field_config)
received_packet = await slave.receive_packet()
Batch Packet Creation¶
def create_test_packets(field_config, count=10):
"""Create a batch of test packets with varied data"""
packets = []
# Create common randomizers
master_randomizer = FlexRandomizer({
'valid_delay': ([(0, 0), (1, 2)], [0.9, 0.1])
})
slave_randomizer = FlexRandomizer({
'ready_delay': ([(0, 1), (2, 5)], [0.7, 0.3])
})
for i in range(count):
packet = GAXIPacket(
field_config=field_config,
master_randomizer=master_randomizer,
slave_randomizer=slave_randomizer,
addr=0x1000 + i*4,
data=0x10000000 + i,
cmd=0x2 if i % 2 == 0 else 0x1 # Alternate READ/write
)
packets.append(packet)
return packets
# Usage
test_packets = create_test_packets(field_config, count=20)
for i, packet in enumerate(test_packets):
print(f"Packet {i}: {packet.formatted(compact=True)}")
print(f" Master delay: {packet.get_master_delay()}")
print(f" Slave delay: {packet.get_slave_delay()}")
Packet Comparison and Validation¶
def validate_packet_sequence(sent_packets, received_packets):
"""Validate that received packets match sent packets"""
assert len(sent_packets) == len(received_packets), \
f"Packet count mismatch: sent {len(sent_packets)}, received {len(received_packets)}"
for i, (sent, received) in enumerate(zip(sent_packets, received_packets)):
# Use inherited comparison (ignores timing fields)
if sent != received:
print(f"Packet {i} mismatch:")
print(f" Sent: {sent.formatted(compact=True)}")
print(f" Received: {received.formatted(compact=True)}")
assert False, f"Packet {i} data mismatch"
else:
print(f"Packet {i}: ✓ Match")
print(f"All {len(sent_packets)} packets validated successfully")
# Usage
sent_packets = create_test_packets(field_config, 10)
# ... send packets through DUT ...
received_packets = capture_received_packets()
validate_packet_sequence(sent_packets, received_packets)
Multi-Field Packets¶
# Create complex field configuration
complex_field_config = FieldConfig()
complex_field_config.add_field(FieldDefinition("addr", 32, format="hex"))
complex_field_config.add_field(FieldDefinition("data", 64, format="hex"))
complex_field_config.add_field(FieldDefinition("cmd", 4, format="hex"))
complex_field_config.add_field(FieldDefinition("id", 8, format="hex"))
complex_field_config.add_field(FieldDefinition("size", 3, format="hex"))
# Create packet with all fields
complex_packet = GAXIPacket(
field_config=complex_field_config,
addr=0x12345678,
data=0xDEADBEEFCAFEBABE,
cmd=0x3,
id=0x55,
size=0x4
)
# All field operations work automatically
print(f"Total packet bits: {complex_packet.get_total_bits()}")
print(f"Formatted packet:\n{complex_packet.formatted()}")
# FIFO operations handle all fields
fifo_data = complex_packet.pack_for_fifo()
print(f"FIFO data: {fifo_data}")
Randomizer Delay Caching¶
Worth understanding before it confuses you: the delay is rolled once per packet and then frozen. The cache exists so a driver can ask for the delay more than once without re-rolling — the flip side is that reusing a packet reuses its timing. New transaction, new random delay? Build a new packet.
def test_delay_caching():
"""Test that delays are cached until randomizer is reset"""
packet = GAXIPacket(field_config)
# Set randomizer
randomizer = FlexRandomizer({
'valid_delay': ([(1, 5)], [1.0])
})
packet.set_master_randomizer(randomizer)
# First call generates and caches delay
delay1 = packet.get_master_delay()
# Subsequent calls return cached value
delay2 = packet.get_master_delay()
delay3 = packet.get_master_delay()
assert delay1 == delay2 == delay3, "Delay should be cached"
print(f"Cached delay: {delay1} cycles")
# Setting new randomizer resets cache
new_randomizer = FlexRandomizer({
'valid_delay': ([(10, 20)], [1.0])
})
packet.set_master_randomizer(new_randomizer)
# New delay generated
new_delay = packet.get_master_delay()
print(f"New delay after randomizer change: {new_delay} cycles")
# This new delay is now cached
cached_new_delay = packet.get_master_delay()
assert new_delay == cached_new_delay, "New delay should be cached"
test_delay_caching()
Error Handling¶
Field Validation Errors¶
# Field validation handled by base Packet class
try:
packet = GAXIPacket(field_config, addr="invalid")
except ValueError as e:
print(f"Invalid field value: {e}")
Missing Randomizers¶
No randomizer attached means zero delay, not an error — a packet without timing constraints just transfers as fast as the handshake allows.
# Graceful handling when randomizers not set
packet = GAXIPacket(field_config)
# Returns 0 when no randomizer
master_delay = packet.get_master_delay() # Returns 0
slave_delay = packet.get_slave_delay() # Returns 0
print(f"Delays without randomizers: master={master_delay}, slave={slave_delay}")
Randomizer Errors¶
# Handle randomizer generation errors
try:
delay = packet.get_master_delay()
except Exception as e:
print(f"Randomizer error: {e}")
delay = 0 # Use default delay
Best Practices¶
1. Separate Randomizers for Each Direction¶
Valid timing and ready timing are different knobs. Keep them in different FlexRandomizer instances so you can tune backpressure without touching launch timing.
# Master randomizer for valid delays
master_randomizer = FlexRandomizer({'valid_delay': ([(0, 5)], [1.0])})
# Slave randomizer for ready delays
slave_randomizer = FlexRandomizer({'ready_delay': ([(0, 3)], [1.0])})
packet = GAXIPacket(field_config,
master_randomizer=master_randomizer,
slave_randomizer=slave_randomizer)
2. Use the Inherited Machinery¶
# Use inherited field operations
packet.addr = 0x1000 # Field validation automatic
fifo_data = packet.pack_for_fifo() # FIFO conversion automatic
formatted = packet.formatted() # Rich formatting automatic
3. Respect the Delay Cache¶
One packet, one delay. Create a fresh packet per transaction when you want fresh randomization.
# Delays are cached per packet - appropriate for single transaction
master_delay = packet.get_master_delay() # Generated and cached
same_delay = packet.get_master_delay() # Returns cached value
# Create new packet for new transaction with fresh delays
new_packet = GAXIPacket(field_config, master_randomizer=randomizer)
new_delay = new_packet.get_master_delay() # Fresh delay
4. Let the Components Attach Timing¶
Masters and slaves own their randomizers; packets created through them pick up the right one automatically.
# Integrate timing into master/slave components
class TimingAwareMaster:
def create_packet(self, **fields):
return GAXIPacket(self.field_config,
master_randomizer=self.randomizer,
**fields)
async def send(self, packet):
delay = packet.get_master_delay()
# Apply delay and send
5. Validate with the Inherited Comparison¶
# Use inherited comparison for validation
assert sent_packet == received_packet, "Packet data mismatch"
# Use inherited formatting for debugging
if sent_packet != received_packet:
print(f"Sent: {sent_packet.formatted(compact=True)}")
print(f"Received: {received_packet.formatted(compact=True)}")
Use GAXIPacket like a plain Packet and only think about the randomizers when you wire up a driver. The one rule to remember is the caching: ask twice, get the same number; want a new number, build a new packet.