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

gaxi_monitor.py

GAXI Monitor implementation using unified base functionality. Preserves exact timing-critical cocotb methods and external API while eliminating code duplication through inheritance from unified base classes.

Overview

The GAXIMonitor class provides: - Pure monitoring functionality with no signal driving - Master-side or slave-side monitoring based on is_slave parameter - Protocol violation detection and reporting - Handshake tracking with precise timing - Inherited functionality from GAXIMonitorBase for common operations - Mode-specific data sampling with corrected timing

Inherits all common functionality from GAXIMonitorBase including signal resolution, data collection, packet management, and memory integration.

Class

GAXIMonitor

class GAXIMonitor(GAXIMonitorBase):
    def __init__(self, dut, title, prefix, clock, field_config, is_slave=False,
                 bus_name='', pkt_prefix='', multi_sig=False,
                 log=None, super_debug=False, signal_map=None, **kwargs)

Parameters: - dut: Device under test - title: Component title/name - prefix: Bus prefix - clock: Clock signal - field_config: Field configuration - is_slave: If True, monitor slave side; if False, monitor master side - mode: GAXI mode ('skid', 'fifo_mux', 'fifo_flop') - for DUT parameter only - bus_name: Bus/channel name - pkt_prefix: Packet field prefix - multi_sig: Whether using multi-signal mode - log: Logger instance - super_debug: Enable detailed debugging - signal_map: Optional manual signal mapping override - **kwargs: Additional arguments

Data Sampling Rules

The GAXIMonitor uses corrected sampling timing based on mode and monitoring side:

Master Side Monitoring (is_slave=False)

  • All modes: Always sample immediately when handshake detected
  • Rationale: Master side signals are stable at handshake time
  • Performance: Lowest latency monitoring

Slave Side Monitoring (is_slave=True)

  • Skid mode: Sample immediately when handshake detected
  • FIFO MUX mode: Sample immediately when handshake detected
  • FIFO FLOP mode: Delay capture by one cycle after handshake
  • Rationale: Matches DUT internal timing for different implementations

Core Methods

Inherited Methods

GAXIMonitor inherits all methods from GAXIMonitorBase: - create_packet(**field_values) - Create packets - get_observed_packets(count=None) - Get observed transactions - clear_queue() - Clear observation queue - handle_memory_write(packet) - Memory integration - handle_memory_read(packet) - Memory integration - get_base_stats() - Statistics collection

Monitor-Specific Methods

get_stats()

Get comprehensive statistics including monitor-specific information.

Returns: Dictionary containing all statistics

stats = monitor.get_stats()
print(f"Monitor type: {stats['monitor_type']}")  # 'master' or 'slave'
print(f"Observed packets: {stats['observed_packets']}")
print(f"Protocol violations: {stats['monitor_stats']['protocol_violations']}")

Usage Patterns

Basic Master Side Monitoring

import cocotb
from cocotb.triggers import Timer
from CocoTBFramework.components.gaxi import GAXIMonitor
from CocoTBFramework.shared.field_config import FieldConfig

@cocotb.test()
async def test_master_monitoring(dut):
    clock = dut.clk

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

    # Create master side monitor
    master_monitor = GAXIMonitor(
        dut=dut,
        title="MasterMonitor",
        prefix="m_",  # Monitor master interface
        clock=clock,
        field_config=field_config,
        is_slave=False,  # Monitor master side
        mode='skid'
    )

    # Add callback to process observed transactions
    def log_transaction(packet):
        print(f"Master sent: addr=0x{packet.addr:X}, data=0x{packet.data:X}")

    master_monitor.add_callback(log_transaction)

    # Run test and let monitor observe
    await Timer(1000, units='ns')

    # Check observed transactions
    packets = master_monitor.get_observed_packets()
    print(f"Master monitor observed {len(packets)} transactions")

Basic Slave Side Monitoring

@cocotb.test()
async def test_slave_monitoring(dut):
    clock = dut.clk

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

    # Create slave side monitor with FIFO FLOP mode
    slave_monitor = GAXIMonitor(
        dut=dut,
        title="SlaveMonitor", 
        prefix="s_",  # Monitor slave interface
        clock=clock,
        field_config=field_config,
        is_slave=True,   # Monitor slave side
        mode='fifo_flop' # DUT uses registered interface
    )

    # Add callback to process observed transactions
    def process_slave_transaction(packet):
        print(f"Slave received: addr=0x{packet.addr:X}, data=0x{packet.data:X}")

        # Additional processing
        if packet.addr >= 0x8000:
            print("  → High address region")

    slave_monitor.add_callback(process_slave_transaction)

    # Run test
    await Timer(1000, units='ns')

    # Analyze slave activity
    packets = slave_monitor.get_observed_packets()
    print(f"Slave monitor observed {len(packets)} transactions")

Dual Monitor Setup

@cocotb.test()
async def test_dual_monitoring(dut):
    clock = dut.clk
    field_config = create_field_config()

    # Monitor both sides of the interface
    master_monitor = GAXIMonitor(
        dut=dut,
        title="MasterSideMonitor",
        prefix="m_",
        clock=clock,
        field_config=field_config,
        is_slave=False  # Master side
    )

    slave_monitor = GAXIMonitor(
        dut=dut,
        title="SlaveSideMonitor", 
        prefix="s_",
        clock=clock,
        field_config=field_config,
        is_slave=True   # Slave side
    )

    # Track transactions on both sides
    master_transactions = []
    slave_transactions = []

    def track_master(packet):
        master_transactions.append(packet)
        print(f"Master → Slave: {packet.formatted(compact=True)}")

    def track_slave(packet):
        slave_transactions.append(packet)
        print(f"Slave ← Master: {packet.formatted(compact=True)}")

    master_monitor.add_callback(track_master)
    slave_monitor.add_callback(track_slave)

    # Run test
    await Timer(2000, units='ns')

    # Compare transaction counts
    print(f"Master side: {len(master_transactions)} transactions")
    print(f"Slave side: {len(slave_transactions)} transactions")

    # Verify transaction matching
    assert len(master_transactions) == len(slave_transactions), \
        "Transaction count mismatch between master and slave sides"

Protocol Violation Monitoring

@cocotb.test()
async def test_protocol_violations(dut):
    # Create monitor with protocol checking
    monitor = GAXIMonitor(
        dut=dut,
        title="ProtocolMonitor",
        prefix="",
        clock=clock,
        field_config=field_config,
        is_slave=False,
        super_debug=True  # Enable detailed logging
    )

    # Track protocol violations
    violations = []

    def check_protocol(packet):
        """Check for protocol violations in observed transactions"""
        # Check address alignment
        if hasattr(packet, 'addr') and packet.addr % 4 != 0:
            violation = f"Unaligned address: 0x{packet.addr:X}"
            violations.append(violation)
            print(f"VIOLATION: {violation}")

        # Check for X/Z values
        for field_name in ['addr', 'data']:
            if hasattr(packet, field_name):
                value = getattr(packet, field_name)
                if value == -1:  # X/Z represented as -1
                    violation = f"X/Z value in {field_name}"
                    violations.append(violation)
                    print(f"VIOLATION: {violation}")

    monitor.add_callback(check_protocol)

    # Run test
    await Timer(1000, units='ns')

    # Report violations
    if violations:
        print(f"Detected {len(violations)} protocol violations:")
        for violation in violations:
            print(f"  - {violation}")
    else:
        print("No protocol violations detected")

    # Get monitor statistics
    stats = monitor.get_stats()
    print(f"Monitor statistics: {stats['monitor_stats']}")

Performance Monitoring

@cocotb.test()
async def test_performance_monitoring(dut):
    monitor = GAXIMonitor(dut, "PerfMonitor", "", clock, field_config)

    # Performance tracking
    transaction_times = []
    inter_transaction_times = []
    last_time = 0

    def track_performance(packet):
        current_time = get_sim_time('ns')
        transaction_times.append(current_time)

        if last_time > 0:
            inter_time = current_time - last_time
            inter_transaction_times.append(inter_time)

        nonlocal last_time
        last_time = current_time

    monitor.add_callback(track_performance)

    # Run test
    await Timer(5000, units='ns')

    # Analyze performance
    if inter_transaction_times:
        avg_interval = sum(inter_transaction_times) / len(inter_transaction_times)
        throughput = 1e9 / avg_interval if avg_interval > 0 else 0

        print(f"Performance Analysis:")
        print(f"  Transactions: {len(transaction_times)}")
        print(f"  Average interval: {avg_interval:.2f} ns")
        print(f"  Throughput: {throughput:.1f} TPS")
        print(f"  Min interval: {min(inter_transaction_times):.2f} ns")
        print(f"  Max interval: {max(inter_transaction_times):.2f} ns")

Mode-Specific Configuration

def create_mode_specific_monitors(dut, clock, field_config):
    """Create monitors for different DUT modes"""

    monitors = {}

    # Skid mode monitor (immediate capture)
    monitors['skid'] = GAXIMonitor(
        dut=dut,
        title="SkidMonitor",
        prefix="skid_",
        clock=clock,
        field_config=field_config,
        is_slave=True,
        mode='skid'
    )

    # FIFO MUX mode monitor (immediate capture)
    monitors['fifo_mux'] = GAXIMonitor(
        dut=dut,
        title="FifoMuxMonitor",
        prefix="mux_",
        clock=clock,
        field_config=field_config,
        is_slave=True,
        mode='fifo_mux'
    )

    # FIFO FLOP mode monitor (delayed capture)
    monitors['fifo_flop'] = GAXIMonitor(
        dut=dut,
        title="FifoFlopMonitor", 
        prefix="flop_",
        clock=clock,
        field_config=field_config,
        is_slave=True,
        mode='fifo_flop'  # One cycle delay for data capture
    )

    return monitors

@cocotb.test()
async def test_mode_specific_monitoring(dut):
    monitors = create_mode_specific_monitors(dut, clock, field_config)

    # Add same callback to all monitors
    def log_transaction(packet):
        print(f"Transaction: {packet.formatted(compact=True)}")

    for mode, monitor in monitors.items():
        monitor.add_callback(log_transaction)

    # Run test
    await Timer(1000, units='ns')

    # Compare results across modes
    for mode, monitor in monitors.items():
        packets = monitor.get_observed_packets()
        stats = monitor.get_stats()
        print(f"{mode} mode: {len(packets)} packets, {stats['monitor_type']} side")

Integration with Scoreboards

from CocoTBFramework.scoreboards.gaxi_scoreboard import GAXIScoreboard

@cocotb.test()
async def test_scoreboard_integration(dut):
    # Create scoreboard
    scoreboard = GAXIScoreboard("TestScoreboard", field_config, log=log)

    # Create monitors
    master_monitor = GAXIMonitor(dut, "MasterMon", "m_", clock, field_config, 
                                is_slave=False)
    slave_monitor = GAXIMonitor(dut, "SlaveMon", "s_", clock, field_config,
                               is_slave=True)

    # Connect monitors to scoreboard
    master_monitor.add_callback(scoreboard.add_expected_transaction)
    slave_monitor.add_callback(scoreboard.add_actual_transaction)

    # Run test
    await Timer(2000, units='ns')

    # Check scoreboard results
    scoreboard_stats = scoreboard.get_stats()
    print(f"Scoreboard: {scoreboard_stats['matches']} matches, "
          f"{scoreboard_stats['mismatches']} mismatches")

    # Verify all transactions matched
    assert scoreboard_stats['mismatches'] == 0, "Transaction mismatches detected"

Memory Validation Monitoring

@cocotb.test()
async def test_memory_validation(dut):
    # Create memory model for validation
    memory = MemoryModel(num_lines=256, bytes_per_line=4, log=log)

    # Create monitor with memory integration
    monitor = GAXIMonitor(
        dut=dut,
        title="MemoryMonitor",
        prefix="",
        clock=clock,
        field_config=field_config,
        memory_model=memory,
        is_slave=True
    )

    # Track memory operations
    def validate_memory_transaction(packet):
        """Validate transactions against memory model"""
        if hasattr(packet, 'cmd'):
            if packet.cmd == 2:  # Write
                success = monitor.handle_memory_write(packet)
                if success:
                    print(f"Memory write: addr=0x{packet.addr:X}, "
                          f"data=0x{packet.data:X}")
                else:
                    print(f"Memory write failed: addr=0x{packet.addr:X}")

            elif packet.cmd == 1:  # Read
                success, data = monitor.handle_memory_read(packet)
                if success:
                    print(f"Memory read: addr=0x{packet.addr:X}, "
                          f"data=0x{data:X}")
                    # Validate read data if available
                    if hasattr(packet, 'data') and packet.data != data:
                        print(f"  WARNING: Expected 0x{data:X}, "
                              f"got 0x{packet.data:X}")

    monitor.add_callback(validate_memory_transaction)

    # Run test
    await Timer(1000, units='ns')

    # Get memory statistics
    stats = monitor.get_stats()
    if 'memory_stats' in stats:
        memory_stats = stats['memory_stats']
        print(f"Memory operations: reads={memory_stats['reads']}, "
              f"writes={memory_stats['writes']}")

Advanced Features

Custom Signal Mapping

# For non-standard signal names
signal_map = {
    'valid': 'master_transaction_valid',
    'ready': 'slave_ready_signal',
    'data': 'transaction_data_bus'
}

monitor = GAXIMonitor(
    dut=dut,
    title="CustomMonitor",
    prefix="",
    clock=clock,
    field_config=field_config,
    signal_map=signal_map  # Override automatic discovery
)

Multi-Field Monitoring

# Create field configuration with multiple fields
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"))
field_config.add_field(FieldDefinition("id", 8, format="hex"))

# Monitor with multi-signal mode
monitor = GAXIMonitor(
    dut=dut,
    title="MultiFieldMonitor",
    prefix="",
    clock=clock,
    field_config=field_config,
    multi_sig=True  # Individual signals for each field
)

Error Handling

Signal Resolution Errors

try:
    monitor = GAXIMonitor(dut, "Monitor", "", clock, field_config)
except RuntimeError as e:
    print(f"Signal resolution failed: {e}")
    # Try with manual signal mapping
    signal_map = create_manual_signal_map()
    monitor = GAXIMonitor(dut, "Monitor", "", clock, field_config,
                         signal_map=signal_map)

Monitoring Errors

# Monitor automatically handles signal errors
# Check statistics for error information
stats = monitor.get_stats()
monitor_stats = stats['monitor_stats']
if monitor_stats['x_z_violations'] > 0:
    print(f"X/Z violations detected: {monitor_stats['x_z_violations']}")

Best Practices

1. Choose Appropriate Monitoring Side

# Monitor master side to see outgoing transactions
master_monitor = GAXIMonitor(..., is_slave=False)

# Monitor slave side to see received transactions  
slave_monitor = GAXIMonitor(..., is_slave=True)

2. Match Mode to DUT Implementation

# For DUT with registered slave interface
monitor = GAXIMonitor(..., is_slave=True, mode='fifo_flop')

# For DUT with combinational interface
monitor = GAXIMonitor(..., is_slave=True, mode='skid')

3. Use Callbacks for Real-Time Processing

# Prefer callbacks over polling
monitor.add_callback(process_transaction)

# Avoid polling the queue directly
# while True:
#     if monitor._recvQ:  # Don't do this
packets = monitor.get_observed_packets()  # Do this instead

4. Enable Debug During Development

monitor = GAXIMonitor(..., super_debug=True)   # Development
monitor = GAXIMonitor(..., super_debug=False)  # Production

5. Monitor Statistics for Health

# Regular statistics checking
stats = monitor.get_stats()
if stats['monitor_stats']['protocol_violations'] > 0:
    print("Protocol violations detected - investigate")

The GAXIMonitor provides comprehensive transaction observation capabilities with precise timing control, protocol violation detection, and flexible configuration for thorough verification of GAXI protocol implementations.