AVL-AXI Manager
The manager side of the AVL-AXI agent follow the standard AVL / UVM structure of sequence, sequencer and driver.
Manager Sequence
A very simple sequence is provided that generates a stream of SequenceItem items.
The length of the sequence is defined by the n_items variable, which defaults to 1, but is expected to be override by the factory.
In addition a list of ranges can be provided to define the address space for the sequence. If not provided, the sequence will randomize the address along with all other variables of the item.
As the item is parameterized, only the manager side attributes present will be randomized.
The user is expected to extend the sequence for custom behavior.
The ManagerSequence provides:
ManagerSequence.nextfunction to generate and send a random transaction
ManagerSequence.writefunction to generate and send a fully constrained write transactions
ManagerSequence.readfunction to generate and send a fully constrained read transactions
In the case of the ManagerSequence.read and ManagerSequence.write functions kwargs that match the signal names are used to define the transfer. Data and response values must be presented as lists where appropriate and undefined fields are left as 0.
Wait For
The ManagerSequence.wait_for variable can be set to None, done, control, data or response. This defines the phase of the items that must complete before the sequence moves on.
This field can be overridden on a per-transaction basis using the wait_for argument of of the ManagerSequence.write and ManagerSequence.read functions.
In order to have multiple outstanding transactions you can wait_for None, done or call the function from a cocotb.start sub-process.
Manager Drivers
Due to the independence of the read and write buses the AXI driver is split in 2:
The manager driver implements the legal protocol and splits the item into 3 tasks, each with a drive and quiesce parts
control
data
response
All manager driven protocol signals (valid, ready, last etc.) and handled by the drivers. In addition all subordinate driven protocolsignals are observed and the sequence item updated accordingly. This means that should the user want to implement directed checks within the sequence they can do this easily.
Callbacks
SequenceItem have a number of events based on the protocol:
done. The standard AVL Driver to Sequencer callback to indicate the items has been processed by the driver.
response. The standard AVL Driver to Sequencer to indicate the transfer is completed by the driver.
control. Custom to AVL-AXI. Callback to indicate the control phase (AW/AR) has completed.
data. Custome to AVL-AXI. Callback to indicate the data phase (W) of write transactions has completed.
The response, control and data callbacks each take the item itself as an argument to the callback.
The WriteMonitor and ReadMonitor also call the control, data and response callbacks for any custom implementations.
Rate Control
The manager 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 valids, on response it defines the delay of ready.
avl.Factory.set_variable("*.agent.mwdrv.control_rate_limit", lambda: 0.1)
avl.Factory.set_variable("*.agent.mwdrv.data_rate_limit", lambda: 0.1)
avl.Factory.set_variable("*.agent.mwdrv.reponse_rate_limit", lambda: 0.1)
Wakeup Control (AXI5)
In AXI5, the manager driver can control the wakeup signal. The pre_wakeup and post_wakeup variables are used to define the pre-wakeup and post-wakeup delays, respectively. These are defined as lambda functions that return a value between 0.0 and 1.0.
This feature ensures the wakeup signals are driven correctly according to the AXI protocol, while providing randomization of the assertion and de-assertion timing.
A dedicated wakeup driver is implemented to co-ordinate the wakeup across the read and write buses.
Before any item is presented on the control (AW or AR) buses or data (W) bus the wake will be asserted. Once an item has requested to goto sleep no more transactions will be started and once all outstanding responses are revieved the awakeup signal will be deasserted.
avl.Factory.set_variable("*.agent.mwakedrv.pre_wakeup", lambda: 0.1) # Early assertion of wakeup sign before driving the manager
avl.Factory.set_variable("*.agent.mwakedrv.post_wakeup", lambda: 0.9) # Quick de-assertion of wakeup signal after driving the manager
Allow Early Data
Technically there is no reason the WDATA cannot be sent before the AW phase. In reality this is unlikely due to the subordinate not knowing how to route the data until the control phase is complete.
However AVL-AXI allows for this scenario. It is only important to the ManagerWriteDriver as Subordinates andmonitors can stash the data regardless of order.
To enable early data the configuration option allow_early_data must be set to True.
avl.Factory.set_variable("*.agent.mwdrv.allow_early_data", True)
# Copyright 2024 Apheleia
#
# Description:
# Apheleia attributes example
import avl
import avl_axi
import cocotb
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-Lite interface with wdata before awaddr phase
: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", True)
avl.Factory.set_variable("*.agent.cfg.has_trace", True)
avl.Factory.set_variable("*.agent.msqr.mseq.n_items", 20)
avl.Factory.set_variable("*.agent.mwdrv.control_rate_limit", lambda: 0.05)
avl.Factory.set_variable("*.agent.mwdrv.data_rate_limit", lambda: 0.5)
avl.Factory.set_variable("*.agent.mwdrv.allow_early_data", False)
avl.Factory.set_override_by_type(avl_axi.SubordinateReadDriver, avl_axi.SubordinateReadRandomDriver)
e = example_env("env", None)
await e.start()
Maximum Outstanding Transactions
The ManagerWriteDriver and ManagerReadDriver both support a :any`max_outstanding`
parameter which allows the user to limit the total number of transaction on the bus at any one time. By default there is no limit.
avl.Factory.set_variable("*.agent.mwdrv.max_outstanding", 16)
# Copyright 2024 Apheleia
#
# Description:
# Apheleia attributes example
import avl
import avl_axi
import cocotb
from z3 import ULE
class CustomWrite(avl_axi.WriteItem):
def __init__(self, name, parent=None):
super().__init__(name, parent=parent)
self.add_constraint("c_custom_write", lambda x : ULE(x, 8), self.awlen)
class CustomRead(avl_axi.ReadItem):
def __init__(self, name, parent=None):
super().__init__(name, parent=parent)
self.add_constraint("c_custom_read", lambda x : ULE(x, 8), self.arlen)
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 max outstanding transactions defined
: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", True)
avl.Factory.set_variable("*.agent.cfg.has_trace", True)
avl.Factory.set_variable("*.agent.msqr.mseq.n_items", 100)
avl.Factory.set_variable("*.agent.mwdrv.max_outstanding", 1)
avl.Factory.set_variable("*.agent.mrdrv.max_outstanding", 8)
avl.Factory.set_variable("*.agent.swdrv.response_rate", 0.05)
avl.Factory.set_variable("*.agent.srdrv.response_rate", 0.05)
avl.Factory.set_override_by_type(avl_axi.SubordinateReadDriver, avl_axi.SubordinateReadRandomDriver)
avl.Factory.set_override_by_type(avl_axi.WriteItem, CustomWrite)
avl.Factory.set_override_by_type(avl_axi.ReadItem, CustomRead)
e = example_env("env", None)
await e.start()
Note
allow_early_data and max_outstanding are mutually exclusive
Unique Indices
The ManagerWriteDriver and ManagerReadDriver both support the Unique_ID_Support parameter. This ensures that only one transaction of a given id (awid or arid) can be outstanding on the bus at any given time. This is enabled via the RTL interface.
# Copyright 2024 Apheleia
#
# Description:
# Apheleia attributes example
import avl
import avl_axi
import cocotb
class DirectedSequence(avl_axi.ManagerSequence):
async def body(self) -> None:
"""
Body of the sequence - no waiting from sequence
"""
self.info(f"Starting Directed Manager sequence {self.get_full_name()}")
# Writes
await self.write(awaddr=0x1000, awid=0, awidunq=1, awlen=7, awsize=3, awburst=1, wdata=[0,1,2,3,4,5,6,7], wstrb=[0xFF]*8)
await self.write(awaddr=0x1100, awid=1, awidunq=1, awlen=15, awsize=0, awburst=2, wdata=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], wstrb=[0x1]*16)
await self.write(awaddr=0x1200, awid=0, awidunq=0, awlen=3, awsize=2, awburst=1, wdata=[20,30,40,50], wstrb=[0xFF]*4) # Should block
await self.write(awaddr=0x1300, awid=2, awidunq=0, awlen=0, awsize=3, awburst=1, wdata=[0xcafebabe], wstrb=[0xFF])
# Reads
await self.read(araddr=0x2000, arid=0, aridunq=1, arlen=1, arsize=2, arburst=2)
await self.read(araddr=0x2100, arid=1, aridunq=1, arlen=2, arsize=1, arburst=1)
await self.read(araddr=0x2200, arid=0, aridunq=0, arlen=4, arsize=3, arburst=1) # Should block
await self.read(araddr=0x2300, arid=2, aridunq=0, arlen=3, arsize=2, arburst=2)
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 Unique IDs
: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)
avl.Factory.set_variable("*.agent.swdrv.response_rate_limit", lambda: 0.05)
avl.Factory.set_variable("*.agent.srdrv.response_rate_limit", lambda: 0.05)
# 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()