Skip to content

AXI5 Interface Classes

The interface classes are where test code actually lives. Each one composes the GAXI channel objects for one side of an AXI5 interface and coordinates transactions across those channels — AR plus R for reads, AW plus W plus B for writes — with the full AXI5 signal set carried through.

AXI5MasterRead

The read half of an AXI5 master: drives AR requests out, collects R responses back.

Class Signature

class AXI5MasterRead:
    def __init__(self, dut, clock, prefix="", log=None, ifc_name="", **kwargs)

Constructor Parameters

Parameter Type Description Default
dut object Device under test (required)
clock Signal Clock signal (required)
prefix str Signal name prefix (e.g., "m_axi_") ""
log Logger Logger instance None
ifc_name str Interface name suffix for debug ""
data_width int Data bus width in bits 32
id_width int ID field width in bits 8
addr_width int Address bus width in bits 32
user_width int User signal width in bits 1
nsaid_width int NSAID field width in bits 4
mpam_width int MPAM field width in bits 11
mecid_width int MECID field width in bits 16
tagop_width int TAGOP field width in bits 2
tag_width int Single tag width in bits 4
chunknum_width int Chunk number width in bits 4
multi_sig bool Use individual signals per field True
timeout_cycles int Transaction timeout in clock cycles 5000

Key Methods

read_transaction(address, burst_len=1, **transaction_kwargs) -> List[Dict]

A complete read in one call: the AR goes out, the burst comes back, and you get every R beat. The AXI5 fields are just keyword arguments:

Parameters:

Parameter Type Description Default
address int Read address (required)
burst_len int Number of data beats 1
id int Transaction ID 0
size int Burst size encoding 2
burst int Burst type (0=FIXED, 1=INCR, 2=WRAP) 1
nsaid int Non-secure access ID 0
trace int Enable transaction tracing 0
mpam int Memory partitioning info 0
mecid int Memory encryption context 0
unique int Unique/exclusive access 0
chunken int Enable chunking 0
tagop int Tag operation type 0

Returns: a list of response dictionaries, one per beat, containing: - data, resp, last, id — the standard AXI fields - trace, poison, chunkv, chunknum, chunkstrb, tag, tagmatch — the AXI5 additions

single_read(address, **kwargs) -> int

Single-beat read with the envelope stripped off. Returns the data value directly.

create_ar_packet(**kwargs)

Builds an AR packet with the current field configuration, for when you want to drive the channel yourself.

Attributes

Attribute Type Description
ar_channel GAXIMaster AR channel master component
r_channel GAXISlave R channel slave component

Usage Examples

# Example 1: Simple single read
master_rd = AXI5MasterRead(dut, clk, prefix="m_axi_", data_width=32)
data = await master_rd.single_read(0x1000, id=1)

# Example 2: Burst read with security context
responses = await master_rd.read_transaction(
    address=0x2000, burst_len=4,
    id=2, nsaid=1, trace=1, tagop=1
)
for resp in responses:
    print(f"data=0x{resp['data']:X}, poison={resp['poison']}")

# Example 3: Chunked read for wide data bus
responses = await master_rd.read_transaction(
    address=0x4000, burst_len=8,
    id=3, chunken=1
)

AXI5MasterWrite

The write half of an AXI5 master — AW, W, and B — and home to the two features that live mostly on the write side: atomic operations and memory tagging.

Class Signature

class AXI5MasterWrite:
    def __init__(self, dut, clock, prefix="", log=None, ifc_name="", **kwargs)

Constructor Parameters

Parameter Type Description Default
dut object Device under test (required)
clock Signal Clock signal (required)
prefix str Signal name prefix ""
log Logger Logger instance None
ifc_name str Interface name suffix ""
data_width int Data bus width in bits 32
id_width int ID field width in bits 8
addr_width int Address bus width in bits 32
user_width int User signal width in bits 1
nsaid_width int NSAID field width in bits 4
mpam_width int MPAM field width in bits 11
mecid_width int MECID field width in bits 16
atop_width int ATOP field width in bits 6
tagop_width int TAGOP field width in bits 2
tag_width int Single tag width in bits 4
multi_sig bool Use individual signals per field True
timeout_cycles int Transaction timeout in clock cycles 5000

Key Methods

write_transaction(address, data, burst_len=None, **transaction_kwargs) -> Dict

A complete write in one call: AW, as many W beats as the data implies, and the B response at the end.

Parameters:

Parameter Type Description Default
address int Write address (required)
data int or List[int] Write data (single or burst) (required)
burst_len int or None Burst length (auto-detected if None) None
id int Transaction ID 0
atop int Atomic operation type 0
nsaid int Non-secure access ID 0
trace int Enable transaction tracing 0
tagop int Tag operation type 0
tag int Memory tag (AW channel) 0
wtag int Memory tag (W channel) 0
wuser int User signal (W channel) 0
poison int Poison indicator (W channel) 0
tagupdate int Tag update indicators (W channel) 0

Returns: the B response as a dictionary — success, response, id, trace, tag, tagmatch.

single_write(address, data, **kwargs) -> Dict

Single-beat convenience wrapper.

atomic_operation(address, data, atop, **kwargs) -> Dict

Runs an atomic: sets atop and forces a single-beat transfer, since atomics don't burst.

ATOP Encoding: - 0x10 — AtomicStore - 0x20 — AtomicLoad - 0x30 — AtomicSwap - 0x31 — AtomicCompare

Attributes

Attribute Type Description
aw_channel GAXIMaster AW channel master component
w_channel GAXIMaster W channel master component
b_channel GAXISlave B channel slave component

Usage Examples

# Example 1: Single write with tracing
master_wr = AXI5MasterWrite(dut, clk, prefix="m_axi_", data_width=64)
result = await master_wr.single_write(0x1000, 0xDEADBEEF, id=1, trace=1)

# Example 2: Atomic swap operation
result = await master_wr.atomic_operation(
    address=0x2000, data=0xCAFEBABE, atop=0x30, id=2
)

# Example 3: Burst write with memory tagging
result = await master_wr.write_transaction(
    address=0x3000,
    data=[0x11111111, 0x22222222, 0x33333333, 0x44444444],
    id=3, tagop=2, tag=0xA, tagupdate=0x1
)

AXI5SlaveRead

The read half of an AXI5 slave: accepts AR requests and generates the R beats, optionally sourcing data from a memory model. The interesting knob is out-of-order response reordering — enable it and the slave shuffles its response order, which is the fastest way to find out whether a master actually tolerates OOO completion or just claims to.

Class Signature

class AXI5SlaveRead:
    def __init__(self, dut, clock, prefix="", log=None, ifc_name="", **kwargs)

Constructor Parameters

Parameter Type Description Default
dut object Device under test (required)
clock Signal Clock signal (required)
prefix str Signal name prefix ""
log Logger Logger instance None
ifc_name str Interface name suffix ""
data_width int Data bus width in bits 32
id_width int ID field width in bits 8
addr_width int Address bus width in bits 32
user_width int User signal width in bits 1
nsaid_width int NSAID field width in bits 4
mpam_width int MPAM field width in bits 11
mecid_width int MECID field width in bits 16
tagop_width int TAGOP field width in bits 2
tag_width int Single tag width in bits 4
chunknum_width int Chunk number width in bits 4
multi_sig bool Use individual signals per field True
memory_model object Memory model for data sourcing None
base_addr int Base address offset 0
response_delay int Response delay in clock cycles 1
enable_ooo bool Enable out-of-order responses False
ooo_config dict Out-of-order configuration See below

OOO Configuration Dict:

{
    'mode': 'random',            # 'random' or 'deterministic'
    'reorder_probability': 0.3,  # Probability of reordering
    'min_delay_cycles': 1,       # Minimum reorder delay
    'max_delay_cycles': 50,      # Maximum reorder delay
    'pattern': None              # Sequence order for deterministic mode
}

Attributes

Attribute Type Description
ar_channel GAXISlave AR channel slave component
r_channel GAXIMaster R channel master component

Usage Examples

# Example 1: Basic slave read with memory model
slave_rd = AXI5SlaveRead(
    dut, clk, prefix="s_axi_",
    memory_model=memory, data_width=32
)

# Example 2: Slave with out-of-order responses
slave_rd = AXI5SlaveRead(
    dut, clk, prefix="s_axi_",
    memory_model=memory,
    enable_ooo=True,
    ooo_config={
        'mode': 'random',
        'reorder_probability': 0.3,
        'min_delay_cycles': 2,
        'max_delay_cycles': 20
    }
)

AXI5SlaveWrite

The write half of an AXI5 slave: accepts AW and W, updates the memory model if there is one, and answers on B. ATOP-aware, so atomic writes get the responses atomics expect.

Class Signature

class AXI5SlaveWrite:
    def __init__(self, dut, clock, prefix="", log=None, ifc_name="", **kwargs)

Constructor Parameters

Same parameters as AXI5SlaveRead, plus one:

Parameter Type Description Default
atop_width int ATOP field width in bits 6

Everything else is identical to AXI5SlaveRead.

Attributes

Attribute Type Description
aw_channel GAXISlave AW channel slave component
w_channel GAXISlave W channel slave component
b_channel GAXIMaster B channel master component

Usage Examples

# Example 1: Basic slave write
slave_wr = AXI5SlaveWrite(
    dut, clk, prefix="s_axi_",
    memory_model=memory, data_width=32
)

# Example 2: Slave write with OOO and security features
slave_wr = AXI5SlaveWrite(
    dut, clk, prefix="s_axi_",
    memory_model=memory,
    data_width=64,
    nsaid_width=4,
    mpam_width=11,
    mecid_width=16,
    enable_ooo=True
)

Factory Functions

Use the factories rather than constructing channels by hand. Each one builds the channel objects and the coordinating interface in a single call, and hands back a dictionary containing both — so you can work at whichever level the test needs.

create_axi5_master_rd(dut, clock, prefix, log, ifc_name, **kwargs) -> Dict

Builds the read side of a master. Returns {'AR': ar_channel, 'R': r_channel, 'interface': AXI5MasterRead}.

create_axi5_master_wr(dut, clock, prefix, log, ifc_name, **kwargs) -> Dict

Builds the write side of a master. Returns {'AW': aw_channel, 'W': w_channel, 'B': b_channel, 'interface': AXI5MasterWrite}.

create_axi5_slave_rd(dut, clock, prefix, log, ifc_name, **kwargs) -> Dict

Builds the read side of a slave. Returns {'AR': ar_channel, 'R': r_channel, 'interface': AXI5SlaveRead}.

create_axi5_slave_wr(dut, clock, prefix, log, ifc_name, **kwargs) -> Dict

Builds the write side of a slave. Returns {'AW': aw_channel, 'W': w_channel, 'B': b_channel, 'interface': AXI5SlaveWrite}.

create_axi5_master_interface(dut, clock, prefix, log, **kwargs) -> Tuple

Builds both master halves. Returns (AXI5MasterWrite, AXI5MasterRead).

create_axi5_slave_interface(dut, clock, prefix, log, **kwargs) -> Tuple

Builds both slave halves. Returns (AXI5SlaveWrite, AXI5SlaveRead).

create_complete_axi5_testbench_components(dut, clock, master_prefix, slave_prefix, log, **kwargs) -> Dict

Builds all four interfaces in one call. Returns a dictionary keyed 'master_write', 'master_read', 'slave_write', 'slave_read', with each entry present only when the corresponding DUT signals actually exist — so it's safe to call against a DUT that exposes just one side.

Usage Example

Pull out just the interface, or keep the channel components around too:

from CocoTBFramework.components.axi5 import (
    create_axi5_master_rd,
    create_axi5_master_wr,
    create_complete_axi5_testbench_components
)

# Individual interface creation
rd_components = create_axi5_master_rd(
    dut, clk, prefix="m_axi_", log=self.log,
    data_width=64, id_width=4
)
master_read = rd_components['interface']

# Complete testbench
all_components = create_complete_axi5_testbench_components(
    dut, clk,
    master_prefix="m_axi_",
    slave_prefix="s_axi_",
    data_width=64, id_width=4
)

Out-of-range accesses

Memory-backed slaves answer an access beyond their model with SLVERR, nothing written and 0xDEADDEAD… read data -- the contract shared by every slave family, described in the memory model page.