From 2fc0a39b521256cd99d2ca668b99a3957f3743c7 Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Fri, 29 Sep 2023 18:42:26 +0200 Subject: [PATCH 01/11] Add Makefile --- verification/block/lib_axi4_to_ahb/Makefile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 verification/block/lib_axi4_to_ahb/Makefile diff --git a/verification/block/lib_axi4_to_ahb/Makefile b/verification/block/lib_axi4_to_ahb/Makefile new file mode 100644 index 00000000000..64f210b459d --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/Makefile @@ -0,0 +1,17 @@ + +null := +space := $(null) # +comma := , + +CURDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +SRCDIR := $(abspath $(CURDIR)../../../../design) + +TEST_FILES = $(sort $(wildcard test_*.py)) + +MODULE ?= $(subst $(space),$(comma),$(subst .py,,$(TEST_FILES))) +TOPLEVEL = axi4_to_ahb + +VERILOG_SOURCES = \ + $(SRCDIR)/lib/axi4_to_ahb.sv + +include $(CURDIR)/../common.mk From 31cb4712eee40930c2809a88a9a7ee5237058acb Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Wed, 4 Oct 2023 18:17:51 +0200 Subject: [PATCH 02/11] AHB: Implement Agent --- .../block/lib_axi4_to_ahb/ahb_lite_agent.py | 71 ++++++++++++++ .../block/lib_axi4_to_ahb/ahb_lite_bfm.py | 98 +++++++++++++++++++ .../block/lib_axi4_to_ahb/ahb_lite_pkg.py | 39 ++++++++ .../block/lib_axi4_to_ahb/ahb_lite_seq.py | 72 ++++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 verification/block/lib_axi4_to_ahb/ahb_lite_agent.py create mode 100644 verification/block/lib_axi4_to_ahb/ahb_lite_bfm.py create mode 100644 verification/block/lib_axi4_to_ahb/ahb_lite_pkg.py create mode 100644 verification/block/lib_axi4_to_ahb/ahb_lite_seq.py diff --git a/verification/block/lib_axi4_to_ahb/ahb_lite_agent.py b/verification/block/lib_axi4_to_ahb/ahb_lite_agent.py new file mode 100644 index 00000000000..01bf9ced0ac --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/ahb_lite_agent.py @@ -0,0 +1,71 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import cocotb +from ahb_lite_bfm import AHBLiteBFM +from cocotb.queue import QueueEmpty +from cocotb.triggers import RisingEdge +from common import BaseMonitor, get_int +from pyuvm import ConfigDB, uvm_agent, uvm_analysis_port, uvm_driver, uvm_sequencer + + +class AHBLiteAgent(uvm_agent): + """ + Seqr <---> Driver + Monitor <--^ + """ + + def build_phase(self): + self.seqr = uvm_sequencer("seqr", self) + ConfigDB().set(None, "*", "ahb_seqr", self.seqr) + self.monitor = AHBLiteMonitor("axi_w_agent", self) + self.driver = AHBLiteDriver("axi_w_driver", self) + + def connect_phase(self): + self.driver.seq_item_port.connect(self.seqr.seq_item_export) + + +class AHBLiteDriver(uvm_driver): + def build_phase(self): + self.ap = uvm_analysis_port("ap", self) + self.rst_n = cocotb.top.rst_l + self.clk = cocotb.top.clk + + def start_of_simulation_phase(self): + self.bfm = AHBLiteBFM() + + async def run_phase(self): + self.bfm.start_bfm() + while True: + if get_int(self.rst_n) == 0: + await RisingEdge(self.rst_n) + self.logger.info("Agent: AHB Lite: Reset Posedge") + + await RisingEdge(self.clk) + try: + response = self.bfm.rsp_driver_q.get_nowait() + self.seq_item_port.put_response(response) + # Expect two items per one response (hready is asserted for 2 cycles) + for _ in range(2): + item = await self.seq_item_port.get_next_item() + await self.drive(item) + self.logger.debug(f"Driven: {item}") + self.seq_item_port.item_done() + except QueueEmpty: + pass + + async def drive(self, item): + await self.bfm.req_driver_q_put( + item.ahb_hrdata, + item.ahb_hready, + item.ahb_hresp, + ) + + +class AHBLiteMonitor(BaseMonitor): + def __init__(self, name, parent): + super().__init__(name, parent) + + def build_phase(self): + super().build_phase() + self.bfm = AHBLiteBFM() diff --git a/verification/block/lib_axi4_to_ahb/ahb_lite_bfm.py b/verification/block/lib_axi4_to_ahb/ahb_lite_bfm.py new file mode 100644 index 00000000000..ad963581cce --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/ahb_lite_bfm.py @@ -0,0 +1,98 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import cocotb +from ahb_lite_pkg import ( + AHB_LITE_NOTIFICATION, + AHB_LITE_RESPONSE_CODES, + AHB_LITE_TRANSFER_TYPE_ENCODING, + AHB_RSP_SIGNALS, +) +from cocotb.queue import QueueEmpty +from cocotb.triggers import RisingEdge +from common import get_int, get_signals +from pyuvm import UVMQueue, utility_classes + + +class AHBLiteBFM(metaclass=utility_classes.Singleton): + def __init__(self): + self.dut = cocotb.top + self.rst_n = cocotb.top.rst_l + self.clk = cocotb.top.clk + self.req_driver_q = UVMQueue(maxsize=1) + self.rsp_driver_q = UVMQueue() + self.req_monitor_q = UVMQueue() + self.rsp_monitor_q = UVMQueue() + + async def req_driver_q_put(self, ahb_hrdata, ahb_hready, ahb_hresp): + item = (ahb_hrdata, ahb_hready, ahb_hresp) + await self.req_driver_q.put(item) + + async def req_monitor_q_get(self): + item = await self.req_monitor_q.get() + return item + + async def rsp_monitor_q_get(self): + result = await self.rsp_monitor_q.get() + return result + + async def drive(self): + prev_htrans = AHB_LITE_TRANSFER_TYPE_ENCODING.IDLE + htrans = AHB_LITE_TRANSFER_TYPE_ENCODING.IDLE + + while True: + if self.rst_n.value == 0: + self.dut.ahb_hrdata.value = 0 + self.dut.ahb_hready.value = 0 + self.dut.ahb_hresp.value = 0 + await RisingEdge(self.rst_n) + await RisingEdge(self.clk) + + prev_htrans = htrans + if get_int(self.dut.ahb_htrans) == AHB_LITE_TRANSFER_TYPE_ENCODING.IDLE: + htrans = AHB_LITE_TRANSFER_TYPE_ENCODING.IDLE + self.dut.ahb_hrdata.value = 0 + self.dut.ahb_hready.value = 0 + self.dut.ahb_hresp.value = AHB_LITE_RESPONSE_CODES.OKAY + elif get_int(self.dut.ahb_htrans) == AHB_LITE_TRANSFER_TYPE_ENCODING.NONSEQ: + htrans = AHB_LITE_TRANSFER_TYPE_ENCODING.NONSEQ + if get_int(self.dut.ahb_hwrite): + if htrans != prev_htrans: + self.rsp_driver_q.put_nowait(AHB_LITE_NOTIFICATION.AHB_LITE_WRITE) + else: + if htrans != prev_htrans: + self.rsp_driver_q.put_nowait(AHB_LITE_NOTIFICATION.AHB_LITE_READ) + + try: + (ahb_hrdata, ahb_hready, ahb_hresp) = self.req_driver_q.get_nowait() + self.dut.ahb_hrdata.value = ahb_hrdata + self.dut.ahb_hready.value = ahb_hready + self.dut.ahb_hresp.value = ahb_hresp + except QueueEmpty: + self.dut.ahb_hrdata.value = 0 + self.dut.ahb_hready.value = 0 + self.dut.ahb_hresp.value = 0 + + async def req_monitor_q_bfm(self): + while True: + await RisingEdge(self.clk) + if get_int(self.dut.ahb_hready): + item = ( + get_int(self.dut.ahb_hrdata), + get_int(self.dut.ahb_hready), + get_int(self.dut.ahb_hresp), + ) + await self.req_monitor_q.put(item) + + async def rsp_monitor_q_bfm(self): + while True: + await RisingEdge(self.clk) + if get_int(self.dut.ahb_hready): + sigs = get_signals(AHB_RSP_SIGNALS, self.dut) + values = tuple(sig.value for sig in sigs) + await self.rsp_monitor_q.put(values) + + def start_bfm(self): + cocotb.start_soon(self.drive()) + cocotb.start_soon(self.req_monitor_q_bfm()) + cocotb.start_soon(self.rsp_monitor_q_bfm()) diff --git a/verification/block/lib_axi4_to_ahb/ahb_lite_pkg.py b/verification/block/lib_axi4_to_ahb/ahb_lite_pkg.py new file mode 100644 index 00000000000..a7f990f0e6a --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/ahb_lite_pkg.py @@ -0,0 +1,39 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + + +from enum import IntEnum + +AHB_DRV_SIGNALS = [ + "ahb_hrdata", + "ahb_hready", + "ahb_hresp", +] + +AHB_RSP_SIGNALS = [ + "ahb_haddr", + "ahb_hburst", + "ahb_hmastlock", + "ahb_hprot", + "ahb_hsize", + "ahb_htrans", + "ahb_hwrite", + "ahb_hwdata", +] + + +class AHB_LITE_RESPONSE_CODES(IntEnum): + OKAY = 0 + + +class AHB_LITE_TRANSFER_TYPE_ENCODING(IntEnum): + IDLE = 0 + BUSY = 1 + NONSEQ = 2 + SEQ = 3 + + +class AHB_LITE_NOTIFICATION(IntEnum): + AHB_LITE_WRITE = 1 + AHB_LITE_READ = 2 + AHB_LITE_IDLE = 3 diff --git a/verification/block/lib_axi4_to_ahb/ahb_lite_seq.py b/verification/block/lib_axi4_to_ahb/ahb_lite_seq.py new file mode 100644 index 00000000000..82b5ac7c772 --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/ahb_lite_seq.py @@ -0,0 +1,72 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import random + +from ahb_lite_pkg import AHB_LITE_RESPONSE_CODES +from common import BaseSeq +from pyuvm import uvm_sequence_item + + +class AHBLiteBaseSeqItem(uvm_sequence_item): + def __init__(self, name): + super().__init__(name) + self.ahb_hrdata = 0 + self.ahb_hready = 0 + self.ahb_hresp = 0 + + def randomize(self): + pass + + def __eq__(self, other): + pass + + def __str__(self): + return self.__class__.__name__ + + +class AHBLiteInactiveSeqItem(AHBLiteBaseSeqItem): + def __init__(self, name): + super().__init__(name) + + +class AHBLiteReadyReadSeqItem(AHBLiteBaseSeqItem): + def __init__(self, name): + super().__init__(name) + self.ahb_hready = 1 + self.ahb_hresp = AHB_LITE_RESPONSE_CODES.OKAY + + def randomize(self): + self.ahb_hrdata = random.randint(0, 255) + + +class AHBLiteReadyWriteSeqItem(AHBLiteBaseSeqItem): + def __init__(self, name): + super().__init__(name) + self.ahb_hready = 1 + self.ahb_hresp = AHB_LITE_RESPONSE_CODES.OKAY + + +class AHBLiteReadyNoDataSeqItem(AHBLiteBaseSeqItem): + def __init__(self, name): + super().__init__(name) + self.ahb_hready = 1 + self.ahb_hresp = AHB_LITE_RESPONSE_CODES.OKAY + + +class AHBLiteAcceptWriteSeq(BaseSeq): + async def body(self): + items = [ + AHBLiteReadyNoDataSeqItem("AHBLiteReadyNoDataSeqItem"), + AHBLiteReadyWriteSeqItem("AHBLiteReadyWriteSeqItem"), + ] + await self.run_items(items) + + +class AHBLiteAcceptReadSeq(BaseSeq): + async def body(self): + items = [ + AHBLiteReadyNoDataSeqItem("AHBLiteReadyNoDataSeqItem"), + AHBLiteReadyReadSeqItem("AHBLiteReadyReadSeqItem"), + ] + await self.run_items(items) From c8f7b2f07f0f0d95d9ab835bfa33b50d23c4c5f3 Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Wed, 4 Oct 2023 18:18:15 +0200 Subject: [PATCH 03/11] AXI Read: Implement Agent --- verification/block/lib_axi4_to_ahb/axi_pkg.py | 87 +++++++++++++ .../block/lib_axi4_to_ahb/axi_r_agent.py | 77 ++++++++++++ .../block/lib_axi4_to_ahb/axi_r_bfm.py | 119 ++++++++++++++++++ .../block/lib_axi4_to_ahb/axi_r_seq.py | 73 +++++++++++ 4 files changed, 356 insertions(+) create mode 100644 verification/block/lib_axi4_to_ahb/axi_pkg.py create mode 100644 verification/block/lib_axi4_to_ahb/axi_r_agent.py create mode 100644 verification/block/lib_axi4_to_ahb/axi_r_bfm.py create mode 100644 verification/block/lib_axi4_to_ahb/axi_r_seq.py diff --git a/verification/block/lib_axi4_to_ahb/axi_pkg.py b/verification/block/lib_axi4_to_ahb/axi_pkg.py new file mode 100644 index 00000000000..d1f898e19d5 --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/axi_pkg.py @@ -0,0 +1,87 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +from enum import IntEnum + +AXI_W_CHAN_DRV_SIGNALS = [ + "axi_awvalid", + "axi_awid", + "axi_awaddr", + "axi_awsize", + "axi_awprot", + "axi_wvalid", + "axi_wdata", + "axi_wstrb", + "axi_wlast", + "axi_bready", +] + +AXI_W_CHAN_RSP_SIGNALS = [ + "axi_awready", + "axi_wready", + "axi_bvalid", + "axi_bresp", + "axi_bid", +] + +AXI_W_CHAN_SIGNALS = AXI_W_CHAN_DRV_SIGNALS + AXI_W_CHAN_RSP_SIGNALS + +AXI_R_CHAN_DRV_SIGNALS = [ + "axi_arvalid", + "axi_arid", + "axi_araddr", + "axi_arsize", + "axi_arprot", + "axi_rready", +] + +AXI_R_CHAN_RSP_SIGNALS = [ + "axi_arready", + "axi_rvalid", + "axi_rid", + "axi_rdata", + "axi_rresp", + "axi_rlast", +] + +AXI_R_CHAN_SIGNALS = AXI_R_CHAN_DRV_SIGNALS + AXI_R_CHAN_RSP_SIGNALS + + +class AXI_WRITE_RESPONSE_CODES(IntEnum): + OKAY = 0 + EXOKAY = 1 + SLVERR = 2 + DECERR = 3 + DEFER = 4 + TRANSFAULT = 5 + RESERVED = 6 + UNSUPPORTED = 7 + + +class AXI_READ_RESPONSE_CODES(IntEnum): + OKAY = 0 + EXOKAY = 1 + SLVERR = 2 + DECERR = 3 + PREFETCHED = 4 + TRANSFAULT = 5 + OKAYDIRTY = 6 + RESERVED = 7 + + +class AXI_AXSIZE_ENCODING(IntEnum): + MAX_1B_TRANSFER = 0 + MAX_2B_TRANSFER = 1 + MAX_4B_TRANSFER = 2 + MAX_8B_TRANSFER = 3 + MAX_16B_TRANSFER = 4 + MAX_32B_TRANSFER = 5 + MAX_64B_TRANSFER = 6 + MAX_128B_TRANSFER = 7 + + +class AXI_NOTIFICATION(IntEnum): + AXI_FREE = 1 + AXI_BUSY = 2 + AXI_AREAD_HANDSHAKE = 3 + AXI_READ_HANDSHAKE = 4 diff --git a/verification/block/lib_axi4_to_ahb/axi_r_agent.py b/verification/block/lib_axi4_to_ahb/axi_r_agent.py new file mode 100644 index 00000000000..37a71c5d44c --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/axi_r_agent.py @@ -0,0 +1,77 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import random + +import cocotb +import pyuvm +from axi_r_bfm import AXIReadChannelBFM +from cocotb.queue import QueueEmpty +from cocotb.triggers import RisingEdge +from common import BaseMonitor +from pyuvm import ConfigDB, uvm_agent, uvm_analysis_port, uvm_driver, uvm_sequencer + + +class AXIReadChannelAgent(uvm_agent): + def build_phase(self): + self.seqr = uvm_sequencer("seqr", self) + ConfigDB().set(None, "*", "axi_r_seqr", self.seqr) + self.monitor = AXIReadChannelMonitor("axi_r_agent", self) + self.driver = AXIReadChannelDriver("axi_r_driver", self) + + def connect_phase(self): + self.driver.seq_item_port.connect(self.seqr.seq_item_export) + + +class AXIReadChannelDriver(uvm_driver): + def build_phase(self): + self.ap = uvm_analysis_port("ap", self) + self.rst_n = cocotb.top.rst_l + self.clk = cocotb.top.clk + + def start_of_simulation_phase(self): + self.bfm = AXIReadChannelBFM() + + async def run_phase(self): + self.bfm.start_bfm() + while True: + if self.rst_n.value == 0: + await RisingEdge(self.rst_n) + self.logger.info("Agent: AXI Read: Reset Posedge") + + try: + pending = 1 + axi_notification = self.bfm.rsp_driver_q.get_nowait() + except QueueEmpty: + pending = 0 + + if pending: + self.seq_item_port.put_response(axi_notification) + + self.seq_item_port.put_response(3) + try: + item = await self.seq_item_port.get_next_item() + await self.drive(item) + self.logger.debug(f"Agent: AXI Read: Driven: {item}") + self.seq_item_port.item_done() + except QueueEmpty: + pass + + async def drive(self, item): + await self.bfm.req_driver_q_put( + item.axi_arvalid, + item.axi_arid, + item.axi_araddr, + item.axi_arsize, + item.axi_arprot, + item.axi_rready, + ) + + +class AXIReadChannelMonitor(BaseMonitor): + def __init__(self, name, parent): + super().__init__(name, parent) + + def build_phase(self): + super().build_phase() + self.bfm = AXIReadChannelBFM() diff --git a/verification/block/lib_axi4_to_ahb/axi_r_bfm.py b/verification/block/lib_axi4_to_ahb/axi_r_bfm.py new file mode 100644 index 00000000000..08cb20945ed --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/axi_r_bfm.py @@ -0,0 +1,119 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import cocotb +from axi_pkg import AXI_NOTIFICATION, AXI_R_CHAN_RSP_SIGNALS +from cocotb.queue import QueueEmpty +from cocotb.triggers import RisingEdge +from common import get_int, get_signals +from pyuvm import UVMQueue, utility_classes, uvm_root + + +class AXIReadChannelBFM(metaclass=utility_classes.Singleton): + def __init__(self): + self.dut = cocotb.top + self.rst_n = cocotb.top.rst_l + self.clk = cocotb.top.clk + self.req_driver_q = UVMQueue(maxsize=1) + self.rsp_driver_q = UVMQueue(maxsize=1) + self.req_monitor_q = UVMQueue(maxsize=0) + self.rsp_monitor_q = UVMQueue(maxsize=0) + self.TIMEOUT_THRESHOLD = 20 + + async def req_driver_q_put( + self, + axi_arvalid, + axi_arid, + axi_araddr, + axi_arsize, + axi_arprot, + axi_rready, + ): + item = ( + axi_arvalid, + axi_arid, + axi_araddr, + axi_arsize, + axi_arprot, + axi_rready, + ) + await self.req_driver_q.put(item) + + async def req_monitor_q_get(self): + item = await self.req_monitor_q.get() + return item + + async def rsp_monitor_q_get(self): + result = await self.rsp_monitor_q.get() + return result + + async def drive(self): + while True: + if self.rst_n.value == 0: + self.dut.axi_arvalid.value = 0 + self.dut.axi_arid.value = 0 + self.dut.axi_araddr.value = 0 + self.dut.axi_arsize.value = 0 + self.dut.axi_arprot.value = 0 + self.dut.axi_rready.value = 0 + await RisingEdge(self.rst_n) + await RisingEdge(self.clk) + try: + ( + axi_arvalid, + axi_arid, + axi_araddr, + axi_arsize, + axi_arprot, + axi_rready, + ) = self.req_driver_q.get_nowait() + self.dut.axi_arvalid.value = axi_arvalid + self.dut.axi_arid.value = axi_arid + self.dut.axi_araddr.value = axi_araddr + self.dut.axi_arsize.value = axi_arsize + self.dut.axi_arprot.value = axi_arprot + self.dut.axi_rready.value = axi_rready + except QueueEmpty: + pass + + # Handshake ARVALID <-> ARREADY + if get_int(self.dut.axi_arvalid): + timeout = 0 + while get_int(self.dut.axi_arready) == 0: + timeout += 1 + self.rsp_driver_q.put_nowait(AXI_NOTIFICATION.AXI_BUSY) + await RisingEdge(self.clk) + if timeout > self.TIMEOUT_THRESHOLD: + raise TimeoutError("Transaction Request Handshake Timeout: AXI Read") + + async def req_monitor_q_bfm(self): + while True: + if self.rst_n.value == 0: + await RisingEdge(self.rst_n) + await RisingEdge(self.clk) + if get_int(self.dut.axi_arvalid): + if get_int(self.dut.axi_arready): + item = ( + get_int(self.dut.axi_arvalid), + get_int(self.dut.axi_arid), + get_int(self.dut.axi_araddr), + get_int(self.dut.axi_arsize), + get_int(self.dut.axi_arprot), + get_int(self.dut.axi_rready), + ) + await self.req_monitor_q.put(item) + + async def rsp_monitor_q_bfm(self): + while True: + if self.rst_n.value == 0: + await RisingEdge(self.rst_n) + await RisingEdge(self.clk) + if get_int(self.dut.axi_rvalid): + sigs = get_signals(AXI_R_CHAN_RSP_SIGNALS, self.dut) + values = tuple(sig.value for sig in sigs) + await self.rsp_monitor_q.put(values) + + def start_bfm(self): + cocotb.start_soon(self.drive()) + cocotb.start_soon(self.req_monitor_q_bfm()) + cocotb.start_soon(self.rsp_monitor_q_bfm()) diff --git a/verification/block/lib_axi4_to_ahb/axi_r_seq.py b/verification/block/lib_axi4_to_ahb/axi_r_seq.py new file mode 100644 index 00000000000..7b5600f2868 --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/axi_r_seq.py @@ -0,0 +1,73 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import random + +from axi_pkg import AXI_AXSIZE_ENCODING +from common import BaseSeq +from pyuvm import ConfigDB, uvm_sequence_item + + +class AXIReadBaseSeqItem(uvm_sequence_item): + def __init__(self, name): + super().__init__(name) + self.AXI_DATA_WIDTH = ConfigDB().get(None, "", "AXI_DATA_WIDTH") + self.AXI_NUM_STRB_BITS = int(self.AXI_DATA_WIDTH / 8) + + self.axi_arvalid = 0 + self.axi_arid = 0 + self.axi_araddr = 0 + self.axi_arsize = int(AXI_AXSIZE_ENCODING.MAX_8B_TRANSFER) + self.axi_arprot = 0 + self.axi_rready = 0 + + def randomize(self): + pass + + def __eq__(self, other): + pass + + def __str__(self): + return self.__class__.__name__ + + +class AXIReadTransactionRequestSeqItem(AXIReadBaseSeqItem): + def __init__(self, name): + super().__init__(name) + self.axi_arvalid = 1 + + def randomize(self): + self.axi_araddr = 8 * random.randint(8, 32) + + +class AXIReadResponseReadSeqItem(AXIReadBaseSeqItem): + def __init__( + self, + name, + ): + super().__init__(name) + self.axi_rready = 1 + + +class AXIReadInactiveSeqItem(AXIReadBaseSeqItem): + def __init__(self, name): + super().__init__(name) + self.axi_arsize = 0 + + +class AXIReadTransactionRequestSeq(BaseSeq): + async def body(self): + items = [ + AXIReadTransactionRequestSeqItem("AXIReadTransactionRequestSeqItem"), + AXIReadInactiveSeqItem("AXIReadInactiveSeqItem"), + ] + await self.run_items(items) + + +class AXIReadTransactionResponseSeq(BaseSeq): + async def body(self): + items = [ + AXIReadResponseReadSeqItem("AXIReadLastDataSeqItem"), + AXIReadInactiveSeqItem("AXIReadInactiveSeqItem"), + ] + await self.run_items(items) From 178093701ceb8a778686e235ba3b6aa2ad85174c Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Wed, 4 Oct 2023 18:18:26 +0200 Subject: [PATCH 04/11] AXI Write: Implement Agent --- .../block/lib_axi4_to_ahb/axi_w_agent.py | 109 ++++++++++++++ .../block/lib_axi4_to_ahb/axi_w_bfm.py | 140 ++++++++++++++++++ .../block/lib_axi4_to_ahb/axi_w_seq.py | 108 ++++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 verification/block/lib_axi4_to_ahb/axi_w_agent.py create mode 100644 verification/block/lib_axi4_to_ahb/axi_w_bfm.py create mode 100644 verification/block/lib_axi4_to_ahb/axi_w_seq.py diff --git a/verification/block/lib_axi4_to_ahb/axi_w_agent.py b/verification/block/lib_axi4_to_ahb/axi_w_agent.py new file mode 100644 index 00000000000..dc7641af2b2 --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/axi_w_agent.py @@ -0,0 +1,109 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import cocotb +from axi_w_bfm import AXIWriteChannelBFM +from axi_w_seq import ( + AXIWriteInactiveSeqItem, + AXIWriteLastDataSeqItem, + AXIWriteResponseWriteSeqItem, + AXIWriteTransactionRequestSeqItem, +) +from cocotb.queue import QueueEmpty +from cocotb.triggers import RisingEdge +from common import BaseMonitor +from pyuvm import ConfigDB, uvm_agent, uvm_driver, uvm_sequencer + + +class AXIWriteChannelAgent(uvm_agent): + """ + Seqr <---> Driver + Monitor <--^ + """ + + def build_phase(self): + self.seqr = uvm_sequencer("seqr", self) + ConfigDB().set(None, "*", "axi_w_seqr", self.seqr) + self.monitor = AXIWriteChannelMonitor("axi_w_agent", self) + self.driver = AXIWriteChannelDriver("axi_w_driver", self) + + def connect_phase(self): + self.driver.seq_item_port.connect(self.seqr.seq_item_export) + + +class AXIWriteChannelDriver(uvm_driver): + def build_phase(self): + self.rst_n = cocotb.top.rst_l + + def start_of_simulation_phase(self): + self.bfm = AXIWriteChannelBFM() + + async def run_phase(self): + self.bfm.start_bfm() + while True: + if self.rst_n.value == 0: + await RisingEdge(self.rst_n) + self.logger.info("Agent: AXI Write: Reset Posedge") + + try: + item = await self.seq_item_port.get_next_item() + except QueueEmpty: + pass + + if isinstance(item, AXIWriteInactiveSeqItem): + await self.drive(item) + self.logger.debug(f"Driven: {item}") + self.seq_item_port.item_done() + + if isinstance(item, AXIWriteTransactionRequestSeqItem): + await self.drive(item) + self.logger.debug(f"Driven: {item}") + await self.wait_handshake(sig_name="axi_awready") + self.seq_item_port.item_done() + + if isinstance(item, AXIWriteLastDataSeqItem): + await self.drive(item) + self.logger.debug(f"Driven: {item}") + await self.wait_handshake(sig_name="axi_wready") + self.seq_item_port.item_done() + + if isinstance(item, AXIWriteResponseWriteSeqItem): + await self.drive(item) + await self.wait_handshake(sig_name="axi_bvalid") + self.logger.debug(f"Driven: {item}") + self.seq_item_port.item_done() + + async def wait_handshake(self, sig_name=None, TIMEOUT_THRESHOLD=30): + timeout = 0 + while True: + timeout += 1 + await RisingEdge(self.bfm.clk) + sig_handle = getattr(self.bfm.dut, sig_name) + if sig_handle.value: + break + + if timeout > TIMEOUT_THRESHOLD: + raise TimeoutError(f"Transaction Request Handshake Timeout: AXI Write: {sig_name}") + + async def drive(self, item): + await self.bfm.req_driver_q_put( + item.axi_awvalid, + item.axi_awid, + item.axi_awaddr, + item.axi_awsize, + item.axi_awprot, + item.axi_wvalid, + item.axi_wdata, + item.axi_wstrb, + item.axi_wlast, + item.axi_bready, + ) + + +class AXIWriteChannelMonitor(BaseMonitor): + def __init__(self, name, parent): + super().__init__(name, parent) + + def build_phase(self): + super().build_phase() + self.bfm = AXIWriteChannelBFM() diff --git a/verification/block/lib_axi4_to_ahb/axi_w_bfm.py b/verification/block/lib_axi4_to_ahb/axi_w_bfm.py new file mode 100644 index 00000000000..0563c9aa88f --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/axi_w_bfm.py @@ -0,0 +1,140 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import cocotb +from axi_pkg import AXI_W_CHAN_RSP_SIGNALS +from cocotb.queue import QueueEmpty +from cocotb.triggers import RisingEdge +from common import get_int, get_signals +from pyuvm import UVMQueue, utility_classes, uvm_root + + +class AXIWriteChannelBFM(metaclass=utility_classes.Singleton): + def __init__(self): + self.dut = cocotb.top + self.rst_n = cocotb.top.rst_l + self.clk = cocotb.top.clk + self.req_driver_q = UVMQueue(maxsize=1) + self.req_monitor_q = UVMQueue() + self.rsp_monitor_q = UVMQueue() + + async def req_driver_q_put( + self, + axi_awvalid, + axi_awid, + axi_awaddr, + axi_awsize, + axi_awprot, + axi_wvalid, + axi_wdata, + axi_wstrb, + axi_wlast, + axi_bready, + ): + item = ( + axi_awvalid, + axi_awid, + axi_awaddr, + axi_awsize, + axi_awprot, + axi_wvalid, + axi_wdata, + axi_wstrb, + axi_wlast, + axi_bready, + ) + await self.req_driver_q.put(item) + + async def req_monitor_q_get(self): + item = await self.req_monitor_q.get() + return item + + async def rsp_monitor_q_get(self): + result = await self.rsp_monitor_q.get() + return result + + async def drive(self): + while True: + if self.rst_n.value == 0: + self.dut.axi_awvalid.value = 0 + self.dut.axi_awid.value = 0 + self.dut.axi_awaddr.value = 0 + self.dut.axi_awsize.value = 0 + self.dut.axi_awprot.value = 0 + self.dut.axi_wvalid.value = 0 + self.dut.axi_wdata.value = 0 + self.dut.axi_wstrb.value = 0 + self.dut.axi_wlast.value = 0 + self.dut.axi_bready.value = 0 + await RisingEdge(self.rst_n) + await RisingEdge(self.clk) + try: + ( + axi_awvalid, + axi_awid, + axi_awaddr, + axi_awsize, + axi_awprot, + axi_wvalid, + axi_wdata, + axi_wstrb, + axi_wlast, + axi_bready, + ) = self.req_driver_q.get_nowait() + self.dut.axi_awvalid.value = axi_awvalid + self.dut.axi_awid.value = axi_awid + self.dut.axi_awaddr.value = axi_awaddr + self.dut.axi_awsize.value = axi_awsize + self.dut.axi_awprot.value = axi_awprot + self.dut.axi_wvalid.value = axi_wvalid + self.dut.axi_wdata.value = axi_wdata + self.dut.axi_wstrb.value = axi_wstrb + self.dut.axi_wlast.value = axi_wlast + self.dut.axi_bready.value = axi_bready + except QueueEmpty: + pass + + async def req_monitor_q_bfm(self): + while True: + if self.rst_n.value == 0: + await RisingEdge(self.rst_n) + await RisingEdge(self.clk) + send_item = 0 + if get_int(self.dut.axi_awvalid): + if get_int(self.dut.axi_awready): + send_item = 1 + + if get_int(self.dut.axi_wvalid): + if get_int(self.dut.axi_wready): + send_item = 1 + + if send_item: + item = ( + get_int(self.dut.axi_awvalid), + get_int(self.dut.axi_awid), + get_int(self.dut.axi_awaddr), + get_int(self.dut.axi_awsize), + get_int(self.dut.axi_awprot), + get_int(self.dut.axi_wvalid), + get_int(self.dut.axi_wdata), + get_int(self.dut.axi_wstrb), + get_int(self.dut.axi_wlast), + get_int(self.dut.axi_bready), + ) + await self.req_monitor_q.put(item) + + async def rsp_monitor_q_bfm(self): + while True: + if self.rst_n.value == 0: + await RisingEdge(self.rst_n) + await RisingEdge(self.clk) + if get_int(self.dut.axi_bvalid): + if get_int(self.dut.axi_bready): + sigs = get_signals(AXI_W_CHAN_RSP_SIGNALS, self.dut) + values = tuple(sig.value for sig in sigs) + await self.rsp_monitor_q.put(values) + + def start_bfm(self): + cocotb.start_soon(self.drive()) + cocotb.start_soon(self.req_monitor_q_bfm()) + cocotb.start_soon(self.rsp_monitor_q_bfm()) diff --git a/verification/block/lib_axi4_to_ahb/axi_w_seq.py b/verification/block/lib_axi4_to_ahb/axi_w_seq.py new file mode 100644 index 00000000000..cd1ee44977a --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/axi_w_seq.py @@ -0,0 +1,108 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import random + +from axi_pkg import AXI_AXSIZE_ENCODING +from cocotb.types import LogicArray +from common import BaseSeq +from pyuvm import ConfigDB, uvm_sequence_item + + +class AXIWriteBaseSeqItem(uvm_sequence_item): + def __init__(self, name): + super().__init__(name) + self.AXI_DATA_WIDTH = ConfigDB().get(None, "", "AXI_DATA_WIDTH") + self.AXI_NUM_STRB_BITS = int(self.AXI_DATA_WIDTH / 8) + + self.axi_awvalid = 0 + self.axi_awid = 0 + self.axi_awaddr = 0 + self.axi_awsize = int(AXI_AXSIZE_ENCODING.MAX_8B_TRANSFER) + self.axi_awprot = 0 + self.axi_wvalid = 0 + self.axi_wdata = 0 + self.axi_wstrb = LogicArray("1" * self.AXI_NUM_STRB_BITS) + self.axi_wlast = 0 + self.axi_bready = 0 + + def randomize(self): + pass + + def __eq__(self, other): + pass + + def __str__(self): + return self.__class__.__name__ + + +class AXIWriteTransactionRequestSeqItem(AXIWriteBaseSeqItem): + def __init__(self, name): + super().__init__(name) + self.axi_awvalid = 1 + + def randomize(self): + self.axi_awid = random.randint(0, 1) + self.axi_awaddr = 8 * random.randint(8, 32) + + +class AXIWriteDataSeqItem(AXIWriteBaseSeqItem): + def __init__(self, name): + super().__init__(name) + self.axi_wvalid = 1 + self.axi_wstrb = LogicArray("1" * self.AXI_NUM_STRB_BITS) + + def randomize(self): + self.axi_wdata = random.randint(0, 255) + + +class AXIWriteLastDataSeqItem(AXIWriteDataSeqItem): + def __init__(self, name): + super().__init__(name) + self.axi_wlast = 1 + + +class AXIWriteResponseWriteSeqItem(AXIWriteBaseSeqItem): + def __init__( + self, + name, + ): + super().__init__(name) + self.axi_bready = 1 + + +class AXIWriteInactiveSeqItem(AXIWriteBaseSeqItem): + def __init__(self, name): + super().__init__(name) + self.axi_awsize = 0 + self.axi_wstrb = 0 + + +class AXIWriteTransactionRequestSeq(BaseSeq): + async def body(self): + items = [ + AXIWriteInactiveSeqItem("AXIWriteInactiveSeqItem"), + AXIWriteTransactionRequestSeqItem("AXIWriteTransactionRequestSeqItem"), + AXIWriteInactiveSeqItem("AXIWriteInactiveSeqItem"), + ] + await self.run_items(items) + + +class AXIWriteDataSeq(BaseSeq): + async def body(self): + items = [ + AXIWriteInactiveSeqItem("AXIWriteInactiveSeqItem"), + AXIWriteLastDataSeqItem("AXIWriteLastDataSeqItem"), + AXIWriteInactiveSeqItem("AXIWriteInactiveSeqItem"), + ] + await self.run_items(items) + + +class AXIWriteResponseSeq(BaseSeq): + async def body(self): + items = [ + AXIWriteInactiveSeqItem("AXIWriteInactiveSeqItem"), + AXIWriteResponseWriteSeqItem("AXIWriteLastDataSeqItem"), + AXIWriteInactiveSeqItem("AXIWriteInactiveSeqItem"), + ] + await self.run_items(items) From c13bfd0292498e873d794722347b76ab7512df1f Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Wed, 4 Oct 2023 18:18:40 +0200 Subject: [PATCH 05/11] Upload common functions --- verification/block/lib_axi4_to_ahb/common.py | 79 ++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 verification/block/lib_axi4_to_ahb/common.py diff --git a/verification/block/lib_axi4_to_ahb/common.py b/verification/block/lib_axi4_to_ahb/common.py new file mode 100644 index 00000000000..52417be8446 --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/common.py @@ -0,0 +1,79 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import logging + +import cocotb +from pyuvm import uvm_analysis_port, uvm_component, uvm_sequence + + +def collect_signals(signals, uut, obj): + """ + Collects signal objects from UUT and attaches them to the given object + """ + + for sig in signals: + if hasattr(uut, sig): + s = getattr(uut, sig) + + else: + s = None + logging.error("Module {} does not have a signal '{}'".format(str(uut), sig)) + + setattr(obj, sig, s) + + +def get_int(signal): + try: + sig = int(signal.value) + except ValueError: + sig = 0 + return sig + + +def get_signals(signals, obj): + """ + Returns signal objects attached to object. + It is presumed that "signals" is a list of strings. + """ + attrs = [] + for sig in signals: + if hasattr(obj, sig): + attrs.append(getattr(obj, sig)) + else: + raise Exception(f"Module {obj} does not have a signal {sig}") + return attrs + + +class BaseSeq(uvm_sequence): + async def run_items(self, items): + for item in items: + await self.start_item(item) + item.randomize() + await self.finish_item(item) + + +class BaseMonitor(uvm_component): + def __init__(self, name, parent): + super().__init__(name, parent) + + def build_phase(self): + self.ap_req = uvm_analysis_port("ap_req", self) + self.ap_rsp = uvm_analysis_port("ap_rsp", self) + self.bfm = None + + async def monitor_req(self): + while True: + datum = await self.bfm.req_monitor_q_get() + self.logger.debug(f"monitor_req: {datum}") + self.ap_req.write(datum) + + async def monitor_rsp(self): + while True: + datum = await self.bfm.rsp_monitor_q_get() + self.logger.debug(f"monitor_rsp: {datum}") + self.ap_rsp.write(datum) + + async def run_phase(self): + cocotb.start_soon(self.monitor_req()) + cocotb.start_soon(self.monitor_rsp()) From 91c6bf5b56a858da1a8fc8bc619850f68283ae54 Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Wed, 4 Oct 2023 18:19:09 +0200 Subject: [PATCH 06/11] Implement coordinator sequence, testbench --- .../block/lib_axi4_to_ahb/coordinator_seq.py | 114 ++++++ .../block/lib_axi4_to_ahb/testbench.py | 344 ++++++++++++++++++ 2 files changed, 458 insertions(+) create mode 100644 verification/block/lib_axi4_to_ahb/coordinator_seq.py create mode 100644 verification/block/lib_axi4_to_ahb/testbench.py diff --git a/verification/block/lib_axi4_to_ahb/coordinator_seq.py b/verification/block/lib_axi4_to_ahb/coordinator_seq.py new file mode 100644 index 00000000000..e0a0cf9a40b --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/coordinator_seq.py @@ -0,0 +1,114 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import random + +import cocotb +from ahb_lite_pkg import AHB_LITE_NOTIFICATION +from ahb_lite_seq import AHBLiteAcceptReadSeq, AHBLiteAcceptWriteSeq +from axi_r_seq import AXIReadTransactionRequestSeq +from axi_w_seq import ( + AXIWriteDataSeq, + AXIWriteResponseSeq, + AXIWriteTransactionRequestSeq, +) +from cocotb.triggers import RisingEdge +from pyuvm import ConfigDB, uvm_root, uvm_sequence + + +class CoordinatorSeq(uvm_sequence): + async def axi_write(self, axi_seqr, ahb_seqr): + axi_trq_seq = AXIWriteTransactionRequestSeq() + axi_w_seq = AXIWriteDataSeq() + axi_wresp_seq = AXIWriteResponseSeq() + + # Write Request + await axi_trq_seq.start(axi_seqr) + await self.delay(5) + + # Write Data + await axi_w_seq.start(axi_seqr) + + # Handle AHB Response + await self.ahb_response_handler(ahb_seqr=ahb_seqr, is_read=False) + + # Write Response + await axi_wresp_seq.start(axi_seqr) + + async def axi_read(self, axi_seqr, ahb_seqr): + axi_trq_seq = AXIReadTransactionRequestSeq() + + # Read Request + await axi_trq_seq.start(axi_seqr) + await self.delay(5) + + # Handle AHB Response + await self.ahb_response_handler(ahb_seqr=ahb_seqr, is_read=True) + await self.delay(5) + + async def delay(self, i): + for _ in range(i): + await RisingEdge(cocotb.top.clk) + + async def ahb_response_handler(self, ahb_seqr, is_read=True): + ahb_read_response_seq = AHBLiteAcceptReadSeq("ahb_accept_read") + ahb_write_response_seq = AHBLiteAcceptWriteSeq("ahb_accept_write") + + response = await ahb_seqr.seq_item_export.get_response() + expected_response = ( + AHB_LITE_NOTIFICATION.AHB_LITE_READ if is_read else AHB_LITE_NOTIFICATION.AHB_LITE_WRITE + ) + + if response == expected_response: + info_string = "CoordinatorSeq: AHB READ" if is_read else "CoordinatorSeq: AHB WRITE" + uvm_root().logger.info(info_string) + if is_read: + await ahb_read_response_seq.start(ahb_seqr) + else: + await ahb_write_response_seq.start(ahb_seqr) + else: + raise ValueError(f"Expected response: {expected_response}. Got: {response}.") + + +class TestWriteChannelSeq(CoordinatorSeq): + async def body(self): + ahb_seqr = ConfigDB().get(None, "", "ahb_seqr") + axi_seqr = ConfigDB().get(None, "", "axi_w_seqr") + + NUM_TRANSACTIONS_PER_TEST = ConfigDB().get(None, "", "NUM_TRANSACTIONS_PER_TEST") + for _ in range(NUM_TRANSACTIONS_PER_TEST): + await self.axi_write(axi_seqr=axi_seqr, ahb_seqr=ahb_seqr) + await self.delay(10) + + +class TestReadChannelSeq(CoordinatorSeq): + async def body(self): + ahb_seqr = ConfigDB().get(None, "", "ahb_seqr") + axi_seqr = ConfigDB().get(None, "", "axi_r_seqr") + + NUM_TRANSACTIONS_PER_TEST = ConfigDB().get(None, "", "NUM_TRANSACTIONS_PER_TEST") + for _ in range(NUM_TRANSACTIONS_PER_TEST): + await self.axi_read(axi_seqr=axi_seqr, ahb_seqr=ahb_seqr) + await self.delay(10) + + +class TestBothChannelsSeq(CoordinatorSeq): + async def body(self): + ahb_seqr = ConfigDB().get(None, "", "ahb_seqr") + axi_w_seqr = ConfigDB().get(None, "", "axi_w_seqr") + axi_r_seqr = ConfigDB().get(None, "", "axi_r_seqr") + NUM_TRANSACTIONS_PER_TEST = ConfigDB().get(None, "", "NUM_TRANSACTIONS_PER_TEST") + + test_all_q = [] + for _ in range(NUM_TRANSACTIONS_PER_TEST): + rw = random.randint(0, 1) + test_all_q += ["READ"] if rw else ["WRITE"] + uvm_root().logger.info(f"TestBothChannelsSeq: Test Sequence: {test_all_q}") + + for i in range(NUM_TRANSACTIONS_PER_TEST): + if test_all_q[i] == "READ": + await self.axi_read(axi_seqr=axi_r_seqr, ahb_seqr=ahb_seqr) + elif test_all_q[i] == "WRITE": + await self.axi_write(axi_seqr=axi_w_seqr, ahb_seqr=ahb_seqr) + else: + raise ValueError("Unexpected value in sequence. Should be READ or WRITE.") diff --git a/verification/block/lib_axi4_to_ahb/testbench.py b/verification/block/lib_axi4_to_ahb/testbench.py new file mode 100644 index 00000000000..c04052c6e24 --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/testbench.py @@ -0,0 +1,344 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import logging +import os +from decimal import Decimal + +import cocotb +from ahb_lite_agent import AHBLiteAgent +from ahb_lite_pkg import AHB_LITE_RESPONSE_CODES, AHB_LITE_TRANSFER_TYPE_ENCODING +from axi_pkg import AXI_READ_RESPONSE_CODES, AXI_WRITE_RESPONSE_CODES +from axi_r_agent import AXIReadChannelAgent +from axi_w_agent import AXIWriteChannelAgent +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, Timer +from pyuvm import ( + ConfigDB, + uvm_component, + uvm_env, + uvm_get_port, + uvm_report_object, + uvm_test, + uvm_tlm_analysis_fifo, +) + + +class Scoreboard(uvm_component): + def build_phase(self): + self.axi_w_req_fifo = uvm_tlm_analysis_fifo("axi_w_req_fifo", self) + self.axi_w_rsp_fifo = uvm_tlm_analysis_fifo("axi_w_rsp_fifo", self) + self.axi_w_req_get_port = uvm_get_port("axi_w_req_get_port", self) + self.axi_w_rsp_get_port = uvm_get_port("axi_w_rsp_get_port", self) + self.axi_w_req_export = self.axi_w_req_fifo.analysis_export + self.axi_w_rsp_export = self.axi_w_rsp_fifo.analysis_export + + self.axi_r_req_fifo = uvm_tlm_analysis_fifo("axi_r_req_fifo", self) + self.axi_r_rsp_fifo = uvm_tlm_analysis_fifo("axi_r_rsp_fifo", self) + self.axi_r_req_get_port = uvm_get_port("axi_r_req_get_port", self) + self.axi_r_rsp_get_port = uvm_get_port("axi_r_rsp_get_port", self) + self.axi_r_req_export = self.axi_r_req_fifo.analysis_export + self.axi_r_rsp_export = self.axi_r_rsp_fifo.analysis_export + + self.ahb_req_fifo = uvm_tlm_analysis_fifo("ahb_req_fifo", self) + self.ahb_rsp_fifo = uvm_tlm_analysis_fifo("ahb_rsp_fifo", self) + self.ahb_req_get_port = uvm_get_port("ahb_req_get_port", self) + self.ahb_rsp_get_port = uvm_get_port("ahb_rsp_get_port", self) + self.ahb_req_export = self.ahb_req_fifo.analysis_export + self.ahb_rsp_export = self.ahb_rsp_fifo.analysis_export + + def connect_phase(self): + self.axi_w_req_get_port.connect(self.axi_w_req_fifo.get_export) + self.axi_w_rsp_get_port.connect(self.axi_w_rsp_fifo.get_export) + + self.axi_r_req_get_port.connect(self.axi_r_req_fifo.get_export) + self.axi_r_rsp_get_port.connect(self.axi_r_rsp_fifo.get_export) + + self.ahb_req_get_port.connect(self.ahb_req_fifo.get_export) + self.ahb_rsp_get_port.connect(self.ahb_rsp_fifo.get_export) + + def check_phase(self): + passed = True + self.logger.info("Check Phase") + axi_w_transactions = self.check_axi_write() + axi_r_transactions = self.check_axi_read() + ahb_transactions = self.check_ahb() + + ahb_w_transactions = [] + ahb_r_transactions = [] + for transaction in ahb_transactions: + if transaction["TYPE"] == "WRITE": + ahb_w_transactions.append(transaction) + else: + ahb_r_transactions.append(transaction) + + assert len(axi_w_transactions) == len(ahb_w_transactions) + assert len(axi_r_transactions) == len(ahb_r_transactions) + + num_w_transactions = len(axi_w_transactions) + for id in range(num_w_transactions): + self.logger.info(f"AXI Wrote {axi_w_transactions[id]}") + self.logger.info(f"AHB Wrote {ahb_w_transactions[id]}") + assert axi_w_transactions[id] == ahb_w_transactions[id] + + num_r_transactions = len(axi_r_transactions) + for id in range(num_r_transactions): + self.logger.info(f"AXI Read {axi_r_transactions[id]}") + self.logger.info(f"AHB Read {ahb_r_transactions[id]}") + assert axi_r_transactions[id] == ahb_r_transactions[id] + + assert passed + + def check_axi_write(self): + axi_w_req_list = [] + while self.axi_w_req_get_port.can_get(): + _, item = self.axi_w_req_get_port.try_get() + axi_w_req_dict = {} + + awvalid = item[0] + awaddr = item[2] + wvalid = item[5] + wdata = item[6] + + if awvalid: + axi_w_req_dict["ADDRESS"] = awaddr + elif wvalid: + axi_w_req_dict["DATA"] = wdata + else: + raise ValueError("Unexpected item in monitor queue.") + + axi_w_req_list.append(axi_w_req_dict) + + # For each request there should be one data item + transaction_requests = axi_w_req_list[0::2] + transaction_data = axi_w_req_list[1::2] + assert len(transaction_requests) == len(transaction_data) + + num_transactions = len(transaction_data) + axi_w_transactions = [] + for id in range(num_transactions): + axi_w_transaction = {} + axi_w_transaction["TYPE"] = "WRITE" + axi_w_transaction["ADDRESS"] = transaction_requests[id]["ADDRESS"] + axi_w_transaction["DATA"] = transaction_data[id]["DATA"] + axi_w_transactions.append(axi_w_transaction) + + # Check if each transaction is confirmed + axi_w_rsp_list = [] + while self.axi_w_rsp_get_port.can_get(): + _, item = self.axi_w_rsp_get_port.try_get() + axi_w_rsp_dict = {} + + bvalid = item[2] + bresp = item[3] + + assert bvalid == 1 + assert bresp == AXI_WRITE_RESPONSE_CODES.OKAY + axi_w_rsp_dict["STATUS"] = "OKAY" + axi_w_rsp_list.append(axi_w_rsp_dict) + + assert len(transaction_requests) == len(axi_w_rsp_list) + + self.logger.debug(f"AXI WRITE TRANSACTIONS {axi_w_transactions}") + return axi_w_transactions + + def check_axi_read(self): + axi_r_req_list = [] + while self.axi_r_req_get_port.can_get(): + _, item = self.axi_r_req_get_port.try_get() + axi_r_req_dict = {} + + arvalid = item[0] + araddr = item[2] + + assert arvalid == 1 + axi_r_req_dict["TYPE"] = "READ" + axi_r_req_dict["ADDRESS"] = araddr + axi_r_req_list.append(axi_r_req_dict) + + self.logger.debug(f"AXI READ REQUESTS: {axi_r_req_list}") + + axi_r_rsp_list = [] + while self.axi_r_rsp_get_port.can_get(): + _, item = self.axi_r_rsp_get_port.try_get() + axi_r_rsp_dict = {} + + rvalid = item[1] + rdata = item[3] + rresp = item[4] + + assert rvalid == 1 + assert rresp == AXI_READ_RESPONSE_CODES.OKAY + + axi_r_rsp_dict["TYPE"] = "READ RESPONSE" + axi_r_rsp_dict["DATA"] = int(rdata) + axi_r_rsp_list.append(axi_r_rsp_dict) + + assert len(axi_r_req_list) == len(axi_r_rsp_list) + + axi_r_transactions = [] + num_transactions = len(axi_r_req_list) + for id in range(num_transactions): + axi_r_transaction = {} + axi_r_transaction["TYPE"] = "READ" + axi_r_transaction["ADDRESS"] = axi_r_req_list[id]["ADDRESS"] + axi_r_transaction["DATA"] = axi_r_rsp_list[id]["DATA"] + axi_r_transactions.append(axi_r_transaction) + + self.logger.debug(f"AXI READ TRANSACTIONS {axi_r_transactions}") + + return axi_r_transactions + + def check_ahb(self): + ahb_rsp_list = [] + is_even = True + ahb_rsp_dict = {} + while self.ahb_rsp_get_port.can_get(): + _, item = self.ahb_rsp_get_port.try_get() + + haddr = item[0] + htrans = item[5] + hwrite = item[6] + hwdata = item[7] + + if is_even: + assert htrans == AHB_LITE_TRANSFER_TYPE_ENCODING.NONSEQ + + if hwrite: + ahb_rsp_dict["TYPE"] = "WRITE" + ahb_rsp_dict["DATA"] = int(hwdata) + else: + ahb_rsp_dict["TYPE"] = "READ" + + ahb_rsp_dict["ADDRESS"] = int(haddr) + + if not is_even: + ahb_rsp_list.append(ahb_rsp_dict) + ahb_rsp_dict = {} + + is_even = not is_even + + self.logger.debug(f"ahb_rsp_list {ahb_rsp_list}") + + ahb_req_list = [] + is_even = True + ahb_req_dict = {} + while self.ahb_req_get_port.can_get(): + _, item = self.ahb_req_get_port.try_get() + + hrdata = item[0] + hready = item[1] + hresp = item[2] + + assert hready == 1 + assert hresp == AHB_LITE_RESPONSE_CODES.OKAY + + if not is_even: + ahb_req_dict["DATA"] = int(hrdata) + ahb_req_list.append(ahb_req_dict) + ahb_req_dict = {} + + is_even = not is_even + + self.logger.debug(f"ahb_req_list {ahb_req_list}") + + assert len(ahb_rsp_list) == len(ahb_req_list) + + ahb_transactions = [] + num_transactions = len(ahb_rsp_list) + for id in range(num_transactions): + ahb_transaction = {} + if ahb_rsp_list[id]["TYPE"] == "WRITE": + ahb_transaction = ahb_rsp_list[id] + ahb_transactions.append(ahb_transaction) + continue + else: + ahb_transaction["TYPE"] = "READ" + ahb_transaction["ADDRESS"] = ahb_rsp_list[id]["ADDRESS"] + ahb_transaction["DATA"] = ahb_req_list[id]["DATA"] + ahb_transactions.append(ahb_transaction) + + self.logger.debug(f"AHB Transactions {ahb_transactions}") + return ahb_transactions + + +class BaseEnvironment(uvm_env): + def build_phase(self): + # Config + ConfigDB().set(None, "*", "TEST_CLK_PERIOD", 1) + ConfigDB().set(None, "*", "AXI_DATA_WIDTH", 64) + ConfigDB().set(None, "*", "DUT_PRTY", cocotb.top.PRTY.value) + ConfigDB().set(None, "*", "DUT_TAG", cocotb.top.TAG.value) + ConfigDB().set(None, "*", "DUT_ID", cocotb.top.ID.value) + + ConfigDB().set(None, "*", "NUM_TRANSACTIONS_PER_TEST", 32) + + self.agent_axi_w = AXIWriteChannelAgent("axi_w_agent", self) + self.agent_axi_r = AXIReadChannelAgent("axi_r_agent", self) + self.agent_ahb = AHBLiteAgent("ahb_lite_agent", self) + + self.scoreboard = Scoreboard("scoreboard", self) + + def connect_phase(self): + self.agent_axi_w.monitor.ap_req.connect(self.scoreboard.axi_w_req_export) + self.agent_axi_w.monitor.ap_rsp.connect(self.scoreboard.axi_w_rsp_export) + + self.agent_axi_r.monitor.ap_req.connect(self.scoreboard.axi_r_req_export) + self.agent_axi_r.monitor.ap_rsp.connect(self.scoreboard.axi_r_rsp_export) + + self.agent_ahb.monitor.ap_req.connect(self.scoreboard.ahb_req_export) + self.agent_ahb.monitor.ap_rsp.connect(self.scoreboard.ahb_rsp_export) + + +class BaseTest(uvm_test): + """ """ + + def __init__(self, name, parent): + super().__init__(name, parent) + + # Synchronize pyuvm logging level with cocotb logging level. + level = logging.getLevelName(os.environ.get("COCOTB_LOG_LEVEL", "INFO")) + uvm_report_object.set_default_logging_level(level) + + def build_phase(self): + self.env = BaseEnvironment("env", self) + + def start_clock(self, name): + period = ConfigDB().get(None, "", "TEST_CLK_PERIOD") + sig = getattr(cocotb.top, name) + clock = Clock(sig, period, units="ns") + cocotb.start_soon(clock.start(start_high=False)) + + async def do_reset(self, signalName, timeLength="100e-9", isActiveHigh=True): + signal = getattr(cocotb.top, signalName) + signal.value = int(isActiveHigh) + self.config() + await Timer(Decimal(timeLength), units="sec") + signal.value = not int(isActiveHigh) + + def config(self): + cocotb.top.scan_mode.value = 0 + cocotb.top.bus_clk_en.value = 1 + cocotb.top.clk_override.value = 0 + cocotb.top.dec_tlu_force_halt.value = 0 + + async def run_phase(self): + self.raise_objection() + + # Start clocks + self.start_clock("clk") + self.start_clock("free_clk") + clk = getattr(cocotb.top, "clk") + + # Issue reset + resetLength = "10e-9" + await self.do_reset(signalName="rst_l", timeLength=resetLength, isActiveHigh=False) + + await ClockCycles(clk, 2) + await self.run() + await ClockCycles(clk, 10) + + self.drop_objection() + + async def run(self): + raise NotImplementedError() From b13116eb2b1f2518c9abc510f67f1dfbf03fbf03 Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Wed, 4 Oct 2023 18:19:27 +0200 Subject: [PATCH 07/11] Implement tests --- verification/block/lib_axi4_to_ahb/test_axi.py | 17 +++++++++++++++++ .../lib_axi4_to_ahb/test_axi_read_channel.py | 17 +++++++++++++++++ .../lib_axi4_to_ahb/test_axi_write_channel.py | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 verification/block/lib_axi4_to_ahb/test_axi.py create mode 100644 verification/block/lib_axi4_to_ahb/test_axi_read_channel.py create mode 100644 verification/block/lib_axi4_to_ahb/test_axi_write_channel.py diff --git a/verification/block/lib_axi4_to_ahb/test_axi.py b/verification/block/lib_axi4_to_ahb/test_axi.py new file mode 100644 index 00000000000..7845cdfb333 --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/test_axi.py @@ -0,0 +1,17 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import pyuvm +from coordinator_seq import TestBothChannelsSeq +from testbench import BaseTest + + +@pyuvm.test() +class TestAXI(BaseTest): + def end_of_elaboration_phase(self): + self.seq = TestBothChannelsSeq.create("stimulus") + + async def run(self): + self.raise_objection() + await self.seq.start() + self.drop_objection() diff --git a/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py b/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py new file mode 100644 index 00000000000..ddce44af826 --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py @@ -0,0 +1,17 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import pyuvm +from coordinator_seq import TestReadChannelSeq +from testbench import BaseTest + + +@pyuvm.test() +class TestAXIReadChannel(BaseTest): + def end_of_elaboration_phase(self): + self.seq = TestReadChannelSeq.create("stimulus") + + async def run(self): + self.raise_objection() + await self.seq.start() + self.drop_objection() diff --git a/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py b/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py new file mode 100644 index 00000000000..9519bae3b42 --- /dev/null +++ b/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py @@ -0,0 +1,17 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +import pyuvm +from coordinator_seq import TestWriteChannelSeq +from testbench import BaseTest + + +@pyuvm.test() +class TestAXIWriteChannel(BaseTest): + def end_of_elaboration_phase(self): + self.seq = TestWriteChannelSeq.create("stimulus") + + async def run(self): + self.raise_objection() + await self.seq.start() + self.drop_objection() From 96b18dd15665116765b32597c3f2141017b7fbdd Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Wed, 4 Oct 2023 18:38:42 +0200 Subject: [PATCH 08/11] Add notice about failing test --- .../block/lib_axi4_to_ahb/test_axi.py | 37 +++++++++++++++++++ .../lib_axi4_to_ahb/test_axi_read_channel.py | 3 ++ .../lib_axi4_to_ahb/test_axi_write_channel.py | 3 ++ 3 files changed, 43 insertions(+) diff --git a/verification/block/lib_axi4_to_ahb/test_axi.py b/verification/block/lib_axi4_to_ahb/test_axi.py index 7845cdfb333..d0f34ed4eb4 100644 --- a/verification/block/lib_axi4_to_ahb/test_axi.py +++ b/verification/block/lib_axi4_to_ahb/test_axi.py @@ -5,6 +5,43 @@ from coordinator_seq import TestBothChannelsSeq from testbench import BaseTest +# FIXME : This test is expected to fail. +# Reason : Handshake sequence is non-compliant with specification +# Faulty code : axi4_to_ahb.sv#L248-256 +# +# Issue #1 BVALID/BREADY Handshake +# Handshake is meant to occur on the Write Response Channel in order: +# * subordinate asserts BVALID +# * manager responds with BREADY +# Quote: "The Subordinate must not wait for the Manager to assert BREADY +# before asserting BVALID" +# Source: AMBA AXI Protocol Specification A3.5.1 Write transaction dependencies +# +# In RTL: +# +# assign axi_bvalid = slave_valid & slave_ready & slave_opc[3]; +# assign slave_ready = axi_bready & axi_rready; +# +# BVALID is calculated from BREADY and RREADY, which is wrong for 2 reasons: +# * BVALID should not depend on RREADY +# * BVALID should be asserted before BREADY. BREADY should depend on BVALID. +# +# Issue #2 RVALID/RREADY Handshake +# Handshake is meant to occur on the Read Response Channel in order: +# * subordinate asserts RVALID +# * manager responds with RREADY +# Quote: "The Subordinate must not wait for the Manager to assert RREADY +# before asserting RVALID" +# Source: AMBA AXI Protocol Specification A3.5.2 Read transaction dependencies +# +# In RTL: +# assign axi_rvalid = slave_valid & slave_ready & (slave_opc[3:2] == 2'b0); +# assign slave_ready = axi_bready & axi_rready; +# +# RVALID is calculated from BREADY and RREADY, which is wrong for 2 reasons: +# * RVALID should not depend on BREADY +# * RVALID should be asserted before RREADY. RREADY should depend on RVALID. + @pyuvm.test() class TestAXI(BaseTest): diff --git a/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py b/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py index ddce44af826..5fed93ec4d3 100644 --- a/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py +++ b/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py @@ -5,6 +5,9 @@ from coordinator_seq import TestReadChannelSeq from testbench import BaseTest +# FIXME : This test is expected to fail. +# See description in `test_axi.py` + @pyuvm.test() class TestAXIReadChannel(BaseTest): diff --git a/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py b/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py index 9519bae3b42..33c6cc8b123 100644 --- a/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py +++ b/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py @@ -5,6 +5,9 @@ from coordinator_seq import TestWriteChannelSeq from testbench import BaseTest +# FIXME : This test is expected to fail. +# See description in `test_axi.py` + @pyuvm.test() class TestAXIWriteChannel(BaseTest): From 763555b7f595b515815ed7e2b0c9132529c810db Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Wed, 4 Oct 2023 18:49:15 +0200 Subject: [PATCH 09/11] Add expected to fail --- verification/block/lib_axi4_to_ahb/test_axi.py | 3 ++- verification/block/lib_axi4_to_ahb/test_axi_read_channel.py | 3 ++- verification/block/lib_axi4_to_ahb/test_axi_write_channel.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/verification/block/lib_axi4_to_ahb/test_axi.py b/verification/block/lib_axi4_to_ahb/test_axi.py index d0f34ed4eb4..8a020e3b53a 100644 --- a/verification/block/lib_axi4_to_ahb/test_axi.py +++ b/verification/block/lib_axi4_to_ahb/test_axi.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import pyuvm +from cocotb.queue import QueueFull from coordinator_seq import TestBothChannelsSeq from testbench import BaseTest @@ -43,7 +44,7 @@ # * RVALID should be asserted before RREADY. RREADY should depend on RVALID. -@pyuvm.test() +@pyuvm.test(expect_error=(TimeoutError, QueueFull)) class TestAXI(BaseTest): def end_of_elaboration_phase(self): self.seq = TestBothChannelsSeq.create("stimulus") diff --git a/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py b/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py index 5fed93ec4d3..1caa2cbc010 100644 --- a/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py +++ b/verification/block/lib_axi4_to_ahb/test_axi_read_channel.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import pyuvm +from cocotb.queue import QueueFull from coordinator_seq import TestReadChannelSeq from testbench import BaseTest @@ -9,7 +10,7 @@ # See description in `test_axi.py` -@pyuvm.test() +@pyuvm.test(expect_error=QueueFull) class TestAXIReadChannel(BaseTest): def end_of_elaboration_phase(self): self.seq = TestReadChannelSeq.create("stimulus") diff --git a/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py b/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py index 33c6cc8b123..598f7afe9b5 100644 --- a/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py +++ b/verification/block/lib_axi4_to_ahb/test_axi_write_channel.py @@ -9,7 +9,7 @@ # See description in `test_axi.py` -@pyuvm.test() +@pyuvm.test(expect_error=TimeoutError) class TestAXIWriteChannel(BaseTest): def end_of_elaboration_phase(self): self.seq = TestWriteChannelSeq.create("stimulus") From 661127e7f8b945a96a4d45050897b6409192dcda Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Wed, 4 Oct 2023 18:55:05 +0200 Subject: [PATCH 10/11] Add tests to noxfile --- verification/block/noxfile.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/verification/block/noxfile.py b/verification/block/noxfile.py index 8727796d9e1..c1e24726cab 100644 --- a/verification/block/noxfile.py +++ b/verification/block/noxfile.py @@ -176,6 +176,7 @@ def dec_tl_verify(session, blockName, testName, coverage): def dma_verify(session, blockName, testName, coverage): verify_block(session, blockName, testName, coverage) + @nox.session(tags=["tests"]) @nox.parametrize("blockName", ["ifu_compress"]) @nox.parametrize("testName", ["test_compress"]) @@ -223,6 +224,7 @@ def exu_mul_verify(session, blockName, testName, coverage): def exu_div_verify(session, blockName, testName, coverage): verify_block(session, blockName, testName, coverage) + @nox.session(tags=["tests"]) @nox.parametrize("blockName", ["iccm"]) @nox.parametrize( @@ -235,6 +237,7 @@ def exu_div_verify(session, blockName, testName, coverage): def iccm_verify(session, blockName, testName, coverage): verify_block(session, blockName, testName, coverage) + @nox.session(tags=["tests"]) @nox.parametrize("blockName", ["dccm"]) @nox.parametrize( @@ -247,6 +250,22 @@ def iccm_verify(session, blockName, testName, coverage): def dccm_verify(session, blockName, testName, coverage): verify_block(session, blockName, testName, coverage) + +@nox.session(tags=["tests"]) +@nox.parametrize("blockName", ["lib_axi4_to_ahb"]) +@nox.parametrize( + "testName", + [ + "test_axi", + "test_axi_read_channel", + "test_axi_write_channel", + ], +) +@nox.parametrize("coverage", coverageTypes) +def lib_axi4_to_ahb_verify(session, blockName, testName, coverage): + verify_block(session, blockName, testName, coverage) + + @nox.session() def isort(session: nox.Session) -> None: """Options are defined in pyproject.toml file""" From a0d0af1d38218ad57791d84c1c14e5b36a474e38 Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Thu, 5 Oct 2023 13:06:41 +0200 Subject: [PATCH 11/11] LIB AXI2AHB: Add converter tests to CI --- .github/workflows/test-uarch.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-uarch.yml b/.github/workflows/test-uarch.yml index cab52935fbc..fcb77c189c6 100644 --- a/.github/workflows/test-uarch.yml +++ b/.github/workflows/test-uarch.yml @@ -23,6 +23,7 @@ jobs: - "block/exu_div" - "block/iccm" - "block/dccm" + - "block/lib_axi4_to_ahb" env: CCACHE_DIR: "/opt/verification/.cache/" VERILATOR_VERSION: v5.010