AVL-AXI Subordinate

Inheritance diagram of avl_axi._swdriver

Inheritance diagram of avl_axi._srdriver

The subordinate side of the AVL-AXI agent does not follow the standard AVL / UVM structure of sequence, sequencer and driver.

As the subordinate is responsive the overhead of interacting between a monitor, sequence and driver is overly complicated and not required.

A single function is used to decide how to complete the request:

This function is called with the an argument of the item to be completed, the request side of the protocol has already populated the item in order to make a decision.

Rate Control

The subordinate driver is responsible for rate control. Each phase (control, data and response) has independent rate control.In the case of control and data the rate limiter sets the delay between valid and ready, on response it defines the delay between valids.

avl.Factory.set_variable("*.agent.swdrv.control_rate_limit", lambda: 0.1)
avl.Factory.set_variable("*.agent.swdrv.data_rate_limit", lambda: 0.1)
avl.Factory.set_variable("*.agent.swdrv.reponse_rate_limit", lambda: 0.1)

Ordered Write Observation

The SubordinateWriteDriver supports ordered write observation if configured on the interface, however even without this configuration it can be set in teh driver explicitly by setting SubordinateWriteDriver.in_order.

When either of these are set the responses are sent in control order. Otherwise the bvalid / bid are returned in random order.

Read Interleaving Disabled

The SubordinateReadDriver supports read interleaving disabled if configured on the interface, however even without this configuration it can be set in teh driver explicitly by setting SubordinateReadDriver.in_order.

When either of these are set the responses are sent in control order. Otherwise the rvalid / rid are returned in random order on a beat-by-beat basis.

QOS Accept

The SubordinateReadDriver and SubordinateWriteDriver support QoS_Accept as static values passed by the factory if configured. The value is set after reset and persists until the next reset, unless explicitly controlled by the user.

avl.Factory.set_variable("*.agent.swdrv.qosaccept", 0x3)
avl.Factory.set_variable("*.agent.srdrv.qosaccept", 0x7)

AVL-AXI Subordinate Memory

Inheritance diagram of avl_axi._smemory

A memory model is provided as part of the SubordinateReadDriver and SubordinateWriteDriver to provide memory like semantics.

The user must provide valid memory ranges for the memory, accesses outside these ranges will return axi_resp_t.DECERR (if supported).

Equally if the Constistant_DECERR property is enabled if any of the memory accesses are outside the memory range, all responses will recieve axi_resp_t.DECERR.

avl.Factory.set_variable("*.agent.cfg.subordinate_ranges", [(0x0000, 0x2FFF)])
avl.Factory.set_override_by_type(avl_axi.SubordinateWriteDriver, avl_axi.SubordinateWriteMemoryDriver)
avl.Factory.set_override_by_type(avl_axi.SubordinateReadDriver, avl_axi.SubordinateReadMemoryDriver)

Note

Users must override both the SubordinateReadDriver and SubordinateWriteDriver to their memory variants to work correctly.

The AVL-AXI SubordinateMemory extends the AVL base class by adding Atomic Transactions.

AWATOP[5:0]

Description

0b000000

Non-atomic operation

0b01exxx

AtomicStore

0b10exxx

AtomicLoad

0b110000

AtomicSwap

0b110001

AtomicCompare

AWATOP[2:0]

Operation

Description

0b000

ADD

Add

0b001

CLR

Bit clear

0b010

EOR

Exclusive OR

0b011

SET

Bit set

0b100

SMAX

Signed maximum

0b101

SMIN

Signed minimum

0b110

UMAX

Unsigned maximum

0b111

UMIN

Unsigned minimum

# Copyright 2024 Apheleia
#
# Description:
# Apheleia attributes example


import avl
import avl_axi
import cocotb
from avl_axi._types import axi_atomic_t, axi_resp_t
from avl_axi._item import WriteItem
from z3 import UGE, ULE, And

class DirectedSequence(avl_axi.ManagerSequence):
    async def body(self) -> None:
        """
        Body of the sequence
        """

        self.info(f"Starting Directed Manager sequence {self.get_full_name()}")
        self.wait_for = "response"

        # Test : Normal operation
        await self.write(awaddr=0x1000, awid=0, awsize=3, wdata=[0xaaaaaaaaaaaaaaaa], wstrb=[0xFF])

        rsp = await self.read(araddr=0x1000, arid=0, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xaaaaaaaaaaaaaaaa]

        # Test : Atomic load add
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.LOAD_LE_ADD, wdata=[1])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xaaaaaaaaaaaaaaaa]

        rsp = await self.read(araddr=0x1000, arid=0, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xaaaaaaaaaaaaaaab]

        # Test : Atomic store add
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.STORE_LE_ADD, wdata=[2])
        assert rsp.bresp == 0

        rsp = await self.read(araddr=0x1000, arid=0, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xaaaaaaaaaaaaaaad]

        # Test : Atomic clr
        await self.write(awaddr=0x1000, awid=1, awsize=3, wdata=[0xffffffffffff], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.LOAD_LE_CLR, wdata=[0xffff00000000ffff])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xffffffffffff]

        rsp = await self.read(araddr=0x1000, arid=0, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0x0000ffffffff0000]

        # Test : Atomic Exclusive OR
        await self.write(awaddr=0x1000, awid=2, awsize=3, wdata=[0xaaaaaaaaaaaaaaaa], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=2, awsize=3, awatop=axi_atomic_t.LOAD_LE_EOR, wdata=[0x5555555555555555])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xaaaaaaaaaaaaaaaa]

        rsp = await self.read(araddr=0x1000, arid=0, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xffffffffffffffff]

        # Test : Atomic Set
        await self.write(awaddr=0x1000, awid=3, awsize=3, wdata=[0x1234567800000000], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=3, awsize=3, awatop=axi_atomic_t.LOAD_LE_SET, wdata=[0xf000000055555555])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0x1234567800000000]

        rsp = await self.read(araddr=0x1000, arid=1, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xf234567855555555]

        # Test : Atomic SMAX
        await self.write(awaddr=0x1000, awid=3, awsize=3, wdata=[10], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.LOAD_LE_SMAX, wdata=[100])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [10]

        rsp = await self.read(araddr=0x1000, arid=4, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [100]

        # Test : Atomic SMIN
        await self.write(awaddr=0x1000, awid=3, awsize=3, wdata=[20], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.LOAD_LE_SMIN, wdata=[-100])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [20]

        rsp = await self.read(araddr=0x1000, arid=4, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [-100&0xffffffffffffffff]

        # Test : Atomic UMAX
        await self.write(awaddr=0x1000, awid=3, awsize=3, wdata=[0x12345], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.LOAD_LE_UMAX, wdata=[0xfffff])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0x12345]

        rsp = await self.read(araddr=0x1000, arid=4, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xfffff]

        # Test : Atomic UMIN
        await self.write(awaddr=0x1000, awid=3, awsize=3, wdata=[0xfff], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.LOAD_LE_UMIN, wdata=[0x23])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xfff]

        rsp = await self.read(araddr=0x1000, arid=4, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0x23]

        # Test : Atomic SWAP
        await self.write(awaddr=0x1000, awid=3, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.SWAP, wdata=[0xcafebabe])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xdeadbeef]

        rsp = await self.read(araddr=0x1000, arid=4, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xcafebabe]

        # Test : Atomic COMPARE
        await self.write(awaddr=0x1000, awid=3, awsize=3, wdata=[0xcafebabedeadbeef], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.COMPARE, wdata=[0xb00bf00ddeadbeef])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xcafebabedeadbeef]

        rsp = await self.read(araddr=0x1000, arid=4, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0xcafebabeb00bf00d]

        # Test : Endianness - ADD
        await self.write(awaddr=0x1000, awid=0, awsize=3, wdata=[0x0001020304050607], wstrb=[0xFF])
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awatop=axi_atomic_t.LOAD_BE_ADD, wdata=[0x01])
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0x0001020304050607]

        rsp = await self.read(araddr=0x1000, arid=4, arsize=3)
        assert rsp.rresp == [axi_resp_t.OKAY] and rsp.rdata == [0x0101020304050607]

        # Test : BURST
        await self.write(awaddr=0x1000, awid=2, awsize=3, awlen=7, awburst=1, wdata=[0xaa00]*8, wstrb=[0xFF]*8)
        rsp = await self.write(awaddr=0x1000, awid=3, awsize=3, awlen=7, awburst=1, awatop=axi_atomic_t.LOAD_LE_SET, wdata=[0x01,0x2,0x3,0x4,0x5,0x6,0x7,0x8])
        assert rsp.rresp == [axi_resp_t.OKAY]*8 and rsp.rdata == [0xaa00]*8

        rsp = await self.read(araddr=0x1000, arid=4, arsize=3, arlen=7, arburst=1)
        assert rsp.rresp == [axi_resp_t.OKAY]*8 and rsp.rdata == [0xaa01,0xaa02,0xaa03,0xaa04,0xaa05,0xaa06,0xaa07,0xaa08]

        # Test : Randomization
        for _ in range(5):
            item = WriteItem(f"from_{self.name}", self)
            item.add_constraint("_c_is_atomic_", lambda x : x != axi_atomic_t.NON_ATOMIC, item.awatop)
            item.add_constraint("_c_in_range_", lambda x : And(UGE(x, 0x0000), ULE(x, 0x1000)), item.awaddr)
            rsp = await self._send_(item, wait_for=self.wait_for)
            assert rsp.bresp == axi_resp_t.OKAY

class example_env(avl.Env):

    def __init__(self, name, parent):
        super().__init__(name, parent)
        self.hdl = avl.Factory.get_variable(f"{self.get_full_name()}.hdl", None)
        self.clk = avl.Factory.get_variable(f"{self.get_full_name()}.clk", None)
        self.rst_n = avl.Factory.get_variable(f"{self.get_full_name()}.rst_n", None)
        self.agent = avl_axi.Agent("agent", self)

    async def run_phase(self):
        self.raise_objection()

        cocotb.start_soon(self.timeout(1, units="ms"))
        cocotb.start_soon(self.clock(self.clk, 100))
        await self.async_reset(self.rst_n, duration=100, units="ns", active_high=False)

        self.drop_objection()

@cocotb.test
async def test(dut):
    """
    Example AXI5 with atomic operations

    :param dut: The DUT instance
    :return: None
    """
    avl.Factory.set_variable("*.clk", dut.clk)
    avl.Factory.set_variable("*.rst_n", dut.rst_n)
    avl.Factory.set_variable("*.hdl", dut.axi_if)
    avl.Factory.set_variable("*.agent.cfg.has_manager", True)
    avl.Factory.set_variable("*.agent.cfg.has_subordinate", True)
    avl.Factory.set_variable("*.agent.cfg.has_monitor", False)
    avl.Factory.set_variable("*.agent.cfg.has_trace", False)
    avl.Factory.set_variable("*.agent.msqr.mseq.n_items", 100)

    # Define memory range
    avl.Factory.set_variable("*.agent.cfg.subordinate_ranges", [(0x0000, 0x2FFF)])

    avl.Factory.set_override_by_type(avl_axi.SubordinateWriteDriver, avl_axi.SubordinateWriteMemoryDriver)
    avl.Factory.set_override_by_type(avl_axi.SubordinateReadDriver, avl_axi.SubordinateReadMemoryDriver)
    avl.Factory.set_override_by_type(avl_axi.ManagerSequence, DirectedSequence)

    e = example_env("env", None)
    await e.start()

AVL-AXI Exclusivity Monitor

Inheritance diagram of avl_axi._emonitor

An exclusivity monitor is provided as part of the SubordinateReadDriver and SubordinateWriteDriver to provide exclusive / lock semantics.

This monitor drives the correct axi_resp_t.OKAY / axi_resp_t.EXOKAY response if the feature is enabled.

# Copyright 2024 Apheleia
#
# Description:
# Apheleia attributes example


import avl
import avl_axi
import cocotb
from avl_axi._types import axi_resp_t


class DirectedSequence(avl_axi.ManagerSequence):
    async def body(self) -> None:
        """
        Body of the sequence
        """

        self.info(f"Starting Directed Manager sequence {self.get_full_name()}")
        self.wait_for = "response"

        # Test : Normal operation
        await self.read(araddr=0x1000, arid=1, arsize=3, arlock=1)

        # Matching Exclusive Write - return EXOKAY
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.EXOKAY

        # Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Test : Clear by partial Match (smaller)
        await self.read(araddr=0x1000, arid=2, arsize=3, arlock=1)

        # Partial Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=2, awsize=2, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=2, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Test : Clear by artial Match (bigger)
        await self.read(araddr=0x1000, arid=3, arsize=0, arlock=1)

        # Partial Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=3, awsize=2, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=3, awsize=0, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Test : Clear by Miss
        await self.read(araddr=0x1000, arid=3, arsize=0, arlock=1)

        # Partial Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x2000, awid=3, awsize=2, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=3, awsize=0, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Test : Clear by exact match with other manager
        await self.read(araddr=0x1000, arid=4, arsize=3, arlock=1)

        # Partial Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=4, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Test : Clear by partial match with other manager
        await self.read(araddr=0x1000, arid=5, arsize=3, arlock=1)

        # Partial Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=2, awsize=1, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Matching Exclusive Write - return OKAY
        rsp = await self.write(awaddr=0x1000, awid=5, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Test : No clear by write to different range
        await self.read(araddr=0x1000, arid=0, arsize=3, arlock=1)

        # Miss (different master) - return OKAY
        rsp = await self.write(awaddr=0x1020, awid=1, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Matching Exclusive Write - return EXOKAY
        rsp = await self.write(awaddr=0x1000, awid=0, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.EXOKAY

        # Test : Update
        await self.read(araddr=0x1000, arid=0, arsize=3, arlock=1)
        await self.read(araddr=0x2000, arid=0, arsize=3, arlock=1)

        # Miss - would have hit first - return OK
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY

        # Matching Exclusive Write - return EXOKAY
        await self.read(araddr=0x1000, arid=0, arsize=3, arlock=1)
        await self.read(araddr=0x2000, arid=0, arsize=3, arlock=1)
        rsp = await self.write(awaddr=0x2000, awid=0, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.EXOKAY

        # Test : Read burst - Write single Match
        await self.read(araddr=0x1000, arid=1, arsize=3, arlen=2, awburst=1, arlock=1)

        # Matching Exclusive Write - return EXOKAY
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.EXOKAY

        # Test : Read Single - Write burst Match
        await self.read(araddr=0x1000, arid=1, arsize=3, arlock=1)

        # Matching Exclusive Write - return EXOKAY
        rsp = await self.write(awaddr=0x1000, awid=1, awsize=3, awlen=1, awburst=2, wdata=[0xdeadbeef, 0xcafebabe], wstrb=[0xFF]*2, awlock=1)
        assert rsp.bresp == axi_resp_t.EXOKAY

        # Test : Read Burst - Write miss 0
        await self.read(araddr=0x1000, arid=1, arsize=3, arlen=1, awburst=1, arlock=1)

        # Matching Exclusive Write (wrong beat) - return OKAY
        rsp = await self.write(awaddr=0x1008, awid=1, awsize=3, wdata=[0xdeadbeef], wstrb=[0xFF], awlock=1)
        assert rsp.bresp == axi_resp_t.OKAY


class example_env(avl.Env):

    def __init__(self, name, parent):
        super().__init__(name, parent)
        self.hdl = avl.Factory.get_variable(f"{self.get_full_name()}.hdl", None)
        self.clk = avl.Factory.get_variable(f"{self.get_full_name()}.clk", None)
        self.rst_n = avl.Factory.get_variable(f"{self.get_full_name()}.rst_n", None)
        self.agent = avl_axi.Agent("agent", self)

    async def run_phase(self):
        self.raise_objection()

        cocotb.start_soon(self.timeout(1, units="ms"))
        cocotb.start_soon(self.clock(self.clk, 100))
        await self.async_reset(self.rst_n, duration=100, units="ns", active_high=False)

        self.drop_objection()

@cocotb.test
async def test(dut):
    """
    Example AXI5 Interface with exclusive accesses

    :param dut: The DUT instance
    :return: None
    """
    avl.Factory.set_variable("*.clk", dut.clk)
    avl.Factory.set_variable("*.rst_n", dut.rst_n)
    avl.Factory.set_variable("*.hdl", dut.axi_if)
    avl.Factory.set_variable("*.agent.cfg.has_manager", True)
    avl.Factory.set_variable("*.agent.cfg.has_subordinate", True)
    avl.Factory.set_variable("*.agent.cfg.has_monitor", False)
    avl.Factory.set_variable("*.agent.cfg.has_trace", False)
    avl.Factory.set_variable("*.agent.msqr.mseq.n_items", 100)

    # Define memory range
    avl.Factory.set_variable("*.agent.cfg.subordinate_ranges", [(0x0000, 0x2FFF)])

    avl.Factory.set_override_by_type(avl_axi.SubordinateWriteDriver, avl_axi.SubordinateWriteMemoryDriver)
    avl.Factory.set_override_by_type(avl_axi.SubordinateReadDriver, avl_axi.SubordinateReadMemoryDriver)
    avl.Factory.set_override_by_type(avl_axi.ManagerSequence, DirectedSequence)

    e = example_env("env", None)
    await e.start()