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

AXISFieldConfigs

AXISFieldConfigs is the factory that builds field configurations for the AXI4-Stream components. A field configuration is the testbench-side description of your bus — how wide TDATA is, which sideband signals exist, how wide they are — and this class exists so that description gets written down exactly once. Build the config from the same parameters you gave the RTL, hand it to the master, the slave, the monitor and every packet, and a 12-bit TID in the SystemVerilog is a 12-bit TID in the scoreboard. No drift, no per-component guesswork.

Class Overview

The docstring spells out what lives on an AXIS T channel:

class AXISFieldConfigs:
    """
    Factory class for creating AXIS field configurations.

    AXI4-Stream protocol uses a single T channel containing:
    - TDATA: Data payload
    - TSTRB: Byte enables (strobe)
    - TLAST: End of packet/frame indicator
    - TID: Stream identifier
    - TDEST: Destination routing
    - TUSER: User-defined sideband data
    - TVALID/TREADY: Handshake signals
    """

Factory Methods

Four ways to get a config, depending on how much you already know about the bus.

Core Configuration Method

create_t_field_config(data_width=32, id_width=8, dest_width=4, user_width=1)

The general-purpose factory — every other method on this page is a wrapper around this one. All optional sidebands are available here; set a width to 0 for any signal your design doesn't have.

Parameters: - data_width (int) - Width of TDATA field (8, 16, 32, 64, 128, 256, 512, 1024) - id_width (int) - Width of TID field (1-16 bits, 0 to disable) - dest_width (int) - Width of TDEST field (1-16 bits, 0 to disable) - user_width (int) - Width of TUSER field (1-32 bits, 0 to disable)

Returns: FieldConfig - Complete field configuration for T channel

Example:

# Create full-featured AXIS configuration
config = AXISFieldConfigs.create_t_field_config(
    data_width=64,
    id_width=16,
    dest_width=8,
    user_width=16
)

# Create configuration with minimal sideband signals
config = AXISFieldConfigs.create_t_field_config(
    data_width=32,
    id_width=4,
    dest_width=0,  # No destination routing
    user_width=0   # No user data
)

Predefined Configurations

Three shortcuts for the cases that come up over and over.

create_default_axis_config(data_width=32)

The middle-of-the-road config: 8-bit ID, 4-bit DEST, 1-bit USER. If your stream has sidebands but nothing exotic about them, start here.

Parameters: - data_width (int) - Width of data field

Returns: FieldConfig - Standard configuration (8-bit ID, 4-bit DEST, 1-bit USER)

Example:

# Create standard 32-bit AXIS configuration
config = AXISFieldConfigs.create_default_axis_config(data_width=32)
# Results in: data=32b, id=8b, dest=4b, user=1b, strb=4b

# Create standard 64-bit AXIS configuration
config = AXISFieldConfigs.create_default_axis_config(data_width=64)
# Results in: data=64b, id=8b, dest=4b, user=1b, strb=8b

create_simple_axis_config(data_width=32)

Data, strobe, last — nothing else. The right choice for a plain data pipe with no routing and no sideband baggage.

Parameters: - data_width (int) - Width of data field

Returns: FieldConfig - Minimal configuration (no ID, DEST, or USER signals)

Example:

# Create simple streaming configuration
config = AXISFieldConfigs.create_simple_axis_config(data_width=32)
# Results in: data=32b, strb=4b, last=1b only

# Create simple wide configuration
config = AXISFieldConfigs.create_simple_axis_config(data_width=512)
# Results in: data=512b, strb=64b, last=1b only

create_axis_config_from_hw_params(data_width, id_width, dest_width, user_width)

Feed it the exact parameter values from your SystemVerilog module and get back a config that matches signal-for-signal — including zero-width parameters, which is how the RTL says "this signal doesn't exist" and how the config says it too.

Parameters: - data_width (int) - AXIS_DATA_WIDTH hardware parameter - id_width (int) - AXIS_ID_WIDTH hardware parameter - dest_width (int) - AXIS_DEST_WIDTH hardware parameter - user_width (int) - AXIS_USER_WIDTH hardware parameter

Returns: FieldConfig - Configuration matching hardware exactly

Example:

# Match hardware module parameters
config = AXISFieldConfigs.create_axis_config_from_hw_params(
    data_width=128,
    id_width=12,
    dest_width=6,
    user_width=8
)

# Zero-width parameter handling (hardware with TID_WIDTH=0)
config = AXISFieldConfigs.create_axis_config_from_hw_params(
    data_width=32,
    id_width=0,     # No ID signal
    dest_width=0,   # No DEST signal
    user_width=4    # 4-bit USER signal
)

Field Definitions

Every config the factory produces is built from these fields. The core three are always present; the sidebands appear only when their width is nonzero.

Core Fields (Always Present)

data Field

  • Width: Configurable (8-1024 bits)
  • Format: Hexadecimal
  • Default: 0
  • Description: Stream data payload — TDATA for the beat

strb Field

  • Width: data_width / 8 bits
  • Format: Binary
  • Default: All bits set (all bytes enabled)
  • Description: Byte enables — one bit per byte lane, marking which bytes of data are valid

last Field

  • Width: 1 bit
  • Format: Binary
  • Default: 0 (not last)
  • Description: TLAST — end of packet/frame indicator
  • Encoding: {0: "Not Last", 1: "Last"}

Optional Fields (Conditionally Present)

id Field (if id_width > 0)

  • Width: Configurable (1-16 bits)
  • Format: Hexadecimal
  • Default: 0
  • Description: TID — stream identifier, for when the bus carries several streams

dest Field (if dest_width > 0)

  • Width: Configurable (1-16 bits)
  • Format: Hexadecimal
  • Default: 0
  • Description: TDEST — destination routing identifier

user Field (if user_width > 0)

  • Width: Configurable (1-32 bits)
  • Format: Binary
  • Default: 0
  • Description: TUSER — user-defined sideband data; the BFM carries it without interpreting it

Usage Examples

Basic Configuration Setup

# Create configuration for different data widths
configs = {}

# 32-bit standard configuration
configs['axis32'] = AXISFieldConfigs.create_default_axis_config(32)

# 64-bit high-performance configuration
configs['axis64'] = AXISFieldConfigs.create_t_field_config(
    data_width=64,
    id_width=16,
    dest_width=8,
    user_width=32
)

# 512-bit simple streaming configuration
configs['axis512'] = AXISFieldConfigs.create_simple_axis_config(512)

# Use with components
master = AXISMaster(dut, "Master", "m_axis_", clk, field_config=configs['axis32'])

Hardware Matching Configuration

This is the pattern I'd recommend as the default: read the parameters off the DUT once, build the config from them, and never think about widths again.

# Match SystemVerilog module parameters
# module axis_master #(
#     parameter AXIS_DATA_WIDTH = 128,
#     parameter AXIS_ID_WIDTH = 8,
#     parameter AXIS_DEST_WIDTH = 4,
#     parameter AXIS_USER_WIDTH = 16
# )

hw_config = AXISFieldConfigs.create_axis_config_from_hw_params(
    data_width=128,
    id_width=8,
    dest_width=4,
    user_width=16
)

# Create components with matching configuration
master = AXISMaster(dut, "HW_Master", "m_axis_", clk, field_config=hw_config)
slave = AXISSlave(dut, "HW_Slave", "s_axis_", clk, field_config=hw_config)

Dynamic Configuration Selection

def get_axis_config(stream_type, data_width):
    """Select appropriate AXIS configuration based on requirements."""

    if stream_type == "simple":
        return AXISFieldConfigs.create_simple_axis_config(data_width)

    elif stream_type == "routing":
        return AXISFieldConfigs.create_t_field_config(
            data_width=data_width,
            id_width=8,
            dest_width=8,
            user_width=0
        )

    elif stream_type == "full_featured":
        return AXISFieldConfigs.create_t_field_config(
            data_width=data_width,
            id_width=16,
            dest_width=16,
            user_width=32
        )

    else:
        return AXISFieldConfigs.create_default_axis_config(data_width)

# Use in test scenarios
test_config = get_axis_config("routing", 64)
master = AXISMaster(dut, "RoutingMaster", "m_axis_", clk, field_config=test_config)

Configuration Analysis and Validation

Handy when a test fails and your first question is "wait, what config did this thing actually get built with?"

def analyze_axis_config(config):
    """Analyze AXIS field configuration."""
    analysis = {
        'total_fields': len(config),
        'data_width': config.get_field('data').bits,
        'strb_width': config.get_field('strb').bits,
        'has_id': config.has_field('id'),
        'has_dest': config.has_field('dest'),
        'has_user': config.has_field('user')
    }

    # Calculate total bus width
    total_bits = sum(field.bits for field in config.fields)
    analysis['total_signal_bits'] = total_bits

    # Add optional field widths
    if analysis['has_id']:
        analysis['id_width'] = config.get_field('id').bits
    if analysis['has_dest']:
        analysis['dest_width'] = config.get_field('dest').bits
    if analysis['has_user']:
        analysis['user_width'] = config.get_field('user').bits

    return analysis

# Analyze different configurations
configs_to_test = [
    AXISFieldConfigs.create_simple_axis_config(32),
    AXISFieldConfigs.create_default_axis_config(32),
    AXISFieldConfigs.create_t_field_config(64, 16, 8, 32)
]

for i, config in enumerate(configs_to_test):
    analysis = analyze_axis_config(config)
    print(f"Config {i}: {analysis}")

Zero-Width Signal Handling

# Handle configurations with zero-width optional signals
def create_flexible_axis_config(data_width, **optional_widths):
    """Create AXIS config with flexible optional signal widths."""

    return AXISFieldConfigs.create_t_field_config(
        data_width=data_width,
        id_width=optional_widths.get('id_width', 0),
        dest_width=optional_widths.get('dest_width', 0),
        user_width=optional_widths.get('user_width', 0)
    )

# Various zero-width combinations
configs = {
    'data_only': create_flexible_axis_config(32),
    'with_id': create_flexible_axis_config(32, id_width=8),
    'with_routing': create_flexible_axis_config(32, id_width=4, dest_width=4),
    'full_custom': create_flexible_axis_config(32, id_width=12, dest_width=6, user_width=8)
}

# Verify field presence
for name, config in configs.items():
    fields = config.field_names()
    print(f"{name}: {fields}")

Integration with Components

Component Configuration

The point of the shared config: master and slave built from it cannot disagree about the bus.

# Configure master and slave with matching configurations
config = AXISFieldConfigs.create_default_axis_config(64)

master = AXISMaster(
    dut=dut,
    title="ConfiguredMaster",
    prefix="m_axis_",
    clock=clk,
    field_config=config
)

slave = AXISSlave(
    dut=dut,
    title="ConfiguredSlave",
    prefix="s_axis_",
    clock=clk,
    field_config=config
)

# Configuration ensures field compatibility

Packet Creation with Configuration

# Create packets using configuration
config = AXISFieldConfigs.create_t_field_config(32, 8, 4, 16)

packet = AXISPacket(field_config=config)
packet.data = 0x12345678
packet.strb = 0xF
packet.last = 1
packet.id = 5
packet.dest = 2
packet.user = 0xABCD

# All fields properly sized according to configuration

That's the whole class — a small factory doing a boring job, which is exactly what you want from it. The width-mismatch bugs it prevents are the kind that otherwise surface three hours into a debug session as mysteriously truncated sidebands. Build the config from the hardware parameters and those bugs simply don't happen.