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

fifo_packet.py

FIFO Packet class providing minimal protocol-specific extensions to the base Packet class for FIFO protocol transactions.

Overview

The FIFOPacket class is a lightweight extension of the base Packet class that adds FIFO-specific randomizer handling for timing control. It inherits all field management, masking, pack/unpack, and formatting functionality from the base Packet class while adding support for master and slave interface timing randomization.

Key Features

  • Minimal Extension: Only adds FIFO-specific functionality to base Packet
  • Randomizer Integration: Support for separate master and slave randomizers
  • Timing Control: Configurable delays for read and write operations
  • Full Inheritance: All base Packet functionality available (formatting, validation, etc.)

Core Class

FIFOPacket

Packet class for FIFO protocol transactions.

Constructor

FIFOPacket(field_config=None,
           master_randomizer=None,
           slave_randomizer=None, 
           **kwargs)

Parameters: - field_config: Field configuration (FieldConfig object or dict that will be converted) - master_randomizer: Optional randomizer for master interface timing - slave_randomizer: Optional randomizer for slave interface timing
- **kwargs: Initial field values passed to parent Packet constructor

Example:

# Basic packet creation
packet = FIFOPacket(field_config, data=0x12345678)

# Packet with randomizers
packet = FIFOPacket(
    field_config=field_config,
    master_randomizer=write_randomizer,
    slave_randomizer=read_randomizer,
    addr=0x1000,
    data=0xDEADBEEF,
    cmd=0x2
)

Core Methods

Randomizer Management

set_master_randomizer(randomizer)

Set the master randomizer for write timing control.

Parameters: - randomizer: FlexRandomizer instance for master interface

# Set master randomizer
write_randomizer = FlexRandomizer({
    'write_delay': ([(0, 0), (1, 5), (10, 20)], [0.6, 0.3, 0.1])
})
packet.set_master_randomizer(write_randomizer)

set_slave_randomizer(randomizer)

Set the slave randomizer for read timing control.

Parameters: - randomizer: FlexRandomizer instance for slave interface

# Set slave randomizer
read_randomizer = FlexRandomizer({
    'read_delay': ([(0, 1), (2, 8), (9, 30)], [5, 2, 1])
})
packet.set_slave_randomizer(read_randomizer)

Timing Access

get_master_delay()

Get the delay for the master interface (write delay).

Returns: Delay in cycles (0 if no randomizer)

# Get write delay
write_delay = packet.get_master_delay()
print(f"Write delay: {write_delay} cycles")

# Use in timing control
for _ in range(write_delay):
    await RisingEdge(clock)

get_slave_delay()

Get the delay for the slave interface (read delay).

Returns: Delay in cycles (0 if no randomizer)

# Get read delay
read_delay = packet.get_slave_delay()
print(f"Read delay: {read_delay} cycles")

# Use in timing control
for _ in range(read_delay):
    await RisingEdge(clock)

Inherited Functionality

FIFOPacket inherits all functionality from the base Packet class:

Field Access

# Direct field access with validation
packet.addr = 0x1000
packet.data = 0xDEADBEEF
packet.cmd = 0x2

# Field values are automatically validated and masked
print(f"Address: 0x{packet.addr:X}")
print(f"Data: 0x{packet.data:X}")

FIFO Operations

# Pack for FIFO transmission
fifo_data = packet.pack_for_fifo()
print(fifo_data)  # {'addr': 0x200, 'data': 0xDEADBEEF} (with bit shifting)

# Unpack from FIFO reception
packet.unpack_from_fifo({'addr': 0x200, 'data': 0x12345678})

Formatting and Display

# Rich formatting
print(packet.formatted())
print(packet.formatted(compact=True))
print(packet.formatted(show_fifo=True))

# String representation
print(packet)  # Detailed format with all fields

Comparison and Copying

# Packet comparison
packet1 = FIFOPacket(field_config, data=0x1234)
packet2 = FIFOPacket(field_config, data=0x1234)
assert packet1 == packet2

# Packet copying
packet_copy = packet.copy()

Usage Patterns

Basic Packet Creation and Usage

# Define 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 = FIFOPacket(field_config, addr=0x1000, data=0xDEADBEEF, cmd=0x2)

# Access fields
print(f"Command: {packet.cmd}, Address: 0x{packet.addr:X}, Data: 0x{packet.data:X}")

Timing-Controlled Packets

# Create randomizers for timing control
write_randomizer = FlexRandomizer({
    'write_delay': ([(0, 0), (1, 5), (10, 20)], [0.6, 0.3, 0.1])
})

read_randomizer = FlexRandomizer({
    'read_delay': ([(0, 1), (2, 8), (9, 30)], [5, 2, 1])
})

# Create packet with timing control
packet = FIFOPacket(
    field_config=field_config,
    master_randomizer=write_randomizer,
    slave_randomizer=read_randomizer,
    data=0x12345678
)

# Use timing in master component
class TimingMaster:
    async def send_packet(self, packet):
        # Apply master delay
        delay = packet.get_master_delay()
        for _ in range(delay):
            await RisingEdge(self.clock)

        # Drive packet
        self.drive_packet_data(packet)
        await RisingEdge(self.clock)

# Use timing in slave component  
class TimingSlave:
    async def receive_packet(self, packet):
        # Apply slave delay
        delay = packet.get_slave_delay()
        for _ in range(delay):
            await RisingEdge(self.clock)

        # Read packet
        self.read_packet_data(packet)

Packet Factory Integration

# Use with PacketFactory for consistent creation
factory = PacketFactory(FIFOPacket, field_config)

# Create packet with factory
packet = factory.create_packet(addr=0x2000, data=0xCAFEBABE)

# Add randomizers after creation
packet.set_master_randomizer(write_randomizer)
packet.set_slave_randomizer(read_randomizer)

# Or create with timing
timed_packet = factory.create_timed_packet(
    start_time=cocotb.utils.get_sim_time(),
    addr=0x3000,
    data=0x87654321
)

Sequence Integration

class TimedFIFOSequence:
    def __init__(self, field_config, master_randomizer=None, slave_randomizer=None):
        self.field_config = field_config
        self.master_randomizer = master_randomizer
        self.slave_randomizer = slave_randomizer
        self.packets = []

    def add_packet(self, **field_values):
        """Add packet with timing randomizers"""
        packet = FIFOPacket(
            field_config=self.field_config,
            master_randomizer=self.master_randomizer,
            slave_randomizer=self.slave_randomizer,
            **field_values
        )
        self.packets.append(packet)
        return packet

    def generate_burst(self, count, base_addr=0x1000, base_data=0x1000):
        """Generate burst with consistent timing"""
        for i in range(count):
            self.add_packet(
                addr=base_addr + i * 4,
                data=base_data + i,
                cmd=0x2  # WRITE
            )

    def get_total_delay(self, interface='master'):
        """Calculate total delay for sequence"""
        total_delay = 0
        for packet in self.packets:
            if interface == 'master':
                total_delay += packet.get_master_delay()
            else:
                total_delay += packet.get_slave_delay()
        return total_delay

# Usage
sequence = TimedFIFOSequence(field_config, write_randomizer, read_randomizer)
sequence.generate_burst(count=10)

print(f"Total write delay: {sequence.get_total_delay('master')} cycles")
print(f"Total read delay: {sequence.get_total_delay('slave')} cycles")

Advanced Timing Control

class AdaptiveTimingPacket:
    """Packet with adaptive timing based on conditions"""

    def __init__(self, field_config):
        self.packet = FIFOPacket(field_config)
        self.timing_mode = 'normal'

        # Create different randomizers for different modes
        self.fast_randomizer = FlexRandomizer({
            'write_delay': ([(0, 0), (1, 1)], [9, 1])
        })

        self.normal_randomizer = FlexRandomizer({
            'write_delay': ([(0, 0), (1, 5), (6, 15)], [5, 3, 2])
        })

        self.slow_randomizer = FlexRandomizer({
            'write_delay': ([(5, 10), (11, 25), (26, 50)], [3, 2, 1])
        })

        # Set default
        self.set_timing_mode('normal')

    def set_timing_mode(self, mode):
        """Set timing mode and update randomizer"""
        self.timing_mode = mode

        if mode == 'fast':
            self.packet.set_master_randomizer(self.fast_randomizer)
        elif mode == 'normal':
            self.packet.set_master_randomizer(self.normal_randomizer)
        elif mode == 'slow':
            self.packet.set_master_randomizer(self.slow_randomizer)

    def adapt_timing_to_congestion(self, congestion_level):
        """Adapt timing based on system congestion"""
        if congestion_level < 0.3:
            self.set_timing_mode('fast')
        elif congestion_level < 0.7:
            self.set_timing_mode('normal')
        else:
            self.set_timing_mode('slow')

    def get_delay_for_mode(self):
        """Get delay for current timing mode"""
        return self.packet.get_master_delay()

# Usage
adaptive_packet = AdaptiveTimingPacket(field_config)
adaptive_packet.packet.addr = 0x4000
adaptive_packet.packet.data = 0xABCDEF00

# Adapt to system conditions
congestion = 0.8  # High congestion
adaptive_packet.adapt_timing_to_congestion(congestion)

delay = adaptive_packet.get_delay_for_mode()
print(f"Adaptive delay for high congestion: {delay} cycles")

Performance Analysis with Timing

class TimingAnalyzer:
    """Analyze packet timing characteristics"""

    def __init__(self):
        self.timing_data = []

    def analyze_packet_timing(self, packets, interface='master'):
        """Analyze timing distribution for packet list"""
        delays = []

        for packet in packets:
            if interface == 'master':
                delay = packet.get_master_delay()
            else:
                delay = packet.get_slave_delay()
            delays.append(delay)

        if not delays:
            return {}

        return {
            'count': len(delays),
            'min_delay': min(delays),
            'max_delay': max(delays),
            'avg_delay': sum(delays) / len(delays),
            'total_delay': sum(delays),
            'delay_distribution': self._analyze_distribution(delays)
        }

    def _analyze_distribution(self, delays):
        """Analyze delay distribution"""
        from collections import Counter
        distribution = Counter(delays)

        return {
            'unique_delays': len(distribution),
            'most_common_delay': distribution.most_common(1)[0] if distribution else (0, 0),
            'distribution': dict(distribution)
        }

    def compare_interfaces(self, packets):
        """Compare master vs slave timing"""
        master_analysis = self.analyze_packet_timing(packets, 'master')
        slave_analysis = self.analyze_packet_timing(packets, 'slave')

        return {
            'master': master_analysis,
            'slave': slave_analysis,
            'total_master_delay': master_analysis.get('total_delay', 0),
            'total_slave_delay': slave_analysis.get('total_delay', 0)
        }

# Usage
analyzer = TimingAnalyzer()

# Create packets with timing
packets = []
for i in range(100):
    packet = FIFOPacket(field_config, data=0x5000 + i)
    packet.set_master_randomizer(write_randomizer)
    packet.set_slave_randomizer(read_randomizer)
    packets.append(packet)

# Analyze timing characteristics
analysis = analyzer.compare_interfaces(packets)
print(f"Timing analysis: {analysis}")

Validation and Testing

def validate_fifo_packet(packet):
    """Validate FIFO packet integrity"""
    errors = []

    # Check that packet has required FIFO functionality
    if not hasattr(packet, 'get_master_delay'):
        errors.append("Missing get_master_delay method")

    if not hasattr(packet, 'get_slave_delay'):
        errors.append("Missing get_slave_delay method")

    # Test randomizer functionality
    if hasattr(packet, 'master_randomizer') and packet.master_randomizer:
        try:
            delay = packet.get_master_delay()
            if not isinstance(delay, int) or delay < 0:
                errors.append(f"Invalid master delay: {delay}")
        except Exception as e:
            errors.append(f"Master delay error: {e}")

    # Check inherited Packet functionality
    try:
        fifo_data = packet.pack_for_fifo()
        if not isinstance(fifo_data, dict):
            errors.append("pack_for_fifo should return dict")
    except Exception as e:
        errors.append(f"FIFO pack error: {e}")

    return errors

# Usage in tests
def test_fifo_packet_creation():
    """Test FIFOPacket creation and functionality"""
    packet = FIFOPacket(field_config, data=0x12345678)

    # Validate basic functionality
    errors = validate_fifo_packet(packet)
    assert not errors, f"Packet validation failed: {errors}"

    # Test timing functionality
    packet.set_master_randomizer(write_randomizer)
    delay1 = packet.get_master_delay()
    delay2 = packet.get_master_delay()

    # Delays should be cached (same value)
    assert delay1 == delay2, "Delay should be cached"

    # Test randomizer reset
    packet.set_master_randomizer(write_randomizer)
    delay3 = packet.get_master_delay()
    # New randomizer should potentially give different delay

    print("FIFOPacket validation passed")

Best Practices

1. Use Factory Functions for Creation

# Recommended
factory = PacketFactory(FIFOPacket, field_config)
packet = factory.create_packet(data=0x12345678)

# Rather than direct instantiation

2. Set Randomizers Early

# Set randomizers immediately after creation
packet = FIFOPacket(field_config, data=0x1000)
packet.set_master_randomizer(write_randomizer)
packet.set_slave_randomizer(read_randomizer)

3. Cache Delay Values

# Delays are cached within a packet instance
delay = packet.get_master_delay()  # Calculated once
same_delay = packet.get_master_delay()  # Returns cached value
assert delay == same_delay

4. Use Appropriate Randomizers

# Different randomizers for different scenarios
fast_randomizer = FlexRandomizer({'write_delay': [(0, 1)]})  # Fast
stress_randomizer = FlexRandomizer({'write_delay': [(0, 50)]})  # Stress

5. Leverage Inherited Functionality

# Use all base Packet capabilities
packet.formatted()  # Rich formatting
packet.pack_for_fifo()  # FIFO operations
packet == other_packet  # Comparison
packet.copy()  # Copying

The FIFOPacket provides FIFO-specific timing control while maintaining all the rich functionality of the base Packet class, making it ideal for complex FIFO protocol verification scenarios.