From e686aa6a1e093913569a50e6d64dae8a048099d2 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Sat, 13 Jun 2026 19:54:24 +0100 Subject: [PATCH 01/22] [dv,axi] Implement interfaces for the subset of AXI in use This is the first step towards a simple AXI agent. It will only have to handle the subset of AXI that is in use in the blocks in question (which work by translating to TLUL, so they aren't doing anything particularly exciting). In this commit, we're just defining interfaces for the five channels (which are AW, W, B, AR and R). Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 22 ++ hw/ip/dv/axi_agent/axi_read_data_if.sv | 212 ++++++++++++++++++ hw/ip/dv/axi_agent/axi_read_request_if.sv | 221 +++++++++++++++++++ hw/ip/dv/axi_agent/axi_write_data_if.sv | 152 +++++++++++++ hw/ip/dv/axi_agent/axi_write_request_if.sv | 226 ++++++++++++++++++++ hw/ip/dv/axi_agent/axi_write_response_if.sv | 154 +++++++++++++ 6 files changed, 987 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_agent.core create mode 100644 hw/ip/dv/axi_agent/axi_read_data_if.sv create mode 100644 hw/ip/dv/axi_agent/axi_read_request_if.sv create mode 100644 hw/ip/dv/axi_agent/axi_write_data_if.sv create mode 100644 hw/ip/dv/axi_agent/axi_write_request_if.sv create mode 100644 hw/ip/dv/axi_agent/axi_write_response_if.sv diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core new file mode 100644 index 000000000..84fd9ede3 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -0,0 +1,22 @@ +CAPI=2: +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +name: "lowrisc:dv:axi_agent:0.1" +description: "AXI agent" +filesets: + files_dv: + depend: + - lowrisc:dv:dv_utils + files: + - axi_read_data_if.sv + - axi_read_request_if.sv + - axi_write_data_if.sv + - axi_write_request_if.sv + - axi_write_response_if.sv + file_type: systemVerilogSource + +targets: + default: + filesets: + - files_dv diff --git a/hw/ip/dv/axi_agent/axi_read_data_if.sv b/hw/ip/dv/axi_agent/axi_read_data_if.sv new file mode 100644 index 000000000..ebef36486 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_read_data_if.sv @@ -0,0 +1,212 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// An interface to track an AXI read data channel + + `include "uvm_macros.svh" + +interface axi_read_data_if (input clk_i, input rst_ni); + import dv_utils_pkg::if_mode_e, dv_utils_pkg::Host, dv_utils_pkg::Device, dv_utils_pkg::Monitor; + import uvm_pkg::*; + + // The interface mode. + // + // - Host: An agent is driving the interface signals through mgr_cb, acting as a Manager. + // - Device: An agent is driving the interface signals through sub_cb, acting as a Subordinate. + // - Monitor: No agent is driving interface signals and the interface is purely passive. + if_mode_e if_mode = Monitor; + + // The ID_R_WIDTH property. Set this by calling set_id_r_width(). + int unsigned id_r_width = 32; + + // The DATA_WIDTH property. Set this by calling set_data_width(). + int unsigned data_width = 1024; + + // The USER_DATA_WIDTH property. Set this by calling set_user_data_width(). + int unsigned user_data_width = 512; + + // The RRESP_WIDTH property. Set this by calling set_rresp_width(). + int unsigned rresp_width = 3; + + // The USER_RESP_WIDTH property. Set this by calling set_user_resp_width(). + int unsigned user_resp_width = 16; + + // The core defined signals for the read data channel. + // + // This interface uses a max footprint approach. Signals of configurable width (like rid) might + // only use the lower bits. + // + // Not all possible read data channel signals are included. Not included are: + // + // - RPOISON (Poison is false) + // - RTRACE (Trace_Signals is false) + // - RLOOP (Loopback_Signals is false) + // - RBUSY (Busy_Support is false) + // - RIDUNQ (Unique_ID_Support is false) + // - RCHUNK* (Read_Data_Chunking is false) + // - RTAG (MTE_Support is false) + wire rvalid; + wire rready; + wire [31:0] rid; + wire [1023:0] rdata; + wire [2:0] rresp; + wire rlast; + wire [527:0] ruser; + + // A copy of rready, which is driven by mgr_cb (only used if if_mode == Host). The rready_driven + // signal is directly driven by the clocking block. The rready_internal signal tracks it, but is + // also cleared on reset. + logic rready_driven, rready_internal; + + // Copies of the signals that are driven by sub_cb (only used if if_mode == Device). The + // "*_driven" signals are directly driven by the clocking block. The "*_internal" signals track + // these, but take masks into account for signals with configurable length and are also cleared on + // reset. + logic rvalid_driven, rvalid_internal; + logic [31:0] rid_driven, rid_internal; + logic [1023:0] rdata_driven, rdata_internal; + logic [2:0] rresp_driven, rresp_internal; + logic rlast_driven, rlast_internal; + logic [527:0] ruser_driven, ruser_internal; + + // Masks used when converting some *_driven signals to *_internal + logic [31:0] rid_mask; + logic [1023:0] rdata_mask; + logic [2:0] rresp_mask; + logic [527:0] ruser_mask; + + assign rid_mask = (32'b1 << id_r_width) - 1; + assign rdata_mask = (1024'b1 << data_width) - 1; + assign rresp_mask = (3'b1 << rresp_width) - 1; + assign ruser_mask = (528'b1 << (user_data_width + user_resp_width)) - 1; + + clocking mon_cb @(posedge clk_i); + input rvalid; + input rready; + input rid; + input rdata; + input rresp; + input rlast; + input ruser; + endclocking + + clocking mgr_cb @(posedge clk_i); + input rvalid; + output rready = rready_driven; + input rid; + input rdata; + input rresp; + input rlast; + input ruser; + endclocking + + clocking sub_cb @(posedge clk_i); + output rvalid = rvalid_driven; + input rready; + output rid = rid_driven; + output rdata = rdata_driven; + output rresp = rresp_driven; + output rlast = rlast_driven; + output ruser = ruser_driven; + endclocking + + always_comb begin + if (!rst_ni) begin + rvalid_internal = '0; + rready_internal = '0; + rid_internal = '0; + rdata_internal = '0; + rresp_internal = '0; + rlast_internal = '0; + ruser_internal = '0; + end else begin + rvalid_internal = rvalid_driven; + rready_internal = rready_driven; + rid_internal = rid_driven & rid_mask; + rdata_internal = rdata_driven & rdata_mask; + rresp_internal = rresp_driven & rresp_mask; + rlast_internal = rlast_driven; + ruser_internal = ruser_driven & ruser_mask; + end + end + + assign rvalid = (if_mode == Device) ? rvalid_internal : 'z; + assign rready = (if_mode == Host) ? rready_internal : 'z; + assign rid = (if_mode == Device) ? rid_internal : 'z; + assign rdata = (if_mode == Device) ? rdata_internal : 'z; + assign rresp = (if_mode == Device) ? rresp_internal : 'z; + assign rlast = (if_mode == Device) ? rlast_internal : 'z; + assign ruser = (if_mode == Device) ? ruser_internal : 'z; + + // Set the ID_R_WIDTH property. The allowed range is 0..32 (see AMBA AXI protocol specification, + // issue J, section B1.3) + function void set_id_r_width(int unsigned width); + if (width > 32) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set ID_R_WIDTH to %0d: maximum in spec is 32.", width)) + width = 32; + end + + id_r_width = width; + endfunction + + // Set the DATA_WIDTH property. The allowed values are 8, 16, 32, 64, 128, 256, 512, 1024 (see + // AMBA AXI protocol specification, issue J, section B1.3) + function void set_data_width(int unsigned width); + if (! (width inside {8, 16, 32, 64, 128, 256, 512, 1024})) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set DATA_WIDTH to %0d: must be a value 2^k between 8 and 1024.", + width)) + width = 1024; + end + if (width < user_data_width * 2) begin + `uvm_error($sformatf("%m"), + $sformatf({"Cannot set DATA_WIDTH to %0d when USER_DATA_WIDTH is %0d: ", + "DATA_WIDTH must be at least 2*USER_DATA_WIDTH."}, + width, user_data_width)) + width = 1024; + end + + data_width = width; + endfunction + + // Set the USER_DATA_WIDTH property. The allowed range depends on DATA_WIDTH and is + // 0..DATA_WIDTH/2 (see AMBA AXI protocol specification, issue J, section B1.3) + function void set_user_data_width(int unsigned width); + if (width > data_width / 2) begin + `uvm_error($sformatf("%m"), + $sformatf({"Cannot set USER_DATA_WIDTH to %0d when DATA_WIDTH is %0d: ", + "USER_DATA_WIDTH must be in the range 0..DATA_WIDTH/2."}, + width, data_width)) + width = data_width / 2; + end + + user_data_width = width; + endfunction + + // Set the RRESP_WIDTH property. The allowed values are 0, 2, 3 (see AMBA AXI protocol + // specification, issue J, section B1.3) + function void set_rresp_width(int unsigned width); + if (! (width inside {0, 2, 3})) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set RRESP_WIDTH to %0d: must be 0, 2 or 3.", width)) + width = 3; + end + + rresp_width = width; + endfunction + + // Set the USER_RESP_WIDTH property. The allowed range is 0..16 (see AMBA AXI protocol + // specification, issue J, section B1.3) + function void set_user_resp_width(int unsigned width); + if (width > 16) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set USER_RESP_WIDTH to %0d: maximum in spec is 16.", width)) + width = 16; + end + + user_resp_width = width; + endfunction + +endinterface diff --git a/hw/ip/dv/axi_agent/axi_read_request_if.sv b/hw/ip/dv/axi_agent/axi_read_request_if.sv new file mode 100644 index 000000000..39f88920a --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_read_request_if.sv @@ -0,0 +1,221 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// An interface to track an AXI read request channel + + `include "uvm_macros.svh" + +interface axi_read_request_if (input clk_i, input rst_ni); + import dv_utils_pkg::if_mode_e, dv_utils_pkg::Host, dv_utils_pkg::Device, dv_utils_pkg::Monitor; + import uvm_pkg::*; + + // The interface mode. + // + // - Host: An agent is driving the interface signals through mgr_cb, acting as a Manager. + // - Device: An agent is driving the interface signals through sub_cb, acting as a Subordinate. + // - Monitor: No agent is driving interface signals and the interface is purely passive. + if_mode_e if_mode = Monitor; + + // The ID_R_WIDTH property. Set this by calling set_id_r_width(). + int unsigned id_r_width = 32; + + // The ADDR_WIDTH property. Set this by calling set_addr_width(). + int unsigned addr_width = 64; + + // The USER_REQ_WIDTH property. Set this by calling set_user_req_width(). + int unsigned user_req_width = 128; + + // The core defined signals for the read request channel. + // + // This interface uses a max footprint approach. Signals of configurable width (like arid) might + // only use the lower bits. + // + // Not all possible read request channel signals are included. Not included are: + // + // - ARNSE (RME_Support is false) + // - ARDOMAIN (Shareable_Transactions is false) + // - ARSNOOP (ARSNOOP_WIDTH is zero) + // - ARTRACE (Trace_Signals is false) + // - ARMMU* (Untranslated_Transactions is false) + // - ARPBHA (Untranslated_Transactions is false) + // - ARNSAID (NSAccess_Identifiers is false) + // - ARSUBSYSID (SUBSYSID_WIDTH is zero) + // - ARMPAM (MPAM_Support is false) + // - ARCHUNKEN (Read_Data_Chunking is false) + // - ARIDUNQ (Unique_ID_Support is false) + // - ARTAGOP (MTE_Support is false) + wire arvalid; + wire arready; + wire [31:0] arid; + wire [63:0] araddr; + wire [3:0] arregion; + wire [7:0] arlen; + wire [2:0] arsize; + wire [1:0] arburst; + wire arlock; + wire [3:0] arcache; + wire [2:0] arprot; + wire [3:0] arqos; + wire [127:0] aruser; + + // Copies of the signals that are driven by mgr_cb (only used if if_mode == Host). The "*_driven" + // signals are directly driven by the clocking block. The "*_internal" signals track these, but + // take masks into account for signals with configurable length and are also cleared on reset. + logic arvalid_driven, arvalid_internal; + logic [31:0] arid_driven, arid_internal; + logic [63:0] araddr_driven, araddr_internal; + logic [3:0] arregion_driven, arregion_internal; + logic [7:0] arlen_driven, arlen_internal; + logic [2:0] arsize_driven, arsize_internal; + logic [1:0] arburst_driven, arburst_internal; + logic arlock_driven, arlock_internal; + logic [3:0] arcache_driven, arcache_internal; + logic [2:0] arprot_driven, arprot_internal; + logic [3:0] arqos_driven, arqos_internal; + logic [127:0] aruser_driven, aruser_internal; + + // A copy of arready, which is driven by sub_cb (only used if if_mode == Device). The + // arready_driven signal is directly driven by the clocking block. The arready_internal signal + // tracks it, but is cleared on reset. + logic arready_driven, arready_internal; + + // Masks used when converting some *_driven signals to *_internal + logic [31:0] arid_mask; + logic [63:0] araddr_mask; + logic [127:0] aruser_mask; + + assign arid_mask = (32'b1 << id_r_width) - 1; + assign araddr_mask = (64'b1 << addr_width) - 1; + assign aruser_mask = (128'b1 << user_req_width) - 1; + + clocking mon_cb @(posedge clk_i); + input arvalid; + input arready; + input arid; + input araddr; + input arregion; + input arlen; + input arsize; + input arburst; + input arlock; + input arcache; + input arprot; + input arqos; + input aruser; + endclocking + + clocking mgr_cb @(posedge clk_i); + output arvalid = arvalid_driven; + input arready; + output arid = arid_driven; + output araddr = araddr_driven; + output arregion = arregion_driven; + output arlen = arlen_driven; + output arsize = arsize_driven; + output arburst = arburst_driven; + output arlock = arlock_driven; + output arcache = arcache_driven; + output arprot = arprot_driven; + output arqos = arqos_driven; + output aruser = aruser_driven; + endclocking + + clocking sub_cb @(posedge clk_i); + input arvalid; + output arready = arready_driven; + input arid; + input araddr; + input arregion; + input arlen; + input arsize; + input arburst; + input arlock; + input arcache; + input arprot; + input arqos; + input aruser; + endclocking + + always_comb begin + if (!rst_ni) begin + arvalid_internal = '0; + arready_internal = '0; + arid_internal = '0; + araddr_internal = '0; + arregion_internal = '0; + arlen_internal = '0; + arsize_internal = '0; + arburst_internal = '0; + arlock_internal = '0; + arcache_internal = '0; + arprot_internal = '0; + arqos_internal = '0; + aruser_internal = '0; + end else begin + arvalid_internal = arvalid_driven; + arready_internal = arready_driven; + arid_internal = arid_driven & arid_mask; + araddr_internal = araddr_driven & araddr_mask; + arregion_internal = arregion_driven; + arlen_internal = arlen_driven; + arsize_internal = arsize_driven; + arburst_internal = arburst_driven; + arlock_internal = arlock_driven; + arcache_internal = arcache_driven; + arprot_internal = arprot_driven; + arqos_internal = arqos_driven; + aruser_internal = aruser_driven & aruser_mask; + end + end + + assign arvalid = (if_mode == Host) ? arvalid_internal : 'z; + assign arready = (if_mode == Device) ? arready_internal : 'z; + assign arid = (if_mode == Host) ? arid_internal : 'z; + assign araddr = (if_mode == Host) ? araddr_internal : 'z; + assign arregion = (if_mode == Host) ? arregion_internal : 'z; + assign arlen = (if_mode == Host) ? arlen_internal : 'z; + assign arsize = (if_mode == Host) ? arsize_internal : 'z; + assign arburst = (if_mode == Host) ? arburst_internal : 'z; + assign arlock = (if_mode == Host) ? arlock_internal : 'z; + assign arcache = (if_mode == Host) ? arcache_internal : 'z; + assign arprot = (if_mode == Host) ? arprot_internal : 'z; + assign arqos = (if_mode == Host) ? arqos_internal : 'z; + assign aruser = (if_mode == Host) ? aruser_internal : 'z; + + // Set the ID_R_WIDTH property. The allowed range is 0..32 (see AMBA AXI protocol specification, + // issue J, section B1.3) + function void set_id_r_width(int unsigned width); + if (width > 32) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set ID_R_WIDTH to %0d: maximum in spec is 32.", width)) + width = 32; + end + + id_r_width = width; + endfunction + + // Set the ADDR_WIDTH property. The allowed range is 1..64 (see AMBA AXI protocol specification, + // issue J, section B1.3) + function void set_addr_width(int unsigned width); + if (width < 1 || width > 64) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set ADDR_WIDTH to %0d: range in spec is 1..64.", width)) + width = 64; + end + + addr_width = width; + endfunction + + // Set the USER_REQ_WIDTH property. The allowed range is 0..128 (see AMBA AXI protocol + // specification, issue J, section B1.3) + function void set_user_req_width(int unsigned width); + if (width > 128) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set USER_REQ_WIDTH to %0d: maximum in spec is 128.", width)) + width = 128; + end + + user_req_width = width; + endfunction +endinterface diff --git a/hw/ip/dv/axi_agent/axi_write_data_if.sv b/hw/ip/dv/axi_agent/axi_write_data_if.sv new file mode 100644 index 000000000..ac0987c35 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_write_data_if.sv @@ -0,0 +1,152 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// An interface to track an AXI write data channel + + `include "uvm_macros.svh" + +interface axi_write_data_if (input clk_i, input rst_ni); + import dv_utils_pkg::if_mode_e, dv_utils_pkg::Host, dv_utils_pkg::Device, dv_utils_pkg::Monitor; + import uvm_pkg::*; + + // The interface mode. + // + // - Host: An agent is driving the interface signals through mgr_cb, acting as a Manager. + // - Device: An agent is driving the interface signals through sub_cb, acting as a Subordinate. + // - Monitor: No agent is driving interface signals and the interface is purely passive. + if_mode_e if_mode = Monitor; + + // The DATA_WIDTH property. Set this by calling set_data_width(). + int unsigned data_width = 1024; + + // The USER_DATA_WIDTH property. Set this by calling set_user_data_width(). + int unsigned user_data_width = 512; + + // The core defined signals for the write request channel. + // + // This interface uses a max footprint approach. Signals of configurable width (like wdata) might + // only use the lower bits. + // + // Not all possible write data channel signals are included. Not included are: + // + // - WPOISON (Poison is false) + // - WTRACE (Trace_Signals is false) + // - WTAG* (MTE_Support is false) + wire wvalid; + wire wready; + wire [1023:0] wdata; + wire [127:0] wstrb; + wire wlast; + wire [511:0] wuser; + + // Copies of the signals that are driven by mgr_cb (only used if if_mode == Host). The "*_driven" + // signals are directly driven by the clocking block. The "*_internal" signals track these, but + // take masks into account for signals with configurable length and are also cleared on reset. + logic wvalid_driven, wvalid_internal; + logic [1023:0] wdata_driven, wdata_internal; + logic [127:0] wstrb_driven, wstrb_internal; + logic wlast_driven, wlast_internal; + logic [511:0] wuser_driven, wuser_internal; + + // A copy of wready, which is driven by sub_cb (only used if if_mode == Device). The + // wready_driven signal is directly driven by the clocking block. The wready_internal signal + // tracks it, but is also cleared on reset. + logic wready_driven, wready_internal; + + // Masks used when converting some *_driven signals to *_internal + logic [1023:0] wdata_mask; + logic [127:0] wstrb_mask; + logic [511:0] wuser_mask; + + assign wdata_mask = (1024'b1 << data_width) - 1; + assign wstrb_mask = (128'b1 << ((data_width + 7) / 8)) - 1; + assign wuser_mask = (512'b1 << user_data_width) - 1; + + clocking mon_cb @(posedge clk_i); + input wvalid; + input wready; + input wdata; + input wstrb; + input wlast; + input wuser; + endclocking + + clocking mgr_cb @(posedge clk_i); + output wvalid = wvalid_driven; + input wready; + output wdata = wdata_driven; + output wstrb = wstrb_driven; + output wlast = wlast_driven; + output wuser = wuser_driven; + endclocking + + clocking sub_cb @(posedge clk_i); + input wvalid; + output wready = wready_driven; + input wdata; + input wstrb; + input wlast; + input wuser; + endclocking + + always_comb begin + if (!rst_ni) begin + wvalid_internal = '0; + wready_internal = '0; + wdata_internal = '0; + wstrb_internal = '0; + wlast_internal = '0; + wuser_internal = '0; + end else begin + wvalid_internal = wvalid_driven; + wready_internal = wready_driven; + wdata_internal = wdata_driven & wdata_mask; + wstrb_internal = wstrb_driven & wstrb_mask; + wlast_internal = wlast_driven; + wuser_internal = wuser_driven & wuser_mask; + end + end + + assign wvalid = (if_mode == Host) ? wvalid_internal : 'z; + assign wready = (if_mode == Device) ? wready_internal : 'z; + assign wdata = (if_mode == Host) ? wdata_internal : 'z; + assign wstrb = (if_mode == Host) ? wstrb_internal : 'z; + assign wlast = (if_mode == Host) ? wlast_internal : 'z; + assign wuser = (if_mode == Host) ? wuser_internal : 'z; + + // Set the DATA_WIDTH property. The allowed values are 8, 16, 32, 64, 128, 256, 512, 1024 (see + // AMBA AXI protocol specification, issue J, section B1.3) + function void set_data_width(int unsigned width); + if (! (width inside {8, 16, 32, 64, 128, 256, 512, 1024})) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set DATA_WIDTH to %0d: must be a value 2^k between 8 and 1024.", + width)) + width = 1024; + end + if (width < user_data_width * 2) begin + `uvm_error($sformatf("%m"), + $sformatf({"Cannot set DATA_WIDTH to %0d when USER_DATA_WIDTH is %0d: ", + "DATA_WIDTH must be at least 2*USER_DATA_WIDTH."}, + width, user_data_width)) + width = 1024; + end + + data_width = width; + endfunction + + // Set the USER_DATA_WIDTH property. The allowed range depends on DATA_WIDTH and is + // 0..DATA_WIDTH/2 (see AMBA AXI protocol specification, issue J, section B1.3) + function void set_user_data_width(int unsigned width); + if (width > data_width / 2) begin + `uvm_error($sformatf("%m"), + $sformatf({"Cannot set USER_DATA_WIDTH to %0d when DATA_WIDTH is %0d: ", + "USER_DATA_WIDTH must be in the range 0..DATA_WIDTH/2."}, + width, data_width)) + width = data_width / 2; + end + + user_data_width = width; + endfunction + +endinterface diff --git a/hw/ip/dv/axi_agent/axi_write_request_if.sv b/hw/ip/dv/axi_agent/axi_write_request_if.sv new file mode 100644 index 000000000..c4b7a7d3b --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_write_request_if.sv @@ -0,0 +1,226 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// An interface to track an AXI write request channel + + `include "uvm_macros.svh" + +interface axi_write_request_if (input clk_i, input rst_ni); + import dv_utils_pkg::if_mode_e, dv_utils_pkg::Host, dv_utils_pkg::Device, dv_utils_pkg::Monitor; + import uvm_pkg::*; + + // The interface mode. + // + // - Host: An agent is driving the interface signals through mgr_cb, acting as a Manager. + // - Device: An agent is driving the interface signals through sub_cb, acting as a Subordinate. + // - Monitor: No agent is driving interface signals and the interface is purely passive. + if_mode_e if_mode = Monitor; + + // The ID_W_WIDTH property. Set this by calling set_id_w_width(). + int unsigned id_w_width = 32; + + // The ADDR_WIDTH property. Set this by calling set_addr_width(). + int unsigned addr_width = 64; + + // The USER_REQ_WIDTH property. Set this by calling set_user_req_width(). + int unsigned user_req_width = 128; + + // The core defined signals for the write request channel. + // + // This interface uses a max footprint approach. Signals of configurable width (like awid) might + // only use the lower bits. + // + // Not all possible write request channel signals are included. Not included are: + // + // - AWDOMAIN (Shareable_Transactions is false) + // - AWSNOOP (AWSNOOP_WIDTH is zero) + // - AWSTASHNID (Cache_Stash_Transactions is false) + // - AWSTASHNIDLEN (Cache_Stash_Transactions is false) + // - AWSTASHLPID (Cache_Stash_Transactions is false) + // - AWSTASHLPIDEN (Cache_Stash_Transactions is false) + // - AWTRACE (Trace_Signals is false) + // - AWLOOP (Loopback_Signals is false) + // - AWMMU* (Untranslated_Transactions is false) + // - AWPBHA (Untranslated_Transactions is false) + // - AWNSAID (NSAccess_Identifiers is false) + // - AWSUBSYSID (SUBSYSID_WIDTH is zero) + // - AWATOP (Atomic_Transactions is false) + // - AWMPAM (MPAM_Support is false) + // - AWIDUNQ (Unique_ID_Support is false) + // - AWCMO (AWCMO_WIDTH is zero) + // - AWTAGOP (MTE_Support is false) + wire awvalid; + wire awready; + wire [31:0] awid; + wire [63:0] awaddr; + wire [3:0] awregion; + wire [7:0] awlen; + wire [2:0] awsize; + wire [1:0] awburst; + wire awlock; + wire [3:0] awcache; + wire [2:0] awprot; + wire [3:0] awqos; + wire [127:0] awuser; + + // Copies of the signals that are driven by mgr_cb (only used if if_mode == Host). The "*_driven" + // signals are directly driven by the clocking block. The "*_internal" signals track these, but + // take masks into account for signals with configurable length and are also cleared on reset. + logic awvalid_driven, awvalid_internal; + logic [31:0] awid_driven, awid_internal; + logic [63:0] awaddr_driven, awaddr_internal; + logic [3:0] awregion_driven, awregion_internal; + logic [7:0] awlen_driven, awlen_internal; + logic [2:0] awsize_driven, awsize_internal; + logic [1:0] awburst_driven, awburst_internal; + logic awlock_driven, awlock_internal; + logic [3:0] awcache_driven, awcache_internal; + logic [2:0] awprot_driven, awprot_internal; + logic [3:0] awqos_driven, awqos_internal; + logic [127:0] awuser_driven, awuser_internal; + + // A copy of awready, which is driven by sub_cb (only used if if_mode == Device). The + // awready_driven signal is directly driven by the clocking block. The awready_internal signal + // tracks it, but is also cleared on reset. + logic awready_driven, awready_internal; + + // Masks used when converting some *_driven signals to *_internal + logic [31:0] awid_mask; + logic [63:0] awaddr_mask; + logic [127:0] awuser_mask; + + assign awid_mask = (32'b1 << id_w_width) - 1; + assign awaddr_mask = (64'b1 << addr_width) - 1; + assign awuser_mask = (128'b1 << user_req_width) - 1; + + clocking mon_cb @(posedge clk_i); + input awvalid; + input awready; + input awid; + input awaddr; + input awregion; + input awlen; + input awsize; + input awburst; + input awlock; + input awcache; + input awprot; + input awqos; + input awuser; + endclocking + + clocking mgr_cb @(posedge clk_i); + output awvalid = awvalid_driven; + input awready; + output awid = awid_driven; + output awaddr = awaddr_driven; + output awregion = awregion_driven; + output awlen = awlen_driven; + output awsize = awsize_driven; + output awburst = awburst_driven; + output awlock = awlock_driven; + output awcache = awcache_driven; + output awprot = awprot_driven; + output awqos = awqos_driven; + output awuser = awuser_driven; + endclocking + + clocking sub_cb @(posedge clk_i); + input awvalid; + output awready = awready_driven; + input awid; + input awaddr; + input awregion; + input awlen; + input awsize; + input awburst; + input awlock; + input awcache; + input awprot; + input awqos; + input awuser; + endclocking + + always_comb begin + if (!rst_ni) begin + awvalid_internal = '0; + awready_internal = '0; + awid_internal = '0; + awaddr_internal = '0; + awregion_internal = '0; + awlen_internal = '0; + awsize_internal = '0; + awburst_internal = '0; + awlock_internal = '0; + awcache_internal = '0; + awprot_internal = '0; + awqos_internal = '0; + awuser_internal = '0; + end else begin + awvalid_internal = awvalid_driven; + awready_internal = awready_driven; + awid_internal = awid_driven & awid_mask; + awaddr_internal = awaddr_driven & awaddr_mask; + awregion_internal = awregion_driven; + awlen_internal = awlen_driven; + awsize_internal = awsize_driven; + awburst_internal = awburst_driven; + awlock_internal = awlock_driven; + awcache_internal = awcache_driven; + awprot_internal = awprot_driven; + awqos_internal = awqos_driven; + awuser_internal = awuser_driven & awuser_mask; + end + end + + assign awvalid = (if_mode == Host) ? awvalid_internal : 'z; + assign awready = (if_mode == Device) ? awready_internal : 'z; + assign awid = (if_mode == Host) ? awid_internal : 'z; + assign awaddr = (if_mode == Host) ? awaddr_internal : 'z; + assign awregion = (if_mode == Host) ? awregion_internal : 'z; + assign awlen = (if_mode == Host) ? awlen_internal : 'z; + assign awsize = (if_mode == Host) ? awsize_internal : 'z; + assign awburst = (if_mode == Host) ? awburst_internal : 'z; + assign awlock = (if_mode == Host) ? awlock_internal : 'z; + assign awcache = (if_mode == Host) ? awcache_internal : 'z; + assign awprot = (if_mode == Host) ? awprot_internal : 'z; + assign awqos = (if_mode == Host) ? awqos_internal : 'z; + assign awuser = (if_mode == Host) ? awuser_internal : 'z; + + // Set the ID_W_WIDTH property. The allowed range is 0..32 (see AMBA AXI protocol specification, + // issue J, section B1.3) + function void set_id_w_width(int unsigned width); + if (width > 32) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set ID_W_WIDTH to %0d: maximum in spec is 32.", width)) + width = 32; + end + + id_w_width = width; + endfunction + + // Set the ADDR_WIDTH property. The allowed range is 1..64 (see AMBA AXI protocol specification, + // issue J, section B1.3) + function void set_addr_width(int unsigned width); + if (width < 1 || width > 64) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set ADDR_WIDTH to %0d: range in spec is 1..64.", width)) + width = 64; + end + + addr_width = width; + endfunction + + // Set the USER_REQ_WIDTH property. The allowed range is 0..128 (see AMBA AXI protocol + // specification, issue J, section B1.3) + function void set_user_req_width(int unsigned width); + if (width > 128) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set USER_REQ_WIDTH to %0d: maximum in spec is 128.", width)) + width = 128; + end + + user_req_width = width; + endfunction +endinterface diff --git a/hw/ip/dv/axi_agent/axi_write_response_if.sv b/hw/ip/dv/axi_agent/axi_write_response_if.sv new file mode 100644 index 000000000..8f4da70ce --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_write_response_if.sv @@ -0,0 +1,154 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// An interface to track an AXI write response channel + + `include "uvm_macros.svh" + +interface axi_write_response_if (input clk_i, input rst_ni); + import dv_utils_pkg::if_mode_e, dv_utils_pkg::Host, dv_utils_pkg::Device, dv_utils_pkg::Monitor; + import uvm_pkg::*; + + // The interface mode. + // + // - Host: An agent is driving the interface signals through mgr_cb, acting as a Manager. + // - Device: An agent is driving the interface signals through sub_cb, acting as a Subordinate. + // - Monitor: No agent is driving interface signals and the interface is purely passive. + if_mode_e if_mode = Monitor; + + // The ID_W_WIDTH property. Set this by calling set_id_w_width(). + int unsigned id_w_width = 32; + + // The BRESP_WIDTH property. Set this by calling set_bresp_width(). + int unsigned bresp_width = 3; + + // The USER_RESP_WIDTH property. Set this by calling set_user_resp_width(). + int unsigned user_resp_width = 16; + + // The core defined signals for the write response channel. + // + // This interface uses a max footprint approach. Signals of configurable width (like wdata) might + // only use the lower bits. + // + // Not all possible write response channel signals are included. Not included are: + // + // - BTRACE (Trace_Signals is false) + // - BLOOP (Loopback_Signals is false) + // - BBUSY (Busy_Support is false) + // - BIDUNQ (Unique_ID_Support is false) + // - BCOMP (Neither cache maintence for persistence nor memory tagging are supported) + // - BPERSIST (Persist_CMO is false) + // - BTAGMATCH (MTE_Support is false) + wire bvalid; + wire bready; + wire [31:0] bid; + wire [2:0] bresp; + wire [15:0] buser; + + // A copy of bready, which is driven by mgr_cb (only used if if_mode == Host). The bready_driven + // signal is directly driven by the clocking block. The bready_internal signal tracks it, but is + // also cleared on reset. + logic bready_driven, bready_internal; + + // Copies of the signals that are driven by sub_cb (only used if if_mode == Device). The + // "*_driven" signals are directly driven by the clocking block. The "*_internal" signals track + // these, but take masks into account for signals with configurable length and are also cleared on + // reset. + logic bvalid_driven, bvalid_internal; + logic [31:0] bid_driven, bid_internal; + logic [2:0] bresp_driven, bresp_internal; + logic [15:0] buser_driven, buser_internal; + + // Masks used when converting some *_driven signals to *_internal + logic [31:0] bid_mask; + logic [2:0] bresp_mask; + logic [15:0] buser_mask; + + assign bid_mask = (32'b1 << id_w_width) - 1; + assign bresp_mask = (3'b1 << bresp_width) - 1; + assign buser_mask = (16'b1 << user_resp_width) - 1; + + clocking mon_cb @(posedge clk_i); + input bvalid; + input bready; + input bid; + input bresp; + input buser; + endclocking + + clocking mgr_cb @(posedge clk_i); + input bvalid; + output bready = bready_driven; + input bid; + input bresp; + input buser; + endclocking + + clocking sub_cb @(posedge clk_i); + output bvalid = bvalid_driven; + input bready; + output bid = bid_driven; + output bresp = bresp_driven; + output buser = buser_driven; + endclocking + + always_comb begin + if (!rst_ni) begin + bvalid_internal = '0; + bready_internal = '0; + bid_internal = '0; + bresp_internal = '0; + buser_internal = '0; + end else begin + bvalid_internal = bvalid_driven; + bready_internal = bready_driven; + bid_internal = bid_driven & bid_mask; + bresp_internal = bresp_driven & bresp_mask; + buser_internal = buser_driven & buser_mask; + end + end + + assign bvalid = (if_mode == Device) ? bvalid_internal : 'z; + assign bready = (if_mode == Host) ? bready_internal : 'z; + assign bid = (if_mode == Device) ? bid_internal : 'z; + assign bresp = (if_mode == Device) ? bresp_internal : 'z; + assign buser = (if_mode == Device) ? buser_internal : 'z; + + // Set the ID_W_WIDTH property. The allowed range is 0..32 (see AMBA AXI protocol specification, + // issue J, section B1.3) + function void set_id_w_width(int unsigned width); + if (width > 32) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set ID_W_WIDTH to %0d: maximum in spec is 32.", width)) + width = 32; + end + + id_w_width = width; + endfunction + + // Set the BRESP_WIDTH property. The allowed values are 0, 2, 3 (see AMBA AXI protocol + // specification, issue J, section B1.3) + function void set_bresp_width(int unsigned width); + if (! (width inside {0, 2, 3})) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set BRESP_WIDTH to %0d: must be 0, 2 or 3.", width)) + width = 3; + end + + bresp_width = width; + endfunction + + // Set the USER_RESP_WIDTH property. The allowed range is 0..16 (see AMBA AXI protocol + // specification, issue J, section B1.3) + function void set_user_resp_width(int unsigned width); + if (width > 16) begin + `uvm_error($sformatf("%m"), + $sformatf("Cannot set USER_RESP_WIDTH to %0d: maximum in spec is 16.", width)) + width = 16; + end + + user_resp_width = width; + endfunction + +endinterface From 579723f5cb7e6eae355ff781d04c8a969d54df1a Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Sat, 13 Jun 2026 23:07:03 +0100 Subject: [PATCH 02/22] [dv,axi] Implement items for AXI read transactions The axi_read_request_item and axi_read_data_item classes are intended to be randomised (when a sequence wishes to send either read requests or data responses). The axi_read_item class can be used by a monitor that sees a read request and then one or more responses. Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 6 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 20 +++ hw/ip/dv/axi_agent/axi_read_data_item.svh | 98 +++++++++++ hw/ip/dv/axi_agent/axi_txn_request_item.svh | 170 ++++++++++++++++++++ 4 files changed, 294 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_agent_pkg.sv create mode 100644 hw/ip/dv/axi_agent/axi_read_data_item.svh create mode 100644 hw/ip/dv/axi_agent/axi_txn_request_item.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 84fd9ede3..98d909e57 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -14,6 +14,12 @@ filesets: - axi_write_data_if.sv - axi_write_request_if.sv - axi_write_response_if.sv + + - axi_agent_pkg.sv + + - axi_txn_request_item.svh: {is_include_file: true} + - axi_read_data_item.svh: {is_include_file: true} + file_type: systemVerilogSource targets: diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv new file mode 100644 index 000000000..4feae6e0d --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -0,0 +1,20 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +package axi_agent_pkg; + import uvm_pkg::*; + `include "uvm_macros.svh" + + // The possible encodings of the AxBURST signal + typedef enum bit [1:0] { + BurstFixed = 0, + BurstIncr = 1, + BurstWrap = 2 + } burst_e; + + `include "axi_agent_cfg.svh" + + `include "axi_txn_request_item.svh" + `include "axi_read_data_item.svh" +endpackage diff --git a/hw/ip/dv/axi_agent/axi_read_data_item.svh b/hw/ip/dv/axi_agent/axi_read_data_item.svh new file mode 100644 index 000000000..b1e2e1de4 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_read_data_item.svh @@ -0,0 +1,98 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item that represents the data sent by an AXI Subordinate to respond to a read + +class axi_read_data_item extends uvm_sequence_item; + `uvm_object_utils(axi_read_data_item) + + // The possible encoded values of the RRESP signal + typedef enum bit [2:0] { + RRespOkay = 0, + RRespExOkay = 1, + RRespSlverr = 2, + RRespDecErr = 3, + RRespPrefetched = 4, + RRespTransfault = 5, + RRespOkayDirty = 6 + } rresp_e; + + // Transaction identifier for the read channel + // + // This is sent over the RID signal, whose width is configurable, based on the ID_R_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand bit [31:0] m_id; + + // Read data + // + // This is sent over the RDATA signal, whose width is configurable, based on the DATA_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand bit [1023:0] m_data; + + // Read response + // + // This is sent over the RRESP signal, whose width is configurable, based on the RRESP_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand rresp_e m_resp; + + // Asserted on the last transfer in a burst + rand bit m_last; + + // Extra user-defined bits that extend m_data and m_resp. This is sent over the RUSER signal, + // whose width is configurable, based on the USER_DATA_WIDTH and USER_RESP_WIDTH properties. The + // representation in the item uses a max footprint approach but the driver will fail with an error + // if bits above the top of the signal are nonzero. + rand bit [527:0] m_user; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_read_data_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_read_data_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_field_int("m_id", m_id, 32, UVM_HEX); + printer.print_field("m_data", m_data, 1024, UVM_HEX); + printer.print_string("m_resp", m_resp.name()); + printer.print_field_int("m_last", m_last, 1, UVM_BIN); + printer.print_field("m_user", m_user, 528, UVM_HEX); +endfunction + +function void axi_read_data_item::do_copy(uvm_object rhs); + axi_read_data_item rhs_; + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + this.m_id = rhs_.m_id; + this.m_data = rhs_.m_data; + this.m_resp = rhs_.m_resp; + this.m_last = rhs_.m_last; + this.m_user = rhs_.m_user; +endfunction + +function bit axi_read_data_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_read_data_item rhs_; + + // These items are only equivalent if rhs is actually an axi_read_data_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_read_data_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field_int("m_id", m_id, rhs_.m_id, 32, UVM_HEX) & + comparer.compare_field("m_data", m_data, rhs_.m_data, 1024, UVM_HEX) & + comparer.compare_field_int("m_resp", m_resp, rhs_.m_resp, 3, UVM_HEX) & + comparer.compare_field_int("m_last", m_last, rhs_.m_last, 1, UVM_BIN) & + comparer.compare_field("m_user", m_user, rhs_.m_user, 528, UVM_HEX)); +endfunction diff --git a/hw/ip/dv/axi_agent/axi_txn_request_item.svh b/hw/ip/dv/axi_agent/axi_txn_request_item.svh new file mode 100644 index 000000000..255a239e8 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_txn_request_item.svh @@ -0,0 +1,170 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item that represents a message sent by an AXI Manager to request a transaction (using +// either the AR or AW channel) + +class axi_txn_request_item extends uvm_sequence_item; + `uvm_object_utils(axi_txn_request_item) + + // Transaction identifier for the read channel + // + // This is sent over the AxID signal, whose width is configurable, based on the ID_x_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand bit [31:0] m_id; + + // Transaction address + // + // This is sent over the AxADDR signal, whose width is configurable, based on the ADDR_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand bit [63:0] m_addr; + + // Region identifier + rand bit [3:0] m_region; + + // Number of data transfers in the transaction + rand bit [7:0] m_len; + + // Maximum number of bytes in each data transfer (encoded as log2(byte_size)) + rand bit [2:0] m_size; + + // Burst attribute (FIXED, INCR or WRAP) + rand burst_e m_burst; + + // Request exclusive access + rand bit m_lock; + + // Memory attributes (applying to caches in the system) + rand bit [3:0] m_cache; + + // Memory access attributes + rand bit [2:0] m_prot; + + // Traffic stream QoS identifier + rand bit [3:0] m_qos; + + // Extra user bits + // + // This is sent over the ARUSER signal, whose width is configurable, based on the USER_REQ_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand bit [127:0] m_user; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); + + // If m_burst is BurstFixed, the AXI spec allows up to 16 transfers (so m_len <= 15). If it is + // BurstWrap, the AXI spec requires the number of transfers to be 2, 4, 8 or 16. + // + // This constraint comes from AMBA AXI protocol specification, issue J, section A4.1.2. + extern constraint burst_length_c; + + // A transaction may not cross a 4kb boundary + // + // This constraint comes from AMBA AXI protocol specification, issue J, section A4.1.2. + extern constraint no_4kb_boundary_crossing_c; + + // If m_burst is BurstWrap, m_addr must be aligned to the size of each transfer. + // + // This constraint comes from AMBA AXI protocol specification, issue J, section A4.1.4. + extern constraint wrap_alignment_c; +endclass + +function axi_txn_request_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_txn_request_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_field_int("m_id", m_id, 32, UVM_HEX); + printer.print_field("m_addr", m_addr, 64, UVM_HEX); + printer.print_field_int("m_region", m_region, 4, UVM_HEX); + printer.print_field_int("m_len", m_len, 8, UVM_DEC); + printer.print_field_int("m_size", m_size, 3, UVM_DEC); + printer.print_string("m_burst", m_burst.name()); + printer.print_field_int("m_lock", m_lock, 1, UVM_BIN); + printer.print_field_int("m_cache", m_cache, 4, UVM_BIN); + printer.print_field_int("m_prot", m_prot, 3, UVM_BIN); + printer.print_field_int("m_qos", m_qos, 4, UVM_HEX); + printer.print_field("m_user", m_user, 128, UVM_HEX); +endfunction + +function void axi_txn_request_item::do_copy(uvm_object rhs); + axi_txn_request_item rhs_; + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + this.m_id = rhs_.m_id; + this.m_addr = rhs_.m_addr; + this.m_region = rhs_.m_region; + this.m_len = rhs_.m_len; + this.m_size = rhs_.m_size; + this.m_burst = rhs_.m_burst; + this.m_lock = rhs_.m_lock; + this.m_cache = rhs_.m_cache; + this.m_prot = rhs_.m_prot; + this.m_qos = rhs_.m_qos; + this.m_user = rhs_.m_user; +endfunction + +function bit axi_txn_request_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_txn_request_item rhs_; + + // These items are only equivalent if rhs is actually an axi_txn_request_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_txn_request_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field_int("m_id", m_id, rhs_.m_id, 32, UVM_HEX) & + comparer.compare_field("m_addr", m_addr, rhs_.m_addr, 64, UVM_HEX) & + comparer.compare_field_int("m_region", m_region, rhs_.m_region, 4, UVM_HEX) & + comparer.compare_field_int("m_len", m_len, rhs_.m_len, 8, UVM_DEC) & + comparer.compare_field_int("m_size", m_size, rhs_.m_size, 3, UVM_DEC) & + comparer.compare_field_int("m_burst", m_burst, rhs_.m_burst, 2, UVM_BIN) & + comparer.compare_field_int("m_lock", m_lock, rhs_.m_lock, 1, UVM_BIN) & + comparer.compare_field_int("m_cache", m_cache, rhs_.m_cache, 4, UVM_BIN) & + comparer.compare_field_int("m_prot", m_prot, rhs_.m_prot, 3, UVM_BIN) & + comparer.compare_field_int("m_qos", m_qos, rhs_.m_qos, 4, UVM_HEX) & + comparer.compare_field("m_user", m_user, rhs_.m_user, 128, UVM_HEX)); +endfunction + +constraint axi_txn_request_item::burst_length_c { + (m_burst == BurstFixed) -> (m_len <= 15); + (m_burst == BurstWrap) -> (m_len inside {1, 3, 7, 15}); +} + +constraint axi_txn_request_item::no_4kb_boundary_crossing_c { + // An incrementing burst steps forward for each of the (m_len + 1) transfers. + // + // The first such transfer starts at Aligned_Addr, which is defined in A4.1.6 of the specification + // to be m_addr after rounding down to a multiple of 1 << m_size. Shifting right by m_size gives + // the address in those units, rounding down. Add m_len + 1 to get the first address after the + // burst, in the same units. Finally, shift left again by m_size and subtract one to get the + // address of the final byte touched by the transfer. + // + // To check that this doesn't cross a 4kb boundary, compare that value with m_addr, after shifting + // right by 12. + (m_burst == BurstIncr) -> + (((((m_addr >> m_size) + m_len + 1) << m_size) - 1) >> 12) == (m_addr >> 12); + + // There is no constraint needed for fixed bursts. Such a transaction will access the same set of + // 1 << m_size bytes repeatedly, starting at Aligned_Addr (as defined above). Because m_size is + // less than 12, the transfers will never cross a 4kb boundary. + + // There is also no constraint needed for wrapping bursts. The wrap region always has a length + // that is a power of two and less than 2048. As such, it cannot cross 4kb boundaries. +} + +constraint axi_txn_request_item::wrap_alignment_c { + // The size of each transfer is (1 << m_size). m_addr is divisible by that value if its bottom + // m_size bits are zero. + (m_burst == BurstWrap) -> (m_addr & ((1 << m_size) - 1)) == '0; +} From b59c688dff312df41fee69574d88d9fc08a80f02 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Sun, 14 Jun 2026 15:30:34 +0100 Subject: [PATCH 03/22] [dv,axi] Implement items for AXI write transactions --- hw/ip/dv/axi_agent/axi_agent.core | 2 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 3 + hw/ip/dv/axi_agent/axi_txn_request_item.svh | 8 +- hw/ip/dv/axi_agent/axi_write_data_item.svh | 78 +++++++++++++++++ .../dv/axi_agent/axi_write_response_item.svh | 83 +++++++++++++++++++ 5 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 hw/ip/dv/axi_agent/axi_write_data_item.svh create mode 100644 hw/ip/dv/axi_agent/axi_write_response_item.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 98d909e57..a3497c703 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -19,6 +19,8 @@ filesets: - axi_txn_request_item.svh: {is_include_file: true} - axi_read_data_item.svh: {is_include_file: true} + - axi_write_data_item.svh: {is_include_file: true} + - axi_write_response_item.svh: {is_include_file: true} file_type: systemVerilogSource diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 4feae6e0d..3aaae1e25 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -17,4 +17,7 @@ package axi_agent_pkg; `include "axi_txn_request_item.svh" `include "axi_read_data_item.svh" + + `include "axi_write_data_item.svh" + `include "axi_write_response_item.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_txn_request_item.svh b/hw/ip/dv/axi_agent/axi_txn_request_item.svh index 255a239e8..78ef8c3dc 100644 --- a/hw/ip/dv/axi_agent/axi_txn_request_item.svh +++ b/hw/ip/dv/axi_agent/axi_txn_request_item.svh @@ -8,7 +8,7 @@ class axi_txn_request_item extends uvm_sequence_item; `uvm_object_utils(axi_txn_request_item) - // Transaction identifier for the read channel + // Transaction identifier for the read or write channel // // This is sent over the AxID signal, whose width is configurable, based on the ID_x_WIDTH // property. The representation in the item uses a max footprint approach but the driver will fail @@ -48,9 +48,9 @@ class axi_txn_request_item extends uvm_sequence_item; // Extra user bits // - // This is sent over the ARUSER signal, whose width is configurable, based on the USER_REQ_WIDTH - // property. The representation in the item uses a max footprint approach but the driver will fail - // with an error if bits above the top of the signal are nonzero. + // This is sent over the ARUSER or AWUSER signal, whose widths are configurable, based on the + // USER_REQ_WIDTH property. The representation in the item uses a max footprint approach but the + // driver will fail with an error if bits above the top of the signal are nonzero. rand bit [127:0] m_user; extern function new(string name = ""); diff --git a/hw/ip/dv/axi_agent/axi_write_data_item.svh b/hw/ip/dv/axi_agent/axi_write_data_item.svh new file mode 100644 index 000000000..3733311c7 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_write_data_item.svh @@ -0,0 +1,78 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item that represents the data sent by an AXI Manager to provide write data for a write +// transaction. + +class axi_write_data_item extends uvm_sequence_item; + `uvm_object_utils(axi_write_data_item) + + // Write data + // + // This is sent over the WDATA signal, whose width is configurable, based on the DATA_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand bit [1023:0] m_data; + + // Write data strobes + // + // This is sent over the WSTRB signal, whose width is configurable, based on the DATA_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand bit [127:0] m_strb; + + // Asserted on the last transfer in a burst + rand bit m_last; + + // Extra user-defined bits that extend m_data. This is sent over the WUSER signal, whose width is + // configurable, based on the USER_DATA_WIDTH property. The representation in the item uses a max + // footprint approach but the driver will fail with an error if bits above the top of the signal + // are nonzero. + rand bit [511:0] m_user; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_write_data_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_write_data_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_field("m_data", m_data, 1024, UVM_HEX); + printer.print_field("m_strb", m_strb, 128, UVM_HEX); + printer.print_field_int("m_last", m_last, 1, UVM_BIN); + printer.print_field("m_user", m_user, 512, UVM_HEX); +endfunction + +function void axi_write_data_item::do_copy(uvm_object rhs); + axi_write_data_item rhs_; + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + this.m_data = rhs_.m_data; + this.m_strb = rhs_.m_strb; + this.m_last = rhs_.m_last; + this.m_user = rhs_.m_user; +endfunction + +function bit axi_write_data_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_write_data_item rhs_; + + // These items are only equivalent if rhs is actually an axi_write_data_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_write_data_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field("m_data", m_data, rhs_.m_data, 1024, UVM_HEX) & + comparer.compare_field("m_strb", m_strb, rhs_.m_strb, 128, UVM_HEX) & + comparer.compare_field_int("m_last", m_last, rhs_.m_last, 1, UVM_BIN) & + comparer.compare_field("m_user", m_user, rhs_.m_user, 512, UVM_HEX)); +endfunction diff --git a/hw/ip/dv/axi_agent/axi_write_response_item.svh b/hw/ip/dv/axi_agent/axi_write_response_item.svh new file mode 100644 index 000000000..dfd20d03f --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_write_response_item.svh @@ -0,0 +1,83 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item that represents the data sent by an AXI Subordinate to respond to a write + +class axi_write_response_item extends uvm_sequence_item; + `uvm_object_utils(axi_write_response_item) + + // The possible encoded values of the BRESP signal + typedef enum bit [2:0] { + BRespOkay = 0, + BRespExOkay = 1, + BRespSlverr = 2, + BRespDecErr = 3, + BRespDefer = 4, + BRespTransfault = 5, + // (Note the gap at index 6: there is a reserved value here, unlike RRESP, which has it at 7) + BRespUnsupported = 7 + } bresp_e; + + // Transaction identifier for the write channels + // + // This is sent over the BID signal, whose width is configurable, based on the ID_W_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand bit [31:0] m_id; + + // Write response + // + // This is sent over the BRESP signal, whose width is configurable, based on the BRESP_WIDTH + // property. The representation in the item uses a max footprint approach but the driver will fail + // with an error if bits above the top of the signal are nonzero. + rand bresp_e m_resp; + + // Extra user-defined bits that extend m_resp. This is sent over the BUSER signal, whose width is + // configurable, based on the USER_RESP_WIDTH property. The representation in the item uses a max + // footprint approach but the driver will fail with an error if bits above the top of the signal + // are nonzero. + rand bit [15:0] m_user; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_write_response_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_write_response_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_field_int("m_id", m_id, 32, UVM_HEX); + printer.print_string("m_resp", m_resp.name()); + printer.print_field_int("m_user", m_user, 16, UVM_HEX); +endfunction + +function void axi_write_response_item::do_copy(uvm_object rhs); + axi_write_response_item rhs_; + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + this.m_id = rhs_.m_id; + this.m_resp = rhs_.m_resp; + this.m_user = rhs_.m_user; +endfunction + +function bit axi_write_response_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_write_response_item rhs_; + + // These items are only equivalent if rhs is actually an axi_write_response_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_write_response_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field_int("m_id", m_id, rhs_.m_id, 32, UVM_HEX) & + comparer.compare_field_int("m_resp", m_resp, rhs_.m_resp, 3, UVM_BIN) & + comparer.compare_field_int("m_user", m_user, rhs_.m_user, 16, UVM_HEX)); +endfunction From 76a54344a4f2d46947f5d9d4050a4af22c384117 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Mon, 15 Jun 2026 10:39:40 +0100 Subject: [PATCH 04/22] [dv,axi] Define an item type to control how we accept responses When the agent is representing an AXI Manager, this will be useful for the B and R channels. Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 1 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 2 + .../dv/axi_agent/axi_response_accept_item.svh | 76 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_response_accept_item.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index a3497c703..49377f20f 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -18,6 +18,7 @@ filesets: - axi_agent_pkg.sv - axi_txn_request_item.svh: {is_include_file: true} + - axi_response_accept_item.svh: {is_include_file: true} - axi_read_data_item.svh: {is_include_file: true} - axi_write_data_item.svh: {is_include_file: true} - axi_write_response_item.svh: {is_include_file: true} diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 3aaae1e25..15d901c5e 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -20,4 +20,6 @@ package axi_agent_pkg; `include "axi_write_data_item.svh" `include "axi_write_response_item.svh" + + `include "axi_response_accept_item.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_response_accept_item.svh b/hw/ip/dv/axi_agent/axi_response_accept_item.svh new file mode 100644 index 000000000..db57cffde --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_response_accept_item.svh @@ -0,0 +1,76 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item that represents the action a driver should take when accepting a response (on the +// B or R channels, for example). + +class axi_response_accept_item extends uvm_sequence_item; + `uvm_object_utils(axi_response_accept_item) + + // Should the driver assert READY some of the time when VALID has not yet been asserted? This + // field gives the percentage of the time that READY should be asserted. To only assert READY + // after VALID, set this to zero. + rand int unsigned m_ready_without_valid_pct; + + // What is the time that the driver should wait after seeing VALID before asserting READY? (This + // might have no effect if m_ready_without_valid_pct is nonzero and we happen to have asserted + // READY on the cycle that VALID is asserted). + rand int unsigned m_valid_to_ready_delay; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); + + // Constrain m_ready_without_valid_pct to be a valid percentage + extern constraint ready_without_valid_c; + + // Constrain m_valid_to_ready_delay to be reasonably short (to avoid wasting time in simulations). + // This is a soft constraint, so it's easy to override. + extern constraint valid_to_ready_c; +endclass + +function axi_response_accept_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_response_accept_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_field_int("m_ready_without_valid_pct", m_ready_without_valid_pct, 32, UVM_DEC); + printer.print_field_int("m_valid_to_ready_delay", m_valid_to_ready_delay, 32, UVM_DEC); +endfunction + +function void axi_response_accept_item::do_copy(uvm_object rhs); + axi_response_accept_item rhs_; + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + this.m_ready_without_valid_pct = rhs_.m_ready_without_valid_pct; + this.m_valid_to_ready_delay = rhs_.m_valid_to_ready_delay; +endfunction + +function bit axi_response_accept_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_response_accept_item rhs_; + + // These items are only equivalent if rhs is actually an axi_response_accept_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_response_accept_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field_int("m_ready_without_valid_pct", m_ready_without_valid_pct, + rhs_.m_ready_without_valid_pct, 32, UVM_DEC) & + comparer.compare_field_int("m_valid_to_ready_delay", m_valid_to_ready_delay, + rhs_.m_valid_to_ready_delay, 32, UVM_DEC)); +endfunction + +constraint axi_response_accept_item::ready_without_valid_c { + m_ready_without_valid_pct <= 100; +} + +constraint axi_response_accept_item::valid_to_ready_c { + soft m_valid_to_ready_delay <= 5; +} From 0ab815d589157606a330b93bf9fd373dd7ea52a7 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Sun, 14 Jun 2026 17:04:37 +0100 Subject: [PATCH 05/22] [dv,axi] Implement drivers for the Manager side of write channels These drive write requests (AW) and write data (W) and also drive the write response channel (B). Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 5 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 6 + .../axi_agent/axi_mgr_write_data_driver.svh | 169 ++++++++++++++++ .../axi_mgr_write_request_driver.svh | 182 ++++++++++++++++++ .../axi_mgr_write_response_driver.svh | 157 +++++++++++++++ hw/ip/dv/axi_agent/axi_status_item.svh | 54 ++++++ 6 files changed, 573 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh create mode 100644 hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh create mode 100644 hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh create mode 100644 hw/ip/dv/axi_agent/axi_status_item.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 49377f20f..dfd6de96f 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -17,12 +17,17 @@ filesets: - axi_agent_pkg.sv + - axi_status_item.svh: {is_include_file: true} - axi_txn_request_item.svh: {is_include_file: true} - axi_response_accept_item.svh: {is_include_file: true} - axi_read_data_item.svh: {is_include_file: true} - axi_write_data_item.svh: {is_include_file: true} - axi_write_response_item.svh: {is_include_file: true} + - axi_mgr_write_request_driver.svh: {is_include_file: true} + - axi_mgr_write_data_driver.svh: {is_include_file: true} + - axi_mgr_write_response_driver.svh: {is_include_file: true} + file_type: systemVerilogSource targets: diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 15d901c5e..85bf7c943 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -22,4 +22,10 @@ package axi_agent_pkg; `include "axi_write_response_item.svh" `include "axi_response_accept_item.svh" + + `include "axi_status_item.svh" + + `include "axi_mgr_write_request_driver.svh" + `include "axi_mgr_write_data_driver.svh" + `include "axi_mgr_write_response_driver.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh new file mode 100644 index 000000000..aef86286f --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh @@ -0,0 +1,169 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A driver for axi_write_data_if, used when the testbench is acting as an AXI Manager that is +// sending write data transfers. + +class axi_mgr_write_data_driver extends uvm_driver#(axi_write_data_item, axi_status_item); + `uvm_component_utils(axi_mgr_write_data_driver) + + local virtual axi_write_data_if m_vif; + + // True if the interface is currently in reset. Maintained by monitor_reset(). + // + // At the very start of the simulation, rst_ni might be 'x or 'z. This isn't considered a "proper" + // reset: in_reset can only be asserted by seeing rst_ni === 0 (and then cleared by seeing it + // become 1). + local bit m_in_reset; + + extern function new(string name, uvm_component parent); + extern virtual task run_phase(uvm_phase phase); + + // Set m_vif. This must be called before run_phase. + extern function void set_vif(virtual axi_write_data_if vif); + + // Run forever, consuming and driving items from seq_item_port + extern local task get_and_drive(); + + // Run forever, tracking rst_ni and maintaining an in_reset class variable. Calls clear_data when + // reset becomes asserted. + extern local task monitor_reset(); + + // A task that is called at the start of a reset and also at the end of driving an item. + extern local task clear_data(); + + // A task that drives the axi_write_data_item in the req class variable + // + // This returns when the item is driven, setting item_sent=1, or returns early if a reset is + // asserted, in which case it sets item_sent=0. + extern local task drive_req(output bit item_sent); + + // Set data values in the interface based on the req item. This task runs in zero time (but uses + // clocking block drives, so cannot be a function). + // + // This task also checks sizes against the DATA_WIDTH and USER_DATA_WIDTH properties that are + // configured in the interface. + extern local task set_data_from_req(); +endclass + +function axi_mgr_write_data_driver::new(string name, uvm_component parent); + super.new(name, parent); +endfunction + +function void axi_mgr_write_data_driver::set_vif(virtual axi_write_data_if vif); + if (m_vif != null) begin + `uvm_fatal(get_full_name(), "Cannot call set_vif: there is already an interface.") + return; + end + + if (vif.if_mode != dv_utils_pkg::Host) begin + `uvm_fatal(get_full_name(), + $sformatf("Cannot drive this interface: it has mode %0s, not Host.", + vif.if_mode.name())) + return; + end + + m_vif = vif; +endfunction + +task axi_mgr_write_data_driver::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") + return; + end + + // Start by clearing the data (and, importantly, setting m_vif.mgr_cb.wvalid = 0). From now, + // wvalid will be zero unless $isunknown on all the data fields is false. + clear_data(); + + fork + get_and_drive(); + monitor_reset(); + join +endtask + +task axi_mgr_write_data_driver::get_and_drive(); + axi_status_item status_item; + forever begin + seq_item_port.get_next_item(req); + status_item = axi_status_item::type_id::create("status_item"); + status_item.set_id_info(req); + drive_req(status_item.m_sending_complete); + seq_item_port.item_done(status_item); + end +endtask + +task axi_mgr_write_data_driver::monitor_reset(); + wait(!$isunknown(m_vif.rst_ni)); + m_in_reset = !m_vif.rst_ni; + forever begin + wait (m_vif.rst_ni); + m_in_reset = 0; + wait (!m_vif.rst_ni); + m_in_reset = 1; + clear_data(); + end +endtask + +task axi_mgr_write_data_driver::clear_data(); + m_vif.mgr_cb.wvalid <= 1'b0; + m_vif.mgr_cb.wdata <= 'x; + m_vif.mgr_cb.wstrb <= 'x; + m_vif.mgr_cb.wlast <= 'x; + m_vif.mgr_cb.wuser <= 'x; +endtask + +task axi_mgr_write_data_driver::drive_req(output bit item_sent); + // If we are currently in reset, there is nothing to do. This check avoids a possible race if + // reset is asserted at the same time as the request appears: we don't want to set wvalid after + // monitor_reset has called clear_data. + if (m_in_reset) return; + + fork : isolation_fork begin + fork + wait(m_in_reset); + begin + set_data_from_req(); + m_vif.mgr_cb.wvalid <= 1; + + do @(m_vif.mgr_cb); while (m_vif.mgr_cb.wready !== 1'b1); + + clear_data(); + + // Because we finished sending the item, set item_sent to cause get_and_drive to set + // m_sending_complete in its response. + item_sent = 1'b1; + end + join_any + disable fork; + end join +endtask + +task axi_mgr_write_data_driver::set_data_from_req(); + // Check that configurable-length item fields actually fit in the interface signals. Note: we can + // safely drive all the bits in the clocking block here anyway: they will be truncated in the + // interface when being reflected in the "*_internal" signal. + if (|(req.m_data >> m_vif.data_width)) begin + `uvm_error(get_full_name(), + $sformatf("Cannot represent req.m_data = 0x%0h. The interface DATA_WIDTH is %0d.", + req.m_data, m_vif.data_width)) + end + if (|(req.m_strb >> ((m_vif.data_width + 7) / 8))) begin + `uvm_error(get_full_name(), + $sformatf({"Cannot represent req.m_strb = 0x%0h. ", + "The interface DATA_WIDTH is %0d, giving a strobe width of %0d."}, + req.m_strb, m_vif.data_width, ((m_vif.data_width + 7) / 8))) + end + if (|(req.m_user >> m_vif.user_data_width)) begin + `uvm_error(get_full_name(), + $sformatf({"Cannot represent req.m_user = 0x%0h. ", + "The interface user_data_width is %0d."}, + req.m_user, m_vif.user_data_width)) + end + + m_vif.mgr_cb.wdata <= req.m_data; + m_vif.mgr_cb.wstrb <= req.m_strb; + m_vif.mgr_cb.wlast <= req.m_last; + m_vif.mgr_cb.wuser <= req.m_user; +endtask diff --git a/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh new file mode 100644 index 000000000..20c9e59d2 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh @@ -0,0 +1,182 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A driver for axi_write_request_if, used when the testbench is acting as an AXI Manager that is +// requesting write transactions. + +class axi_mgr_write_request_driver extends uvm_driver#(axi_txn_request_item, axi_status_item); + `uvm_component_utils(axi_mgr_write_request_driver) + + local virtual axi_write_request_if m_vif; + + // True if the interface is currently in reset. Maintained by monitor_reset(). + // + // At the very start of the simulation, rst_ni might be 'x or 'z. This isn't considered a "proper" + // reset: in_reset can only be asserted by seeing rst_ni === 0 (and then cleared by seeing it + // become 1). + local bit m_in_reset; + + extern function new(string name, uvm_component parent); + extern virtual task run_phase(uvm_phase phase); + + // Set m_vif. This must be called before run_phase. + extern function void set_vif(virtual axi_write_request_if vif); + + // Run forever, consuming and driving items from seq_item_port + extern local task get_and_drive(); + + // Run forever, tracking rst_ni and maintaining an in_reset class variable. Calls clear_data when + // reset becomes asserted. + extern local task monitor_reset(); + + // A task that is called at the start of a reset and also at the end of driving an item. + extern local task clear_data(); + + // A task that drives the axi_txn_request_item in the req class variable + // + // This returns when the item is driven, setting item_sent=1, or returns early if a reset is + // asserted, in which case it sets item_sent=0. + extern local task drive_req(output bit item_sent); + + // Set data values in the interface based on the req item. This task runs in zero time (but uses + // clocking block drives, so cannot be a function). + // + // This task also checks sizes against the ID_W_WIDTH, ADDR_WIDTH and USER_REQ_WIDTH properties + // that are configured in the interface. + extern local task set_data_from_req(); +endclass + +function axi_mgr_write_request_driver::new(string name, uvm_component parent); + super.new(name, parent); +endfunction + +function void axi_mgr_write_request_driver::set_vif(virtual axi_write_request_if vif); + if (m_vif != null) begin + `uvm_fatal(get_full_name(), "Cannot call set_vif: there is already an interface.") + return; + end + + if (vif.if_mode != dv_utils_pkg::Host) begin + `uvm_fatal(get_full_name(), + $sformatf("Cannot drive this interface: it has mode %0s, not Host.", + vif.if_mode.name())) + return; + end + + m_vif = vif; +endfunction + +task axi_mgr_write_request_driver::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") + return; + end + + // Start by clearing the data (and, importantly, setting m_vif.mgr_cb.awvalid = 0). From now, + // awvalid will be zero unless $isunknown on all the data fields is false. + clear_data(); + + fork + get_and_drive(); + monitor_reset(); + join +endtask + +task axi_mgr_write_request_driver::get_and_drive(); + axi_status_item status_item; + forever begin + seq_item_port.get_next_item(req); + status_item = axi_status_item::type_id::create("status_item"); + status_item.set_id_info(req); + drive_req(status_item.m_sending_complete); + seq_item_port.item_done(status_item); + end +endtask + +task axi_mgr_write_request_driver::monitor_reset(); + wait(!$isunknown(m_vif.rst_ni)); + m_in_reset = !m_vif.rst_ni; + forever begin + wait (m_vif.rst_ni); + m_in_reset = 0; + wait (!m_vif.rst_ni); + m_in_reset = 1; + clear_data(); + end +endtask + +task axi_mgr_write_request_driver::clear_data(); + m_vif.mgr_cb.awvalid <= 1'b0; + m_vif.mgr_cb.awid <= 'x; + m_vif.mgr_cb.awaddr <= 'x; + m_vif.mgr_cb.awregion <= 'x; + m_vif.mgr_cb.awlen <= 'x; + m_vif.mgr_cb.awsize <= 'x; + m_vif.mgr_cb.awburst <= 'x; + m_vif.mgr_cb.awlock <= 'x; + m_vif.mgr_cb.awcache <= 'x; + m_vif.mgr_cb.awprot <= 'x; + m_vif.mgr_cb.awqos <= 'x; + m_vif.mgr_cb.awuser <= 'x; +endtask + +task axi_mgr_write_request_driver::drive_req(output bit item_sent); + // If we are currently in reset, there is nothing to do. This check avoids a possible race if + // reset is asserted at the same time as the request appears: we don't want to set awvalid after + // monitor_reset has called clear_data. + if (m_in_reset) return; + + fork : isolation_fork begin + fork + wait(m_in_reset); + begin + set_data_from_req(); + m_vif.mgr_cb.awvalid <= 1; + + do @(m_vif.mgr_cb); while (m_vif.mgr_cb.awready !== 1'b1); + + clear_data(); + + // Because we finished sending the item, set item_sent to cause get_and_drive to set + // m_sending_complete in its response. + item_sent = 1'b1; + end + join_any + disable fork; + end join +endtask + +task axi_mgr_write_request_driver::set_data_from_req(); + // Check that configurable-length item fields actually fit in the interface signals. Note: we can + // safely drive all the bits in the clocking block here anyway: they will be truncated in the + // interface when being reflected in the "*_internal" signal. + if (|(req.m_id >> m_vif.id_w_width)) begin + `uvm_error(get_full_name(), + $sformatf("Cannot represent req.m_id = 0x%0h. The interface ID_W_WIDTH is %0d.", + req.m_id, m_vif.id_w_width)) + end + if (|(req.m_addr >> m_vif.addr_width)) begin + `uvm_error(get_full_name(), + $sformatf("Cannot represent req.m_addr = 0x%0h. The interface ADDR_WIDTH is %0d.", + req.m_addr, m_vif.addr_width)) + end + if (|(req.m_user >> m_vif.user_req_width)) begin + `uvm_error(get_full_name(), + $sformatf({"Cannot represent req.m_user = 0x%0h. ", + "The interface USER_REQ_WIDTH is %0d."}, + req.m_user, m_vif.user_req_width)) + end + + m_vif.mgr_cb.awid <= req.m_id; + m_vif.mgr_cb.awaddr <= req.m_addr; + m_vif.mgr_cb.awregion <= req.m_region; + m_vif.mgr_cb.awlen <= req.m_len; + m_vif.mgr_cb.awsize <= req.m_size; + m_vif.mgr_cb.awburst <= req.m_burst; + m_vif.mgr_cb.awlock <= req.m_lock; + m_vif.mgr_cb.awcache <= req.m_cache; + m_vif.mgr_cb.awprot <= req.m_prot; + m_vif.mgr_cb.awqos <= req.m_qos; + m_vif.mgr_cb.awuser <= req.m_user; +endtask diff --git a/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh new file mode 100644 index 000000000..a6fa0ef3e --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh @@ -0,0 +1,157 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A driver for axi_write_response_if, used when the testbench is acting as an AXI Manager that is +// accepting write responses. +// +// This will normally send an axi_write_response_item as a response to the sequencer. If sending the +// request is interrupted by a reset, this will instead return a axi_status_item with +// m_sending_complete == 0. + +class axi_mgr_write_response_driver extends uvm_driver#(axi_response_accept_item, + uvm_sequence_item); + `uvm_component_utils(axi_mgr_write_response_driver) + + local virtual axi_write_response_if m_vif; + + // True if the interface is currently in reset. Maintained by monitor_reset(). + // + // At the very start of the simulation, rst_ni might be 'x or 'z. This isn't considered a "proper" + // reset: in_reset can only be asserted by seeing rst_ni === 0 (and then cleared by seeing it + // become 1). + local bit m_in_reset; + + extern function new(string name, uvm_component parent); + extern virtual task run_phase(uvm_phase phase); + + // Set m_vif. This must be called before run_phase. + extern function void set_vif(virtual axi_write_response_if vif); + + // Run forever, consuming and driving items from seq_item_port + extern local task get_and_drive(); + + // Run forever, tracking rst_ni and maintaining an in_reset class variable. Clears bready in the + // clocking block when a reset is seen. + extern local task monitor_reset(); + + // A task that drives the axi_response_accept_item in the req class variable + // + // This returns when the response has been accepted, setting rsp to be an axi_write_response_item + // with its contents. If there is a reset, the task returns early and sets rsp to be an + // axi_status_item with m_sending_complete == 0. + extern local task drive_req(); +endclass + +function axi_mgr_write_response_driver::new(string name, uvm_component parent); + super.new(name, parent); +endfunction + +function void axi_mgr_write_response_driver::set_vif(virtual axi_write_response_if vif); + if (m_vif != null) begin + `uvm_fatal(get_full_name(), "Cannot call set_vif: there is already an interface.") + return; + end + + if (vif.if_mode != dv_utils_pkg::Host) begin + `uvm_fatal(get_full_name(), + $sformatf("Cannot drive this interface: it has mode %0s, not Host.", + vif.if_mode.name())) + return; + end + + m_vif = vif; +endfunction + +task axi_mgr_write_response_driver::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") + return; + end + + // Clear bready (we only wish to assert that we are ready if we are driving an item) + m_vif.mgr_cb.bready <= 1'b0; + + fork + get_and_drive(); + monitor_reset(); + join +endtask + +task axi_mgr_write_response_driver::get_and_drive(); + forever begin + seq_item_port.get_next_item(req); + drive_req(); + rsp.set_id_info(req); + seq_item_port.item_done(rsp); + end +endtask + +task axi_mgr_write_response_driver::monitor_reset(); + wait(!$isunknown(m_vif.rst_ni)); + m_in_reset = !m_vif.rst_ni; + forever begin + wait (m_vif.rst_ni); + m_in_reset = 0; + wait (!m_vif.rst_ni); + m_in_reset = 1; + m_vif.mgr_cb.bready <= 1'b0; + end +endtask + +task axi_mgr_write_response_driver::drive_req(); + // Create a default response, which is an axi_status_item with its default state + // (m_sending_complete = 0). This will be overridden with an axi_write_response_item if a response + // is read. + rsp = axi_status_item::type_id::create("rsp"); + + // If we are currently in reset, there is nothing to do. This check avoids a possible race if + // reset is asserted at the same time as the response appears: we don't want to set bready after + // monitor_reset has cleared it. + if (m_in_reset) begin + return; + end + + fork : isolation_fork begin + fork + wait(m_in_reset); + begin + axi_write_response_item response; + + // For the time until bvalid is asserted, obey req.m_ready_without_valid_pct and assert + // bready for some fraction of the total time. + while (m_vif.mgr_cb.bvalid !== 1'b1) begin + m_vif.mgr_cb.bready <= $urandom_range(0, 99) < req.m_ready_without_valid_pct; + @(m_vif.mgr_cb); + end + + // At this point, bvalid has been asserted. If bready is true, the transfer has gone + // through. If not, wait req.m_valid_to_ready_delay cycles before we assert ready. + // + // Read bready from the clocking block to see the last value we wrote. Practically speaking, + // mgr_cb.bready will not be true unless we had at least one cycle in the loop above: we set + // mgr_cb.bready <= 0 at the end of this task and also when a reset is seen in monitor_reset. + if (m_vif.mgr_cb.bready !== 1'b1) begin + repeat (req.m_valid_to_ready_delay) @(m_vif.mgr_cb); + m_vif.mgr_cb.bready <= 1'b1; + @(m_vif.mgr_cb); + end + + // When we get here, we have finished accepting a response and we are at the end of a cycle + // where bvalid and bready were asserted. Fill the response output variable with a sequence + // item to represent the response that we have seen. + response = axi_write_response_item::type_id::create("response"); + response.m_id = m_vif.mgr_cb.bid; + response.m_resp = axi_write_response_item::bresp_e'(m_vif.mgr_cb.bresp); + response.m_user = m_vif.mgr_cb.buser; + + // Set rsp, which will pass the response back to the sequencer. + rsp = response; + + // Finally, clear bready (for next cycle). + m_vif.mgr_cb.bready <= 1'b0; + end + join_any + disable fork; + end join +endtask diff --git a/hw/ip/dv/axi_agent/axi_status_item.svh b/hw/ip/dv/axi_agent/axi_status_item.svh new file mode 100644 index 000000000..1ae99c015 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_status_item.svh @@ -0,0 +1,54 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item is sent back by drivers to show whether an item has actually been sent + +class axi_status_item extends uvm_sequence_item; + `uvm_object_utils(axi_status_item) + + // If this bit is set then the driver that was sending an item (and is responding with this one) + // has finished sending the item. + // + // If the driver was interrupted by a reset, this bit will be clear. + bit m_sending_complete; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_status_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_status_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_field_int("m_sending_complete", m_sending_complete, 1, UVM_BIN); +endfunction + +function void axi_status_item::do_copy(uvm_object rhs); + axi_status_item rhs_; + + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + + m_sending_complete = rhs_.m_sending_complete; +endfunction + +function bit axi_status_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_status_item rhs_; + + // These items are only equivalent if rhs is actually an axi_status_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_status_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field_int("m_sending_complete", + m_sending_complete, rhs_.m_sending_complete, 1, UVM_BIN)); +endfunction From eeea7eca0e15d38178cd3705d80824c2078becc0 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Mon, 15 Jun 2026 11:45:00 +0100 Subject: [PATCH 06/22] [dv,axi] Implement drivers for the Manager side of read channels Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 2 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 3 + .../dv/axi_agent/axi_mgr_read_data_driver.svh | 155 +++++++++++++++ .../axi_agent/axi_mgr_read_request_driver.svh | 187 ++++++++++++++++++ .../axi_mgr_write_request_driver.svh | 5 + 5 files changed, 352 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh create mode 100644 hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index dfd6de96f..02bfe0411 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -27,6 +27,8 @@ filesets: - axi_mgr_write_request_driver.svh: {is_include_file: true} - axi_mgr_write_data_driver.svh: {is_include_file: true} - axi_mgr_write_response_driver.svh: {is_include_file: true} + - axi_mgr_read_request_driver.svh: {is_include_file: true} + - axi_mgr_read_data_driver.svh: {is_include_file: true} file_type: systemVerilogSource diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 85bf7c943..ea8e916e3 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -28,4 +28,7 @@ package axi_agent_pkg; `include "axi_mgr_write_request_driver.svh" `include "axi_mgr_write_data_driver.svh" `include "axi_mgr_write_response_driver.svh" + + `include "axi_mgr_read_request_driver.svh" + `include "axi_mgr_read_data_driver.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh new file mode 100644 index 000000000..ce5996972 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh @@ -0,0 +1,155 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A driver for axi_read_data_if, used when the testbench is acting as an AXI Manager that is +// accepting read data (responding to read requests). + +class axi_mgr_read_data_driver extends uvm_driver#(axi_response_accept_item, uvm_sequence_item); + `uvm_component_utils(axi_mgr_read_data_driver) + + local virtual axi_read_data_if m_vif; + + // True if the interface is currently in reset. Maintained by monitor_reset(). + // + // At the very start of the simulation, rst_ni might be 'x or 'z. This isn't considered a "proper" + // reset: in_reset can only be asserted by seeing rst_ni === 0 (and then cleared by seeing it + // become 1). + local bit m_in_reset; + + extern function new(string name, uvm_component parent); + extern virtual task run_phase(uvm_phase phase); + + // Set m_vif. This must be called before run_phase. + extern function void set_vif(virtual axi_read_data_if vif); + + // Run forever, consuming and driving items from seq_item_port + extern local task get_and_drive(); + + // Run forever, tracking rst_ni and maintaining an in_reset class variable. Clears rready in the + // clocking block when a reset is seen. + extern local task monitor_reset(); + + // A task that drives the axi_response_accept_item in the req class variable + // + // This returns when the item is driven, but returns early if there is a reset. When an item is + // driven, the data that was read is sampled and is used to populate an axi_read_data_item which + // is returned in the response output argument. + // + // If there is a reset (causing the task to return early), the rsp class variable is populated + // with an axi_status_item with m_sending_complete=0. That way, the driver always returns a + // sequence item of some sort to the sequencer, which can pass that back to the sequence (which + // can then handle the response). + extern local task drive_req(); +endclass + +function axi_mgr_read_data_driver::new(string name, uvm_component parent); + super.new(name, parent); +endfunction + +function void axi_mgr_read_data_driver::set_vif(virtual axi_read_data_if vif); + if (m_vif != null) begin + `uvm_fatal(get_full_name(), "Cannot call set_vif: there is already an interface.") + return; + end + + if (vif.if_mode != dv_utils_pkg::Host) begin + `uvm_fatal(get_full_name(), + $sformatf("Cannot drive this interface: it has mode %0s, not Host.", + vif.if_mode.name())) + return; + end + + m_vif = vif; +endfunction + +task axi_mgr_read_data_driver::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") + return; + end + + // Clear rready (we only wish to assert that we are ready if we are driving an item) + m_vif.mgr_cb.rready <= 1'b0; + + fork + get_and_drive(); + monitor_reset(); + join +endtask + +task axi_mgr_read_data_driver::get_and_drive(); + forever begin + seq_item_port.get_next_item(req); + drive_req(); + rsp.set_id_info(req); + seq_item_port.item_done(rsp); + end +endtask + +task axi_mgr_read_data_driver::monitor_reset(); + wait(!$isunknown(m_vif.rst_ni)); + m_in_reset = !m_vif.rst_ni; + forever begin + wait (m_vif.rst_ni); + m_in_reset = 0; + wait (!m_vif.rst_ni); + m_in_reset = 1; + m_vif.mgr_cb.rready <= 1'b0; + end +endtask + +task axi_mgr_read_data_driver::drive_req(); + // Set *some* response, which is the default axi_status_item (with m_sending_complete=0). This + // will be overridden by an axi_read_data_item if we finish reading data. + rsp = axi_status_item::type_id::create("rsp"); + + // If we are currently in reset, there is nothing to do. This check avoids a possible race if + // reset is asserted at the same time as the response appears: we don't want to set rready after + // monitor_reset has cleared it. + if (m_in_reset) return; + + fork : isolation_fork begin + fork + wait(m_in_reset); + begin + axi_read_data_item read_data_item; + + // For the time until rvalid is asserted, obey req.m_ready_without_valid_pct and assert + // rready for some fraction of the total time. + while (m_vif.mgr_cb.rvalid !== 1'b1) begin + m_vif.mgr_cb.rready <= $urandom_range(0, 99) < req.m_ready_without_valid_pct; + @(m_vif.mgr_cb); + end + + // At this point, rvalid has been asserted. If rready is true, the transfer has gone + // through. If not, wait req.m_valid_to_ready_delay cycles before we assert ready. + // + // Read rready from the clocking block to see the last value we wrote. Practically speaking, + // mgr_cb.rready will not be true unless we had at least one cycle in the loop above: we set + // mgr_cb.rready <= 0 at the end of this task and also when a reset is seen in monitor_reset. + if (m_vif.mgr_cb.rready !== 1'b1) begin + repeat (req.m_valid_to_ready_delay) @(m_vif.mgr_cb); + m_vif.mgr_cb.rready <= 1'b1; + @(m_vif.mgr_cb); + end + + // When we get here, we have finished accepting a response and we are at the end of a cycle + // where rvalid and rready were asserted. Write a sequence item to represent the response + // that we have seen to our read_data_item output argument. + read_data_item = axi_read_data_item::type_id::create("read_data_item"); + read_data_item.m_id = m_vif.mgr_cb.rid; + read_data_item.m_data = m_vif.mgr_cb.rdata; + read_data_item.m_resp = axi_read_data_item::rresp_e'(m_vif.mgr_cb.rresp); + read_data_item.m_last = m_vif.mgr_cb.rlast; + read_data_item.m_user = m_vif.mgr_cb.ruser; + + rsp = read_data_item; + + // Finally, clear rready (for next cycle). + m_vif.mgr_cb.rready <= 1'b0; + end + join_any + disable fork; + end join +endtask diff --git a/hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh new file mode 100644 index 000000000..741532697 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh @@ -0,0 +1,187 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A driver for axi_read_request_if, used when the testbench is acting as an AXI Manager that is +// requesting read transactions. +// +// Note: This is very similar to axi_mgr_write_request_driver (because the read and write request +// interfaces are very similar). Separating the interfaces and classes will allow future versions of +// the agent to support signals that only appear on one side, like the "stash" signals on the write +// side. + +class axi_mgr_read_request_driver extends uvm_driver#(axi_txn_request_item, axi_status_item); + `uvm_component_utils(axi_mgr_read_request_driver) + + local virtual axi_read_request_if m_vif; + + // True if the interface is currently in reset. Maintained by monitor_reset(). + // + // At the very start of the simulation, rst_ni might be 'x or 'z. This isn't considered a "proper" + // reset: in_reset can only be asserted by seeing rst_ni === 0 (and then cleared by seeing it + // become 1). + local bit m_in_reset; + + extern function new(string name, uvm_component parent); + extern virtual task run_phase(uvm_phase phase); + + // Set m_vif. This must be called before run_phase. + extern function void set_vif(virtual axi_read_request_if vif); + + // Run forever, consuming and driving items from seq_item_port + extern local task get_and_drive(); + + // Run forever, tracking rst_ni and maintaining an in_reset class variable. Calls clear_data when + // reset becomes asserted. + extern local task monitor_reset(); + + // A task that is called at the start of a reset and also at the end of driving an item. + extern local task clear_data(); + + // A task that drives the axi_txn_request_item in the req class variable + // + // This returns when the item is driven, setting item_sent=1, or returns early if a reset is + // asserted, in which case it sets item_sent=0. + extern local task drive_req(output bit item_sent); + + // Set data values in the interface based on the req item. This task runs in zero time (but uses + // clocking block drives, so cannot be a function). + // + // This task also checks sizes against the ID_R_WIDTH, ADDR_WIDTH and USER_REQ_WIDTH properties + // that are configured in the interface. + extern local task set_data_from_req(); +endclass + +function axi_mgr_read_request_driver::new(string name, uvm_component parent); + super.new(name, parent); +endfunction + +function void axi_mgr_read_request_driver::set_vif(virtual axi_read_request_if vif); + if (m_vif != null) begin + `uvm_fatal(get_full_name(), "Cannot call set_vif: there is already an interface.") + return; + end + + if (vif.if_mode != dv_utils_pkg::Host) begin + `uvm_fatal(get_full_name(), + $sformatf("Cannot drive this interface: it has mode %0s, not Host.", + vif.if_mode.name())) + return; + end + + m_vif = vif; +endfunction + +task axi_mgr_read_request_driver::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") + return; + end + + // Start by clearing the data (and, importantly, setting m_vif.mgr_cb.arvalid = 0). From now, + // arvalid will be zero unless $isunknown on all the data fields is false. + clear_data(); + + fork + get_and_drive(); + monitor_reset(); + join +endtask + +task axi_mgr_read_request_driver::get_and_drive(); + axi_status_item status_item; + forever begin + seq_item_port.get_next_item(req); + status_item = axi_status_item::type_id::create("status_item"); + status_item.set_id_info(req); + drive_req(status_item.m_sending_complete); + seq_item_port.item_done(status_item); + end +endtask + +task axi_mgr_read_request_driver::monitor_reset(); + wait(!$isunknown(m_vif.rst_ni)); + m_in_reset = !m_vif.rst_ni; + forever begin + wait (m_vif.rst_ni); + m_in_reset = 0; + wait (!m_vif.rst_ni); + m_in_reset = 1; + clear_data(); + end +endtask + +task axi_mgr_read_request_driver::clear_data(); + m_vif.mgr_cb.arvalid <= 1'b0; + m_vif.mgr_cb.arid <= 'x; + m_vif.mgr_cb.araddr <= 'x; + m_vif.mgr_cb.arregion <= 'x; + m_vif.mgr_cb.arlen <= 'x; + m_vif.mgr_cb.arsize <= 'x; + m_vif.mgr_cb.arburst <= 'x; + m_vif.mgr_cb.arlock <= 'x; + m_vif.mgr_cb.arcache <= 'x; + m_vif.mgr_cb.arprot <= 'x; + m_vif.mgr_cb.arqos <= 'x; + m_vif.mgr_cb.aruser <= 'x; +endtask + +task axi_mgr_read_request_driver::drive_req(output bit item_sent); + // If we are currently in reset, there is nothing to do. This check avoids a possible race if + // reset is asserted at the same time as the request appears: we don't want to set arvalid after + // monitor_reset has called clear_data. + if (m_in_reset) return; + + fork : isolation_fork begin + fork + wait(m_in_reset); + begin + set_data_from_req(); + m_vif.mgr_cb.arvalid <= 1; + + do @(m_vif.mgr_cb); while (m_vif.mgr_cb.arready !== 1'b1); + + clear_data(); + + // Because we finished sending the item, set item_sent to cause get_and_drive to set + // m_sending_complete in its response. + item_sent = 1'b1; + end + join_any + disable fork; + end join +endtask + +task axi_mgr_read_request_driver::set_data_from_req(); + // Check that configurable-length item fields actually fit in the interface signals. Note: we can + // safely drive all the bits in the clocking block here anyway: they will be truncated in the + // interface when being reflected in the "*_internal" signal. + if (|(req.m_id >> m_vif.id_r_width)) begin + `uvm_error(get_full_name(), + $sformatf("Cannot represent req.m_id = 0x%0h. The interface ID_R_WIDTH is %0d.", + req.m_id, m_vif.id_r_width)) + end + if (|(req.m_addr >> m_vif.addr_width)) begin + `uvm_error(get_full_name(), + $sformatf("Cannot represent req.m_addr = 0x%0h. The interface ADDR_WIDTH is %0d.", + req.m_addr, m_vif.addr_width)) + end + if (|(req.m_user >> m_vif.user_req_width)) begin + `uvm_error(get_full_name(), + $sformatf({"Cannot represent req.m_user = 0x%0h. ", + "The interface USER_REQ_WIDTH is %0d."}, + req.m_user, m_vif.user_req_width)) + end + + m_vif.mgr_cb.arid <= req.m_id; + m_vif.mgr_cb.araddr <= req.m_addr; + m_vif.mgr_cb.arregion <= req.m_region; + m_vif.mgr_cb.arlen <= req.m_len; + m_vif.mgr_cb.arsize <= req.m_size; + m_vif.mgr_cb.arburst <= req.m_burst; + m_vif.mgr_cb.arlock <= req.m_lock; + m_vif.mgr_cb.arcache <= req.m_cache; + m_vif.mgr_cb.arprot <= req.m_prot; + m_vif.mgr_cb.arqos <= req.m_qos; + m_vif.mgr_cb.aruser <= req.m_user; +endtask diff --git a/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh index 20c9e59d2..8a671a0c0 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh @@ -4,6 +4,11 @@ // A driver for axi_write_request_if, used when the testbench is acting as an AXI Manager that is // requesting write transactions. +// +// Note: This is very similar to axi_mgr_read_request_driver (because the read and write request +// interfaces are very similar). Separating the interfaces and classes will allow future versions of +// the agent to support signals that only appear on one side, like the "stash" signals on the write +// side. class axi_mgr_write_request_driver extends uvm_driver#(axi_txn_request_item, axi_status_item); `uvm_component_utils(axi_mgr_write_request_driver) From 867670935d1004009d6c3a7fb2c00a2d9dbb285d Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Tue, 16 Jun 2026 11:07:37 +0100 Subject: [PATCH 07/22] [dv,axi] Define item and monitor for resets on each AXI interface Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 7 +++ hw/ip/dv/axi_agent/axi_agent_pkg.sv | 10 +++++ hw/ip/dv/axi_agent/axi_reset_item.svh | 50 +++++++++++++++++++++ hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh | 48 ++++++++++++++++++++ hw/ip/dv/axi_agent/axi_reset_monitor_aw.svh | 48 ++++++++++++++++++++ hw/ip/dv/axi_agent/axi_reset_monitor_b.svh | 48 ++++++++++++++++++++ hw/ip/dv/axi_agent/axi_reset_monitor_r.svh | 48 ++++++++++++++++++++ hw/ip/dv/axi_agent/axi_reset_monitor_w.svh | 48 ++++++++++++++++++++ 8 files changed, 307 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_reset_item.svh create mode 100644 hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh create mode 100644 hw/ip/dv/axi_agent/axi_reset_monitor_aw.svh create mode 100644 hw/ip/dv/axi_agent/axi_reset_monitor_b.svh create mode 100644 hw/ip/dv/axi_agent/axi_reset_monitor_r.svh create mode 100644 hw/ip/dv/axi_agent/axi_reset_monitor_w.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 02bfe0411..161ded73b 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -17,6 +17,7 @@ filesets: - axi_agent_pkg.sv + - axi_reset_item.svh: {is_include_file: true} - axi_status_item.svh: {is_include_file: true} - axi_txn_request_item.svh: {is_include_file: true} - axi_response_accept_item.svh: {is_include_file: true} @@ -24,6 +25,12 @@ filesets: - axi_write_data_item.svh: {is_include_file: true} - axi_write_response_item.svh: {is_include_file: true} + - axi_reset_monitor_aw.svh: {is_include_file: true} + - axi_reset_monitor_w.svh: {is_include_file: true} + - axi_reset_monitor_b.svh: {is_include_file: true} + - axi_reset_monitor_ar.svh: {is_include_file: true} + - axi_reset_monitor_r.svh: {is_include_file: true} + - axi_mgr_write_request_driver.svh: {is_include_file: true} - axi_mgr_write_data_driver.svh: {is_include_file: true} - axi_mgr_write_response_driver.svh: {is_include_file: true} diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index ea8e916e3..8151921ec 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -31,4 +31,14 @@ package axi_agent_pkg; `include "axi_mgr_read_request_driver.svh" `include "axi_mgr_read_data_driver.svh" + + `include "axi_reset_item.svh" + + // Reset monitors for the five interfaces (all essentially the same thing, but the interface types + // are different so we have to copy-paste the classes) + `include "axi_reset_monitor_aw.svh" + `include "axi_reset_monitor_w.svh" + `include "axi_reset_monitor_b.svh" + `include "axi_reset_monitor_ar.svh" + `include "axi_reset_monitor_r.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_reset_item.svh b/hw/ip/dv/axi_agent/axi_reset_item.svh new file mode 100644 index 000000000..fbf5db586 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_reset_item.svh @@ -0,0 +1,50 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item representing a change of reset state on an interface + +class axi_reset_item extends uvm_sequence_item; + `uvm_object_utils(axi_reset_item) + + // True if the interface is in reset after the change of state + bit m_in_reset; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_reset_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_reset_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_field_int("m_in_reset", m_in_reset, 1, UVM_BIN); +endfunction + +function void axi_reset_item::do_copy(uvm_object rhs); + axi_reset_item rhs_; + + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + + m_in_reset = rhs_.m_in_reset; +endfunction + +function bit axi_reset_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_reset_item rhs_; + + // These items are only equivalent if rhs is actually an axi_reset_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_reset_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field_int("m_in_reset", m_in_reset, rhs_.m_in_reset, 1, UVM_BIN)); +endfunction diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh b/hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh new file mode 100644 index 000000000..414650c02 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh @@ -0,0 +1,48 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A monitor for an axi_read_request_if (the AXI AR channel) + +class axi_reset_monitor_ar extends uvm_monitor; + `uvm_component_utils(axi_reset_monitor_ar); + + // A port that broadcasts an item on every reset state change. + uvm_analysis_port #(axi_reset_item) m_analysis_port; + + // The interface being tracked. Set this with set_vif before run_phase. + local virtual axi_read_request_if m_vif; + + extern function new(string name, uvm_component parent); + extern task run_phase(uvm_phase phase); + + extern function void set_vif(virtual axi_read_request_if vif); +endclass + +function void axi_reset_monitor_ar::set_vif(virtual axi_read_request_if vif); + m_vif = vif; +endfunction + +function axi_reset_monitor_ar::new(string name, uvm_component parent); + super.new(name, parent); + m_analysis_port = new("m_analysis_port", this); +endfunction + +task axi_reset_monitor_ar::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") + return; + end + + wait(!$isunknown(m_vif.rst_ni)); + forever begin + axi_reset_item rst_item; + bit rst_n_bit = bit'(m_vif.rst_ni); + + rst_item = axi_reset_item::type_id::create("rst_item"); + rst_item.m_in_reset = !rst_n_bit; + m_analysis_port.write(rst_item); + + wait(m_vif.rst_ni === !rst_n_bit); + end +endtask diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_aw.svh b/hw/ip/dv/axi_agent/axi_reset_monitor_aw.svh new file mode 100644 index 000000000..a63155cdd --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_reset_monitor_aw.svh @@ -0,0 +1,48 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A monitor for an axi_write_request_if (the AXI AW channel) + +class axi_reset_monitor_aw extends uvm_monitor; + `uvm_component_utils(axi_reset_monitor_aw); + + // A port that broadcasts an item on every reset state change. + uvm_analysis_port #(axi_reset_item) m_analysis_port; + + // The interface being tracked. Set this with set_vif before run_phase. + local virtual axi_write_request_if m_vif; + + extern function new(string name, uvm_component parent); + extern task run_phase(uvm_phase phase); + + extern function void set_vif(virtual axi_write_request_if vif); +endclass + +function void axi_reset_monitor_aw::set_vif(virtual axi_write_request_if vif); + m_vif = vif; +endfunction + +function axi_reset_monitor_aw::new(string name, uvm_component parent); + super.new(name, parent); + m_analysis_port = new("m_analysis_port", this); +endfunction + +task axi_reset_monitor_aw::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") + return; + end + + wait(!$isunknown(m_vif.rst_ni)); + forever begin + axi_reset_item rst_item; + bit rst_n_bit = bit'(m_vif.rst_ni); + + rst_item = axi_reset_item::type_id::create("rst_item"); + rst_item.m_in_reset = !rst_n_bit; + m_analysis_port.write(rst_item); + + wait(m_vif.rst_ni === !rst_n_bit); + end +endtask diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_b.svh b/hw/ip/dv/axi_agent/axi_reset_monitor_b.svh new file mode 100644 index 000000000..4b4bce02c --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_reset_monitor_b.svh @@ -0,0 +1,48 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A monitor for an axi_write_response_if (the AXI B channel) + +class axi_reset_monitor_b extends uvm_monitor; + `uvm_component_utils(axi_reset_monitor_b); + + // A port that broadcasts an item on every reset state change. + uvm_analysis_port #(axi_reset_item) m_analysis_port; + + // The interface being tracked. Set this with set_vif before run_phase. + local virtual axi_write_response_if m_vif; + + extern function new(string name, uvm_component parent); + extern task run_phase(uvm_phase phase); + + extern function void set_vif(virtual axi_write_response_if vif); +endclass + +function void axi_reset_monitor_b::set_vif(virtual axi_write_response_if vif); + m_vif = vif; +endfunction + +function axi_reset_monitor_b::new(string name, uvm_component parent); + super.new(name, parent); + m_analysis_port = new("m_analysis_port", this); +endfunction + +task axi_reset_monitor_b::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") + return; + end + + wait(!$isunknown(m_vif.rst_ni)); + forever begin + axi_reset_item rst_item; + bit rst_n_bit = bit'(m_vif.rst_ni); + + rst_item = axi_reset_item::type_id::create("rst_item"); + rst_item.m_in_reset = !rst_n_bit; + m_analysis_port.write(rst_item); + + wait(m_vif.rst_ni === !rst_n_bit); + end +endtask diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_r.svh b/hw/ip/dv/axi_agent/axi_reset_monitor_r.svh new file mode 100644 index 000000000..f355b132e --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_reset_monitor_r.svh @@ -0,0 +1,48 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A monitor for an axi_read_data_if (the AXI R channel) + +class axi_reset_monitor_r extends uvm_monitor; + `uvm_component_utils(axi_reset_monitor_r); + + // A port that broadcasts an item on every reset state change. + uvm_analysis_port #(axi_reset_item) m_analysis_port; + + // The interface being tracked. Set this with set_vif before run_phase. + local virtual axi_read_data_if m_vif; + + extern function new(string name, uvm_component parent); + extern task run_phase(uvm_phase phase); + + extern function void set_vif(virtual axi_read_data_if vif); +endclass + +function void axi_reset_monitor_r::set_vif(virtual axi_read_data_if vif); + m_vif = vif; +endfunction + +function axi_reset_monitor_r::new(string name, uvm_component parent); + super.new(name, parent); + m_analysis_port = new("m_analysis_port", this); +endfunction + +task axi_reset_monitor_r::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") + return; + end + + wait(!$isunknown(m_vif.rst_ni)); + forever begin + axi_reset_item rst_item; + bit rst_n_bit = bit'(m_vif.rst_ni); + + rst_item = axi_reset_item::type_id::create("rst_item"); + rst_item.m_in_reset = !rst_n_bit; + m_analysis_port.write(rst_item); + + wait(m_vif.rst_ni === !rst_n_bit); + end +endtask diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_w.svh b/hw/ip/dv/axi_agent/axi_reset_monitor_w.svh new file mode 100644 index 000000000..3068d5bb8 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_reset_monitor_w.svh @@ -0,0 +1,48 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A monitor for an axi_write_data_if (the AXI W channel) + +class axi_reset_monitor_w extends uvm_monitor; + `uvm_component_utils(axi_reset_monitor_w); + + // A port that broadcasts an item on every reset state change. + uvm_analysis_port #(axi_reset_item) m_analysis_port; + + // The interface being tracked. Set this with set_vif before run_phase. + local virtual axi_write_data_if m_vif; + + extern function new(string name, uvm_component parent); + extern task run_phase(uvm_phase phase); + + extern function void set_vif(virtual axi_write_data_if vif); +endclass + +function void axi_reset_monitor_w::set_vif(virtual axi_write_data_if vif); + m_vif = vif; +endfunction + +function axi_reset_monitor_w::new(string name, uvm_component parent); + super.new(name, parent); + m_analysis_port = new("m_analysis_port", this); +endfunction + +task axi_reset_monitor_w::run_phase(uvm_phase phase); + if (m_vif == null) begin + `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") + return; + end + + wait(!$isunknown(m_vif.rst_ni)); + forever begin + axi_reset_item rst_item; + bit rst_n_bit = bit'(m_vif.rst_ni); + + rst_item = axi_reset_item::type_id::create("rst_item"); + rst_item.m_in_reset = !rst_n_bit; + m_analysis_port.write(rst_item); + + wait(m_vif.rst_ni === !rst_n_bit); + end +endtask From c1089f4453e3143480c312c01a6823bb61c166d7 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Tue, 16 Jun 2026 10:57:41 +0100 Subject: [PATCH 08/22] [dv,axi] Define a router that maps IDs to responses For an example use-case, suppose you are reading with AR / R transfers. You send an AR transfer with some ARID that will have a burst with k beats. Now you want consume read data with that many (k) R transfers using the some ID. To do so: - Run k axi_mgr_read_data_seq sequences, calling on_response() after each finishes. - (In parallel) call wait_for_response() k times. The trick is that these sequences might respond with different IDs, but that doesn't matter: the sequences are just actings as tokens to allow *something* to come back. --- hw/ip/dv/axi_agent/axi_agent.core | 2 + hw/ip/dv/axi_agent/axi_response_router.svh | 99 ++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_response_router.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 161ded73b..4376828dd 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -37,6 +37,8 @@ filesets: - axi_mgr_read_request_driver.svh: {is_include_file: true} - axi_mgr_read_data_driver.svh: {is_include_file: true} + - axi_response_router.svh: {is_include_file: true} + file_type: systemVerilogSource targets: diff --git a/hw/ip/dv/axi_agent/axi_response_router.svh b/hw/ip/dv/axi_agent/axi_response_router.svh new file mode 100644 index 000000000..69992eff0 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_response_router.svh @@ -0,0 +1,99 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A base class for routing responses from AXI Subordinates to Managers. These are routed based on +// RID / BID. + +class axi_response_router extends uvm_component; + `uvm_component_utils(axi_response_router) + + typedef uvm_sequence_item item_queue_t[$]; + + // An associative array that maps ID to a queue of response items that have been seen for that ID + // (the FIFO works by pop_front and push_back). + // + // We never store an empty list as a value in this associative array, so can wait for a response + // on a given ID by just waiting for the ID to have some value. + local item_queue_t m_id_to_responses[int unsigned]; + + // A flag that is set by assert_reset and cleared by clear_reset. When this is set, + // m_id_to_responses will be empty and all calls to wait_for_response will return immediately. + local bit m_in_reset; + + // An event that triggers all waiting tasks. This runs when a new response is added to + // m_id_to_responses or when a reset is asserted. + local uvm_event m_update_event; + + // An import for reset events (asserting or clearing reset) + uvm_analysis_imp #(axi_reset_item, axi_response_router) reset_imp; + + extern function new (string name, uvm_component parent); + + // The write function for reset_imp, which is called when a reset event is broadcast + extern virtual function void write(axi_reset_item item); + + // Wait for a response to be seen with the given ID. + // + // This returns when such a response is seen, setting rsp. If there is a reset, this task will + // return, setting rsp = null. + extern task wait_for_response(int unsigned id, output uvm_sequence_item rsp); + + // Notify the router that there has been the given response, for the supplied id. + extern function void on_response(int unsigned id, uvm_sequence_item rsp); +endclass + +function axi_response_router::new(string name, uvm_component parent); + super.new(name, parent); + m_update_event = new("m_update_event"); + reset_imp = new("reset_imp", this); +endfunction + +function void axi_response_router::write(axi_reset_item item); + if (item.m_in_reset) begin + // Reset is being asserted. If we don't know about it already, set a flag, clear + // m_id_to_responses and trigger the event to cause all tasks waiting to finish. + if (!m_in_reset) begin + m_in_reset = 1'b1; + m_id_to_responses.delete(); + m_update_event.trigger(); + end + end else begin + // Reset is being cleared. All we have to do is clear m_in_reset. + m_in_reset = 1'b0; + end +endfunction + +task axi_response_router::wait_for_response(int unsigned id, + output uvm_sequence_item rsp); + while (!m_id_to_responses.exists(id) && !m_in_reset) m_update_event.wait_trigger(); + + if (m_in_reset) begin + rsp = null; + end else begin + // The logic just below should ensure that m_id_to_responses[id] is never an empty queue, but it + // probably makes sense to add an extra check here for debugging. + if (m_id_to_responses[id].size() == 0) begin + `uvm_fatal(get_full_name(), + $sformatf("m_id_to_responses[0x%0h] is an empty queue.", id)) + end + + rsp = m_id_to_responses[id].pop_front; + + if (m_id_to_responses[id].size() == 0) begin + m_id_to_responses.delete(id); + end + end +endtask + +function void axi_response_router::on_response(int unsigned id, uvm_sequence_item rsp); + if (rsp == null) `uvm_fatal(get_full_name(), "rsp must not be null.") + + if (!m_in_reset) begin + if (!m_id_to_responses.exists(id)) begin + m_id_to_responses[id] = '{}; + end + m_id_to_responses[id].push_back(rsp); + m_update_event.trigger(); + end +endfunction From e07847034e07764e04c551d3ac01dfd7a03b492e Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Mon, 15 Jun 2026 14:10:21 +0100 Subject: [PATCH 09/22] [dv,axi] Define axi_mgr_agent This is a bare-bones agent for the five AXI interfaces. Note that there are no monitors, so the agent won't actually do anything in passive mode (yet). Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 3 + hw/ip/dv/axi_agent/axi_agent_cfg.svh | 19 ++ hw/ip/dv/axi_agent/axi_agent_pkg.sv | 9 + hw/ip/dv/axi_agent/axi_mgr_agent.svh | 261 +++++++++++++++++++++++++++ 4 files changed, 292 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_agent_cfg.svh create mode 100644 hw/ip/dv/axi_agent/axi_mgr_agent.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 4376828dd..11432f9f7 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -16,6 +16,7 @@ filesets: - axi_write_response_if.sv - axi_agent_pkg.sv + - axi_agent_cfg.svh: {is_include_file: true} - axi_reset_item.svh: {is_include_file: true} - axi_status_item.svh: {is_include_file: true} @@ -39,6 +40,8 @@ filesets: - axi_response_router.svh: {is_include_file: true} + - axi_mgr_agent.svh: {is_include_file: true} + file_type: systemVerilogSource targets: diff --git a/hw/ip/dv/axi_agent/axi_agent_cfg.svh b/hw/ip/dv/axi_agent/axi_agent_cfg.svh new file mode 100644 index 000000000..f9c7cff83 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_agent_cfg.svh @@ -0,0 +1,19 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// The configuration for an agent driving the interfaces for AXI (AW, W, B, AR, R). + +class axi_agent_cfg extends uvm_object; + `uvm_object_utils(axi_agent_cfg) + + virtual axi_write_request_if write_request_vif; + virtual axi_write_data_if write_data_vif; + virtual axi_write_response_if write_response_vif; + virtual axi_read_request_if read_request_vif; + virtual axi_read_data_if read_data_vif; + + function new(string name = "axi_agent_cfg"); + super.new(name); + endfunction +endclass diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 8151921ec..21c197f93 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -41,4 +41,13 @@ package axi_agent_pkg; `include "axi_reset_monitor_b.svh" `include "axi_reset_monitor_ar.svh" `include "axi_reset_monitor_r.svh" + + typedef uvm_sequencer#(axi_txn_request_item, axi_status_item) write_request_sequencer_t; + typedef uvm_sequencer#(axi_write_data_item, axi_status_item) write_data_sequencer_t; + typedef uvm_sequencer#(axi_response_accept_item, uvm_sequence_item) write_response_sequencer_t; + typedef uvm_sequencer#(axi_txn_request_item, axi_status_item) read_request_sequencer_t; + typedef uvm_sequencer#(axi_response_accept_item, uvm_sequence_item) read_data_sequencer_t; + + `include "axi_agent_cfg.svh" + `include "axi_mgr_agent.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_mgr_agent.svh b/hw/ip/dv/axi_agent/axi_mgr_agent.svh new file mode 100644 index 000000000..3615f8240 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_agent.svh @@ -0,0 +1,261 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +class axi_mgr_agent extends uvm_agent; + `uvm_component_utils(axi_mgr_agent) + + // The agent config object, which allows the testbench to supply virtual interfaces. This can + // either be set by calling set_cfg() before the build phase, or provided through uvm_config_db. + local axi_agent_cfg m_cfg; + + // The write request channel (AW) + local axi_reset_monitor_aw m_reset_monitor_aw; + local axi_mgr_write_request_driver m_write_request_driver; + local write_request_sequencer_t m_write_request_sequencer; + + // The write data channel (W) + local axi_reset_monitor_w m_reset_monitor_w; + local axi_mgr_write_data_driver m_write_data_driver; + local write_data_sequencer_t m_write_data_sequencer; + + // The write response channel (B) + local axi_reset_monitor_b m_reset_monitor_b; + local axi_mgr_write_response_driver m_write_response_driver; + local write_response_sequencer_t m_write_response_sequencer; + + // The read request channel (AR) + local axi_reset_monitor_ar m_reset_monitor_ar; + local axi_mgr_read_request_driver m_read_request_driver; + local read_request_sequencer_t m_read_request_sequencer; + + // The read data channel (R) + local axi_reset_monitor_r m_reset_monitor_r; + local axi_mgr_read_data_driver m_read_data_driver; + local read_data_sequencer_t m_read_data_sequencer; + + // A response router for writes + local axi_response_router m_write_response_router; + + // A response router for reads + local axi_response_router m_read_response_router; + + extern function new (string name, uvm_component parent); + extern function void build_phase(uvm_phase phase); + extern function void connect_phase(uvm_phase phase); + + // Set m_cfg to the provided cfg. + // + // This can only run once, and must be run before the build phase. If it is not run, build_phase + // will try to get the config object from uvm_config_db. + extern function void set_cfg(axi_agent_cfg cfg); + + // Get the reset_monitor for the write request channel (AW). Can only be called after build_phase. + extern function axi_reset_monitor_aw get_write_request_reset_monitor(); + + // Get the reset_monitor for the write data channel (W). Can only be called after build_phase. + extern function axi_reset_monitor_w get_write_data_reset_monitor(); + + // Get the reset_monitor for the write response channel (B). Can only be called after build_phase. + extern function axi_reset_monitor_b get_write_response_reset_monitor(); + + // Get the reset_monitor for the read request channel (AR). Can only be called after build_phase. + extern function axi_reset_monitor_ar get_read_request_reset_monitor(); + + // Get the reset_monitor for the read data channel (R). Can only be called after build_phase. + extern function axi_reset_monitor_r get_read_data_reset_monitor(); + + // Get the sequencer for the write request channel (AW). Can only be called after build_phase, and + // the agent must be active. + extern function write_request_sequencer_t get_write_request_sequencer(); + + // Get the sequencer for the write data channel (W). Can only be called after build_phase, and the + // agent must be active. + extern function write_data_sequencer_t get_write_data_sequencer(); + + // Get the sequencer for the write response channel (B). Can only be called after build_phase, and + // the agent must be active. + extern function write_response_sequencer_t get_write_response_sequencer(); + + // Get the sequencer for the read request channel (AR). Can only be called after build_phase, and + // the agent must be active. + extern function read_request_sequencer_t get_read_request_sequencer(); + + // Get the sequencer for the read data channel (R). Can only be called after build_phase, and the + // agent must be active. + extern function read_data_sequencer_t get_read_data_sequencer(); + + // Get the write response router. Can only be called after build_phase, and the + // agent must be active. + extern function axi_response_router get_write_response_router(); + + // Get the read response router. Can only be called after build_phase, and the + // agent must be active. + extern function axi_response_router get_read_response_router(); +endclass + +function axi_mgr_agent::new(string name, uvm_component parent); + super.new(name, parent); +endfunction + +function void axi_mgr_agent::build_phase(uvm_phase phase); + super.build_phase(phase); + + if (m_cfg == null && !uvm_config_db#(axi_agent_cfg)::get(this, "", "cfg", m_cfg)) begin + `uvm_fatal(get_full_name(), "failed to get cfg object from uvm_config_db") + end + + // Generate a reset monitor for each of the five channels. + m_reset_monitor_aw = axi_reset_monitor_aw::type_id::create("m_reset_monitor_aw", this); + m_reset_monitor_w = axi_reset_monitor_w::type_id::create("m_reset_monitor_w", this); + m_reset_monitor_b = axi_reset_monitor_b::type_id::create("m_reset_monitor_b", this); + m_reset_monitor_ar = axi_reset_monitor_ar::type_id::create("m_reset_monitor_ar", this); + m_reset_monitor_r = axi_reset_monitor_r::type_id::create("m_reset_monitor_r", this); + + if (get_is_active() == UVM_ACTIVE) begin + // Create routers for write and read responses + m_write_response_router = axi_response_router::type_id::create("m_write_response_router", this); + m_read_response_router = axi_response_router::type_id::create("m_read_response_router", this); + + // Generate drivers and sequencers for the five channels + // The write request channel (AW) + m_write_request_driver = + axi_mgr_write_request_driver::type_id::create("m_write_request_driver", this); + m_write_request_sequencer = + write_request_sequencer_t::type_id::create("m_write_request_sequencer", this); + + // The write data channel (W) + m_write_data_driver = axi_mgr_write_data_driver::type_id::create("m_write_data_driver", this); + m_write_data_sequencer = + write_data_sequencer_t::type_id::create("m_write_data_sequencer", this); + + // The write response channel (B) + m_write_response_driver = + axi_mgr_write_response_driver::type_id::create("m_write_response_driver", this); + m_write_response_sequencer = + write_response_sequencer_t::type_id::create("m_write_response_sequencer", this); + + // The read request channel (AR) + m_read_request_driver = + axi_mgr_read_request_driver::type_id::create("m_read_request_driver", this); + m_read_request_sequencer = + read_request_sequencer_t::type_id::create("m_read_request_sequencer", this); + + // The read data channel (R) + m_read_data_driver = axi_mgr_read_data_driver::type_id::create("m_read_data_driver", this); + m_read_data_sequencer = read_data_sequencer_t::type_id::create("m_read_data_sequencer", this); + end +endfunction + +function void axi_mgr_agent::connect_phase(uvm_phase phase); + super.connect_phase(phase); + + m_reset_monitor_aw.set_vif(m_cfg.write_request_vif); + m_reset_monitor_w.set_vif(m_cfg.write_data_vif); + m_reset_monitor_b.set_vif(m_cfg.write_response_vif); + m_reset_monitor_ar.set_vif(m_cfg.read_request_vif); + m_reset_monitor_r.set_vif(m_cfg.read_data_vif); + + // If the agent is active, connect drivers to interfaces and sequencers + if (get_is_active() == UVM_ACTIVE) begin + m_write_request_driver.set_vif(m_cfg.write_request_vif); + m_write_request_driver.seq_item_port.connect(m_write_request_sequencer.seq_item_export); + + m_write_data_driver.set_vif(m_cfg.write_data_vif); + m_write_data_driver.seq_item_port.connect(m_write_data_sequencer.seq_item_export); + + m_write_response_driver.set_vif(m_cfg.write_response_vif); + m_write_response_driver.seq_item_port.connect(m_write_response_sequencer.seq_item_export); + + m_read_request_driver.set_vif(m_cfg.read_request_vif); + m_read_request_driver.seq_item_port.connect(m_read_request_sequencer.seq_item_export); + + m_read_data_driver.set_vif(m_cfg.read_data_vif); + m_read_data_driver.seq_item_port.connect(m_read_data_sequencer.seq_item_export); + + // Connect the write response router to reset events from the AW channel (which will match the + // events from W and B) + m_reset_monitor_aw.m_analysis_port.connect(m_write_response_router.reset_imp); + + // Connect the read response router to reset events from the AR channel (which will match the + // events from R) + m_reset_monitor_ar.m_analysis_port.connect(m_read_response_router.reset_imp); + end +endfunction + +function void axi_mgr_agent::set_cfg(axi_agent_cfg cfg); + if (m_cfg != null) `uvm_fatal(get_full_name(), "Cannot set cfg: m_cfg is already non-null.") + m_cfg = cfg; +endfunction + +function axi_reset_monitor_aw axi_mgr_agent::get_write_request_reset_monitor(); + if (m_reset_monitor_aw == null) + `uvm_fatal(get_full_name(), "m_reset_monitor_aw is null.") + return m_reset_monitor_aw; +endfunction + +function axi_reset_monitor_w axi_mgr_agent::get_write_data_reset_monitor(); + if (m_reset_monitor_w == null) + `uvm_fatal(get_full_name(), "m_reset_monitor_w is null.") + return m_reset_monitor_w; +endfunction + +function axi_reset_monitor_b axi_mgr_agent::get_write_response_reset_monitor(); + if (m_reset_monitor_b == null) + `uvm_fatal(get_full_name(), "m_reset_monitor_b is null.") + return m_reset_monitor_b; +endfunction + +function axi_reset_monitor_ar axi_mgr_agent::get_read_request_reset_monitor(); + if (m_reset_monitor_ar == null) + `uvm_fatal(get_full_name(), "m_reset_monitor_ar is null.") + return m_reset_monitor_ar; +endfunction + +function axi_reset_monitor_r axi_mgr_agent::get_read_data_reset_monitor(); + if (m_reset_monitor_r == null) + `uvm_fatal(get_full_name(), "m_reset_monitor_r is null.") + return m_reset_monitor_r; +endfunction + +function write_request_sequencer_t axi_mgr_agent::get_write_request_sequencer(); + if (m_write_request_sequencer == null) + `uvm_fatal(get_full_name(), "m_write_request_sequencer is null.") + return m_write_request_sequencer; +endfunction + +function write_data_sequencer_t axi_mgr_agent::get_write_data_sequencer(); + if (m_write_data_sequencer == null) + `uvm_fatal(get_full_name(), "m_write_data_sequencer is null.") + return m_write_data_sequencer; +endfunction + +function write_response_sequencer_t axi_mgr_agent::get_write_response_sequencer(); + if (m_write_response_sequencer == null) + `uvm_fatal(get_full_name(), "m_write_response_sequencer is null.") + return m_write_response_sequencer; +endfunction + +function read_request_sequencer_t axi_mgr_agent::get_read_request_sequencer(); + if (m_read_request_sequencer == null) + `uvm_fatal(get_full_name(), "m_read_request_sequencer is null.") + return m_read_request_sequencer; +endfunction + +function read_data_sequencer_t axi_mgr_agent::get_read_data_sequencer(); + if (m_read_data_sequencer == null) + `uvm_fatal(get_full_name(), "m_read_data_sequencer is null.") + return m_read_data_sequencer; +endfunction + +function axi_response_router axi_mgr_agent::get_write_response_router(); + if (m_write_response_router == null) + `uvm_fatal(get_full_name(), "m_write_response_router is null.") + return m_write_response_router; +endfunction + +function axi_response_router axi_mgr_agent::get_read_response_router(); + if (m_read_response_router == null) + `uvm_fatal(get_full_name(), "m_read_response_router is null.") + return m_read_response_router; +endfunction From 270499c9a62d46b1e30d0e4a2197ad33c773903f Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Mon, 15 Jun 2026 15:20:51 +0100 Subject: [PATCH 10/22] [dv,axi] Define a sequence with a single read or write request To make it easier to "write address 123", there is an easy way to constrain the (single) sequence item in the sequence. Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 2 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 2 + .../seq_lib/axi_mgr_txn_request_seq.svh | 92 +++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_txn_request_seq.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 11432f9f7..7c8996d30 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -42,6 +42,8 @@ filesets: - axi_mgr_agent.svh: {is_include_file: true} + - seq_lib/axi_mgr_txn_request_seq.svh: {is_include_file: true} + file_type: systemVerilogSource targets: diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 21c197f93..3b13bcffd 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -50,4 +50,6 @@ package axi_agent_pkg; `include "axi_agent_cfg.svh" `include "axi_mgr_agent.svh" + + `include "seq_lib/axi_mgr_txn_request_seq.svh" endpackage diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_txn_request_seq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_txn_request_seq.svh new file mode 100644 index 000000000..3fad6ae16 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_txn_request_seq.svh @@ -0,0 +1,92 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence that sends a single item (a write request (AW) or read request (AR) transfer) +// +// When it completes, the rsp field will contain a status item that shows whether the sequence ran +// to completion (rather than being interrupted by a reset). + +class axi_mgr_txn_request_seq extends uvm_sequence #(axi_txn_request_item, axi_status_item); + `uvm_object_utils(axi_mgr_txn_request_seq) + + // This sequence uses late randomisation, so doesn't randomise the request until it is scheduled + // on the sequencer. Of course, this is a bit tricky to use in a situation where you want to + // control a particular variable when randomising. To do so for a field called XYZ, set + // m_use_fixed_XYZ=1 and set m_fixed_XYZ to the required value. Do this before starting the + // sequence and m_XYZ in the generated item will be "randomised" to match m_fixed_XYZ. + // + // The pairs of variables below are named to match the fields of axi_txn_request_item. + + bit m_use_fixed_id; + bit [31:0] m_fixed_id; + + bit m_use_fixed_addr; + bit [63:0] m_fixed_addr; + + bit m_use_fixed_region; + bit [3:0] m_fixed_region; + + bit m_use_fixed_len; + bit [7:0] m_fixed_len; + + bit m_use_fixed_size; + bit [2:0] m_fixed_size; + + bit m_use_fixed_burst; + burst_e m_fixed_burst; + + bit m_use_fixed_lock; + bit m_fixed_lock; + + bit m_use_fixed_cache; + bit [3:0] m_fixed_cache; + + bit m_use_fixed_prot; + bit [2:0] m_fixed_prot; + + bit m_use_fixed_qos; + bit [3:0] m_fixed_qos; + + bit m_use_fixed_user; + bit [127:0] m_fixed_user; + + extern function new(string name=""); + extern task body(); +endclass + +function axi_mgr_txn_request_seq::new(string name=""); + super.new(name); +endfunction + +task axi_mgr_txn_request_seq::body(); + axi_txn_request_item item = axi_txn_request_item::type_id::create("item"); + uvm_sequence_item base_status_item; + + start_item(item); + + if (!item.randomize() with { + local::m_use_fixed_id -> m_id == local::m_fixed_id; + local::m_use_fixed_addr -> m_addr == local::m_fixed_addr; + local::m_use_fixed_region -> m_region == local::m_fixed_region; + local::m_use_fixed_len -> m_len == local::m_fixed_len; + local::m_use_fixed_size -> m_size == local::m_fixed_size; + local::m_use_fixed_burst -> m_burst == local::m_fixed_burst; + local::m_use_fixed_lock -> m_lock == local::m_fixed_lock; + local::m_use_fixed_cache -> m_cache == local::m_fixed_cache; + local::m_use_fixed_prot -> m_prot == local::m_fixed_prot; + local::m_use_fixed_qos -> m_qos == local::m_fixed_qos; + local::m_use_fixed_user -> m_user == local::m_fixed_user; + }) begin + `uvm_fatal(get_full_name(), "Failed to randomise item.") + end + + finish_item(item); + + // Get a response, which will always be sent by the driver (and is available already: there's no + // pipelining and finish_item just completed). + get_base_response(base_status_item); + if (!$cast(rsp, base_status_item)) begin + `uvm_fatal(get_full_name(), "Status response is not an axi_status_item") + end +endtask From a474fd232b4f67a4bb8c35ed3feb753392dedeb1 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Mon, 15 Jun 2026 15:39:00 +0100 Subject: [PATCH 11/22] [dv,axi] Define sequences with AXI data writes The general sequence is axi_mgr_write_data_seq, but there is also a specialisation that sends exactly one transfer. This makes it easier to randomise a degenerate, length-one burst. Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 2 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 2 + .../seq_lib/axi_mgr_write_data_seq.svh | 66 +++++++++++++++++++ .../seq_lib/axi_mgr_write_single_data_seq.svh | 38 +++++++++++ 4 files changed, 108 insertions(+) create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_data_seq.svh create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_single_data_seq.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 7c8996d30..b8b50e598 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -43,6 +43,8 @@ filesets: - axi_mgr_agent.svh: {is_include_file: true} - seq_lib/axi_mgr_txn_request_seq.svh: {is_include_file: true} + - seq_lib/axi_mgr_write_data_seq.svh: {is_include_file: true} + - seq_lib/axi_mgr_write_single_data_seq.svh: {is_include_file: true} file_type: systemVerilogSource diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 3b13bcffd..af777b876 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -52,4 +52,6 @@ package axi_agent_pkg; `include "axi_mgr_agent.svh" `include "seq_lib/axi_mgr_txn_request_seq.svh" + `include "seq_lib/axi_mgr_write_data_seq.svh" + `include "seq_lib/axi_mgr_write_single_data_seq.svh" endpackage diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_data_seq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_data_seq.svh new file mode 100644 index 000000000..a6b117606 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_data_seq.svh @@ -0,0 +1,66 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence that sends a sequence of axi_write_data_item items, representing a single burst. + +class axi_mgr_write_data_seq extends uvm_sequence #(axi_write_data_item, axi_status_item); + `uvm_object_utils(axi_mgr_write_data_seq) + + // The number of items to send in the burst. This should be constrained by the virtual sequence + // that creates this sequence to match the m_len value that the virtual sequence is sending on AW. + rand int unsigned m_number_of_items; + + extern function new(string name=""); + extern task body(); + + // Randomize an item that is about to be sent. Defining this explicitly allows a sequence + // extending this one to more easily constrain the randomisation of the items that get sent. + // + // The is_last argument is set when this is the last item in the stream of data words. + extern protected virtual function void randomize_item(axi_write_data_item item, bit is_last); + + // The number of items needs to match m_len from the AW channel, which is represented by an 8-bit + // value that gives the last index. As such, m_number_of_items should be in the range 1..256. + extern constraint number_of_items_c; +endclass + +function axi_mgr_write_data_seq::new(string name=""); + super.new(name); +endfunction + +task axi_mgr_write_data_seq::body(); + for (int unsigned i = 0; i < m_number_of_items; i++) begin + uvm_sequence_item base_status_item; + axi_write_data_item item = axi_write_data_item::type_id::create("item"); + + start_item(item); + randomize_item(item, i + 1 == m_number_of_items); + finish_item(item); + + // Get the response from the driver and check it is actually of the expected axi_status_item + // type. + get_base_response(base_status_item); + if (!$cast(rsp, base_status_item)) begin + `uvm_fatal(get_full_name(), "Status response is not an axi_status_item") + end + + // If the driver didn't finish sending the item, there has been a reset and we should stop + // immediately. The virtual sequence can see that we saw the reset because it will see + // m_sending_complete false in the rsp field. + if (!rsp.m_sending_complete) break; + end +endtask + +function void axi_mgr_write_data_seq::randomize_item(axi_write_data_item item, bit is_last); + if (!item.randomize() with { + m_last == local::is_last; + }) begin + `uvm_fatal(get_full_name(), "Failed to randomise item.") + end +endfunction + +constraint axi_mgr_write_data_seq::number_of_items_c { + 1 <= m_number_of_items; + m_number_of_items <= 256; +} diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_single_data_seq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_single_data_seq.svh new file mode 100644 index 000000000..831bdce39 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_single_data_seq.svh @@ -0,0 +1,38 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A subclass of axi_mgr_write_data_seq that sends a single sequence item +// +// This can be used for a write where LEN is zero. + +class axi_mgr_write_single_data_seq extends axi_mgr_write_data_seq; + `uvm_object_utils(axi_mgr_write_single_data_seq) + + // The data to be written + rand axi_write_data_item m_write_data_item; + + extern function new(string name=""); + + // An override of axi_mgr_write_data_seq::randomize_item, ensuring that the item that is sent + // matches m_write_data_item. + extern protected virtual function void randomize_item(axi_write_data_item item, bit is_last); + + // Write exactly one item + extern constraint single_item_c; +endclass + +function axi_mgr_write_single_data_seq::new(string name=""); + super.new(name); + m_write_data_item = axi_write_data_item::type_id::create("m_write_data_item"); +endfunction + +function void axi_mgr_write_single_data_seq::randomize_item(axi_write_data_item item, bit is_last); + // This completely replaces the base class function, and the "randomisation" is done by copying + // from m_write_data_item. + item.copy(m_write_data_item); +endfunction + +constraint axi_mgr_write_single_data_seq::single_item_c { + m_number_of_items == 1; +} From ebca3b503c67b89e1c4b41975d18cb0757793eb3 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Mon, 15 Jun 2026 17:10:49 +0100 Subject: [PATCH 12/22] [dv,axi] Define a sequence to consume the AXI write response --- hw/ip/dv/axi_agent/axi_agent.core | 1 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 1 + .../seq_lib/axi_mgr_write_response_seq.svh | 76 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_response_seq.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index b8b50e598..cf6f3175f 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -45,6 +45,7 @@ filesets: - seq_lib/axi_mgr_txn_request_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_write_data_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_write_single_data_seq.svh: {is_include_file: true} + - seq_lib/axi_mgr_write_response_seq.svh: {is_include_file: true} file_type: systemVerilogSource diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index af777b876..8ebdd05e5 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -54,4 +54,5 @@ package axi_agent_pkg; `include "seq_lib/axi_mgr_txn_request_seq.svh" `include "seq_lib/axi_mgr_write_data_seq.svh" `include "seq_lib/axi_mgr_write_single_data_seq.svh" + `include "seq_lib/axi_mgr_write_response_seq.svh" endpackage diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_response_seq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_response_seq.svh new file mode 100644 index 000000000..a45032d78 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_response_seq.svh @@ -0,0 +1,76 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence that sends a single axi_response_accept_item. The response to that item will be stored +// in the rsp class variable, which will be null if the sequence saw a reset before a response. + +class axi_mgr_write_response_seq extends uvm_sequence #(axi_response_accept_item, + axi_write_response_item); + `uvm_object_utils(axi_mgr_write_response_seq) + + // This sequence uses late randomisation, so doesn't randomise the request until it is scheduled + // on the sequencer. Of course, this is a bit tricky to use in a situation where you want to + // control a particular variable when randomising. To do so for a field called XYZ, set + // m_use_fixed_XYZ=1 and set m_fixed_XYZ to the required value. Do this before starting the + // sequence and m_XYZ in the generated item will be "randomised" to match m_fixed_XYZ. + // + // The pairs of variables below are named to match the fields of axi_response_accept_item. + + bit m_use_fixed_ready_without_valid_pct; + int unsigned m_fixed_ready_without_valid_pct; + + bit m_use_fixed_valid_to_ready_delay; + int unsigned m_fixed_valid_to_ready_delay; + + extern function new(string name=""); + extern task body(); +endclass + +function axi_mgr_write_response_seq::new(string name=""); + super.new(name); +endfunction + +task axi_mgr_write_response_seq::body(); + axi_response_accept_item item = axi_response_accept_item::type_id::create("item"); + uvm_sequence_item base_response_item; + + start_item(item); + + if (!item.randomize() with { + local::m_use_fixed_ready_without_valid_pct -> + m_ready_without_valid_pct == local::m_fixed_ready_without_valid_pct; + + local::m_use_fixed_valid_to_ready_delay -> + m_valid_to_ready_delay == local::m_fixed_valid_to_ready_delay; + }) begin + `uvm_fatal(get_full_name(), "Failed to randomise item.") + end + + finish_item(item); + + // Get a response from the driver, which will either be an axi_write_response_item (meaning that + // we have seen a response on the interface) or an axi_status_item with m_sending_complete == 0 + // (meaning that we saw a reset). + // + // If there is a response, set rsp to match. If not, leave rsp == null. + get_base_response(base_response_item); + + if ($cast(rsp, base_response_item)) begin + // The response was an axi_write_response_item and we have now set the sequence response to + // match: we're done and can just return. + end else begin + axi_status_item status_item; + if (!$cast(status_item, base_response_item)) begin + `uvm_fatal(get_full_name(), + {"Driver responded with an item that is ", + "neither an axi_write_response_item nor an axi_status_item."}) + end + + // If the cast succeeded, check that the driver did indeed leave m_sending_complete=0, then do + // nothing more: we can just leave rsp null. + if (status_item.m_sending_complete) begin + `uvm_fatal(get_full_name(), "Driver responded with a complete status. (?!)") + end + end +endtask From c161b610921764d213bf164d68256444eb636745 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Mon, 15 Jun 2026 20:07:26 +0100 Subject: [PATCH 13/22] [dv,axi] Define a sequence to consume AXI read data --- hw/ip/dv/axi_agent/axi_agent.core | 1 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 1 + .../seq_lib/axi_mgr_read_data_seq.svh | 74 +++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_data_seq.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index cf6f3175f..9f9e73923 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -46,6 +46,7 @@ filesets: - seq_lib/axi_mgr_write_data_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_write_single_data_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_write_response_seq.svh: {is_include_file: true} + - seq_lib/axi_mgr_read_data_seq.svh: {is_include_file: true} file_type: systemVerilogSource diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 8ebdd05e5..43be4e63c 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -55,4 +55,5 @@ package axi_agent_pkg; `include "seq_lib/axi_mgr_write_data_seq.svh" `include "seq_lib/axi_mgr_write_single_data_seq.svh" `include "seq_lib/axi_mgr_write_response_seq.svh" + `include "seq_lib/axi_mgr_read_data_seq.svh" endpackage diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_data_seq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_data_seq.svh new file mode 100644 index 000000000..ced9ddcc6 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_data_seq.svh @@ -0,0 +1,74 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence that sends a single axi_response_accept_item. The read_data that is accepted in +// response to that item will be stored in the rsp class variable, which will be null if the +// sequence saw a reset and stopped early. + +class axi_mgr_read_data_seq extends uvm_sequence #(axi_response_accept_item, axi_read_data_item); + `uvm_object_utils(axi_mgr_read_data_seq) + + // This sequence uses late randomisation, so doesn't randomise the request until it is scheduled + // on the sequencer. Of course, this is a bit tricky to use in a situation where you want to + // control a particular variable when randomising. To do so for a field called XYZ, set + // m_use_fixed_XYZ=1 and set m_fixed_XYZ to the required value. Do this before starting the + // sequence and m_XYZ in the generated item will be "randomised" to match m_fixed_XYZ. + // + // The pairs of variables below are named to match the fields of axi_response_accept_item. + + bit m_use_fixed_ready_without_valid_pct; + int unsigned m_fixed_ready_without_valid_pct; + + bit m_use_fixed_valid_to_ready_delay; + int unsigned m_fixed_valid_to_ready_delay; + + extern function new(string name=""); + extern task body(); +endclass + +function axi_mgr_read_data_seq::new(string name=""); + super.new(name); +endfunction + +task axi_mgr_read_data_seq::body(); + axi_response_accept_item item = axi_response_accept_item::type_id::create("item"); + uvm_sequence_item base_response_item; + + start_item(item); + + if (!item.randomize() with { + local::m_use_fixed_ready_without_valid_pct -> + m_ready_without_valid_pct == local::m_fixed_ready_without_valid_pct; + + local::m_use_fixed_valid_to_ready_delay -> + m_valid_to_ready_delay == local::m_fixed_valid_to_ready_delay; + }) begin + `uvm_fatal(get_full_name(), "Failed to randomise item.") + end + + finish_item(item); + + // Get the response from the driver. If read data has been seen, it will be sent as an + // axi_read_data_item, which we can pass back as the sequence response. If not, there should be an + // axi_status_item, which we consume but needn't pass back further: the sequence can just have a + // null rsp. + get_base_response(base_response_item); + + if ($cast(rsp, base_response_item)) begin + // The response was an axi_read_data_item. We're done and can just return. + end else begin + axi_status_item status_item; + if (!$cast(status_item, base_response_item)) begin + `uvm_fatal(get_full_name(), + {"Driver responded with an item that is neither ", + "an axi_read_data_item nor an axi_status_item."}) + end + + // If the cast succeeded, check that the driver did indeed leave m_sending_complete=0, then do + // nothing more: we can just leave rsp null. + if (status_item.m_sending_complete) begin + `uvm_fatal(get_full_name(), "Driver responded with a complete status. (?!)") + end + end +endtask From 5df00542e629323b5afe2cc8bb636f14ec4de96a Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Mon, 15 Jun 2026 20:44:10 +0100 Subject: [PATCH 14/22] [dv,axi] A virtual sequence that does a single write transfer Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 5 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 4 + .../dv/axi_agent/axi_fixed_write_req_item.svh | 113 +++++++++++++ .../dv/axi_agent/axi_fixed_write_rsp_item.svh | 75 +++++++++ .../seq_lib/axi_mgr_write_fixed_vseq.svh | 151 ++++++++++++++++++ 5 files changed, 348 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_fixed_write_req_item.svh create mode 100644 hw/ip/dv/axi_agent/axi_fixed_write_rsp_item.svh create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_fixed_vseq.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 9f9e73923..d055d7aa0 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -26,6 +26,9 @@ filesets: - axi_write_data_item.svh: {is_include_file: true} - axi_write_response_item.svh: {is_include_file: true} + - axi_fixed_write_req_item.svh: {is_include_file: true} + - axi_fixed_write_rsp_item.svh: {is_include_file: true} + - axi_reset_monitor_aw.svh: {is_include_file: true} - axi_reset_monitor_w.svh: {is_include_file: true} - axi_reset_monitor_b.svh: {is_include_file: true} @@ -48,6 +51,8 @@ filesets: - seq_lib/axi_mgr_write_response_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_read_data_seq.svh: {is_include_file: true} + - seq_lib/axi_mgr_write_fixed_vseq.svh: {is_include_file: true} + file_type: systemVerilogSource targets: diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 43be4e63c..58e5e7bf0 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -56,4 +56,8 @@ package axi_agent_pkg; `include "seq_lib/axi_mgr_write_single_data_seq.svh" `include "seq_lib/axi_mgr_write_response_seq.svh" `include "seq_lib/axi_mgr_read_data_seq.svh" + + `include "axi_fixed_write_req_item.svh" + `include "axi_fixed_write_rsp_item.svh" + `include "seq_lib/axi_mgr_write_fixed_vseq.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_fixed_write_req_item.svh b/hw/ip/dv/axi_agent/axi_fixed_write_req_item.svh new file mode 100644 index 000000000..f7e80b41b --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_fixed_write_req_item.svh @@ -0,0 +1,113 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item that represents the request that should be made to send a single AXI write with +// AWBURST set to FIXED and AWLEN = 0 (so just a single data transfer). + +class axi_fixed_write_req_item extends uvm_sequence_item; + `uvm_object_utils(axi_fixed_write_req_item) + // Transaction identifier for the write channel + // + // This is sent as AWID, whose width is configurable (and will be checked against the + // corresponding width in the interface by the driver). + rand bit [31:0] m_id; + + // The address. + // + // This is AWADDR, whose width is configurable (and will be checked against the + // corresponding width in the interface by the driver). + rand bit [63:0] m_addr; + + // The region identifier, sent as AWREGION in the request transfer. + rand bit [3:0] m_region; + + // Number of bytes in the data transfer. Sent as AWSIZE and encoded as log2(byte_size). + rand bit [2:0] m_size; + + // Request exclusive access? Sent as AWLOCK. + rand bit m_lock; + + // Memory attributes (applying to caches in the system). Sent as AWCACHE. + rand bit [3:0] m_cache; + + // Memory access attributes. Sent as AWPROT. + rand bit [2:0] m_prot; + + // Traffic stream QoS identifier. Sent as AWQOS. + rand bit [3:0] m_qos; + + // Extra user bits. + // + // This is sent as AWUSER, whose width is configurable (and will be checked against the + // corresponding width in the interface by the driver). + rand bit [127:0] m_user; + + // An item representing the data to be written + rand axi_write_data_item m_write_data_item; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_fixed_write_req_item::new(string name = ""); + super.new(name); + m_write_data_item = axi_write_data_item::type_id::create("m_write_data_item"); +endfunction + +function void axi_fixed_write_req_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_field_int("m_id", m_id, 32, UVM_HEX); + printer.print_field("m_addr", m_addr, 64, UVM_HEX); + printer.print_field_int("m_region", m_region, 4, UVM_HEX); + printer.print_field_int("m_size", m_size, 3, UVM_DEC); + printer.print_field_int("m_lock", m_lock, 1, UVM_BIN); + printer.print_field_int("m_cache", m_cache, 4, UVM_BIN); + printer.print_field_int("m_prot", m_prot, 3, UVM_BIN); + printer.print_field_int("m_qos", m_qos, 4, UVM_HEX); + printer.print_field("m_user", m_user, 128, UVM_HEX); + printer.print_object("m_write_data_item", m_write_data_item); +endfunction + +function void axi_fixed_write_req_item::do_copy(uvm_object rhs); + axi_fixed_write_req_item rhs_; + + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + this.m_id = rhs_.m_id; + this.m_addr = rhs_.m_addr; + this.m_region = rhs_.m_region; + this.m_size = rhs_.m_size; + this.m_lock = rhs_.m_lock; + this.m_cache = rhs_.m_cache; + this.m_prot = rhs_.m_prot; + this.m_qos = rhs_.m_qos; + this.m_user = rhs_.m_user; + this.m_write_data_item.copy(rhs_.m_write_data_item); +endfunction + +function bit axi_fixed_write_req_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_fixed_write_req_item rhs_; + + // These items are only equivalent if rhs is actually an axi_fixed_write_req_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_fixed_write_req_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field_int("m_id", m_id, rhs_.m_id, 32, UVM_HEX) & + comparer.compare_field("m_addr", m_addr, rhs_.m_addr, 64, UVM_HEX) & + comparer.compare_field_int("m_region", m_region, rhs_.m_region, 4, UVM_HEX) & + comparer.compare_field_int("m_size", m_size, rhs_.m_size, 3, UVM_DEC) & + comparer.compare_field_int("m_lock", m_lock, rhs_.m_lock, 1, UVM_BIN) & + comparer.compare_field_int("m_cache", m_cache, rhs_.m_cache, 4, UVM_BIN) & + comparer.compare_field_int("m_prot", m_prot, rhs_.m_prot, 3, UVM_BIN) & + comparer.compare_field_int("m_qos", m_qos, rhs_.m_qos, 4, UVM_HEX) & + comparer.compare_field("m_user", m_user, rhs_.m_user, 128, UVM_HEX) & + comparer.compare_object("m_write_data_item", m_write_data_item, rhs_.m_write_data_item)); +endfunction diff --git a/hw/ip/dv/axi_agent/axi_fixed_write_rsp_item.svh b/hw/ip/dv/axi_agent/axi_fixed_write_rsp_item.svh new file mode 100644 index 000000000..bac0581e2 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_fixed_write_rsp_item.svh @@ -0,0 +1,75 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item that represents a response to an AXI write with a single beat +// +// The fields are null when the instance is created, and should be filled with results of the three +// sequences (which may be null if the sequences were interrupted by a reset). + +class axi_fixed_write_rsp_item extends uvm_sequence_item; + `uvm_object_utils(axi_fixed_write_rsp_item) + // The status of the write request transfer (which either completed or was interrupted by reset) + axi_status_item m_aw_status; + + // The status of the write data transfer (which either completed or was interrupted by reset) + axi_status_item m_w_status; + + // The write response seen (on the B channel) + axi_write_response_item m_write_response; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_fixed_write_rsp_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_fixed_write_rsp_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_object("m_aw_status", m_aw_status); + printer.print_object("m_w_status", m_w_status); + printer.print_object("m_write_response", m_write_response); +endfunction + +function void axi_fixed_write_rsp_item::do_copy(uvm_object rhs); + axi_fixed_write_rsp_item rhs_; + + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + if (rhs_.m_aw_status == null) m_aw_status = null; + else if (!$cast(m_aw_status, rhs_.m_aw_status.clone())) begin + `uvm_fatal("do_copy", "Failed to clone m_aw_status.") + end + + if (rhs_.m_w_status == null) m_w_status = null; + else if (!$cast(m_w_status, rhs_.m_w_status.clone())) begin + `uvm_fatal("do_copy", "Failed to clone m_w_status.") + end + + if (rhs_.m_write_response == null) m_write_response = null; + if (!$cast(m_write_response, rhs_.m_write_response.clone())) begin + `uvm_fatal("do_copy", "Failed to clone m_write_response.") + end +endfunction + +function bit axi_fixed_write_rsp_item::do_compare(uvm_object rhs, uvm_comparer comparer); + bit all_match = 1; + axi_fixed_write_rsp_item rhs_; + + // These items are only equivalent if rhs is actually an axi_fixed_write_rsp_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_fixed_write_rsp_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_object("m_aw_status", m_aw_status, rhs_.m_aw_status) & + comparer.compare_object("m_w_status", m_w_status, rhs_.m_w_status) & + comparer.compare_object("m_write_response", m_write_response, rhs_.m_write_response)); +endfunction diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_fixed_vseq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_fixed_vseq.svh new file mode 100644 index 000000000..f071a4d2d --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_fixed_vseq.svh @@ -0,0 +1,151 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A virtual sequence that sends a single AXI write with AWBURST set to FIXED and AWLEN = 0 (so +// there is a single data transfer) +// +// The single request item is a randomised field (m_fixed_req) and the response is created in the +// rsp field, which will be created before the sequence completes. + +class axi_mgr_write_fixed_vseq extends uvm_sequence#(uvm_sequence_item, axi_fixed_write_rsp_item); + `uvm_object_utils(axi_mgr_write_fixed_vseq) + + // The write response router. Set this by calling set_write_response_router before starting the + // sequence. + local axi_response_router m_write_response_router; + + // Sequencers for AW, W and B. Set these by calling set_sequencers before starting the sequence. + local write_request_sequencer_t m_write_request_sequencer; + local write_data_sequencer_t m_write_data_sequencer; + local write_response_sequencer_t m_write_response_sequencer; + + // An item representing the single request that will be sent. + rand axi_fixed_write_req_item m_fixed_req; + + extern function new(string name=""); + extern task body(); + + // Set the write response router + extern function void set_write_response_router(axi_response_router router); + + // Set sequencers for the AW, W and B channels + extern function void set_sequencers(write_request_sequencer_t write_request_sequencer, + write_data_sequencer_t write_data_sequencer, + write_response_sequencer_t write_response_sequencer); +endclass + +function axi_mgr_write_fixed_vseq::new(string name=""); + super.new(name); + m_fixed_req = axi_fixed_write_req_item::type_id::create("m_fixed_req"); +endfunction + +task axi_mgr_write_fixed_vseq::body(); + axi_mgr_txn_request_seq aw_seq; + axi_mgr_write_single_data_seq w_seq; + axi_mgr_write_response_seq b_seq; + uvm_sequence_item write_response_item; + axi_write_response_item write_response; + + if (m_write_response_router == null) begin + `uvm_fatal(get_full_name(), "Cannot run sequence because there is no write response router.") + end + + if (m_write_request_sequencer == null || + m_write_data_sequencer == null || + m_write_response_sequencer == null) begin + `uvm_fatal(get_full_name(), "Cannot run sequence because sequencers are not all set.") + end + + // Create the sequence to send the write request (AW). It doesn't need much randomising: we've + // actually picked all the fields already in this virtual sequence. Use the "m_use_fixed_*" + // variables to set things. + aw_seq = axi_mgr_txn_request_seq::type_id::create("aw_seq"); + aw_seq.m_use_fixed_id = 1'b1; + aw_seq.m_fixed_id = m_fixed_req.m_id; + aw_seq.m_use_fixed_addr = 1'b1; + aw_seq.m_fixed_addr = m_fixed_req.m_addr; + aw_seq.m_use_fixed_region = 1'b1; + aw_seq.m_fixed_region = m_fixed_req.m_region; + aw_seq.m_use_fixed_len = 1'b1; + aw_seq.m_fixed_len = 8'd0; + aw_seq.m_use_fixed_size = 1'b1; + aw_seq.m_fixed_size = m_fixed_req.m_size; + aw_seq.m_use_fixed_burst = 1'b1; + aw_seq.m_fixed_burst = BurstFixed; + aw_seq.m_use_fixed_lock = 1'b1; + aw_seq.m_fixed_lock = m_fixed_req.m_lock; + aw_seq.m_use_fixed_cache = 1'b1; + aw_seq.m_fixed_cache = m_fixed_req.m_cache; + aw_seq.m_use_fixed_prot = 1'b1; + aw_seq.m_fixed_prot = m_fixed_req.m_prot; + aw_seq.m_use_fixed_qos = 1'b1; + aw_seq.m_fixed_qos = m_fixed_req.m_qos; + aw_seq.m_use_fixed_user = 1'b1; + aw_seq.m_fixed_user = m_fixed_req.m_user; + + // Create the sequence to send the write data (W). Its m_write_data_item field doesn't need + // randomising: we can just set it to equal the value of m_fixed_req.m_write_data_item. + w_seq = axi_mgr_write_single_data_seq::type_id::create("w_seq"); + w_seq.m_write_data_item.rand_mode(0); + if (!w_seq.randomize()) begin + `uvm_fatal(get_full_name(), "Failed to randomize w_seq.") + end + w_seq.m_write_data_item.copy(m_fixed_req.m_write_data_item); + + // Create a sequence to receive a single write response (B). (This might or might not match our + // ID, but that doesn't matter: the point is that we need to send it as a token) + b_seq = axi_mgr_write_response_seq::type_id::create("b_seq"); + if (!b_seq.randomize()) begin + `uvm_fatal(get_full_name(), "Failed to randomize b_seq.") + end + + // Run the sequence to consume a write response in the background. The response in question might + // not be for the ID we're tracking, so we don't wait directly for it to finish. + fork begin + b_seq.start(m_write_response_sequencer); + + // Once the B channel sequence has finished, pass its response to the router (unless there is no + // response, which means that both it and the router are seeing a reset). + if (b_seq.rsp != null) begin + m_write_response_router.on_response(b_seq.rsp.m_id, b_seq.rsp); + end + end join_none + + // Run the other sequences. This waits for aw_seq and w_seq to complete and also waits for a + // response from m_write_response_router (which may or may not be the response to b_seq). + fork + aw_seq.start(m_write_request_sequencer); + w_seq.start(m_write_data_sequencer); + m_write_response_router.wait_for_response(m_fixed_req.m_id, write_response_item); + join + + // At this point, the AW and W sequences have completed (either by sending their items or by + // seeing a reset) and wait_for_response has completed, writing write_response_item (which is + // null on reset). Downcast it to the concrete axi_write_response_item type. + if (!$cast(write_response, write_response_item)) + `uvm_fatal(get_full_name(), "wait_for_response returned unexpected item type") + + rsp = axi_fixed_write_rsp_item::type_id::create("rsp"); + rsp.m_aw_status = aw_seq.rsp; + rsp.m_w_status = w_seq.rsp; + rsp.m_write_response = write_response; +endtask + +function void axi_mgr_write_fixed_vseq::set_write_response_router(axi_response_router router); + if (router == null) `uvm_fatal(get_full_name(), "Router is null.") + m_write_response_router = router; +endfunction + +function void + axi_mgr_write_fixed_vseq::set_sequencers(write_request_sequencer_t write_request_sequencer, + write_data_sequencer_t write_data_sequencer, + write_response_sequencer_t write_response_sequencer); + if (write_request_sequencer == null) `uvm_fatal(get_full_name(), "No write_request_sequencer.") + if (write_data_sequencer == null) `uvm_fatal(get_full_name(), "No write_data_sequencer.") + if (write_response_sequencer == null) `uvm_fatal(get_full_name(), "No write_response_sequencer.") + + m_write_request_sequencer = write_request_sequencer; + m_write_data_sequencer = write_data_sequencer; + m_write_response_sequencer = write_response_sequencer; +endfunction From fa138f2b4b0e5b140fa67feb6a1f2faaee85b4ba Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Tue, 16 Jun 2026 15:08:19 +0100 Subject: [PATCH 15/22] [dv,axi] A virtual sequence that does a single read transfer Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 3 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 4 + .../dv/axi_agent/axi_fixed_read_req_item.svh | 106 ++++++++++++++ .../dv/axi_agent/axi_fixed_read_rsp_item.svh | 65 +++++++++ .../seq_lib/axi_mgr_read_fixed_vseq.svh | 133 ++++++++++++++++++ 5 files changed, 311 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_fixed_read_req_item.svh create mode 100644 hw/ip/dv/axi_agent/axi_fixed_read_rsp_item.svh create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_fixed_vseq.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index d055d7aa0..9ad33eed9 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -28,6 +28,8 @@ filesets: - axi_fixed_write_req_item.svh: {is_include_file: true} - axi_fixed_write_rsp_item.svh: {is_include_file: true} + - axi_fixed_read_req_item.svh: {is_include_file: true} + - axi_fixed_read_rsp_item.svh: {is_include_file: true} - axi_reset_monitor_aw.svh: {is_include_file: true} - axi_reset_monitor_w.svh: {is_include_file: true} @@ -52,6 +54,7 @@ filesets: - seq_lib/axi_mgr_read_data_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_write_fixed_vseq.svh: {is_include_file: true} + - seq_lib/axi_mgr_read_fixed_vseq.svh: {is_include_file: true} file_type: systemVerilogSource diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 58e5e7bf0..19dc22ab3 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -60,4 +60,8 @@ package axi_agent_pkg; `include "axi_fixed_write_req_item.svh" `include "axi_fixed_write_rsp_item.svh" `include "seq_lib/axi_mgr_write_fixed_vseq.svh" + + `include "axi_fixed_read_req_item.svh" + `include "axi_fixed_read_rsp_item.svh" + `include "seq_lib/axi_mgr_read_fixed_vseq.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_fixed_read_req_item.svh b/hw/ip/dv/axi_agent/axi_fixed_read_req_item.svh new file mode 100644 index 000000000..daccaf11a --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_fixed_read_req_item.svh @@ -0,0 +1,106 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item that represents the request that should be made to send a single AXI read with +// AWBURST set to FIXED and AWLEN = 0 (so just a single data transfer). + +class axi_fixed_read_req_item extends uvm_sequence_item; + `uvm_object_utils(axi_fixed_read_req_item) + // Transaction identifier for the read channel + // + // This is sent as AWID, whose width is configurable (and will be checked against the + // corresponding width in the interface by the driver). + rand bit [31:0] m_id; + + // The address. + // + // This is AWADDR, whose width is configurable (and will be checked against the + // corresponding width in the interface by the driver). + rand bit [63:0] m_addr; + + // The region identifier, sent as AWREGION in the request transfer. + rand bit [3:0] m_region; + + // Number of bytes in the data transfer. Sent as AWSIZE and encoded as log2(byte_size). + rand bit [2:0] m_size; + + // Request exclusive access? Sent as AWLOCK. + rand bit m_lock; + + // Memory attributes (applying to caches in the system). Sent as AWCACHE. + rand bit [3:0] m_cache; + + // Memory access attributes. Sent as AWPROT. + rand bit [2:0] m_prot; + + // Traffic stream QoS identifier. Sent as AWQOS. + rand bit [3:0] m_qos; + + // Extra user bits. + // + // This is sent as AWUSER, whose width is configurable (and will be checked against the + // corresponding width in the interface by the driver). + rand bit [127:0] m_user; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_fixed_read_req_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_fixed_read_req_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_field_int("m_id", m_id, 32, UVM_HEX); + printer.print_field("m_addr", m_addr, 64, UVM_HEX); + printer.print_field_int("m_region", m_region, 4, UVM_HEX); + printer.print_field_int("m_size", m_size, 3, UVM_DEC); + printer.print_field_int("m_lock", m_lock, 1, UVM_BIN); + printer.print_field_int("m_cache", m_cache, 4, UVM_BIN); + printer.print_field_int("m_prot", m_prot, 3, UVM_BIN); + printer.print_field_int("m_qos", m_qos, 4, UVM_HEX); + printer.print_field("m_user", m_user, 128, UVM_HEX); +endfunction + +function void axi_fixed_read_req_item::do_copy(uvm_object rhs); + axi_fixed_read_req_item rhs_; + + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + this.m_id = rhs_.m_id; + this.m_addr = rhs_.m_addr; + this.m_region = rhs_.m_region; + this.m_size = rhs_.m_size; + this.m_lock = rhs_.m_lock; + this.m_cache = rhs_.m_cache; + this.m_prot = rhs_.m_prot; + this.m_qos = rhs_.m_qos; + this.m_user = rhs_.m_user; +endfunction + +function bit axi_fixed_read_req_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_fixed_read_req_item rhs_; + + // These items are only equivalent if rhs is actually an axi_fixed_read_req_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_fixed_read_req_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field_int("m_id", m_id, rhs_.m_id, 32, UVM_HEX) & + comparer.compare_field("m_addr", m_addr, rhs_.m_addr, 64, UVM_HEX) & + comparer.compare_field_int("m_region", m_region, rhs_.m_region, 4, UVM_HEX) & + comparer.compare_field_int("m_size", m_size, rhs_.m_size, 3, UVM_DEC) & + comparer.compare_field_int("m_lock", m_lock, rhs_.m_lock, 1, UVM_BIN) & + comparer.compare_field_int("m_cache", m_cache, rhs_.m_cache, 4, UVM_BIN) & + comparer.compare_field_int("m_prot", m_prot, rhs_.m_prot, 3, UVM_BIN) & + comparer.compare_field_int("m_qos", m_qos, rhs_.m_qos, 4, UVM_HEX) & + comparer.compare_field("m_user", m_user, rhs_.m_user, 128, UVM_HEX)); +endfunction diff --git a/hw/ip/dv/axi_agent/axi_fixed_read_rsp_item.svh b/hw/ip/dv/axi_agent/axi_fixed_read_rsp_item.svh new file mode 100644 index 000000000..ea4f9817b --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_fixed_read_rsp_item.svh @@ -0,0 +1,65 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A sequence item that represents a response to an AXI read with a single beat +// +// The fields are null when the instance is created, and should be filled with results of the two +// sequences (which may be null if the sequences were interrupted by a reset). + +class axi_fixed_read_rsp_item extends uvm_sequence_item; + `uvm_object_utils(axi_fixed_read_rsp_item) + // The status of the read request transfer (which either completed or was interrupted by reset) + axi_status_item m_ar_status; + + // The single read data item seen (on the R channel) + axi_read_data_item m_read_data; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_fixed_read_rsp_item::new(string name = ""); + super.new(name); +endfunction + +function void axi_fixed_read_rsp_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_object("m_ar_status", m_ar_status); + printer.print_object("m_read_data", m_read_data); +endfunction + +function void axi_fixed_read_rsp_item::do_copy(uvm_object rhs); + axi_fixed_read_rsp_item rhs_; + + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + if (rhs_.m_ar_status == null) m_ar_status = null; + else if (!$cast(m_ar_status, rhs_.m_ar_status.clone())) begin + `uvm_fatal("do_copy", "Failed to clone m_ar_status.") + end + + if (rhs_.m_read_data == null) m_read_data = null; + else if (!$cast(m_read_data, rhs_.m_read_data.clone())) begin + `uvm_fatal("do_copy", "Failed to clone m_read_data.") + end +endfunction + +function bit axi_fixed_read_rsp_item::do_compare(uvm_object rhs, uvm_comparer comparer); + bit all_match = 1; + axi_fixed_read_rsp_item rhs_; + + // These items are only equivalent if rhs is actually an axi_fixed_read_rsp_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_fixed_read_rsp_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_object("m_ar_status", m_ar_status, rhs_.m_ar_status) & + comparer.compare_object("m_read_data", m_read_data, rhs_.m_read_data)); +endfunction diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_fixed_vseq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_fixed_vseq.svh new file mode 100644 index 000000000..7c5110952 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_fixed_vseq.svh @@ -0,0 +1,133 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A virtual sequence that sends a single AXI read with ARBURST set to FIXED and ARLEN = 0 (so +// there is a single data transfer) +// +// The single request item is a randomised field (m_fixed_req) and the response is created in the +// rsp field, which will be created before the sequence completes. + +class axi_mgr_read_fixed_vseq extends uvm_sequence#(uvm_sequence_item, axi_fixed_read_rsp_item); + `uvm_object_utils(axi_mgr_read_fixed_vseq) + + // The read response router. Set this by calling set_read_response_router before starting the + // sequence. + local axi_response_router m_read_response_router; + + // Sequencers for AR and R. Set these by calling set_sequencers before starting the sequence. + local read_request_sequencer_t m_read_request_sequencer; + local read_data_sequencer_t m_read_data_sequencer; + + // An item representing the single request that will be sent. + rand axi_fixed_read_req_item m_fixed_req; + + extern function new(string name=""); + extern task body(); + + // Set the read response router + extern function void set_read_response_router(axi_response_router router); + + // Set sequencers for the AR and R channels + extern function void set_sequencers(read_request_sequencer_t read_request_sequencer, + read_data_sequencer_t read_data_sequencer); +endclass + +function axi_mgr_read_fixed_vseq::new(string name=""); + super.new(name); + m_fixed_req = axi_fixed_read_req_item::type_id::create("m_fixed_req"); +endfunction + +task axi_mgr_read_fixed_vseq::body(); + axi_mgr_txn_request_seq ar_seq; + axi_mgr_read_data_seq r_seq; + uvm_sequence_item read_data_item; + axi_read_data_item read_data; + + if (m_read_response_router == null) begin + `uvm_fatal(get_full_name(), "Cannot run sequence because there is no read response router.") + end + + if (m_read_request_sequencer == null || m_read_data_sequencer == null) begin + `uvm_fatal(get_full_name(), "Cannot run sequence because sequencers are not both set.") + end + + // Create the sequence to send the read request (AR). It doesn't need much randomising: we've + // actually picked all the fields already in this virtual sequence. Use the "m_use_fixed_*" + // variables to set things. + ar_seq = axi_mgr_txn_request_seq::type_id::create("ar_seq"); + ar_seq.m_use_fixed_id = 1'b1; + ar_seq.m_fixed_id = m_fixed_req.m_id; + ar_seq.m_use_fixed_addr = 1'b1; + ar_seq.m_fixed_addr = m_fixed_req.m_addr; + ar_seq.m_use_fixed_region = 1'b1; + ar_seq.m_fixed_region = m_fixed_req.m_region; + ar_seq.m_use_fixed_len = 1'b1; + ar_seq.m_fixed_len = 8'd0; + ar_seq.m_use_fixed_size = 1'b1; + ar_seq.m_fixed_size = m_fixed_req.m_size; + ar_seq.m_use_fixed_burst = 1'b1; + ar_seq.m_fixed_burst = BurstFixed; + ar_seq.m_use_fixed_lock = 1'b1; + ar_seq.m_fixed_lock = m_fixed_req.m_lock; + ar_seq.m_use_fixed_cache = 1'b1; + ar_seq.m_fixed_cache = m_fixed_req.m_cache; + ar_seq.m_use_fixed_prot = 1'b1; + ar_seq.m_fixed_prot = m_fixed_req.m_prot; + ar_seq.m_use_fixed_qos = 1'b1; + ar_seq.m_fixed_qos = m_fixed_req.m_qos; + ar_seq.m_use_fixed_user = 1'b1; + ar_seq.m_fixed_user = m_fixed_req.m_user; + if (!ar_seq.randomize()) begin + `uvm_fatal(get_full_name(), "Failed to randomize ar_seq.") + end + + // Create a sequence to receive a single read data transaction (R). (This might or might not match + // our ID, but that doesn't matter: the point is that we need to send it as a token) + r_seq = axi_mgr_read_data_seq::type_id::create("r_seq"); + if (!r_seq.randomize()) begin + `uvm_fatal(get_full_name(), "Failed to randomize r_seq.") + end + + // Run the sequence to consume a read data item in the background. The item in question might not + // be for the ID we're tracking, so we don't wait directly for it to finish. + fork begin + r_seq.start(m_read_data_sequencer); + + // Once the R channel sequence has finished, pass its read data to the router (unless there is + // none, which means that both it and the router are seeing a reset). + if (r_seq.rsp != null) begin + m_read_response_router.on_response(r_seq.rsp.m_id, r_seq.rsp); + end + end join_none + + // Run the other sequences. This waits for ar_seq to complete and for a response from + // m_read_response_router (which may or may not be the response to r_seq). + fork + ar_seq.start(m_read_request_sequencer); + m_read_response_router.wait_for_response(m_fixed_req.m_id, read_data_item); + join + + // At this point, the AR sequence has completed (either by sending its items or by seeing a reset) + // and wait_for_response has completed, writing to read_data. + if (!$cast(read_data, read_data_item)) + `uvm_fatal(get_full_name(), "wait_for_response returned unexpected item type") + rsp = axi_fixed_read_rsp_item::type_id::create("rsp"); + rsp.m_ar_status = ar_seq.rsp; + rsp.m_read_data = read_data; +endtask + +function void axi_mgr_read_fixed_vseq::set_read_response_router(axi_response_router router); + if (router == null) `uvm_fatal(get_full_name(), "Router is null.") + m_read_response_router = router; +endfunction + +function void + axi_mgr_read_fixed_vseq::set_sequencers(read_request_sequencer_t read_request_sequencer, + read_data_sequencer_t read_data_sequencer); + if (read_request_sequencer == null) `uvm_fatal(get_full_name(), "No read_request_sequencer.") + if (read_data_sequencer == null) `uvm_fatal(get_full_name(), "No read_data_sequencer.") + + m_read_request_sequencer = read_request_sequencer; + m_read_data_sequencer = read_data_sequencer; +endfunction From 525ed3e7756a46026ba159e21332490279b38367 Mon Sep 17 00:00:00 2001 From: Rupert Swarbrick Date: Tue, 16 Jun 2026 20:19:11 +0100 Subject: [PATCH 16/22] [dv,axi] Allow axi_agent to connect to the register layer To do so, we add a (pretty trivial) reg_adapter and a sequencer in a layered vseq that runs the resulting translated items. Co-authored-by: tchilikov-semify --- hw/ip/dv/axi_agent/axi_agent.core | 5 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 11 +- hw/ip/dv/axi_agent/axi_mgr_agent.svh | 60 +++++ hw/ip/dv/axi_agent/axi_reg_adapter.svh | 42 ++++ hw/ip/dv/axi_agent/axi_reg_op_item.svh | 65 +++++ .../seq_lib/axi_mgr_register_layer_vseq.svh | 223 ++++++++++++++++++ 6 files changed, 404 insertions(+), 2 deletions(-) create mode 100644 hw/ip/dv/axi_agent/axi_reg_adapter.svh create mode 100644 hw/ip/dv/axi_agent/axi_reg_op_item.svh create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_register_layer_vseq.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 9ad33eed9..fe346beea 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -45,6 +45,9 @@ filesets: - axi_response_router.svh: {is_include_file: true} + - axi_reg_op_item.svh: {is_include_file: true} + - axi_reg_adapter.svh: {is_include_file: true} + - axi_mgr_agent.svh: {is_include_file: true} - seq_lib/axi_mgr_txn_request_seq.svh: {is_include_file: true} @@ -56,6 +59,8 @@ filesets: - seq_lib/axi_mgr_write_fixed_vseq.svh: {is_include_file: true} - seq_lib/axi_mgr_read_fixed_vseq.svh: {is_include_file: true} + - seq_lib/axi_mgr_register_layer_vseq.svh: {is_include_file: true} + file_type: systemVerilogSource targets: diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 19dc22ab3..1c2a83585 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -42,14 +42,17 @@ package axi_agent_pkg; `include "axi_reset_monitor_ar.svh" `include "axi_reset_monitor_r.svh" + `include "axi_reg_op_item.svh" + `include "axi_reg_adapter.svh" + typedef uvm_sequencer#(axi_txn_request_item, axi_status_item) write_request_sequencer_t; typedef uvm_sequencer#(axi_write_data_item, axi_status_item) write_data_sequencer_t; typedef uvm_sequencer#(axi_response_accept_item, uvm_sequence_item) write_response_sequencer_t; typedef uvm_sequencer#(axi_txn_request_item, axi_status_item) read_request_sequencer_t; typedef uvm_sequencer#(axi_response_accept_item, uvm_sequence_item) read_data_sequencer_t; + typedef uvm_sequencer#(axi_reg_op_item, uvm_sequence_item) reg_op_sequencer_t; - `include "axi_agent_cfg.svh" - `include "axi_mgr_agent.svh" + `include "axi_response_router.svh" `include "seq_lib/axi_mgr_txn_request_seq.svh" `include "seq_lib/axi_mgr_write_data_seq.svh" @@ -64,4 +67,8 @@ package axi_agent_pkg; `include "axi_fixed_read_req_item.svh" `include "axi_fixed_read_rsp_item.svh" `include "seq_lib/axi_mgr_read_fixed_vseq.svh" + + `include "seq_lib/axi_mgr_register_layer_vseq.svh" + + `include "axi_mgr_agent.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_mgr_agent.svh b/hw/ip/dv/axi_agent/axi_mgr_agent.svh index 3615f8240..5e635868f 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_agent.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_agent.svh @@ -5,6 +5,8 @@ class axi_mgr_agent extends uvm_agent; `uvm_component_utils(axi_mgr_agent) + typedef uvm_sequencer #(axi_reg_op_item) layered_reg_sequencer_t; + // The agent config object, which allows the testbench to supply virtual interfaces. This can // either be set by calling set_cfg() before the build phase, or provided through uvm_config_db. local axi_agent_cfg m_cfg; @@ -40,6 +42,15 @@ class axi_mgr_agent extends uvm_agent; // A response router for reads local axi_response_router m_read_response_router; + // A reg adapter. This is stateless, so gets created in build_phase whenever the agent is active. + // It's useful in conjunction with a layered sequencer (which is created by + // run_layered_register_vseq and can be retrieved with get_register_layering_sequencer). + local axi_reg_adapter m_reg_adapter; + + // A sequencer that controls access to an instance of axi_mgr_register_layer_vseq that is + // currently running. + local layered_reg_sequencer_t m_layered_reg_sequencer; + extern function new (string name, uvm_component parent); extern function void build_phase(uvm_phase phase); extern function void connect_phase(uvm_phase phase); @@ -92,6 +103,19 @@ class axi_mgr_agent extends uvm_agent; // Get the read response router. Can only be called after build_phase, and the // agent must be active. extern function axi_response_router get_read_response_router(); + + // Get the reg adapter. Can only be called after build_phase, and the agent must be active. + extern function axi_reg_adapter get_layered_reg_adapter(); + + // Run the layered register vseq, which shouldn't already be running. + // + // This sequence will run forever and its layering sequencer can be retrieved with + // get_register_layering_sequencer(). + extern task run_layered_register_vseq(); + + // Get a handle a the sequencer for a layered register vseq that is currently running. If there is + // not yet one running, this returns null. + extern function layered_reg_sequencer_t get_register_layering_sequencer(); endclass function axi_mgr_agent::new(string name, uvm_component parent); @@ -117,6 +141,8 @@ function void axi_mgr_agent::build_phase(uvm_phase phase); m_write_response_router = axi_response_router::type_id::create("m_write_response_router", this); m_read_response_router = axi_response_router::type_id::create("m_read_response_router", this); + m_reg_adapter = axi_reg_adapter::type_id::create("m_reg_adapter"); + // Generate drivers and sequencers for the five channels // The write request channel (AW) m_write_request_driver = @@ -259,3 +285,37 @@ function axi_response_router axi_mgr_agent::get_read_response_router(); `uvm_fatal(get_full_name(), "m_read_response_router is null.") return m_read_response_router; endfunction + +function axi_reg_adapter axi_mgr_agent::get_layered_reg_adapter(); + if (m_reg_adapter == null) `uvm_fatal(get_full_name(), "m_reg_adapter is null.") + return m_reg_adapter; +endfunction + +task axi_mgr_agent::run_layered_register_vseq(); + axi_mgr_register_layer_vseq layer_vseq; + + if (m_layered_reg_sequencer != null) begin + `uvm_fatal(get_full_name(), "Overlapping runs of layered register vseq.") + end + + m_layered_reg_sequencer = layered_reg_sequencer_t::type_id::create("m_layered_reg_sequencer", this); + + layer_vseq = axi_mgr_register_layer_vseq::type_id::create("layer_vseq"); + + layer_vseq.set_sequencers(m_layered_reg_sequencer, + m_write_request_sequencer, + m_write_data_sequencer, + m_write_response_sequencer, + m_read_request_sequencer, + m_read_data_sequencer); + layer_vseq.set_response_routers(m_read_response_router, m_write_response_router); + + layer_vseq.start(null); + + // Because layer_vseq never completes, we don't expect to get her. + `uvm_fatal(get_full_name(), "Instance of axi_mgr_register_layer_vseq ran to completion.") +endtask + +function axi_mgr_agent::layered_reg_sequencer_t axi_mgr_agent::get_register_layering_sequencer(); + return m_layered_reg_sequencer; +endfunction diff --git a/hw/ip/dv/axi_agent/axi_reg_adapter.svh b/hw/ip/dv/axi_agent/axi_reg_adapter.svh new file mode 100644 index 000000000..76dc3923e --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_reg_adapter.svh @@ -0,0 +1,42 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// An extremely simple uvm_reg_adapter that wraps a uvm_reg_op in an axi_reg_op_item or copies the +// response fields back from that item. + +class axi_reg_adapter extends uvm_reg_adapter; + `uvm_object_utils(axi_reg_adapter) + + extern function new(string name=""); + extern function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw); + extern function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw); +endclass + +function axi_reg_adapter::new(string name=""); + super.new(name); + supports_byte_enable = 1; + + // Setting this to true means that the "bus driver" (here, axi_mgr_register_layer_vseq) sends a + // response to the sequencer. + provides_responses = 1; +endfunction + +function uvm_sequence_item axi_reg_adapter::reg2bus(const ref uvm_reg_bus_op rw); + axi_reg_op_item bus_item = axi_reg_op_item::type_id::create("bus_item"); + + // Take a copy of the uvm_reg_bus_op (which will be a deep copy because uvm_reg_bus_op is a + // struct) + bus_item.m_rw = rw; + + return bus_item; +endfunction + +function void axi_reg_adapter::bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw); + axi_reg_op_item item; + if (!$cast(item, bus_item)) `uvm_fatal("bus2reg", "bus_item is not an axi_reg_op_item") + rw.kind = item.m_rw.kind; + rw.addr = item.m_rw.addr; + rw.data = item.m_rw.data; + rw.status = item.m_rw.status; +endfunction diff --git a/hw/ip/dv/axi_agent/axi_reg_op_item.svh b/hw/ip/dv/axi_agent/axi_reg_op_item.svh new file mode 100644 index 000000000..6d1026a86 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_reg_op_item.svh @@ -0,0 +1,65 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A simple uvm_sequence_item that holds a uvm_reg_bus_op and can be passed to the sequencer in +// axi_mgr_register_layer_vseq. + +class axi_reg_op_item extends uvm_sequence_item; + `uvm_object_utils(axi_reg_op_item) + + // The wrapped uvm_reg_bus_op + uvm_reg_bus_op m_rw; + + extern function new(string name = ""); + extern function void do_print(uvm_printer printer); + extern function void do_copy(uvm_object rhs); + extern function bit do_compare(uvm_object rhs, uvm_comparer comparer); +endclass + +function axi_reg_op_item::new(string name=""); + super.new(name); +endfunction + +function void axi_reg_op_item::do_print(uvm_printer printer); + super.do_print(printer); + printer.print_string("m_rw.kind", m_rw.kind.name()); + printer.print_field("m_rw.addr", m_rw.addr, `UVM_REG_ADDR_WIDTH, UVM_HEX); + printer.print_field("m_rw.data", m_rw.data, `UVM_REG_DATA_WIDTH, UVM_HEX); + printer.print_field_int("m_rw.n_bits", m_rw.n_bits, 32, UVM_DEC); + printer.print_field("m_rw.byte_en", m_rw.byte_en, `UVM_REG_BYTENABLE_WIDTH, UVM_HEX); + printer.print_string("m_rw.status", m_rw.status.name()); +endfunction + +function void axi_reg_op_item::do_copy(uvm_object rhs); + axi_reg_op_item rhs_; + + if (rhs == null) `uvm_fatal("do_copy", "Cannot copy from RHS: it is null.") + if (!$cast(rhs_, rhs)) `uvm_fatal("do_copy", "Cannot cast RHS: wrong type?") + + super.do_copy(rhs); + m_rw = rhs_.m_rw; +endfunction + +function bit axi_reg_op_item::do_compare(uvm_object rhs, uvm_comparer comparer); + axi_reg_op_item rhs_; + + // These items are only equivalent if rhs is actually an axi_reg_op_item. + if (rhs == null || !$cast(rhs_, rhs)) begin + comparer.print_msg("RHS is null or is not an axi_reg_op_item."); + return 0; + end + + return (super.do_compare(rhs, comparer) & + comparer.compare_field_int("m_rw.kind", m_rw.kind, rhs_.m_rw.kind, + $bits(m_rw.kind), UVM_HEX) & + comparer.compare_field("m_rw.addr", m_rw.addr, rhs_.m_rw.addr, + `UVM_REG_ADDR_WIDTH, UVM_HEX) & + comparer.compare_field("m_rw.data", m_rw.data, rhs_.m_rw.data, + `UVM_REG_DATA_WIDTH, UVM_HEX) & + comparer.compare_field_int("m_rw.n_bits", m_rw.n_bits, rhs_.m_rw.n_bits, 32, UVM_DEC) & + comparer.compare_field("m_rw.byte_en", m_rw.byte_en, rhs_.m_rw.byte_en, + `UVM_REG_BYTENABLE_WIDTH, UVM_HEX) & + comparer.compare_field_int("m_rw.status", m_rw.status, rhs_.m_rw.status, + $bits(m_rw.status), UVM_HEX)); +endfunction diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_register_layer_vseq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_register_layer_vseq.svh new file mode 100644 index 000000000..64d0e3d14 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_register_layer_vseq.svh @@ -0,0 +1,223 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// This is a register layering virtual sequence. It consumes axi_reg_op_item objects from a +// sequencer and uses them to trigger multiple bus sequences for the different AXI channels (AW, W, +// B, AR and R). +// +// It is designed to be used with axi_reg_adapter with provides_responses=1, so calls put() on +// m_layered_sequencer to provide responses to the operations. + +class axi_mgr_register_layer_vseq extends uvm_sequence; + `uvm_object_utils(axi_mgr_register_layer_vseq) + + // A sequencer used as a source of register transactions can be run, and the five AXI channels. + // These can be provided by calling set_sequencers, which must be done before starting this + // sequence. + local uvm_sequencer #(axi_reg_op_item) m_layered_sequencer; + local write_request_sequencer_t m_aw_sequencer; + local write_data_sequencer_t m_w_sequencer; + local write_response_sequencer_t m_b_sequencer; + local read_request_sequencer_t m_ar_sequencer; + local read_data_sequencer_t m_r_sequencer; + + // Read and write response routers + local axi_response_router m_read_response_router; + local axi_response_router m_write_response_router; + + extern function new(string name=""); + extern task body(); + + extern function void set_sequencers(uvm_sequencer#(axi_reg_op_item) layered_sequencer, + write_request_sequencer_t aw_sequencer, + write_data_sequencer_t w_sequencer, + write_response_sequencer_t b_sequencer, + read_request_sequencer_t ar_sequencer, + read_data_sequencer_t r_sequencer); + + extern function void set_response_routers(axi_response_router read_response_router, + axi_response_router write_response_router); + + // Send the request item through a layered sequence, passing any information back by modifying the + // item argument (specifically, by modifying its m_rw field). + extern local task send_op_item(axi_reg_op_item item); +endclass + +function axi_mgr_register_layer_vseq::new(string name=""); + super.new(name); +endfunction + +task axi_mgr_register_layer_vseq::body(); + if (m_layered_sequencer == null || + m_aw_sequencer == null || + m_w_sequencer == null || + m_b_sequencer == null || + m_ar_sequencer == null || + m_r_sequencer == null || + m_read_response_router == null || + m_write_response_router == null) begin + `uvm_fatal(get_full_name(), + "Cannot run sequence because at least one sequencer or router is null.") + end + + fork : isolation_fork begin + forever begin + axi_reg_op_item item; + m_layered_sequencer.get(item); + + // Run the item in the background. The send_op_item task completes when the generated virtual + // sequence runs to completion, modifying the item in its item argument to represent the + // response. + // + // At that point, we call m_layered_sequencer.put(item). The item will have originally come + // from an axi_reg_adapter and the response will now be passed back to that adapter's bus2reg, + // which will be able to update a uvm_reg_bus_op in a uvm_reg_map, completing the operation + // handshake. + fork begin + send_op_item(item); + m_layered_sequencer.put(item); + end join_none + end + end join +endtask + +function void + axi_mgr_register_layer_vseq::set_sequencers(uvm_sequencer#(axi_reg_op_item) layered_sequencer, + write_request_sequencer_t aw_sequencer, + write_data_sequencer_t w_sequencer, + write_response_sequencer_t b_sequencer, + read_request_sequencer_t ar_sequencer, + read_data_sequencer_t r_sequencer); + if (layered_sequencer == null) `uvm_fatal(get_full_name(), "No layered sequencer") + if (aw_sequencer == null) `uvm_fatal(get_full_name(), "No aw sequencer") + if (w_sequencer == null) `uvm_fatal(get_full_name(), "No w sequencer") + if (b_sequencer == null) `uvm_fatal(get_full_name(), "No b sequencer") + if (ar_sequencer == null) `uvm_fatal(get_full_name(), "No ar sequencer") + if (r_sequencer == null) `uvm_fatal(get_full_name(), "No r sequencer") + + m_layered_sequencer = layered_sequencer; + m_aw_sequencer = aw_sequencer; + m_w_sequencer = w_sequencer; + m_b_sequencer = b_sequencer; + m_ar_sequencer = ar_sequencer; + m_r_sequencer = r_sequencer; +endfunction + +function void + axi_mgr_register_layer_vseq::set_response_routers(axi_response_router read_response_router, + axi_response_router write_response_router); + if (read_response_router == null) `uvm_fatal(get_full_name(), "No read response router.") + if (write_response_router == null) `uvm_fatal(get_full_name(), "No write response router.") + + m_read_response_router = read_response_router; + m_write_response_router = write_response_router; +endfunction + +task axi_mgr_register_layer_vseq::send_op_item(axi_reg_op_item item); + // This task handles the bulk of the work that would normally be done by a uvm_reg_adapter's + // reg2bus and bus2reg functions. + + // item.m_rw.n_bits gives the number of bits that are being accessed. Since we don't support burst + // accesses, round this up to the next value for ARSIZE / AWSIZE by dividing down to bytes (and + // taking the ceiling), then taking clog2. + int unsigned axsize = $clog2((item.m_rw.n_bits + 7) / 8); + + // This is the maximum strb value possible, given axsize. If byte_en is not maximal, the strb + // value to actually use might not have all of the bits set. + bit [127:0] strb_from_size = (128'd1 << (1 << axsize)) - 1; + + // The strb value to send on W, or the byte mask to use with rdata in an R response. This takes + // size and byte_en into account. + bit [127:0] byte_mask = strb_from_size & item.m_rw.byte_en; + + if (axsize > 7) begin + `uvm_error(get_full_name(), + $sformatf({"Cannot generate a sequence to represent an access with n_bits = %d: ", + "for a single transfer, this would need an AxSIZE of %0d."}, + item.m_rw.n_bits, axsize)) + item.m_rw.status = UVM_NOT_OK; + return; + end + + case (item.m_rw.kind) + UVM_READ: begin + // Single read + axi_mgr_read_fixed_vseq read_vseq = axi_mgr_read_fixed_vseq::type_id::create("read_vseq"); + bit ar_complete, r_complete; + bit [1023:0] bit_mask; + + read_vseq.set_sequencers(m_ar_sequencer, m_r_sequencer); + read_vseq.set_read_response_router(m_read_response_router); + + if (!read_vseq.randomize() with { + m_fixed_req.m_addr == local::item.m_rw.addr; + m_fixed_req.m_size == local::axsize; + }) begin + `uvm_fatal(get_full_name(), "Failed to randomise read_vseq.") + end + + // Run read_vseq to completion, which will set a rsp field (the sequence is designed to do so + // on every path). + read_vseq.start(null); + + // Convert byte_mask into a mask that can be used with rdata + for (int unsigned i = 0; i < 128; i++) begin + if (byte_mask[i]) bit_mask |= 1024'hff << 8 * i; + end + + ar_complete = (read_vseq.rsp.m_ar_status != null && + read_vseq.rsp.m_ar_status.m_sending_complete); + r_complete = (read_vseq.rsp.m_read_data != null); + + item.m_rw.status = (ar_complete && r_complete && + read_vseq.rsp.m_read_data.m_resp inside {axi_read_data_item::RRespOkay, axi_read_data_item::RRespExOkay}) ? + UVM_IS_OK : + UVM_NOT_OK; + item.m_rw.data = r_complete ? (read_vseq.rsp.m_read_data.m_data & bit_mask) : 0; + end + + UVM_WRITE: begin + // Single write + axi_mgr_write_fixed_vseq write_vseq = axi_mgr_write_fixed_vseq::type_id::create("write_vseq"); + bit aw_complete, w_complete, b_complete; + + write_vseq.set_sequencers(m_aw_sequencer, m_w_sequencer, m_b_sequencer); + write_vseq.set_write_response_router(m_write_response_router); + + if (!write_vseq.randomize() with { + m_fixed_req.m_addr == local::item.m_rw.addr; + m_fixed_req.m_size == local::axsize; + + m_fixed_req.m_write_data_item.m_data == local::item.m_rw.data; + m_fixed_req.m_write_data_item.m_strb == local::byte_mask; + }) begin + `uvm_fatal(get_full_name(), "Failed to randomise write_vseq.") + end + + // Run write_vseq to completion, which will set a rsp field (the sequence is designed to do so + // on every path). + write_vseq.start(null); + + aw_complete = (write_vseq.rsp.m_aw_status != null && + write_vseq.rsp.m_aw_status.m_sending_complete); + w_complete = (write_vseq.rsp.m_w_status != null && + write_vseq.rsp.m_w_status.m_sending_complete); + b_complete = (write_vseq.rsp.m_write_response != null); + + item.m_rw.status = (aw_complete && w_complete && b_complete && + write_vseq.rsp.m_write_response.m_resp inside {axi_write_response_item::BRespOkay, axi_write_response_item::BRespExOkay}) ? + UVM_IS_OK : + UVM_NOT_OK; + end + + default: begin + // Something else (a burst read or write). Not yet supported. + `uvm_error(get_full_name(), + $sformatf("Cannot send this uvm_reg_op. kind is %0s, which is not supported.", + item.m_rw.kind.name())) + item.m_rw.status = UVM_NOT_OK; + return; + end + endcase +endtask From 216931eeae5fdee39e4cbaa54198f50628bdc75a Mon Sep 17 00:00:00 2001 From: tchilikov-semify Date: Wed, 8 Jul 2026 10:20:27 +0100 Subject: [PATCH 17/22] [dv,axi] fix axi handshaking for beat sampling on multi-beat traffic --- .../dv/axi_agent/axi_mgr_read_data_driver.svh | 51 +++++++++++------- .../axi_mgr_write_response_driver.svh | 52 ++++++++++++------- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh index ce5996972..44b710331 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh @@ -114,29 +114,39 @@ task axi_mgr_read_data_driver::drive_req(); wait(m_in_reset); begin axi_read_data_item read_data_item; - - // For the time until rvalid is asserted, obey req.m_ready_without_valid_pct and assert - // rready for some fraction of the total time. - while (m_vif.mgr_cb.rvalid !== 1'b1) begin - m_vif.mgr_cb.rready <= $urandom_range(0, 99) < req.m_ready_without_valid_pct; + bit valid_seen = 1'b0; // rvalid has been observed at least once + int unsigned delay = 0; // cycles counted since rvalid was first seen + + // Drive rready and watch for the actual transfer. We track the rready we drive in rready + // rather than reading it back from the clocking block, and we sample on that exact + // handshake edge. This is what makes the accept protocol-correct: + // * a speculative rready (asserted before rvalid via ready_without_valid_pct) that + // catches the beat is detected and sampled, instead of consuming the beat unsampled + // * two accepts running back-to-back (no clock edge between sequence items) cannot + // re-sample the same beat, because each iteration advances a clock before checking. + forever begin + // the value we drive onto rready this cycle + bit rready; + + if (!valid_seen) begin + // Before rvalid: optionally assert rready speculatively. + rready = ($urandom_range(0, 99) < req.m_ready_without_valid_pct); + end else begin + // After rvalid: hold rready low for valid_to_ready_delay cycles, then assert it. + rready = (delay >= req.m_valid_to_ready_delay); + end + + m_vif.mgr_cb.rready <= rready; @(m_vif.mgr_cb); - end - // At this point, rvalid has been asserted. If rready is true, the transfer has gone - // through. If not, wait req.m_valid_to_ready_delay cycles before we assert ready. - // - // Read rready from the clocking block to see the last value we wrote. Practically speaking, - // mgr_cb.rready will not be true unless we had at least one cycle in the loop above: we set - // mgr_cb.rready <= 0 at the end of this task and also when a reset is seen in monitor_reset. - if (m_vif.mgr_cb.rready !== 1'b1) begin - repeat (req.m_valid_to_ready_delay) @(m_vif.mgr_cb); - m_vif.mgr_cb.rready <= 1'b1; - @(m_vif.mgr_cb); + if (m_vif.mgr_cb.rvalid === 1'b1) begin + if (rready) break; // rvalid && rready on this edge: beat transferred + if (!valid_seen) valid_seen = 1'b1; // first rvalid: start the valid-to-ready countdown + else delay++; + end end - // When we get here, we have finished accepting a response and we are at the end of a cycle - // where rvalid and rready were asserted. Write a sequence item to represent the response - // that we have seen to our read_data_item output argument. + // The beat transferred on the edge we just broke out of: sample it. read_data_item = axi_read_data_item::type_id::create("read_data_item"); read_data_item.m_id = m_vif.mgr_cb.rid; read_data_item.m_data = m_vif.mgr_cb.rdata; @@ -146,7 +156,8 @@ task axi_mgr_read_data_driver::drive_req(); rsp = read_data_item; - // Finally, clear rready (for next cycle). + // Deassert rready. If the next accept follows immediately, its first iteration re-drives + // rready before the next clock edge, so consecutive beats are still accepted seamlessly. m_vif.mgr_cb.rready <= 1'b0; end join_any diff --git a/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh index a6fa0ef3e..fdab0ddb7 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh @@ -117,29 +117,40 @@ task axi_mgr_write_response_driver::drive_req(); wait(m_in_reset); begin axi_write_response_item response; - - // For the time until bvalid is asserted, obey req.m_ready_without_valid_pct and assert - // bready for some fraction of the total time. - while (m_vif.mgr_cb.bvalid !== 1'b1) begin - m_vif.mgr_cb.bready <= $urandom_range(0, 99) < req.m_ready_without_valid_pct; + bit valid_seen = 1'b0; // bvalid has been observed at least once + int unsigned delay = 0; // cycles counted since bvalid was first seen + + // Drive bready and watch for the actual transfer. We track the bready we drive in bready + // rather than reading it back from the clocking block, and we sample on that exact + // handshake edge. This is what makes the accept protocol-correct: + // * a speculative bready (asserted before bvalid via ready_without_valid_pct) that + // catches the response is detected and sampled, instead of consuming the + // response unsampled + // * two accepts running back-to-back (no clock edge between sequence items) cannot + // re-sample the same response, because each iteration advances a clock before checking. + forever begin + // the value we drive onto bready this cycle + bit bready; + + if (!valid_seen) begin + // Before bvalid: optionally assert bready speculatively. + bready = ($urandom_range(0, 99) < req.m_ready_without_valid_pct); + end else begin + // After bvalid: hold bready low for valid_to_ready_delay cycles, then assert it. + bready = (delay >= req.m_valid_to_ready_delay); + end + + m_vif.mgr_cb.bready <= bready; @(m_vif.mgr_cb); - end - // At this point, bvalid has been asserted. If bready is true, the transfer has gone - // through. If not, wait req.m_valid_to_ready_delay cycles before we assert ready. - // - // Read bready from the clocking block to see the last value we wrote. Practically speaking, - // mgr_cb.bready will not be true unless we had at least one cycle in the loop above: we set - // mgr_cb.bready <= 0 at the end of this task and also when a reset is seen in monitor_reset. - if (m_vif.mgr_cb.bready !== 1'b1) begin - repeat (req.m_valid_to_ready_delay) @(m_vif.mgr_cb); - m_vif.mgr_cb.bready <= 1'b1; - @(m_vif.mgr_cb); + if (m_vif.mgr_cb.bvalid === 1'b1) begin + if (bready) break; // bvalid && bready on this edge: response accepted + if (!valid_seen) valid_seen = 1'b1; // first bvalid: start the valid-to-ready countdown + else delay++; + end end - // When we get here, we have finished accepting a response and we are at the end of a cycle - // where bvalid and bready were asserted. Fill the response output variable with a sequence - // item to represent the response that we have seen. + // The response transferred on the edge we just broke out of: sample it. response = axi_write_response_item::type_id::create("response"); response.m_id = m_vif.mgr_cb.bid; response.m_resp = axi_write_response_item::bresp_e'(m_vif.mgr_cb.bresp); @@ -148,7 +159,8 @@ task axi_mgr_write_response_driver::drive_req(); // Set rsp, which will pass the response back to the sequencer. rsp = response; - // Finally, clear bready (for next cycle). + // Deassert bready. If the next accept follows immediately, its first iteration re-drives + // bready before the next clock edge, so consecutive responses are still accepted seamlessly. m_vif.mgr_cb.bready <= 1'b0; end join_any From ced4ab0e6c39104cc4126213f4feb245bb1e7bb4 Mon Sep 17 00:00:00 2001 From: tchilikov-semify Date: Wed, 8 Jul 2026 12:49:00 +0100 Subject: [PATCH 18/22] [dv,axi] add multi-beat transaction support to driver --- hw/ip/dv/axi_agent/axi_agent.core | 3 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 3 + .../seq_lib/axi_mgr_read_burst_vseq.svh | 139 +++++++++++++++++ .../seq_lib/axi_mgr_read_fixed_vseq.svh | 3 - .../seq_lib/axi_mgr_txn_request_seq.svh | 57 ++++--- .../seq_lib/axi_mgr_write_burst_vseq.svh | 146 ++++++++++++++++++ .../seq_lib/axi_mgr_write_data_seq.svh | 11 +- .../seq_lib/axi_mgr_write_listed_data_seq.svh | 47 ++++++ .../seq_lib/axi_mgr_write_single_data_seq.svh | 6 +- 9 files changed, 381 insertions(+), 34 deletions(-) create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_burst_vseq.svh create mode 100644 hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_listed_data_seq.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index fe346beea..d42cc4b99 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -53,11 +53,14 @@ filesets: - seq_lib/axi_mgr_txn_request_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_write_data_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_write_single_data_seq.svh: {is_include_file: true} + - seq_lib/axi_mgr_write_listed_data_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_write_response_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_read_data_seq.svh: {is_include_file: true} - seq_lib/axi_mgr_write_fixed_vseq.svh: {is_include_file: true} + - seq_lib/axi_mgr_write_burst_vseq.svh: {is_include_file: true} - seq_lib/axi_mgr_read_fixed_vseq.svh: {is_include_file: true} + - seq_lib/axi_mgr_read_burst_vseq.svh: {is_include_file: true} - seq_lib/axi_mgr_register_layer_vseq.svh: {is_include_file: true} diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index 1c2a83585..a3b2edeaa 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -57,16 +57,19 @@ package axi_agent_pkg; `include "seq_lib/axi_mgr_txn_request_seq.svh" `include "seq_lib/axi_mgr_write_data_seq.svh" `include "seq_lib/axi_mgr_write_single_data_seq.svh" + `include "seq_lib/axi_mgr_write_listed_data_seq.svh" `include "seq_lib/axi_mgr_write_response_seq.svh" `include "seq_lib/axi_mgr_read_data_seq.svh" `include "axi_fixed_write_req_item.svh" `include "axi_fixed_write_rsp_item.svh" `include "seq_lib/axi_mgr_write_fixed_vseq.svh" + `include "seq_lib/axi_mgr_write_burst_vseq.svh" `include "axi_fixed_read_req_item.svh" `include "axi_fixed_read_rsp_item.svh" `include "seq_lib/axi_mgr_read_fixed_vseq.svh" + `include "seq_lib/axi_mgr_read_burst_vseq.svh" `include "seq_lib/axi_mgr_register_layer_vseq.svh" diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh new file mode 100644 index 000000000..73396b0dc --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh @@ -0,0 +1,139 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A virtual sequence that sends a single AXI read burst of arbitrary length and collects every +// returned beat. + +class axi_mgr_read_burst_vseq extends uvm_sequence#(uvm_sequence_item, axi_fixed_read_rsp_item); + `uvm_object_utils(axi_mgr_read_burst_vseq) + + // Read response router. Set by calling set_read_response_router before starting. + local axi_response_router m_read_response_router; + + // Sequencers for AR and R. Set by calling set_sequencers before starting. + local read_request_sequencer_t m_read_request_sequencer; + local read_data_sequencer_t m_read_data_sequencer; + + // The AR request to send. Randomised as part of this sequence. For a + // purely directed burst, set m_ar_req fields directly before start(). + rand axi_txn_request_item m_ar_req; + + // R-channel accept (backpressure) template. Configure its m_use_fixed_*/m_fixed_* pins to control + // rready timing: pinned fields take the fixed value on every beat; unpinned fields are randomised + // independently per beat (the default). + axi_mgr_read_data_seq m_r_accept; + + // The read data beats that came back, in beat order. Populated by body(). + axi_read_data_item m_read_beats[$]; + + extern function new(string name=""); + extern task body(); + + // Accept one R beat: run the given read-data seq and deposit its response into the router. + // Spawned once per beat by body(). + extern local task accept_one_beat(axi_mgr_read_data_seq r_seq); + + // Set the read response router + extern function void set_read_response_router(axi_response_router router); + + // Set sequencers for the AR and R channels + extern function void set_sequencers(read_request_sequencer_t read_request_sequencer, + read_data_sequencer_t read_data_sequencer); +endclass + +function axi_mgr_read_burst_vseq::new(string name=""); + super.new(name); + m_ar_req = axi_txn_request_item::type_id::create("m_ar_req"); + m_r_accept = axi_mgr_read_data_seq::type_id::create("m_r_accept"); +endfunction + +task axi_mgr_read_burst_vseq::body(); + axi_mgr_txn_request_seq ar_seq; + int unsigned n_beats = 32'(m_ar_req.m_len) + 1; + + if (m_read_response_router == null) begin + `uvm_fatal(get_full_name(), "Cannot run sequence because there is no read response router.") + end + if (m_read_request_sequencer == null || m_read_data_sequencer == null) begin + `uvm_fatal(get_full_name(), "Cannot run sequence because sequencers are not both set.") + end + + // Check the request is AXI-legal. + if (!m_ar_req.randomize(null)) begin + `uvm_error(get_full_name(), + "AR request violates AXI legality constraints (see axi_txn_request_item)") + end + + // Send the read request (AR): hand m_ar_req to the request seq to send verbatim. + ar_seq = axi_mgr_txn_request_seq::type_id::create("ar_seq"); + ar_seq.m_req = m_ar_req; + + // Accept every R beat, all inside one isolation fork so no accept process outlives this task. + // Each accept is spawned up front (so rready is asserted before data arrives) and deposits its + // beat into the router keyed by RID via accept_one_beat(). Because beats arrive in order, the + // router's per-ID FIFO preserves beat order regardless of sequencer arbitration order. + m_read_beats.delete(); + fork : isolation_fork + begin + // Spawn one accept process per beat, before AR is sent. + for (int unsigned i = 0; i < n_beats; i++) begin + automatic axi_mgr_read_data_seq r_seq = + axi_mgr_read_data_seq::type_id::create($sformatf("r_seq_%0d", i)); + r_seq.m_use_fixed_ready_without_valid_pct = m_r_accept.m_use_fixed_ready_without_valid_pct; + r_seq.m_fixed_ready_without_valid_pct = m_r_accept.m_fixed_ready_without_valid_pct; + r_seq.m_use_fixed_valid_to_ready_delay = m_r_accept.m_use_fixed_valid_to_ready_delay; + r_seq.m_fixed_valid_to_ready_delay = m_r_accept.m_fixed_valid_to_ready_delay; + if (!r_seq.randomize()) begin + `uvm_fatal(get_full_name(), "Failed to randomize r_seq.") + end + fork + accept_one_beat(r_seq); + join_none + end + + // Send AR and drain the router for our n_beats responses, concurrently with the accepts. + fork + ar_seq.start(m_read_request_sequencer); + begin + for (int unsigned i = 0; i < n_beats; i++) begin + uvm_sequence_item read_data_item; + axi_read_data_item read_data; + m_read_response_router.wait_for_response(m_ar_req.m_id, read_data_item); + if (read_data_item == null) break; // reset + if (!$cast(read_data, read_data_item)) + `uvm_fatal(get_full_name(), "wait_for_response returned unexpected item type") + m_read_beats.push_back(read_data); + end + end + join + + wait fork; + end + join + + rsp = axi_fixed_read_rsp_item::type_id::create("rsp"); + rsp.m_ar_status = ar_seq.rsp; + rsp.m_read_data = (m_read_beats.size() > 0) ? m_read_beats[0] : null; +endtask + +task axi_mgr_read_burst_vseq::accept_one_beat(axi_mgr_read_data_seq r_seq); + r_seq.start(m_read_data_sequencer); + if (r_seq.rsp != null) begin + m_read_response_router.on_response(r_seq.rsp.m_id, r_seq.rsp); + end +endtask : accept_one_beat + +function void axi_mgr_read_burst_vseq::set_read_response_router(axi_response_router router); + if (router == null) `uvm_fatal(get_full_name(), "Router is null.") + m_read_response_router = router; +endfunction + +function void axi_mgr_read_burst_vseq::set_sequencers(read_request_sequencer_t read_request_sequencer, + read_data_sequencer_t read_data_sequencer); + if (read_request_sequencer == null) `uvm_fatal(get_full_name(), "No read_request_sequencer.") + if (read_data_sequencer == null) `uvm_fatal(get_full_name(), "No read_data_sequencer.") + + m_read_request_sequencer = read_request_sequencer; + m_read_data_sequencer = read_data_sequencer; +endfunction diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_fixed_vseq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_fixed_vseq.svh index 7c5110952..72d8a796d 100644 --- a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_fixed_vseq.svh +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_fixed_vseq.svh @@ -78,9 +78,6 @@ task axi_mgr_read_fixed_vseq::body(); ar_seq.m_fixed_qos = m_fixed_req.m_qos; ar_seq.m_use_fixed_user = 1'b1; ar_seq.m_fixed_user = m_fixed_req.m_user; - if (!ar_seq.randomize()) begin - `uvm_fatal(get_full_name(), "Failed to randomize ar_seq.") - end // Create a sequence to receive a single read data transaction (R). (This might or might not match // our ID, but that doesn't matter: the point is that we need to send it as a token) diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_txn_request_seq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_txn_request_seq.svh index 3fad6ae16..6b729314c 100644 --- a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_txn_request_seq.svh +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_txn_request_seq.svh @@ -10,10 +10,14 @@ class axi_mgr_txn_request_seq extends uvm_sequence #(axi_txn_request_item, axi_status_item); `uvm_object_utils(axi_mgr_txn_request_seq) - // This sequence uses late randomisation, so doesn't randomise the request until it is scheduled - // on the sequencer. Of course, this is a bit tricky to use in a situation where you want to - // control a particular variable when randomising. To do so for a field called XYZ, set - // m_use_fixed_XYZ=1 and set m_fixed_XYZ to the required value. Do this before starting the + // If non-null, this exact item is sent verbatim; no randomisation, and the + // m_use_fixed_* pins below are ignored. + axi_txn_request_item m_req; + + // If m_req is null, this sequence uses late randomisation, so doesn't randomise the request until + // it is scheduled on the sequencer. Of course, this is a bit tricky to use in a situation where + // you want to control a particular variable when randomising. To do so for a field called XYZ, + // set m_use_fixed_XYZ=1 and set m_fixed_XYZ to the required value. Do this before starting the // sequence and m_XYZ in the generated item will be "randomised" to match m_fixed_XYZ. // // The pairs of variables below are named to match the fields of axi_txn_request_item. @@ -60,29 +64,36 @@ function axi_mgr_txn_request_seq::new(string name=""); endfunction task axi_mgr_txn_request_seq::body(); - axi_txn_request_item item = axi_txn_request_item::type_id::create("item"); + axi_txn_request_item item; uvm_sequence_item base_status_item; - start_item(item); - - if (!item.randomize() with { - local::m_use_fixed_id -> m_id == local::m_fixed_id; - local::m_use_fixed_addr -> m_addr == local::m_fixed_addr; - local::m_use_fixed_region -> m_region == local::m_fixed_region; - local::m_use_fixed_len -> m_len == local::m_fixed_len; - local::m_use_fixed_size -> m_size == local::m_fixed_size; - local::m_use_fixed_burst -> m_burst == local::m_fixed_burst; - local::m_use_fixed_lock -> m_lock == local::m_fixed_lock; - local::m_use_fixed_cache -> m_cache == local::m_fixed_cache; - local::m_use_fixed_prot -> m_prot == local::m_fixed_prot; - local::m_use_fixed_qos -> m_qos == local::m_fixed_qos; - local::m_use_fixed_user -> m_user == local::m_fixed_user; - }) begin - `uvm_fatal(get_full_name(), "Failed to randomise item.") + if (m_req != null) begin + // Send the caller-supplied item verbatim: it is already built, so no randomisation. + item = m_req; + start_item(item); + finish_item(item); + end else begin + // Late randomisation: build an item and randomise it on the sequencer, honouring m_use_fixed_*. + item = axi_txn_request_item::type_id::create("item"); + start_item(item); + if (!item.randomize() with { + local::m_use_fixed_id -> m_id == local::m_fixed_id; + local::m_use_fixed_addr -> m_addr == local::m_fixed_addr; + local::m_use_fixed_region -> m_region == local::m_fixed_region; + local::m_use_fixed_len -> m_len == local::m_fixed_len; + local::m_use_fixed_size -> m_size == local::m_fixed_size; + local::m_use_fixed_burst -> m_burst == local::m_fixed_burst; + local::m_use_fixed_lock -> m_lock == local::m_fixed_lock; + local::m_use_fixed_cache -> m_cache == local::m_fixed_cache; + local::m_use_fixed_prot -> m_prot == local::m_fixed_prot; + local::m_use_fixed_qos -> m_qos == local::m_fixed_qos; + local::m_use_fixed_user -> m_user == local::m_fixed_user; + }) begin + `uvm_fatal(get_full_name(), "Failed to randomise item.") + end + finish_item(item); end - finish_item(item); - // Get a response, which will always be sent by the driver (and is available already: there's no // pipelining and finish_item just completed). get_base_response(base_status_item); diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_burst_vseq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_burst_vseq.svh new file mode 100644 index 000000000..cd1125551 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_burst_vseq.svh @@ -0,0 +1,146 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A virtual sequence that sends a single AXI write burst of arbitrary length. +// +// This is the multi-beat generalisation of axi_mgr_write_fixed_vseq. The caller configures the AW +// request fields and supplies one write data item per beat in m_data_items (its length sets AWLEN). +// The B response is collected into the rsp field, which is created before the sequence completes +// (or is left with a null m_write_response on reset). + +class axi_mgr_write_burst_vseq extends uvm_sequence#(uvm_sequence_item, axi_fixed_write_rsp_item); + `uvm_object_utils(axi_mgr_write_burst_vseq) + + // The write response router. Set this by calling set_write_response_router before starting. + local axi_response_router m_write_response_router; + + // Sequencers for AW, W and B. Set these by calling set_sequencers before starting. + local write_request_sequencer_t m_write_request_sequencer; + local write_data_sequencer_t m_write_data_sequencer; + local write_response_sequencer_t m_write_response_sequencer; + + // The AW request to send. Randomised as part of this sequence. For a purely directed burst, + // set m_aw_req fields directly before start(). AWLEN is always taken from supplied W beats. + rand axi_txn_request_item m_aw_req; + + // Tie randomised length to the number of supplied W beats. Set m_data_items before randomize(). + constraint len_matches_data_c { + m_data_items.size() > 0 -> m_aw_req.m_len == m_data_items.size() - 1; + } + + // The write data beats, in order; one item per beat. AWLEN is set to m_data_items.size()-1. + axi_write_data_item m_data_items[$]; + + // B-channel accept (backpressure) template. Configure its m_use_fixed_*/m_fixed_* pins to control + // bready timing: pinned fields take the fixed value; unpinned fields are randomised. + axi_mgr_write_response_seq m_b_accept; + + extern function new(string name=""); + extern task body(); + + // Set the write response router + extern function void set_write_response_router(axi_response_router router); + + // Set sequencers for the AW, W and B channels + extern function void set_sequencers(write_request_sequencer_t write_request_sequencer, + write_data_sequencer_t write_data_sequencer, + write_response_sequencer_t write_response_sequencer); +endclass + +function axi_mgr_write_burst_vseq::new(string name=""); + super.new(name); + m_aw_req = axi_txn_request_item::type_id::create("m_aw_req"); + m_b_accept = axi_mgr_write_response_seq::type_id::create("m_b_accept"); +endfunction + +task axi_mgr_write_burst_vseq::body(); + axi_mgr_txn_request_seq aw_seq; + axi_mgr_write_listed_data_seq w_seq; + axi_mgr_write_response_seq b_seq; + uvm_sequence_item write_response_item; + axi_write_response_item write_response; + + if (m_write_response_router == null) begin + `uvm_fatal(get_full_name(), "Cannot run sequence because there is no write response router.") + end + if (m_write_request_sequencer == null || + m_write_data_sequencer == null || + m_write_response_sequencer == null) begin + `uvm_fatal(get_full_name(), "Cannot run sequence because sequencers are not all set.") + end + if (m_data_items.size() == 0) begin + `uvm_fatal(get_full_name(), "Cannot run sequence: m_data_items is empty.") + end + + // Send the write request (AW): AWLEN always follows the supplied W beats, then hand m_aw_req to + // the request seq to send verbatim. (In random mode len_matches_data_c already ties m_len to the + // beat count; this assignment also covers the directed path, where the constraint does not run.) + aw_seq = axi_mgr_txn_request_seq::type_id::create("aw_seq"); + m_aw_req.m_len = 8'(m_data_items.size() - 1); + // Check the request is AXI-legal now that m_len is final. + if (!m_aw_req.randomize(null)) begin + `uvm_error(get_full_name(), + "AW request violates AXI legality constraints (see axi_txn_request_item)") + end + aw_seq.m_req = m_aw_req; + + // Send the write data (W): one beat per item in m_data_items. + w_seq = axi_mgr_write_listed_data_seq::type_id::create("w_seq"); + w_seq.m_items = m_data_items; + if (!w_seq.randomize()) begin + `uvm_fatal(get_full_name(), "Failed to randomize w_seq.") + end + + // Create a sequence to receive a single write response (B). It might or might not match our ID; + // the router sorts that out by ID. + b_seq = axi_mgr_write_response_seq::type_id::create("b_seq"); + b_seq.m_use_fixed_ready_without_valid_pct = m_b_accept.m_use_fixed_ready_without_valid_pct; + b_seq.m_fixed_ready_without_valid_pct = m_b_accept.m_fixed_ready_without_valid_pct; + b_seq.m_use_fixed_valid_to_ready_delay = m_b_accept.m_use_fixed_valid_to_ready_delay; + b_seq.m_fixed_valid_to_ready_delay = m_b_accept.m_fixed_valid_to_ready_delay; + if (!b_seq.randomize()) begin + `uvm_fatal(get_full_name(), "Failed to randomize b_seq.") + end + + // Consume a write response in the background and hand it to the router. + fork begin + b_seq.start(m_write_response_sequencer); + if (b_seq.rsp != null) begin + m_write_response_router.on_response(b_seq.rsp.m_id, b_seq.rsp); + end + end join_none + + // Run AW and W, and wait for our B response. + fork + aw_seq.start(m_write_request_sequencer); + w_seq.start(m_write_data_sequencer); + m_write_response_router.wait_for_response(m_aw_req.m_id, write_response_item); + join + + // write_response_item is null on reset; otherwise downcast to the concrete type. + if (write_response_item != null && !$cast(write_response, write_response_item)) + `uvm_fatal(get_full_name(), "wait_for_response returned unexpected item type") + + rsp = axi_fixed_write_rsp_item::type_id::create("rsp"); + rsp.m_aw_status = aw_seq.rsp; + rsp.m_w_status = w_seq.rsp; + rsp.m_write_response = write_response; +endtask + +function void axi_mgr_write_burst_vseq::set_write_response_router(axi_response_router router); + if (router == null) `uvm_fatal(get_full_name(), "Router is null.") + m_write_response_router = router; +endfunction + +function void axi_mgr_write_burst_vseq::set_sequencers(write_request_sequencer_t write_request_sequencer, + write_data_sequencer_t write_data_sequencer, + write_response_sequencer_t write_response_sequencer); + if (write_request_sequencer == null) `uvm_fatal(get_full_name(), "No write_request_sequencer.") + if (write_data_sequencer == null) `uvm_fatal(get_full_name(), "No write_data_sequencer.") + if (write_response_sequencer == null) `uvm_fatal(get_full_name(), "No write_response_sequencer.") + + m_write_request_sequencer = write_request_sequencer; + m_write_data_sequencer = write_data_sequencer; + m_write_response_sequencer = write_response_sequencer; +endfunction diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_data_seq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_data_seq.svh index a6b117606..079e97162 100644 --- a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_data_seq.svh +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_data_seq.svh @@ -14,11 +14,12 @@ class axi_mgr_write_data_seq extends uvm_sequence #(axi_write_data_item, axi_sta extern function new(string name=""); extern task body(); - // Randomize an item that is about to be sent. Defining this explicitly allows a sequence - // extending this one to more easily constrain the randomisation of the items that get sent. + // Populate an item that is about to be sent. The base implementation randomises it; defining this + // as a hook lets a subclass constrain that randomisation, or replace it entirely (e.g. by copying + // a caller-supplied beat). // // The is_last argument is set when this is the last item in the stream of data words. - extern protected virtual function void randomize_item(axi_write_data_item item, bit is_last); + extern protected virtual function void populate_item(axi_write_data_item item, bit is_last); // The number of items needs to match m_len from the AW channel, which is represented by an 8-bit // value that gives the last index. As such, m_number_of_items should be in the range 1..256. @@ -35,7 +36,7 @@ task axi_mgr_write_data_seq::body(); axi_write_data_item item = axi_write_data_item::type_id::create("item"); start_item(item); - randomize_item(item, i + 1 == m_number_of_items); + populate_item(item, i + 1 == m_number_of_items); finish_item(item); // Get the response from the driver and check it is actually of the expected axi_status_item @@ -52,7 +53,7 @@ task axi_mgr_write_data_seq::body(); end endtask -function void axi_mgr_write_data_seq::randomize_item(axi_write_data_item item, bit is_last); +function void axi_mgr_write_data_seq::populate_item(axi_write_data_item item, bit is_last); if (!item.randomize() with { m_last == local::is_last; }) begin diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_listed_data_seq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_listed_data_seq.svh new file mode 100644 index 000000000..9d3626f15 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_listed_data_seq.svh @@ -0,0 +1,47 @@ +// Copyright lowRISC contributors +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A subclass of axi_mgr_write_data_seq that sends a caller-supplied list of write data items, one +// per beat, in order. Used for multi-beat write bursts. The beats are not randomised: each item is +// driven verbatim as supplied, except WLAST. +// +// Usage: populate m_items with the beats to send, in order, before starting the sequence. The +// listed_items_c constraint ties the beat count to m_items.size(), so the list must be in place +// before randomize()/start(); each entry is then driven as one beat. +class axi_mgr_write_listed_data_seq extends axi_mgr_write_data_seq; + `uvm_object_utils(axi_mgr_write_listed_data_seq) + + // The data beats to send, in order. One item is sent per beat. Beats are not randomized. + axi_write_data_item m_items[$]; + + // Index of the next beat to send. + local int unsigned m_idx; + + extern function new(string name=""); + + // Overrides axi_mgr_write_data_seq::populate_item to "produce" each beat by copying the next + // caller-supplied item rather than randomising it. + extern protected virtual function void populate_item(axi_write_data_item item, bit is_last); + + // Send exactly one beat per item in m_items. + extern constraint listed_items_c; +endclass + +function axi_mgr_write_listed_data_seq::new(string name=""); + super.new(name); + m_idx = 0; +endfunction + +function void axi_mgr_write_listed_data_seq::populate_item(axi_write_data_item item, bit is_last); + // This replaces the base class function. Items are copied from m_items. The base body() + // drives m_last via is_last, so honour that here. The last beat in the burst must assert + // WLAST regardless of what the caller stored in the item. + item.copy(m_items[m_idx]); + item.m_last = is_last; + m_idx++; +endfunction + +constraint axi_mgr_write_listed_data_seq::listed_items_c { + m_number_of_items == m_items.size(); +} diff --git a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_single_data_seq.svh b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_single_data_seq.svh index 831bdce39..df28f7665 100644 --- a/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_single_data_seq.svh +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_single_data_seq.svh @@ -14,9 +14,9 @@ class axi_mgr_write_single_data_seq extends axi_mgr_write_data_seq; extern function new(string name=""); - // An override of axi_mgr_write_data_seq::randomize_item, ensuring that the item that is sent + // An override of axi_mgr_write_data_seq::populate_item, ensuring that the item that is sent // matches m_write_data_item. - extern protected virtual function void randomize_item(axi_write_data_item item, bit is_last); + extern protected virtual function void populate_item(axi_write_data_item item, bit is_last); // Write exactly one item extern constraint single_item_c; @@ -27,7 +27,7 @@ function axi_mgr_write_single_data_seq::new(string name=""); m_write_data_item = axi_write_data_item::type_id::create("m_write_data_item"); endfunction -function void axi_mgr_write_single_data_seq::randomize_item(axi_write_data_item item, bit is_last); +function void axi_mgr_write_single_data_seq::populate_item(axi_write_data_item item, bit is_last); // This completely replaces the base class function, and the "randomisation" is done by copying // from m_write_data_item. item.copy(m_write_data_item); From 6d26f5f5818e400c9c3ab6fd2c6f3004129a70c7 Mon Sep 17 00:00:00 2001 From: tchilikov-semify Date: Thu, 16 Jul 2026 11:57:52 +0100 Subject: [PATCH 19/22] [dv,axi] add clk_rst_if to axi interfaces --- hw/ip/dv/axi_agent/axi_agent.core | 7 +- hw/ip/dv/axi_agent/axi_agent_cfg.svh | 3 + hw/ip/dv/axi_agent/axi_agent_pkg.sv | 9 +- hw/ip/dv/axi_agent/axi_mgr_agent.svh | 84 +++++-------------- .../dv/axi_agent/axi_mgr_read_data_driver.svh | 18 ++-- .../axi_agent/axi_mgr_read_request_driver.svh | 18 ++-- .../axi_agent/axi_mgr_write_data_driver.svh | 18 ++-- .../axi_mgr_write_request_driver.svh | 18 ++-- .../axi_mgr_write_response_driver.svh | 18 ++-- ...t_monitor_aw.svh => axi_reset_monitor.svh} | 26 +++--- hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh | 48 ----------- hw/ip/dv/axi_agent/axi_reset_monitor_b.svh | 48 ----------- hw/ip/dv/axi_agent/axi_reset_monitor_r.svh | 48 ----------- hw/ip/dv/axi_agent/axi_reset_monitor_w.svh | 48 ----------- 14 files changed, 105 insertions(+), 306 deletions(-) rename hw/ip/dv/axi_agent/{axi_reset_monitor_aw.svh => axi_reset_monitor.svh} (51%) delete mode 100644 hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh delete mode 100644 hw/ip/dv/axi_agent/axi_reset_monitor_b.svh delete mode 100644 hw/ip/dv/axi_agent/axi_reset_monitor_r.svh delete mode 100644 hw/ip/dv/axi_agent/axi_reset_monitor_w.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index d42cc4b99..1c5677e89 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -8,6 +8,7 @@ filesets: files_dv: depend: - lowrisc:dv:dv_utils + - lowrisc:dv:common_ifs files: - axi_read_data_if.sv - axi_read_request_if.sv @@ -31,11 +32,7 @@ filesets: - axi_fixed_read_req_item.svh: {is_include_file: true} - axi_fixed_read_rsp_item.svh: {is_include_file: true} - - axi_reset_monitor_aw.svh: {is_include_file: true} - - axi_reset_monitor_w.svh: {is_include_file: true} - - axi_reset_monitor_b.svh: {is_include_file: true} - - axi_reset_monitor_ar.svh: {is_include_file: true} - - axi_reset_monitor_r.svh: {is_include_file: true} + - axi_reset_monitor.svh: {is_include_file: true} - axi_mgr_write_request_driver.svh: {is_include_file: true} - axi_mgr_write_data_driver.svh: {is_include_file: true} diff --git a/hw/ip/dv/axi_agent/axi_agent_cfg.svh b/hw/ip/dv/axi_agent/axi_agent_cfg.svh index f9c7cff83..b4694e407 100644 --- a/hw/ip/dv/axi_agent/axi_agent_cfg.svh +++ b/hw/ip/dv/axi_agent/axi_agent_cfg.svh @@ -7,6 +7,9 @@ class axi_agent_cfg extends uvm_object; `uvm_object_utils(axi_agent_cfg) + // Interfaces + virtual clk_rst_if clk_rst_vif; // ACLK/ARESETn + virtual axi_write_request_if write_request_vif; virtual axi_write_data_if write_data_vif; virtual axi_write_response_if write_response_vif; diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index a3b2edeaa..efd01c7c1 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -34,13 +34,8 @@ package axi_agent_pkg; `include "axi_reset_item.svh" - // Reset monitors for the five interfaces (all essentially the same thing, but the interface types - // are different so we have to copy-paste the classes) - `include "axi_reset_monitor_aw.svh" - `include "axi_reset_monitor_w.svh" - `include "axi_reset_monitor_b.svh" - `include "axi_reset_monitor_ar.svh" - `include "axi_reset_monitor_r.svh" + // Reset monitor for the shared AXI clock/reset (one instance covers all channels). + `include "axi_reset_monitor.svh" `include "axi_reg_op_item.svh" `include "axi_reg_adapter.svh" diff --git a/hw/ip/dv/axi_agent/axi_mgr_agent.svh b/hw/ip/dv/axi_agent/axi_mgr_agent.svh index 5e635868f..cd3d58132 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_agent.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_agent.svh @@ -11,28 +11,26 @@ class axi_mgr_agent extends uvm_agent; // either be set by calling set_cfg() before the build phase, or provided through uvm_config_db. local axi_agent_cfg m_cfg; + // Reset monitor for the shared AXI clock/reset (covers all five channels). + local axi_reset_monitor m_reset_monitor; + // The write request channel (AW) - local axi_reset_monitor_aw m_reset_monitor_aw; local axi_mgr_write_request_driver m_write_request_driver; local write_request_sequencer_t m_write_request_sequencer; // The write data channel (W) - local axi_reset_monitor_w m_reset_monitor_w; local axi_mgr_write_data_driver m_write_data_driver; local write_data_sequencer_t m_write_data_sequencer; // The write response channel (B) - local axi_reset_monitor_b m_reset_monitor_b; local axi_mgr_write_response_driver m_write_response_driver; local write_response_sequencer_t m_write_response_sequencer; // The read request channel (AR) - local axi_reset_monitor_ar m_reset_monitor_ar; local axi_mgr_read_request_driver m_read_request_driver; local read_request_sequencer_t m_read_request_sequencer; // The read data channel (R) - local axi_reset_monitor_r m_reset_monitor_r; local axi_mgr_read_data_driver m_read_data_driver; local read_data_sequencer_t m_read_data_sequencer; @@ -61,20 +59,8 @@ class axi_mgr_agent extends uvm_agent; // will try to get the config object from uvm_config_db. extern function void set_cfg(axi_agent_cfg cfg); - // Get the reset_monitor for the write request channel (AW). Can only be called after build_phase. - extern function axi_reset_monitor_aw get_write_request_reset_monitor(); - - // Get the reset_monitor for the write data channel (W). Can only be called after build_phase. - extern function axi_reset_monitor_w get_write_data_reset_monitor(); - - // Get the reset_monitor for the write response channel (B). Can only be called after build_phase. - extern function axi_reset_monitor_b get_write_response_reset_monitor(); - - // Get the reset_monitor for the read request channel (AR). Can only be called after build_phase. - extern function axi_reset_monitor_ar get_read_request_reset_monitor(); - - // Get the reset_monitor for the read data channel (R). Can only be called after build_phase. - extern function axi_reset_monitor_r get_read_data_reset_monitor(); + // Get the reset monitor for the shared AXI clock/reset. Can only be called after build_phase. + extern function axi_reset_monitor get_reset_monitor(); // Get the sequencer for the write request channel (AW). Can only be called after build_phase, and // the agent must be active. @@ -129,12 +115,8 @@ function void axi_mgr_agent::build_phase(uvm_phase phase); `uvm_fatal(get_full_name(), "failed to get cfg object from uvm_config_db") end - // Generate a reset monitor for each of the five channels. - m_reset_monitor_aw = axi_reset_monitor_aw::type_id::create("m_reset_monitor_aw", this); - m_reset_monitor_w = axi_reset_monitor_w::type_id::create("m_reset_monitor_w", this); - m_reset_monitor_b = axi_reset_monitor_b::type_id::create("m_reset_monitor_b", this); - m_reset_monitor_ar = axi_reset_monitor_ar::type_id::create("m_reset_monitor_ar", this); - m_reset_monitor_r = axi_reset_monitor_r::type_id::create("m_reset_monitor_r", this); + // One reset monitor for the shared AXI clock/reset (ACLK/ARESETn). + m_reset_monitor = axi_reset_monitor::type_id::create("m_reset_monitor", this); if (get_is_active() == UVM_ACTIVE) begin // Create routers for write and read responses @@ -176,36 +158,33 @@ endfunction function void axi_mgr_agent::connect_phase(uvm_phase phase); super.connect_phase(phase); - m_reset_monitor_aw.set_vif(m_cfg.write_request_vif); - m_reset_monitor_w.set_vif(m_cfg.write_data_vif); - m_reset_monitor_b.set_vif(m_cfg.write_response_vif); - m_reset_monitor_ar.set_vif(m_cfg.read_request_vif); - m_reset_monitor_r.set_vif(m_cfg.read_data_vif); + m_reset_monitor.set_vif(m_cfg.clk_rst_vif); // If the agent is active, connect drivers to interfaces and sequencers if (get_is_active() == UVM_ACTIVE) begin m_write_request_driver.set_vif(m_cfg.write_request_vif); + m_write_request_driver.set_clk_rst_vif(m_cfg.clk_rst_vif); m_write_request_driver.seq_item_port.connect(m_write_request_sequencer.seq_item_export); m_write_data_driver.set_vif(m_cfg.write_data_vif); + m_write_data_driver.set_clk_rst_vif(m_cfg.clk_rst_vif); m_write_data_driver.seq_item_port.connect(m_write_data_sequencer.seq_item_export); m_write_response_driver.set_vif(m_cfg.write_response_vif); + m_write_response_driver.set_clk_rst_vif(m_cfg.clk_rst_vif); m_write_response_driver.seq_item_port.connect(m_write_response_sequencer.seq_item_export); m_read_request_driver.set_vif(m_cfg.read_request_vif); + m_read_request_driver.set_clk_rst_vif(m_cfg.clk_rst_vif); m_read_request_driver.seq_item_port.connect(m_read_request_sequencer.seq_item_export); m_read_data_driver.set_vif(m_cfg.read_data_vif); + m_read_data_driver.set_clk_rst_vif(m_cfg.clk_rst_vif); m_read_data_driver.seq_item_port.connect(m_read_data_sequencer.seq_item_export); - // Connect the write response router to reset events from the AW channel (which will match the - // events from W and B) - m_reset_monitor_aw.m_analysis_port.connect(m_write_response_router.reset_imp); - - // Connect the read response router to reset events from the AR channel (which will match the - // events from R) - m_reset_monitor_ar.m_analysis_port.connect(m_read_response_router.reset_imp); + // Both response routers observe the same shared reset. + m_reset_monitor.m_analysis_port.connect(m_write_response_router.reset_imp); + m_reset_monitor.m_analysis_port.connect(m_read_response_router.reset_imp); end endfunction @@ -214,34 +193,9 @@ function void axi_mgr_agent::set_cfg(axi_agent_cfg cfg); m_cfg = cfg; endfunction -function axi_reset_monitor_aw axi_mgr_agent::get_write_request_reset_monitor(); - if (m_reset_monitor_aw == null) - `uvm_fatal(get_full_name(), "m_reset_monitor_aw is null.") - return m_reset_monitor_aw; -endfunction - -function axi_reset_monitor_w axi_mgr_agent::get_write_data_reset_monitor(); - if (m_reset_monitor_w == null) - `uvm_fatal(get_full_name(), "m_reset_monitor_w is null.") - return m_reset_monitor_w; -endfunction - -function axi_reset_monitor_b axi_mgr_agent::get_write_response_reset_monitor(); - if (m_reset_monitor_b == null) - `uvm_fatal(get_full_name(), "m_reset_monitor_b is null.") - return m_reset_monitor_b; -endfunction - -function axi_reset_monitor_ar axi_mgr_agent::get_read_request_reset_monitor(); - if (m_reset_monitor_ar == null) - `uvm_fatal(get_full_name(), "m_reset_monitor_ar is null.") - return m_reset_monitor_ar; -endfunction - -function axi_reset_monitor_r axi_mgr_agent::get_read_data_reset_monitor(); - if (m_reset_monitor_r == null) - `uvm_fatal(get_full_name(), "m_reset_monitor_r is null.") - return m_reset_monitor_r; +function axi_reset_monitor axi_mgr_agent::get_reset_monitor(); + if (m_reset_monitor == null) `uvm_fatal(get_full_name(), "m_reset_monitor is null.") + return m_reset_monitor; endfunction function write_request_sequencer_t axi_mgr_agent::get_write_request_sequencer(); diff --git a/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh index 44b710331..c6c0c323a 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh @@ -9,6 +9,7 @@ class axi_mgr_read_data_driver extends uvm_driver#(axi_response_accept_item, uvm `uvm_component_utils(axi_mgr_read_data_driver) local virtual axi_read_data_if m_vif; + local virtual clk_rst_if m_clk_rst_vif; // True if the interface is currently in reset. Maintained by monitor_reset(). // @@ -23,6 +24,9 @@ class axi_mgr_read_data_driver extends uvm_driver#(axi_response_accept_item, uvm // Set m_vif. This must be called before run_phase. extern function void set_vif(virtual axi_read_data_if vif); + // Set the shared clock/reset interface. This must be called before run_phase. + extern function void set_clk_rst_vif(virtual clk_rst_if vif); + // Run forever, consuming and driving items from seq_item_port extern local task get_and_drive(); @@ -63,8 +67,12 @@ function void axi_mgr_read_data_driver::set_vif(virtual axi_read_data_if vif); m_vif = vif; endfunction +function void axi_mgr_read_data_driver::set_clk_rst_vif(virtual clk_rst_if vif); + m_clk_rst_vif = vif; +endfunction + task axi_mgr_read_data_driver::run_phase(uvm_phase phase); - if (m_vif == null) begin + if (m_vif == null || m_clk_rst_vif == null) begin `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") return; end @@ -88,12 +96,12 @@ task axi_mgr_read_data_driver::get_and_drive(); endtask task axi_mgr_read_data_driver::monitor_reset(); - wait(!$isunknown(m_vif.rst_ni)); - m_in_reset = !m_vif.rst_ni; + wait(!$isunknown(m_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; forever begin - wait (m_vif.rst_ni); + wait (m_clk_rst_vif.rst_n); m_in_reset = 0; - wait (!m_vif.rst_ni); + wait (!m_clk_rst_vif.rst_n); m_in_reset = 1; m_vif.mgr_cb.rready <= 1'b0; end diff --git a/hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh index 741532697..b606233af 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh @@ -14,6 +14,7 @@ class axi_mgr_read_request_driver extends uvm_driver#(axi_txn_request_item, axi_ `uvm_component_utils(axi_mgr_read_request_driver) local virtual axi_read_request_if m_vif; + local virtual clk_rst_if m_clk_rst_vif; // True if the interface is currently in reset. Maintained by monitor_reset(). // @@ -28,6 +29,9 @@ class axi_mgr_read_request_driver extends uvm_driver#(axi_txn_request_item, axi_ // Set m_vif. This must be called before run_phase. extern function void set_vif(virtual axi_read_request_if vif); + // Set the shared clock/reset interface. This must be called before run_phase. + extern function void set_clk_rst_vif(virtual clk_rst_if vif); + // Run forever, consuming and driving items from seq_item_port extern local task get_and_drive(); @@ -72,8 +76,12 @@ function void axi_mgr_read_request_driver::set_vif(virtual axi_read_request_if v m_vif = vif; endfunction +function void axi_mgr_read_request_driver::set_clk_rst_vif(virtual clk_rst_if vif); + m_clk_rst_vif = vif; +endfunction + task axi_mgr_read_request_driver::run_phase(uvm_phase phase); - if (m_vif == null) begin + if (m_vif == null || m_clk_rst_vif == null) begin `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") return; end @@ -100,12 +108,12 @@ task axi_mgr_read_request_driver::get_and_drive(); endtask task axi_mgr_read_request_driver::monitor_reset(); - wait(!$isunknown(m_vif.rst_ni)); - m_in_reset = !m_vif.rst_ni; + wait(!$isunknown(m_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; forever begin - wait (m_vif.rst_ni); + wait (m_clk_rst_vif.rst_n); m_in_reset = 0; - wait (!m_vif.rst_ni); + wait (!m_clk_rst_vif.rst_n); m_in_reset = 1; clear_data(); end diff --git a/hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh index aef86286f..be3e69c02 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh @@ -9,6 +9,7 @@ class axi_mgr_write_data_driver extends uvm_driver#(axi_write_data_item, axi_sta `uvm_component_utils(axi_mgr_write_data_driver) local virtual axi_write_data_if m_vif; + local virtual clk_rst_if m_clk_rst_vif; // True if the interface is currently in reset. Maintained by monitor_reset(). // @@ -23,6 +24,9 @@ class axi_mgr_write_data_driver extends uvm_driver#(axi_write_data_item, axi_sta // Set m_vif. This must be called before run_phase. extern function void set_vif(virtual axi_write_data_if vif); + // Set the shared clock/reset interface. This must be called before run_phase. + extern function void set_clk_rst_vif(virtual clk_rst_if vif); + // Run forever, consuming and driving items from seq_item_port extern local task get_and_drive(); @@ -67,8 +71,12 @@ function void axi_mgr_write_data_driver::set_vif(virtual axi_write_data_if vif); m_vif = vif; endfunction +function void axi_mgr_write_data_driver::set_clk_rst_vif(virtual clk_rst_if vif); + m_clk_rst_vif = vif; +endfunction + task axi_mgr_write_data_driver::run_phase(uvm_phase phase); - if (m_vif == null) begin + if (m_vif == null || m_clk_rst_vif == null) begin `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") return; end @@ -95,12 +103,12 @@ task axi_mgr_write_data_driver::get_and_drive(); endtask task axi_mgr_write_data_driver::monitor_reset(); - wait(!$isunknown(m_vif.rst_ni)); - m_in_reset = !m_vif.rst_ni; + wait(!$isunknown(m_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; forever begin - wait (m_vif.rst_ni); + wait (m_clk_rst_vif.rst_n); m_in_reset = 0; - wait (!m_vif.rst_ni); + wait (!m_clk_rst_vif.rst_n); m_in_reset = 1; clear_data(); end diff --git a/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh index 8a671a0c0..ae36d9267 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh @@ -14,6 +14,7 @@ class axi_mgr_write_request_driver extends uvm_driver#(axi_txn_request_item, axi `uvm_component_utils(axi_mgr_write_request_driver) local virtual axi_write_request_if m_vif; + local virtual clk_rst_if m_clk_rst_vif; // True if the interface is currently in reset. Maintained by monitor_reset(). // @@ -28,6 +29,9 @@ class axi_mgr_write_request_driver extends uvm_driver#(axi_txn_request_item, axi // Set m_vif. This must be called before run_phase. extern function void set_vif(virtual axi_write_request_if vif); + // Set the shared clock/reset interface. This must be called before run_phase. + extern function void set_clk_rst_vif(virtual clk_rst_if vif); + // Run forever, consuming and driving items from seq_item_port extern local task get_and_drive(); @@ -72,8 +76,12 @@ function void axi_mgr_write_request_driver::set_vif(virtual axi_write_request_if m_vif = vif; endfunction +function void axi_mgr_write_request_driver::set_clk_rst_vif(virtual clk_rst_if vif); + m_clk_rst_vif = vif; +endfunction + task axi_mgr_write_request_driver::run_phase(uvm_phase phase); - if (m_vif == null) begin + if (m_vif == null || m_clk_rst_vif == null) begin `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") return; end @@ -100,12 +108,12 @@ task axi_mgr_write_request_driver::get_and_drive(); endtask task axi_mgr_write_request_driver::monitor_reset(); - wait(!$isunknown(m_vif.rst_ni)); - m_in_reset = !m_vif.rst_ni; + wait(!$isunknown(m_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; forever begin - wait (m_vif.rst_ni); + wait (m_clk_rst_vif.rst_n); m_in_reset = 0; - wait (!m_vif.rst_ni); + wait (!m_clk_rst_vif.rst_n); m_in_reset = 1; clear_data(); end diff --git a/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh index fdab0ddb7..1784f0567 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh @@ -14,6 +14,7 @@ class axi_mgr_write_response_driver extends uvm_driver#(axi_response_accept_item `uvm_component_utils(axi_mgr_write_response_driver) local virtual axi_write_response_if m_vif; + local virtual clk_rst_if m_clk_rst_vif; // True if the interface is currently in reset. Maintained by monitor_reset(). // @@ -28,6 +29,9 @@ class axi_mgr_write_response_driver extends uvm_driver#(axi_response_accept_item // Set m_vif. This must be called before run_phase. extern function void set_vif(virtual axi_write_response_if vif); + // Set the shared clock/reset interface. This must be called before run_phase. + extern function void set_clk_rst_vif(virtual clk_rst_if vif); + // Run forever, consuming and driving items from seq_item_port extern local task get_and_drive(); @@ -63,8 +67,12 @@ function void axi_mgr_write_response_driver::set_vif(virtual axi_write_response_ m_vif = vif; endfunction +function void axi_mgr_write_response_driver::set_clk_rst_vif(virtual clk_rst_if vif); + m_clk_rst_vif = vif; +endfunction + task axi_mgr_write_response_driver::run_phase(uvm_phase phase); - if (m_vif == null) begin + if (m_vif == null || m_clk_rst_vif == null) begin `uvm_fatal(get_full_name(), "Cannot drive interface: vif is null.") return; end @@ -88,12 +96,12 @@ task axi_mgr_write_response_driver::get_and_drive(); endtask task axi_mgr_write_response_driver::monitor_reset(); - wait(!$isunknown(m_vif.rst_ni)); - m_in_reset = !m_vif.rst_ni; + wait(!$isunknown(m_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; forever begin - wait (m_vif.rst_ni); + wait (m_clk_rst_vif.rst_n); m_in_reset = 0; - wait (!m_vif.rst_ni); + wait (!m_clk_rst_vif.rst_n); m_in_reset = 1; m_vif.mgr_cb.bready <= 1'b0; end diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_aw.svh b/hw/ip/dv/axi_agent/axi_reset_monitor.svh similarity index 51% rename from hw/ip/dv/axi_agent/axi_reset_monitor_aw.svh rename to hw/ip/dv/axi_agent/axi_reset_monitor.svh index a63155cdd..a7f289c35 100644 --- a/hw/ip/dv/axi_agent/axi_reset_monitor_aw.svh +++ b/hw/ip/dv/axi_agent/axi_reset_monitor.svh @@ -2,47 +2,49 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -// A monitor for an axi_write_request_if (the AXI AW channel) +// A monitor for the shared AXI clock/reset (ACLK/ARESETn). AXI is a single-reset +// protocol, so one instance covers all five channels: it broadcasts an +// axi_reset_item on every reset state change. -class axi_reset_monitor_aw extends uvm_monitor; - `uvm_component_utils(axi_reset_monitor_aw); +class axi_reset_monitor extends uvm_monitor; + `uvm_component_utils(axi_reset_monitor); // A port that broadcasts an item on every reset state change. uvm_analysis_port #(axi_reset_item) m_analysis_port; - // The interface being tracked. Set this with set_vif before run_phase. - local virtual axi_write_request_if m_vif; + // The clock/reset interface being tracked. Set this with set_vif before run_phase. + local virtual clk_rst_if m_vif; extern function new(string name, uvm_component parent); extern task run_phase(uvm_phase phase); - extern function void set_vif(virtual axi_write_request_if vif); + extern function void set_vif(virtual clk_rst_if vif); endclass -function void axi_reset_monitor_aw::set_vif(virtual axi_write_request_if vif); +function void axi_reset_monitor::set_vif(virtual clk_rst_if vif); m_vif = vif; endfunction -function axi_reset_monitor_aw::new(string name, uvm_component parent); +function axi_reset_monitor::new(string name, uvm_component parent); super.new(name, parent); m_analysis_port = new("m_analysis_port", this); endfunction -task axi_reset_monitor_aw::run_phase(uvm_phase phase); +task axi_reset_monitor::run_phase(uvm_phase phase); if (m_vif == null) begin `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") return; end - wait(!$isunknown(m_vif.rst_ni)); + wait(!$isunknown(m_vif.rst_n)); forever begin axi_reset_item rst_item; - bit rst_n_bit = bit'(m_vif.rst_ni); + bit rst_n_bit = bit'(m_vif.rst_n); rst_item = axi_reset_item::type_id::create("rst_item"); rst_item.m_in_reset = !rst_n_bit; m_analysis_port.write(rst_item); - wait(m_vif.rst_ni === !rst_n_bit); + wait(m_vif.rst_n === !rst_n_bit); end endtask diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh b/hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh deleted file mode 100644 index 414650c02..000000000 --- a/hw/ip/dv/axi_agent/axi_reset_monitor_ar.svh +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright lowRISC contributors -// Licensed under the Apache License, Version 2.0, see LICENSE for details. -// SPDX-License-Identifier: Apache-2.0 - -// A monitor for an axi_read_request_if (the AXI AR channel) - -class axi_reset_monitor_ar extends uvm_monitor; - `uvm_component_utils(axi_reset_monitor_ar); - - // A port that broadcasts an item on every reset state change. - uvm_analysis_port #(axi_reset_item) m_analysis_port; - - // The interface being tracked. Set this with set_vif before run_phase. - local virtual axi_read_request_if m_vif; - - extern function new(string name, uvm_component parent); - extern task run_phase(uvm_phase phase); - - extern function void set_vif(virtual axi_read_request_if vif); -endclass - -function void axi_reset_monitor_ar::set_vif(virtual axi_read_request_if vif); - m_vif = vif; -endfunction - -function axi_reset_monitor_ar::new(string name, uvm_component parent); - super.new(name, parent); - m_analysis_port = new("m_analysis_port", this); -endfunction - -task axi_reset_monitor_ar::run_phase(uvm_phase phase); - if (m_vif == null) begin - `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") - return; - end - - wait(!$isunknown(m_vif.rst_ni)); - forever begin - axi_reset_item rst_item; - bit rst_n_bit = bit'(m_vif.rst_ni); - - rst_item = axi_reset_item::type_id::create("rst_item"); - rst_item.m_in_reset = !rst_n_bit; - m_analysis_port.write(rst_item); - - wait(m_vif.rst_ni === !rst_n_bit); - end -endtask diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_b.svh b/hw/ip/dv/axi_agent/axi_reset_monitor_b.svh deleted file mode 100644 index 4b4bce02c..000000000 --- a/hw/ip/dv/axi_agent/axi_reset_monitor_b.svh +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright lowRISC contributors -// Licensed under the Apache License, Version 2.0, see LICENSE for details. -// SPDX-License-Identifier: Apache-2.0 - -// A monitor for an axi_write_response_if (the AXI B channel) - -class axi_reset_monitor_b extends uvm_monitor; - `uvm_component_utils(axi_reset_monitor_b); - - // A port that broadcasts an item on every reset state change. - uvm_analysis_port #(axi_reset_item) m_analysis_port; - - // The interface being tracked. Set this with set_vif before run_phase. - local virtual axi_write_response_if m_vif; - - extern function new(string name, uvm_component parent); - extern task run_phase(uvm_phase phase); - - extern function void set_vif(virtual axi_write_response_if vif); -endclass - -function void axi_reset_monitor_b::set_vif(virtual axi_write_response_if vif); - m_vif = vif; -endfunction - -function axi_reset_monitor_b::new(string name, uvm_component parent); - super.new(name, parent); - m_analysis_port = new("m_analysis_port", this); -endfunction - -task axi_reset_monitor_b::run_phase(uvm_phase phase); - if (m_vif == null) begin - `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") - return; - end - - wait(!$isunknown(m_vif.rst_ni)); - forever begin - axi_reset_item rst_item; - bit rst_n_bit = bit'(m_vif.rst_ni); - - rst_item = axi_reset_item::type_id::create("rst_item"); - rst_item.m_in_reset = !rst_n_bit; - m_analysis_port.write(rst_item); - - wait(m_vif.rst_ni === !rst_n_bit); - end -endtask diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_r.svh b/hw/ip/dv/axi_agent/axi_reset_monitor_r.svh deleted file mode 100644 index f355b132e..000000000 --- a/hw/ip/dv/axi_agent/axi_reset_monitor_r.svh +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright lowRISC contributors -// Licensed under the Apache License, Version 2.0, see LICENSE for details. -// SPDX-License-Identifier: Apache-2.0 - -// A monitor for an axi_read_data_if (the AXI R channel) - -class axi_reset_monitor_r extends uvm_monitor; - `uvm_component_utils(axi_reset_monitor_r); - - // A port that broadcasts an item on every reset state change. - uvm_analysis_port #(axi_reset_item) m_analysis_port; - - // The interface being tracked. Set this with set_vif before run_phase. - local virtual axi_read_data_if m_vif; - - extern function new(string name, uvm_component parent); - extern task run_phase(uvm_phase phase); - - extern function void set_vif(virtual axi_read_data_if vif); -endclass - -function void axi_reset_monitor_r::set_vif(virtual axi_read_data_if vif); - m_vif = vif; -endfunction - -function axi_reset_monitor_r::new(string name, uvm_component parent); - super.new(name, parent); - m_analysis_port = new("m_analysis_port", this); -endfunction - -task axi_reset_monitor_r::run_phase(uvm_phase phase); - if (m_vif == null) begin - `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") - return; - end - - wait(!$isunknown(m_vif.rst_ni)); - forever begin - axi_reset_item rst_item; - bit rst_n_bit = bit'(m_vif.rst_ni); - - rst_item = axi_reset_item::type_id::create("rst_item"); - rst_item.m_in_reset = !rst_n_bit; - m_analysis_port.write(rst_item); - - wait(m_vif.rst_ni === !rst_n_bit); - end -endtask diff --git a/hw/ip/dv/axi_agent/axi_reset_monitor_w.svh b/hw/ip/dv/axi_agent/axi_reset_monitor_w.svh deleted file mode 100644 index 3068d5bb8..000000000 --- a/hw/ip/dv/axi_agent/axi_reset_monitor_w.svh +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright lowRISC contributors -// Licensed under the Apache License, Version 2.0, see LICENSE for details. -// SPDX-License-Identifier: Apache-2.0 - -// A monitor for an axi_write_data_if (the AXI W channel) - -class axi_reset_monitor_w extends uvm_monitor; - `uvm_component_utils(axi_reset_monitor_w); - - // A port that broadcasts an item on every reset state change. - uvm_analysis_port #(axi_reset_item) m_analysis_port; - - // The interface being tracked. Set this with set_vif before run_phase. - local virtual axi_write_data_if m_vif; - - extern function new(string name, uvm_component parent); - extern task run_phase(uvm_phase phase); - - extern function void set_vif(virtual axi_write_data_if vif); -endclass - -function void axi_reset_monitor_w::set_vif(virtual axi_write_data_if vif); - m_vif = vif; -endfunction - -function axi_reset_monitor_w::new(string name, uvm_component parent); - super.new(name, parent); - m_analysis_port = new("m_analysis_port", this); -endfunction - -task axi_reset_monitor_w::run_phase(uvm_phase phase); - if (m_vif == null) begin - `uvm_fatal(get_full_name(), "Cannot monitor interface: vif is null.") - return; - end - - wait(!$isunknown(m_vif.rst_ni)); - forever begin - axi_reset_item rst_item; - bit rst_n_bit = bit'(m_vif.rst_ni); - - rst_item = axi_reset_item::type_id::create("rst_item"); - rst_item.m_in_reset = !rst_n_bit; - m_analysis_port.write(rst_item); - - wait(m_vif.rst_ni === !rst_n_bit); - end -endtask From f17087d91e61c2c977213b5ad813656ec1b1871b Mon Sep 17 00:00:00 2001 From: tchilikov-semify Date: Wed, 8 Jul 2026 11:08:50 +0100 Subject: [PATCH 20/22] [dv,axi] add monitor to AXI agent --- hw/ip/dv/axi_agent/axi_agent.core | 6 + hw/ip/dv/axi_agent/axi_agent_cfg.svh | 26 ++- hw/ip/dv/axi_agent/axi_agent_pkg.sv | 6 + hw/ip/dv/axi_agent/axi_mgr_agent.svh | 19 +- hw/ip/dv/axi_agent/axi_mon_item.svh | 41 ++++ hw/ip/dv/axi_agent/axi_mon_read_item.svh | 81 +++++++ hw/ip/dv/axi_agent/axi_mon_types.svh | 11 + hw/ip/dv/axi_agent/axi_mon_write_item.svh | 89 ++++++++ hw/ip/dv/axi_agent/axi_monitor.svh | 267 ++++++++++++++++++++++ 9 files changed, 541 insertions(+), 5 deletions(-) create mode 100644 hw/ip/dv/axi_agent/axi_mon_item.svh create mode 100644 hw/ip/dv/axi_agent/axi_mon_read_item.svh create mode 100644 hw/ip/dv/axi_agent/axi_mon_types.svh create mode 100644 hw/ip/dv/axi_agent/axi_mon_write_item.svh create mode 100644 hw/ip/dv/axi_agent/axi_monitor.svh diff --git a/hw/ip/dv/axi_agent/axi_agent.core b/hw/ip/dv/axi_agent/axi_agent.core index 1c5677e89..1a620fd7a 100644 --- a/hw/ip/dv/axi_agent/axi_agent.core +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -19,6 +19,12 @@ filesets: - axi_agent_pkg.sv - axi_agent_cfg.svh: {is_include_file: true} + - axi_mon_types.svh: {is_include_file: true} + - axi_mon_item.svh: {is_include_file: true} + - axi_mon_write_item.svh: {is_include_file: true} + - axi_mon_read_item.svh: {is_include_file: true} + - axi_monitor.svh: {is_include_file: true} + - axi_reset_item.svh: {is_include_file: true} - axi_status_item.svh: {is_include_file: true} - axi_txn_request_item.svh: {is_include_file: true} diff --git a/hw/ip/dv/axi_agent/axi_agent_cfg.svh b/hw/ip/dv/axi_agent/axi_agent_cfg.svh index b4694e407..3ec7d5267 100644 --- a/hw/ip/dv/axi_agent/axi_agent_cfg.svh +++ b/hw/ip/dv/axi_agent/axi_agent_cfg.svh @@ -16,7 +16,27 @@ class axi_agent_cfg extends uvm_object; virtual axi_read_request_if read_request_vif; virtual axi_read_data_if read_data_vif; - function new(string name = "axi_agent_cfg"); - super.new(name); - endfunction + // ID and knobs + string inst_id = "axi_mgr"; // tag for logging + uvm_active_passive_enum is_active = UVM_ACTIVE; // gates the driver; the monitor is always built + bit enable_coverage = 1'b0; + + extern function new(string name = "axi_agent_cfg"); + + // Set the config fields in one call. Interfaces are set by the VIP instantiation. + extern function void set_config(string inst_id = "axi_mgr", + uvm_active_passive_enum is_active = UVM_ACTIVE, + bit enable_coverage = 1'b0); endclass + +function axi_agent_cfg::new(string name = "axi_agent_cfg"); + super.new(name); +endfunction + +function void axi_agent_cfg::set_config(string inst_id = "axi_mgr", + uvm_active_passive_enum is_active = UVM_ACTIVE, + bit enable_coverage = 1'b0); + this.inst_id = inst_id; + this.is_active = is_active; + this.enable_coverage = enable_coverage; +endfunction diff --git a/hw/ip/dv/axi_agent/axi_agent_pkg.sv b/hw/ip/dv/axi_agent/axi_agent_pkg.sv index efd01c7c1..3ea5c4486 100644 --- a/hw/ip/dv/axi_agent/axi_agent_pkg.sv +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -68,5 +68,11 @@ package axi_agent_pkg; `include "seq_lib/axi_mgr_register_layer_vseq.svh" + `include "axi_mon_types.svh" + `include "axi_mon_item.svh" + `include "axi_mon_write_item.svh" + `include "axi_mon_read_item.svh" + `include "axi_monitor.svh" + `include "axi_mgr_agent.svh" endpackage diff --git a/hw/ip/dv/axi_agent/axi_mgr_agent.svh b/hw/ip/dv/axi_agent/axi_mgr_agent.svh index cd3d58132..6bd35bdf5 100644 --- a/hw/ip/dv/axi_agent/axi_mgr_agent.svh +++ b/hw/ip/dv/axi_agent/axi_mgr_agent.svh @@ -40,6 +40,9 @@ class axi_mgr_agent extends uvm_agent; // A response router for reads local axi_response_router m_read_response_router; + // A transaction monitor. Built whether the agent is active or passive. + local axi_monitor m_monitor; + // A reg adapter. This is stateless, so gets created in build_phase whenever the agent is active. // It's useful in conjunction with a layered sequencer (which is created by // run_layered_register_vseq and can be retrieved with get_register_layering_sequencer). @@ -62,6 +65,9 @@ class axi_mgr_agent extends uvm_agent; // Get the reset monitor for the shared AXI clock/reset. Can only be called after build_phase. extern function axi_reset_monitor get_reset_monitor(); + // Get the transaction monitor. Can only be called after build_phase. + extern function axi_monitor get_monitor(); + // Get the sequencer for the write request channel (AW). Can only be called after build_phase, and // the agent must be active. extern function write_request_sequencer_t get_write_request_sequencer(); @@ -118,7 +124,11 @@ function void axi_mgr_agent::build_phase(uvm_phase phase); // One reset monitor for the shared AXI clock/reset (ACLK/ARESETn). m_reset_monitor = axi_reset_monitor::type_id::create("m_reset_monitor", this); - if (get_is_active() == UVM_ACTIVE) begin + // Passive transaction monitor: built in both active and passive agents. + m_monitor = axi_monitor::type_id::create("m_monitor", this); + m_monitor.set_cfg(m_cfg); + + if (m_cfg.is_active == UVM_ACTIVE) begin // Create routers for write and read responses m_write_response_router = axi_response_router::type_id::create("m_write_response_router", this); m_read_response_router = axi_response_router::type_id::create("m_read_response_router", this); @@ -161,7 +171,7 @@ function void axi_mgr_agent::connect_phase(uvm_phase phase); m_reset_monitor.set_vif(m_cfg.clk_rst_vif); // If the agent is active, connect drivers to interfaces and sequencers - if (get_is_active() == UVM_ACTIVE) begin + if (m_cfg.is_active == UVM_ACTIVE) begin m_write_request_driver.set_vif(m_cfg.write_request_vif); m_write_request_driver.set_clk_rst_vif(m_cfg.clk_rst_vif); m_write_request_driver.seq_item_port.connect(m_write_request_sequencer.seq_item_export); @@ -198,6 +208,11 @@ function axi_reset_monitor axi_mgr_agent::get_reset_monitor(); return m_reset_monitor; endfunction +function axi_monitor axi_mgr_agent::get_monitor(); + if (m_monitor == null) `uvm_fatal(get_full_name(), "m_monitor is null.") + return m_monitor; +endfunction + function write_request_sequencer_t axi_mgr_agent::get_write_request_sequencer(); if (m_write_request_sequencer == null) `uvm_fatal(get_full_name(), "m_write_request_sequencer is null.") diff --git a/hw/ip/dv/axi_agent/axi_mon_item.svh b/hw/ip/dv/axi_agent/axi_mon_item.svh new file mode 100644 index 000000000..50a21a51e --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mon_item.svh @@ -0,0 +1,41 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// Abstract base for a monitored AXI transaction. The concrete items are +// axi_mon_write_item (AW + W beats + B) and axi_mon_read_item (AR + R beats); the +// base exists only so the monitor's analysis ports and the scoreboard can carry +// either through a single handle. +// +// The accessors below are the direction-agnostic view the scoreboard's routing needs +// (address decode, ID keying). Direction-specific fields (attributes, data, response) +// live on the concrete types and are reached by $cast where the scoreboard genuinely +// does different work per direction. + +virtual class axi_mon_item extends uvm_sequence_item; + + extern function new(string name = ""); + + // Request-phase AXI ID (awid / arid). + pure virtual function bit [31:0] get_id(); + + // Request-phase address (awaddr / araddr). + pure virtual function bit [63:0] get_addr(); + + // Transaction direction (implied by the concrete type). + pure virtual function axi_dir_e get_dir(); + + // Clone and return the result already cast to the axi_mon_item base handle. Relies on + // the concrete type's factory registration + field automation, so it yields the right + // dynamic type without each child reimplementing it. + extern virtual function axi_mon_item item_clone(); + +endclass : axi_mon_item + +function axi_mon_item::new(string name = ""); + super.new(name); +endfunction : new + +function axi_mon_item axi_mon_item::item_clone(); + $cast(item_clone, clone()); +endfunction : item_clone diff --git a/hw/ip/dv/axi_agent/axi_mon_read_item.svh b/hw/ip/dv/axi_agent/axi_mon_read_item.svh new file mode 100644 index 000000000..e78f25e80 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mon_read_item.svh @@ -0,0 +1,81 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A monitored AXI read transaction: the read address (AR) and the read data beats (R). +// Emitted partial on ar_ap (AR only) and r_ap (R beats only); fully merged on tx_ap. +// Field widths mirror the axi_*_if max footprint. + +class axi_mon_read_item extends axi_mon_item; + + // Read address (AR) + bit [31:0] arid; + bit [63:0] araddr; + bit [7:0] arlen; + bit [2:0] arsize; + bit [1:0] arburst; + bit arlock; + bit [3:0] arcache; + bit [2:0] arprot; + bit [3:0] arqos; + bit [3:0] arregion; + bit [127:0] aruser; + + // Read data (R) — one entry per beat + bit [31:0] rid; + bit [1023:0] rdata[$]; + bit [2:0] rresp[$]; + bit rlast[$]; + bit [527:0] ruser[$]; + + `uvm_object_utils_begin(axi_mon_read_item) + // Read Address + `uvm_field_int(arid, UVM_DEFAULT) + `uvm_field_int(araddr, UVM_DEFAULT) + `uvm_field_int(arlen, UVM_DEFAULT) + `uvm_field_int(arsize, UVM_DEFAULT) + `uvm_field_int(arburst, UVM_DEFAULT) + `uvm_field_int(arlock, UVM_DEFAULT) + `uvm_field_int(arcache, UVM_DEFAULT) + `uvm_field_int(arprot, UVM_DEFAULT) + `uvm_field_int(arqos, UVM_DEFAULT) + `uvm_field_int(arregion, UVM_DEFAULT) + `uvm_field_int(aruser, UVM_DEFAULT) + + // Read Data (Queues) + `uvm_field_int(rid, UVM_DEFAULT) + `uvm_field_queue_int(rdata, UVM_DEFAULT) + `uvm_field_queue_int(rresp, UVM_DEFAULT) + `uvm_field_queue_int(rlast, UVM_DEFAULT) + `uvm_field_queue_int(ruser, UVM_DEFAULT) + `uvm_object_utils_end + + extern function new(string name = ""); + + extern virtual function bit [31:0] get_id(); + extern virtual function bit [63:0] get_addr(); + extern virtual function axi_dir_e get_dir(); + extern virtual function string convert2string(); + +endclass : axi_mon_read_item + +function axi_mon_read_item::new(string name = ""); + super.new(name); +endfunction : new + +function bit [31:0] axi_mon_read_item::get_id(); + return arid; +endfunction : get_id + +function bit [63:0] axi_mon_read_item::get_addr(); + return araddr; +endfunction : get_addr + +function axi_dir_e axi_mon_read_item::get_dir(); + return AXI_READ; +endfunction : get_dir + +function string axi_mon_read_item::convert2string(); + return $sformatf("READ id=%0h addr=%0h len=%0d size=%0d burst=%0d beats=%0d", + arid, araddr, arlen, arsize, arburst, rdata.size()); +endfunction : convert2string diff --git a/hw/ip/dv/axi_agent/axi_mon_types.svh b/hw/ip/dv/axi_agent/axi_mon_types.svh new file mode 100644 index 000000000..bfe099ba8 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mon_types.svh @@ -0,0 +1,11 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// Enumerations for the passive AXI transaction monitor (axi_monitor / axi_mon_item). + +// Direction of a monitored transaction. +typedef enum { + AXI_READ, + AXI_WRITE +} axi_dir_e; diff --git a/hw/ip/dv/axi_agent/axi_mon_write_item.svh b/hw/ip/dv/axi_agent/axi_mon_write_item.svh new file mode 100644 index 000000000..cca1ae84a --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mon_write_item.svh @@ -0,0 +1,89 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// A monitored AXI write transaction: the write address (AW), the write data beats (W) +// and the write response (B). Emitted partial on aw_ap (AW only) and w_ap (W beats +// only); fully merged on tx_ap. Field widths mirror the axi_*_if max footprint. + +class axi_mon_write_item extends axi_mon_item; + + // Write address (AW) + bit [31:0] awid; + bit [63:0] awaddr; + bit [7:0] awlen; + bit [2:0] awsize; + bit [1:0] awburst; + bit awlock; + bit [3:0] awcache; + bit [2:0] awprot; + bit [3:0] awqos; + bit [3:0] awregion; + bit [127:0] awuser; + + // Write data (W) — one entry per beat + bit [1023:0] wdata[$]; + bit [127:0] wstrb[$]; + bit wlast[$]; + bit [511:0] wuser[$]; + + // Write response (B) + bit [31:0] bid; + bit [2:0] bresp; + bit [15:0] buser; + + `uvm_object_utils_begin(axi_mon_write_item) + // Write Address + `uvm_field_int(awid, UVM_DEFAULT) + `uvm_field_int(awaddr, UVM_DEFAULT) + `uvm_field_int(awlen, UVM_DEFAULT) + `uvm_field_int(awsize, UVM_DEFAULT) + `uvm_field_int(awburst, UVM_DEFAULT) + `uvm_field_int(awlock, UVM_DEFAULT) + `uvm_field_int(awcache, UVM_DEFAULT) + `uvm_field_int(awprot, UVM_DEFAULT) + `uvm_field_int(awqos, UVM_DEFAULT) + `uvm_field_int(awregion, UVM_DEFAULT) + `uvm_field_int(awuser, UVM_DEFAULT) + + // Write Data (Queues) + `uvm_field_queue_int(wdata, UVM_DEFAULT) + `uvm_field_queue_int(wstrb, UVM_DEFAULT) + `uvm_field_queue_int(wlast, UVM_DEFAULT) + `uvm_field_queue_int(wuser, UVM_DEFAULT) + + // Write Response + `uvm_field_int(bid, UVM_DEFAULT) + `uvm_field_int(bresp, UVM_DEFAULT) + `uvm_field_int(buser, UVM_DEFAULT) + `uvm_object_utils_end + + extern function new(string name = ""); + + extern virtual function bit [31:0] get_id(); + extern virtual function bit [63:0] get_addr(); + extern virtual function axi_dir_e get_dir(); + extern virtual function string convert2string(); + +endclass : axi_mon_write_item + +function axi_mon_write_item::new(string name = ""); + super.new(name); +endfunction : new + +function bit [31:0] axi_mon_write_item::get_id(); + return awid; +endfunction : get_id + +function bit [63:0] axi_mon_write_item::get_addr(); + return awaddr; +endfunction : get_addr + +function axi_dir_e axi_mon_write_item::get_dir(); + return AXI_WRITE; +endfunction : get_dir + +function string axi_mon_write_item::convert2string(); + return $sformatf("WRITE id=%0h addr=%0h len=%0d size=%0d burst=%0d beats=%0d bresp=%0h", + awid, awaddr, awlen, awsize, awburst, wdata.size(), bresp); +endfunction : convert2string diff --git a/hw/ip/dv/axi_agent/axi_monitor.svh b/hw/ip/dv/axi_agent/axi_monitor.svh new file mode 100644 index 000000000..237f06e4f --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_monitor.svh @@ -0,0 +1,267 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// Passive AXI transaction monitor. Snoops the five axi_*_if mon_cb clocking +// blocks and rebuilds whole transactions: AW+W are paired in AW order, B/R are +// matched to their request by ID. Per-channel items go out on aw/w/ar/r_ap; +// fully merged transactions on tx_ap (write completed at B, read completed at RLAST). +// Interfaces come from axi_agent_cfg. + +class axi_monitor extends uvm_monitor; + `uvm_component_utils(axi_monitor) + + local axi_agent_cfg m_cfg; + + local virtual axi_write_request_if m_aw_vif; + local virtual axi_write_data_if m_w_vif; + local virtual axi_write_response_if m_b_vif; + local virtual axi_read_request_if m_ar_vif; + local virtual axi_read_data_if m_r_vif; + local virtual clk_rst_if m_clk_rst_vif; // shared ACLK/ARESETn + + uvm_analysis_port #(axi_mon_item) aw_ap; + uvm_analysis_port #(axi_mon_item) w_ap; + uvm_analysis_port #(axi_mon_item) ar_ap; + uvm_analysis_port #(axi_mon_item) r_ap; + uvm_analysis_port #(axi_mon_item) tx_ap; + + // Write requests/data awaiting their counterpart on the other write channel. + protected axi_mon_write_item aw_pending_q[$]; + protected axi_mon_write_item w_pending_q[$]; + + // Outstanding (merged) requests awaiting their response, keyed by AXI ID. + protected axi_mon_write_item write_q_by_id [bit [31:0]][$]; + protected axi_mon_read_item read_q_by_id [bit [31:0]][$]; + + extern function new(string name, uvm_component parent); + extern function void set_cfg(axi_agent_cfg cfg); + extern function void build_phase(uvm_phase phase); + extern task run_phase(uvm_phase phase); + + extern protected function void cleanup_queues(); + extern protected task collect_aw_channel(); + extern protected task collect_w_channel(); + extern protected task collect_b_channel(); + extern protected task collect_ar_channel(); + extern protected task collect_r_channel(); + + // Return a copy of req with the AW attributes from aw_item merged in. + extern protected function axi_mon_write_item merge_aw(axi_mon_write_item req, + axi_mon_write_item aw_item); +endclass : axi_monitor + +function axi_monitor::new(string name, uvm_component parent); + super.new(name, parent); + aw_ap = new("aw_ap", this); + w_ap = new("w_ap", this); + ar_ap = new("ar_ap", this); + r_ap = new("r_ap", this); + tx_ap = new("tx_ap", this); +endfunction : new + +function void axi_monitor::set_cfg(axi_agent_cfg cfg); + if (m_cfg != null) `uvm_fatal(get_full_name(), "Cannot set cfg: m_cfg is already non-null.") + m_cfg = cfg; +endfunction : set_cfg + +function void axi_monitor::build_phase(uvm_phase phase); + super.build_phase(phase); + + if (m_cfg == null && !uvm_config_db#(axi_agent_cfg)::get(this, "", "cfg", m_cfg)) begin + `uvm_fatal(get_full_name(), "failed to get cfg object from uvm_config_db") + end + + m_aw_vif = m_cfg.write_request_vif; + m_w_vif = m_cfg.write_data_vif; + m_b_vif = m_cfg.write_response_vif; + m_ar_vif = m_cfg.read_request_vif; + m_r_vif = m_cfg.read_data_vif; + m_clk_rst_vif = m_cfg.clk_rst_vif; +endfunction : build_phase + +task axi_monitor::run_phase(uvm_phase phase); + forever begin + wait (m_clk_rst_vif.rst_n === 1'b1); + + fork : isolation_fork + begin + fork + wait (m_clk_rst_vif.rst_n === 1'b0); + + collect_aw_channel(); + collect_w_channel(); + collect_b_channel(); + collect_ar_channel(); + collect_r_channel(); + join_any + + disable fork; + end + join + + cleanup_queues(); + end +endtask : run_phase + +function void axi_monitor::cleanup_queues(); + aw_pending_q.delete(); + w_pending_q.delete(); + write_q_by_id.delete(); + read_q_by_id.delete(); +endfunction : cleanup_queues + +task axi_monitor::collect_aw_channel(); + forever begin + @(m_aw_vif.mon_cb); + if (m_aw_vif.mon_cb.awvalid && m_aw_vif.mon_cb.awready) begin + axi_mon_write_item tr = axi_mon_write_item::type_id::create("aw_tr"); + + tr.awid = m_aw_vif.mon_cb.awid; + tr.awaddr = m_aw_vif.mon_cb.awaddr; + tr.awlen = m_aw_vif.mon_cb.awlen; + tr.awsize = m_aw_vif.mon_cb.awsize; + tr.awburst = m_aw_vif.mon_cb.awburst; + tr.awlock = m_aw_vif.mon_cb.awlock; + tr.awcache = m_aw_vif.mon_cb.awcache; + tr.awprot = m_aw_vif.mon_cb.awprot; + tr.awqos = m_aw_vif.mon_cb.awqos; + tr.awregion = m_aw_vif.mon_cb.awregion; + tr.awuser = m_aw_vif.mon_cb.awuser; + + if (w_pending_q.size() > 0) begin + axi_mon_write_item w_tr = w_pending_q.pop_front(); + write_q_by_id[tr.awid].push_back(merge_aw(w_tr, tr)); + end else begin + aw_pending_q.push_back(tr); + end + `uvm_info(get_full_name(), + $sformatf("AW collected: ID=%0h Addr=%0h", tr.awid, tr.awaddr), UVM_HIGH) + aw_ap.write(tr.item_clone()); + end + end +endtask : collect_aw_channel + +task axi_monitor::collect_w_channel(); + axi_mon_write_item w_burst; + + forever begin + @(m_w_vif.mon_cb); + if (m_w_vif.mon_cb.wvalid && m_w_vif.mon_cb.wready) begin + if (w_burst == null) w_burst = axi_mon_write_item::type_id::create("w_burst"); + + w_burst.wdata.push_back(m_w_vif.mon_cb.wdata); + w_burst.wstrb.push_back(m_w_vif.mon_cb.wstrb); + w_burst.wuser.push_back(m_w_vif.mon_cb.wuser); + w_burst.wlast.push_back(m_w_vif.mon_cb.wlast); + + if (m_w_vif.mon_cb.wlast) begin + if (aw_pending_q.size() > 0) begin + axi_mon_write_item aw_tr = aw_pending_q.pop_front(); + write_q_by_id[aw_tr.awid].push_back(merge_aw(w_burst, aw_tr)); + end else begin + w_pending_q.push_back(w_burst); + end + + `uvm_info(get_full_name(), "W burst collected", UVM_HIGH) + w_ap.write(w_burst.item_clone()); + w_burst = null; + end + end + end +endtask : collect_w_channel + +task axi_monitor::collect_b_channel(); + bit [31:0] id; + + forever begin + @(m_b_vif.mon_cb); + if (m_b_vif.mon_cb.bvalid && m_b_vif.mon_cb.bready) begin + id = m_b_vif.mon_cb.bid; + if (write_q_by_id.exists(id) && write_q_by_id[id].size() > 0) begin + axi_mon_write_item tr = write_q_by_id[id].pop_front(); + tr.bid = id; + tr.bresp = m_b_vif.mon_cb.bresp; + tr.buser = m_b_vif.mon_cb.buser; + `uvm_info(get_full_name(), $sformatf("FULL write complete: ID=%0h", id), UVM_HIGH) + tx_ap.write(tr.item_clone()); + end else begin + `uvm_error("MON_B", $sformatf("B response for unexpected ID: %0h", id)) + end + end + end +endtask : collect_b_channel + +task axi_monitor::collect_ar_channel(); + forever begin + @(m_ar_vif.mon_cb); + if (m_ar_vif.mon_cb.arvalid && m_ar_vif.mon_cb.arready) begin + axi_mon_read_item tr = axi_mon_read_item::type_id::create("ar_tr"); + + tr.arid = m_ar_vif.mon_cb.arid; + tr.araddr = m_ar_vif.mon_cb.araddr; + tr.arlen = m_ar_vif.mon_cb.arlen; + tr.arsize = m_ar_vif.mon_cb.arsize; + tr.arburst = m_ar_vif.mon_cb.arburst; + tr.arlock = m_ar_vif.mon_cb.arlock; + tr.arcache = m_ar_vif.mon_cb.arcache; + tr.arprot = m_ar_vif.mon_cb.arprot; + tr.arqos = m_ar_vif.mon_cb.arqos; + tr.arregion = m_ar_vif.mon_cb.arregion; + tr.aruser = m_ar_vif.mon_cb.aruser; + + read_q_by_id[tr.arid].push_back(tr); + `uvm_info(get_full_name(), + $sformatf("AR collected: ID=%0h Addr=%0h", tr.arid, tr.araddr), UVM_HIGH) + ar_ap.write(tr.item_clone()); + end + end +endtask : collect_ar_channel + +task axi_monitor::collect_r_channel(); + bit [31:0] id; + + forever begin + @(m_r_vif.mon_cb); + if (m_r_vif.mon_cb.rvalid && m_r_vif.mon_cb.rready) begin + id = m_r_vif.mon_cb.rid; + if (read_q_by_id.exists(id) && read_q_by_id[id].size() > 0) begin + axi_mon_read_item tr = read_q_by_id[id][0]; + tr.rid = id; + tr.rdata.push_back(m_r_vif.mon_cb.rdata); + tr.rresp.push_back(m_r_vif.mon_cb.rresp); + tr.rlast.push_back(m_r_vif.mon_cb.rlast); + tr.ruser.push_back(m_r_vif.mon_cb.ruser); + + if (m_r_vif.mon_cb.rlast) begin + void'(read_q_by_id[id].pop_front()); + `uvm_info(get_full_name(), $sformatf("FULL read complete: ID=%0h", id), UVM_HIGH) + tx_ap.write(tr.item_clone()); + end + r_ap.write(tr.item_clone()); + end else begin + `uvm_error("MON_R", $sformatf("R data for unexpected ID: %0h", id)) + end + end + end +endtask : collect_r_channel + +function axi_mon_write_item axi_monitor::merge_aw(axi_mon_write_item req, + axi_mon_write_item aw_item); + axi_mon_write_item write_item; + $cast(write_item, req.clone()); + + write_item.awid = aw_item.awid; + write_item.awaddr = aw_item.awaddr; + write_item.awlen = aw_item.awlen; + write_item.awsize = aw_item.awsize; + write_item.awburst = aw_item.awburst; + write_item.awlock = aw_item.awlock; + write_item.awcache = aw_item.awcache; + write_item.awprot = aw_item.awprot; + write_item.awqos = aw_item.awqos; + write_item.awregion = aw_item.awregion; + write_item.awuser = aw_item.awuser; + + return write_item; +endfunction : merge_aw From f46f8d11138963b42dc7797417bfebf84c9f4cd5 Mon Sep 17 00:00:00 2001 From: tchilikov-semify Date: Fri, 17 Jul 2026 09:13:28 +0100 Subject: [PATCH 21/22] [dv,axi] add axi_vip wrapper to agent --- hw/ip/dv/axi_agent/axi_vip/axi_vip.core | 22 +++ hw/ip/dv/axi_agent/axi_vip/axi_vip_if.sv | 189 +++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 hw/ip/dv/axi_agent/axi_vip/axi_vip.core create mode 100644 hw/ip/dv/axi_agent/axi_vip/axi_vip_if.sv diff --git a/hw/ip/dv/axi_agent/axi_vip/axi_vip.core b/hw/ip/dv/axi_agent/axi_vip/axi_vip.core new file mode 100644 index 000000000..50ac64b4e --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_vip/axi_vip.core @@ -0,0 +1,22 @@ +CAPI=2: +# Copyright lowRISC contributors (COSMIC project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +name: "lowrisc:mocha_dv:axi_vip" +description: "Reusable parameterized AXI VIP interface" + +filesets: + files_vip: + depend: + - lowrisc:dv:dv_utils + - lowrisc:dv:common_ifs + - lowrisc:dv:axi_agent:0.1 + files: + - axi_vip_if.sv + file_type: systemVerilogSource + +targets: + default: + filesets: + - files_vip diff --git a/hw/ip/dv/axi_agent/axi_vip/axi_vip_if.sv b/hw/ip/dv/axi_agent/axi_vip/axi_vip_if.sv new file mode 100644 index 000000000..7206c9071 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_vip/axi_vip_if.sv @@ -0,0 +1,189 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// Reusable AXI VIP connection interface: instantiates the five axi_agent channel +// interfaces, bridges them to a packed AXI req/resp struct pair, and publishes them +// to the UVM config_db as one axi_agent_cfg. Parameterized on the req/resp struct +// types + widths, and on IsActive: +// UVM_ACTIVE -> manager: drives axi_req from the driven channels (Host mode). +// UVM_PASSIVE -> monitor: observes axi_req + axi_resp (Monitor mode). + +interface axi_vip_if #( + parameter type req_t = logic, + parameter type resp_t = logic, + parameter int IdWidth = 32, + parameter int AddrWidth = 64, + parameter int DataWidth = 64, + parameter int UserWidth = 1, + parameter uvm_pkg::uvm_active_passive_enum IsActive = uvm_pkg::UVM_ACTIVE, + parameter string InstId = "axi_mgr", // cfg.inst_id (log tag) + parameter string CfgScope = "*" // config_db publish scope glob +) ( + input logic clk_i, + input logic rst_ni +); + + import uvm_pkg::*; + import axi_agent_pkg::*; + `include "uvm_macros.svh" + + // DUT-facing struct pair. axi_req is driven by this interface when ACTIVE and + // observed when PASSIVE; axi_resp is always observed. + req_t axi_req; + resp_t axi_resp; + + clk_rst_if u_clk_rst_if (.clk (clk_i), .rst_n (rst_ni)); + axi_write_request_if aw_if (.clk_i(clk_i), .rst_ni(rst_ni)); + axi_write_data_if w_if (.clk_i(clk_i), .rst_ni(rst_ni)); + axi_write_response_if b_if (.clk_i(clk_i), .rst_ni(rst_ni)); + axi_read_request_if ar_if (.clk_i(clk_i), .rst_ni(rst_ni)); + axi_read_data_if r_if (.clk_i(clk_i), .rst_ni(rst_ni)); + + // --------------------------------------------------------------------------- + // Request bridge (manager-driven channels): ACTIVE packs axi_req from the + // driven interfaces; PASSIVE unpacks the observed axi_req onto the interfaces. + // --------------------------------------------------------------------------- + if (IsActive == UVM_ACTIVE) begin : gen_req_drive + // AW + assign axi_req.aw.id = $bits(axi_req.aw.id)'(aw_if.awid); + assign axi_req.aw.addr = $bits(axi_req.aw.addr)'(aw_if.awaddr); + assign axi_req.aw.len = $bits(axi_req.aw.len)'(aw_if.awlen); + assign axi_req.aw.size = $bits(axi_req.aw.size)'(aw_if.awsize); + assign axi_req.aw.burst = $bits(axi_req.aw.burst)'(aw_if.awburst); + assign axi_req.aw.lock = aw_if.awlock; + assign axi_req.aw.cache = $bits(axi_req.aw.cache)'(aw_if.awcache); + assign axi_req.aw.prot = $bits(axi_req.aw.prot)'(aw_if.awprot); + assign axi_req.aw.qos = $bits(axi_req.aw.qos)'(aw_if.awqos); + assign axi_req.aw.region = $bits(axi_req.aw.region)'(aw_if.awregion); + assign axi_req.aw.atop = '0; + assign axi_req.aw.user = $bits(axi_req.aw.user)'(aw_if.awuser); + assign axi_req.aw_valid = aw_if.awvalid; + // W: WDATA/WSTRB/WUSER are don't-cares (undriven X) when WVALID is low; drive + // defined zeros so the SRAM adapter never forwards X into its request FIFO. + assign axi_req.w.data = w_if.wvalid ? $bits(axi_req.w.data)'(w_if.wdata) : '0; + assign axi_req.w.strb = w_if.wvalid ? $bits(axi_req.w.strb)'(w_if.wstrb) : '0; + assign axi_req.w.last = w_if.wlast; + assign axi_req.w.user = w_if.wvalid ? $bits(axi_req.w.user)'(w_if.wuser) : '0; + assign axi_req.w_valid = w_if.wvalid; + // B + assign axi_req.b_ready = b_if.bready; + // AR + assign axi_req.ar.id = $bits(axi_req.ar.id)'(ar_if.arid); + assign axi_req.ar.addr = $bits(axi_req.ar.addr)'(ar_if.araddr); + assign axi_req.ar.len = $bits(axi_req.ar.len)'(ar_if.arlen); + assign axi_req.ar.size = $bits(axi_req.ar.size)'(ar_if.arsize); + assign axi_req.ar.burst = $bits(axi_req.ar.burst)'(ar_if.arburst); + assign axi_req.ar.lock = ar_if.arlock; + assign axi_req.ar.cache = $bits(axi_req.ar.cache)'(ar_if.arcache); + assign axi_req.ar.prot = $bits(axi_req.ar.prot)'(ar_if.arprot); + assign axi_req.ar.qos = $bits(axi_req.ar.qos)'(ar_if.arqos); + assign axi_req.ar.region = $bits(axi_req.ar.region)'(ar_if.arregion); + assign axi_req.ar.user = $bits(axi_req.ar.user)'(ar_if.aruser); + assign axi_req.ar_valid = ar_if.arvalid; + // R + assign axi_req.r_ready = r_if.rready; + end else begin : gen_req_observe + // AW + assign aw_if.awid = $bits(aw_if.awid)'(axi_req.aw.id); + assign aw_if.awaddr = $bits(aw_if.awaddr)'(axi_req.aw.addr); + assign aw_if.awlen = $bits(aw_if.awlen)'(axi_req.aw.len); + assign aw_if.awsize = $bits(aw_if.awsize)'(axi_req.aw.size); + assign aw_if.awburst = $bits(aw_if.awburst)'(axi_req.aw.burst); + assign aw_if.awlock = axi_req.aw.lock; + assign aw_if.awcache = $bits(aw_if.awcache)'(axi_req.aw.cache); + assign aw_if.awprot = $bits(aw_if.awprot)'(axi_req.aw.prot); + assign aw_if.awqos = $bits(aw_if.awqos)'(axi_req.aw.qos); + assign aw_if.awregion = $bits(aw_if.awregion)'(axi_req.aw.region); + assign aw_if.awuser = $bits(aw_if.awuser)'(axi_req.aw.user); + assign aw_if.awvalid = axi_req.aw_valid; + // W + assign w_if.wdata = $bits(w_if.wdata)'(axi_req.w.data); + assign w_if.wstrb = $bits(w_if.wstrb)'(axi_req.w.strb); + assign w_if.wlast = axi_req.w.last; + assign w_if.wuser = $bits(w_if.wuser)'(axi_req.w.user); + assign w_if.wvalid = axi_req.w_valid; + // B + assign b_if.bready = axi_req.b_ready; + // AR + assign ar_if.arid = $bits(ar_if.arid)'(axi_req.ar.id); + assign ar_if.araddr = $bits(ar_if.araddr)'(axi_req.ar.addr); + assign ar_if.arlen = $bits(ar_if.arlen)'(axi_req.ar.len); + assign ar_if.arsize = $bits(ar_if.arsize)'(axi_req.ar.size); + assign ar_if.arburst = $bits(ar_if.arburst)'(axi_req.ar.burst); + assign ar_if.arlock = axi_req.ar.lock; + assign ar_if.arcache = $bits(ar_if.arcache)'(axi_req.ar.cache); + assign ar_if.arprot = $bits(ar_if.arprot)'(axi_req.ar.prot); + assign ar_if.arqos = $bits(ar_if.arqos)'(axi_req.ar.qos); + assign ar_if.arregion = $bits(ar_if.arregion)'(axi_req.ar.region); + assign ar_if.aruser = $bits(ar_if.aruser)'(axi_req.ar.user); + assign ar_if.arvalid = axi_req.ar_valid; + // R + assign r_if.rready = axi_req.r_ready; + end + + // --------------------------------------------------------------------------- + // Response bridge (subordinate-driven channels): always observed onto the ifs. + // --------------------------------------------------------------------------- + assign aw_if.awready = axi_resp.aw_ready; + assign w_if.wready = axi_resp.w_ready; + assign b_if.bvalid = axi_resp.b_valid; + assign b_if.bid = $bits(b_if.bid)'(axi_resp.b.id); + assign b_if.bresp = $bits(b_if.bresp)'(axi_resp.b.resp); + assign b_if.buser = $bits(b_if.buser)'(axi_resp.b.user); + assign ar_if.arready = axi_resp.ar_ready; + assign r_if.rvalid = axi_resp.r_valid; + assign r_if.rid = $bits(r_if.rid)'(axi_resp.r.id); + assign r_if.rdata = $bits(r_if.rdata)'(axi_resp.r.data); + assign r_if.rresp = $bits(r_if.rresp)'(axi_resp.r.resp); + assign r_if.rlast = axi_resp.r.last; + assign r_if.ruser = $bits(r_if.ruser)'(axi_resp.r.user); + + // --------------------------------------------------------------------------- + // Interface widths + mode; build + publish the cfg (is_active from IsActive). + // --------------------------------------------------------------------------- + initial begin + dv_utils_pkg::if_mode_e mode = (IsActive == UVM_ACTIVE) ? dv_utils_pkg::Host + : dv_utils_pkg::Monitor; + axi_agent_cfg agent_cfg = new("agent_cfg"); + agent_cfg.set_config(.inst_id(InstId), .is_active(IsActive)); + + aw_if.set_id_w_width(IdWidth); + aw_if.set_addr_width(AddrWidth); + aw_if.set_user_req_width(UserWidth); + aw_if.if_mode = mode; + + w_if.set_user_data_width(UserWidth); + w_if.set_data_width(DataWidth); + w_if.if_mode = mode; + + b_if.set_id_w_width(IdWidth); + b_if.set_bresp_width($bits(axi_resp.b.resp)); + b_if.set_user_resp_width(UserWidth); + b_if.if_mode = mode; + + ar_if.set_id_r_width(IdWidth); + ar_if.set_addr_width(AddrWidth); + ar_if.set_user_req_width(UserWidth); + ar_if.if_mode = mode; + + r_if.set_id_r_width(IdWidth); + r_if.set_user_data_width(UserWidth); + r_if.set_data_width(DataWidth); + r_if.set_rresp_width($bits(axi_resp.r.resp)); + r_if.set_user_resp_width(UserWidth); + r_if.if_mode = mode; + + agent_cfg.write_request_vif = aw_if; + agent_cfg.write_data_vif = w_if; + agent_cfg.write_response_vif = b_if; + agent_cfg.read_request_vif = ar_if; + agent_cfg.read_data_vif = r_if; + agent_cfg.clk_rst_vif = u_clk_rst_if; + uvm_config_db#(axi_agent_cfg)::set(null, CfgScope, "cfg", agent_cfg); + end + + // Direction labels (documentary; the DUT connection uses struct assigns). + modport manager (output axi_req, input axi_resp); + modport monitor (input axi_req, input axi_resp); +endinterface From d9b35d79704e4759131cf050066bcf64548d6f2f Mon Sep 17 00:00:00 2001 From: tchilikov-semify Date: Wed, 22 Jul 2026 11:51:16 +0100 Subject: [PATCH 22/22] [dv,top] integrate AXI4 monitors in top tb --- .../dv/env/top_chip_dv_axi_scoreboard.sv | 337 ++++++++++++++++++ hw/top_chip/dv/env/top_chip_dv_env.core | 2 + hw/top_chip/dv/env/top_chip_dv_env.sv | 49 ++- hw/top_chip/dv/env/top_chip_dv_env_pkg.sv | 11 +- hw/top_chip/dv/tb/chip_hier_macros.svh | 1 + hw/top_chip/dv/tb/tb.sv | 48 +++ hw/top_chip/dv/test/top_chip_dv_base_test.sv | 2 + hw/top_chip/dv/test/top_chip_dv_test_pkg.sv | 1 + hw/top_chip/dv/top_chip_sim.core | 1 + 9 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 hw/top_chip/dv/env/top_chip_dv_axi_scoreboard.sv diff --git a/hw/top_chip/dv/env/top_chip_dv_axi_scoreboard.sv b/hw/top_chip/dv/env/top_chip_dv_axi_scoreboard.sv new file mode 100644 index 000000000..7bd5d268a --- /dev/null +++ b/hw/top_chip/dv/env/top_chip_dv_axi_scoreboard.sv @@ -0,0 +1,337 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +class top_chip_dv_axi_scoreboard extends uvm_scoreboard; + `uvm_component_utils(top_chip_dv_axi_scoreboard) + + // Device-side AXI ID. + typedef bit [top_pkg::AxiDevIdWidth-1:0] dev_id_t; + + axi_addr_range_t mem_map[$]; + string default_subordinate_id = "INTERNAL_XBAR_DEFAULT"; + + `uvm_analysis_imp_decl(_mgr0_cva6) + `uvm_analysis_imp_decl(_mgr0_cva6_req) + `uvm_analysis_imp_decl(_mgr1_dm_host) + `uvm_analysis_imp_decl(_mgr1_dm_host_req) + `uvm_analysis_imp_decl(_sub0_romctrlmem) + `uvm_analysis_imp_decl(_sub1_sram) + `uvm_analysis_imp_decl(_sub2_mailbox) + `uvm_analysis_imp_decl(_sub3_tlcrossbar) + `uvm_analysis_imp_decl(_sub4_dram) + `uvm_analysis_imp_decl(_sub5_dm_dev) + `uvm_analysis_imp_decl(_sub6_restofchip) + `uvm_analysis_imp_decl(_reset) + + uvm_analysis_imp_mgr0_cva6 #(axi_mon_item, top_chip_dv_axi_scoreboard) mgr0_cva6_imp; + uvm_analysis_imp_mgr0_cva6_req #(axi_mon_item, top_chip_dv_axi_scoreboard) mgr0_cva6_req_imp; + uvm_analysis_imp_mgr1_dm_host #(axi_mon_item, top_chip_dv_axi_scoreboard) mgr1_dm_host_imp; + uvm_analysis_imp_mgr1_dm_host_req #(axi_mon_item, top_chip_dv_axi_scoreboard) mgr1_dm_host_req_imp; + uvm_analysis_imp_sub0_romctrlmem #(axi_mon_item, top_chip_dv_axi_scoreboard) sub0_romctrlmem_imp; + uvm_analysis_imp_sub1_sram #(axi_mon_item, top_chip_dv_axi_scoreboard) sub1_sram_imp; + uvm_analysis_imp_sub2_mailbox #(axi_mon_item, top_chip_dv_axi_scoreboard) sub2_mailbox_imp; + uvm_analysis_imp_sub3_tlcrossbar #(axi_mon_item, top_chip_dv_axi_scoreboard) sub3_tlcrossbar_imp; + uvm_analysis_imp_sub4_dram #(axi_mon_item, top_chip_dv_axi_scoreboard) sub4_dram_imp; + uvm_analysis_imp_sub5_dm_dev #(axi_mon_item, top_chip_dv_axi_scoreboard) sub5_dm_dev_imp; + uvm_analysis_imp_sub6_restofchip #(axi_mon_item, top_chip_dv_axi_scoreboard) sub6_restofchip_imp; + uvm_analysis_imp_reset #(axi_reset_item, top_chip_dv_axi_scoreboard) reset_imp; + + protected axi_mon_item expected_queue[string][dev_id_t][$]; + protected axi_mon_item actual_queue[string][dev_id_t][$]; + + // Manager requests issued but not yet completed. A leftover subordinate response at + // end-of-test is a real error only if it exceeds this count + protected int unsigned mgr_outstanding[string][dev_id_t]; + + // Set while the AXI fabric reset is asserted. Queued transactions are flushed + protected bit under_reset; + + extern function new(string name, uvm_component parent); + extern virtual function void build_phase(uvm_phase phase); + // Record a subordinate address range from its base and size. + extern protected function void add_mem_range(string name, + bit [63:0] start_addr, + bit [63:0] addr_size); + // Device-side ID observed at a subordinate port (already carries the host index). + extern protected function dev_id_t get_dev_id(bit [63:0] raw_id); + // Device-side ID for a manager transaction: prepend the host index to its slave-side ID. + extern protected function dev_id_t mgr_dev_id(int unsigned host_idx, bit [63:0] raw_id); + // Return the subordinate range name that contains addr, or the default target. + extern protected function string addr_to_mem_range(bit [63:0] addr); + extern virtual function void perform_comparison(axi_mon_item exp, + axi_mon_item act, + string sub); + + // Match/queue a manager completion, and clear its outstanding request, for host host_idx. + extern protected function void record_manager_completion(axi_mon_item tr, int unsigned host_idx); + // Count a manager request against its target subordinate, for host host_idx. + extern protected function void record_manager_request(axi_mon_item tr, int unsigned host_idx); + + extern virtual function void write_mgr0_cva6(axi_mon_item tr); + extern virtual function void write_mgr0_cva6_req(axi_mon_item tr); + extern virtual function void write_mgr1_dm_host(axi_mon_item tr); + extern virtual function void write_mgr1_dm_host_req(axi_mon_item tr); + extern virtual function void write_sub0_romctrlmem(axi_mon_item tr); + extern virtual function void write_sub1_sram(axi_mon_item tr); + extern virtual function void write_sub2_mailbox(axi_mon_item tr); + extern virtual function void write_sub3_tlcrossbar(axi_mon_item tr); + extern virtual function void write_sub4_dram(axi_mon_item tr); + extern virtual function void write_sub5_dm_dev(axi_mon_item tr); + extern virtual function void write_sub6_restofchip(axi_mon_item tr); + + extern protected function void check_subordinate_arrival(axi_mon_item act, string sub_id); + extern virtual function void write_reset(axi_reset_item item); + extern virtual function void reset(); + extern virtual function void check_phase(uvm_phase phase); + +endclass : top_chip_dv_axi_scoreboard + +function top_chip_dv_axi_scoreboard::new(string name, uvm_component parent); + super.new(name, parent); + mgr0_cva6_imp = new("mgr0_cva6_imp", this); + mgr0_cva6_req_imp = new("mgr0_cva6_req_imp", this); + mgr1_dm_host_imp = new("mgr1_dm_host_imp", this); + mgr1_dm_host_req_imp = new("mgr1_dm_host_req_imp", this); + sub0_romctrlmem_imp = new("sub0_romctrlmem_imp", this); + sub1_sram_imp = new("sub1_sram_imp", this); + sub2_mailbox_imp = new("sub2_mailbox_imp", this); + sub3_tlcrossbar_imp = new("sub3_tlcrossbar_imp", this); + sub4_dram_imp = new("sub4_dram_imp", this); + sub5_dm_dev_imp = new("sub5_dm_dev_imp", this); + sub6_restofchip_imp = new("sub6_restofchip_imp", this); + reset_imp = new("reset_imp", this); +endfunction : new + +function void top_chip_dv_axi_scoreboard::build_phase(uvm_phase phase); + super.build_phase(phase); + + add_mem_range("sub0_romctrlmem", top_pkg::RomCtrlMemBase, top_pkg::RomCtrlMemLength); + add_mem_range("sub1_sram", top_pkg::SRAMBase, top_pkg::SRAMLength); + add_mem_range("sub2_mailbox", top_pkg::MailboxBase, top_pkg::MailboxLength); + add_mem_range("sub3_tlcrossbar", top_pkg::TlCrossbarBase, top_pkg::TlCrossbarLength); + add_mem_range("sub4_dram", top_pkg::DRAMBase, top_pkg::DRAMUsableLength); + add_mem_range("sub5_dm_dev", top_pkg::DebugMemBase, top_pkg::DebugMemLength); + add_mem_range("sub6_restofchip", top_pkg::RestOfChipBase, top_pkg::RestOfChipLength); +endfunction : build_phase + +function void top_chip_dv_axi_scoreboard::add_mem_range(string name, + bit [63:0] start_addr, + bit [63:0] addr_size); + bit [63:0] end_addr = start_addr + addr_size - 1; + mem_map.push_back('{name, start_addr, end_addr}); +endfunction : add_mem_range + +function top_chip_dv_axi_scoreboard::dev_id_t + top_chip_dv_axi_scoreboard::get_dev_id(bit [63:0] raw_id); + bit [63:0] mask = (64'(1) << top_pkg::AxiDevIdWidth) - 1; + return dev_id_t'(raw_id & mask); +endfunction : get_dev_id + +function top_chip_dv_axi_scoreboard::dev_id_t + top_chip_dv_axi_scoreboard::mgr_dev_id(int unsigned host_idx, bit [63:0] raw_id); + bit [63:0] id_mask = (64'(1) << top_pkg::AxiIdWidth) - 1; + return dev_id_t'((raw_id & id_mask) | (host_idx << top_pkg::AxiIdWidth)); +endfunction : mgr_dev_id + +function string top_chip_dv_axi_scoreboard::addr_to_mem_range(bit [63:0] addr); + foreach (mem_map[i]) begin + if (addr >= mem_map[i].start_addr && addr <= mem_map[i].end_addr) begin + return mem_map[i].subordinate_name; + end + end + return default_subordinate_id; +endfunction : addr_to_mem_range + +function void top_chip_dv_axi_scoreboard::perform_comparison(axi_mon_item exp, + axi_mon_item act, + string sub); + dev_id_t did = get_dev_id(act.get_id()); + + // If directions disagree, flag it rather than comparing. + if (exp.get_dir() != act.get_dir()) begin + `uvm_error("SCB_DIR_MISMATCH", + $sformatf("Direction mismatch on %s ID:%h: exp=%s act=%s", + sub, did, exp.get_dir().name(), act.get_dir().name())) + return; + end + + if (act.get_dir() == AXI_WRITE) begin + axi_mon_write_item a, e; + `DV_CHECK_FATAL($cast(a, act)) + `DV_CHECK_FATAL($cast(e, exp)) + if (a.awaddr !== e.awaddr || a.awlen !== e.awlen || + a.awsize !== e.awsize || a.awburst !== e.awburst) begin + `uvm_error("SCB_ATTR_WR", + $sformatf({"Write attribute mismatch on %s ID:%h: ", + "act={addr:%h len:%h size:%h burst:%h} ", + "exp={addr:%h len:%h size:%h burst:%h}"}, + sub, did, a.awaddr, a.awlen, a.awsize, a.awburst, + e.awaddr, e.awlen, e.awsize, e.awburst)) + end + if (a.wdata != e.wdata) begin + `uvm_error("SCB_WDATA", + $sformatf("Write data mismatch on %s ID:%h: act=%p exp=%p", + sub, did, a.wdata, e.wdata)) + end + if (a.wstrb != e.wstrb) begin + `uvm_error("SCB_WSTRB", + $sformatf("Write strobe mismatch on %s ID:%h: act=%p exp=%p", + sub, did, a.wstrb, e.wstrb)) + end + end else begin + axi_mon_read_item a, e; + `DV_CHECK_FATAL($cast(a, act)) + `DV_CHECK_FATAL($cast(e, exp)) + if (a.araddr !== e.araddr || a.arlen !== e.arlen || + a.arsize !== e.arsize || a.arburst !== e.arburst) begin + `uvm_error("SCB_ATTR_RD", + $sformatf({"Read attribute mismatch on %s ID:%h: ", + "act={addr:%h len:%h size:%h burst:%h} ", + "exp={addr:%h len:%h size:%h burst:%h}"}, + sub, did, a.araddr, a.arlen, a.arsize, a.arburst, + e.araddr, e.arlen, e.arsize, e.arburst)) + end + if (a.rdata != e.rdata) begin + `uvm_error("SCB_RDATA", + $sformatf("Read data mismatch on %s ID:%h: act=%p exp=%p", + sub, did, a.rdata, e.rdata)) + end + if (a.rresp != e.rresp) begin + `uvm_error("SCB_RRESP", + $sformatf("Read response mismatch on %s ID:%h: act=%p exp=%p", + sub, did, a.rresp, e.rresp)) + end + end +endfunction : perform_comparison + +function void top_chip_dv_axi_scoreboard::record_manager_completion(axi_mon_item tr, + int unsigned host_idx); + bit [63:0] addr = tr.get_addr(); + bit [63:0] raw_id = tr.get_id(); + string target_sub = addr_to_mem_range(addr); + dev_id_t did = mgr_dev_id(host_idx, raw_id); + + if (under_reset) return; + + if (target_sub == default_subordinate_id) begin + `uvm_error("SCB_ADDR_DECODE", $sformatf("Manager access to unmapped address: %h", addr)) + end else begin + if (actual_queue[target_sub].exists(did) && actual_queue[target_sub][did].size() > 0) begin + axi_mon_item act_tr = actual_queue[target_sub][did].pop_front(); + perform_comparison(tr, act_tr, target_sub); + end else begin + expected_queue[target_sub][did].push_back(tr.item_clone()); + end + // This request has completed at the manager; clear it from the outstanding count. + if (mgr_outstanding.exists(target_sub) && mgr_outstanding[target_sub].exists(did) && + mgr_outstanding[target_sub][did] > 0) begin + mgr_outstanding[target_sub][did]--; + end + end +endfunction : record_manager_completion + +function void top_chip_dv_axi_scoreboard::record_manager_request(axi_mon_item tr, + int unsigned host_idx); + bit [63:0] addr = tr.get_addr(); + bit [63:0] raw_id = tr.get_id(); + string target_sub = addr_to_mem_range(addr); + dev_id_t did = mgr_dev_id(host_idx, raw_id); + if (under_reset) return; + if (target_sub != default_subordinate_id) mgr_outstanding[target_sub][did]++; +endfunction : record_manager_request + +function void top_chip_dv_axi_scoreboard::write_mgr0_cva6(axi_mon_item tr); + record_manager_completion(tr, int'(top_pkg::CVA6)); +endfunction : write_mgr0_cva6 + +function void top_chip_dv_axi_scoreboard::write_mgr0_cva6_req(axi_mon_item tr); + record_manager_request(tr, int'(top_pkg::CVA6)); +endfunction : write_mgr0_cva6_req + +function void top_chip_dv_axi_scoreboard::write_mgr1_dm_host(axi_mon_item tr); + record_manager_completion(tr, int'(top_pkg::DM_HOST)); +endfunction : write_mgr1_dm_host + +function void top_chip_dv_axi_scoreboard::write_mgr1_dm_host_req(axi_mon_item tr); + record_manager_request(tr, int'(top_pkg::DM_HOST)); +endfunction : write_mgr1_dm_host_req + +function void top_chip_dv_axi_scoreboard::check_subordinate_arrival(axi_mon_item act, + string sub_id); + dev_id_t did = get_dev_id(act.get_id()); + + if (under_reset) return; + + if (expected_queue[sub_id].exists(did) && expected_queue[sub_id][did].size() > 0) begin + axi_mon_item exp_tr = expected_queue[sub_id][did].pop_front(); + perform_comparison(exp_tr, act, sub_id); + end else begin + actual_queue[sub_id][did].push_back(act.item_clone()); + end +endfunction : check_subordinate_arrival + +function void top_chip_dv_axi_scoreboard::write_sub0_romctrlmem(axi_mon_item tr); + check_subordinate_arrival(tr, "sub0_romctrlmem"); +endfunction : write_sub0_romctrlmem + +function void top_chip_dv_axi_scoreboard::write_sub1_sram(axi_mon_item tr); + check_subordinate_arrival(tr, "sub1_sram"); +endfunction : write_sub1_sram + +function void top_chip_dv_axi_scoreboard::write_sub2_mailbox(axi_mon_item tr); + check_subordinate_arrival(tr, "sub2_mailbox"); +endfunction : write_sub2_mailbox + +function void top_chip_dv_axi_scoreboard::write_sub3_tlcrossbar(axi_mon_item tr); + check_subordinate_arrival(tr, "sub3_tlcrossbar"); +endfunction : write_sub3_tlcrossbar + +function void top_chip_dv_axi_scoreboard::write_sub4_dram(axi_mon_item tr); + check_subordinate_arrival(tr, "sub4_dram"); +endfunction : write_sub4_dram + +function void top_chip_dv_axi_scoreboard::write_sub5_dm_dev(axi_mon_item tr); + check_subordinate_arrival(tr, "sub5_dm_dev"); +endfunction : write_sub5_dm_dev + +function void top_chip_dv_axi_scoreboard::write_sub6_restofchip(axi_mon_item tr); + check_subordinate_arrival(tr, "sub6_restofchip"); +endfunction : write_sub6_restofchip + +function void top_chip_dv_axi_scoreboard::write_reset(axi_reset_item item); + under_reset = item.m_in_reset; + if (under_reset) reset(); +endfunction : write_reset + +function void top_chip_dv_axi_scoreboard::reset(); + expected_queue.delete(); + actual_queue.delete(); + mgr_outstanding.delete(); +endfunction : reset + +function void top_chip_dv_axi_scoreboard::check_phase(uvm_phase phase); + super.check_phase(phase); + + foreach (expected_queue[s, i]) begin + if (expected_queue[s][i].size() > 0) begin + `uvm_error("SCB_DRAIN_DROP", $sformatf("DROPPED: Manager request for %s (ID %h) lost", s, i)) + end + end + + foreach (actual_queue[s, i]) begin + int unsigned n_leftover = actual_queue[s][i].size(); + int unsigned n_inflight = 0; + if (mgr_outstanding.exists(s) && mgr_outstanding[s].exists(i)) n_inflight = mgr_outstanding[s][i]; + + // Leftovers covered by an outstanding manager request are in-flight transactions whose + // manager-side completion the monitor stopped before observing; only the excess is an error. + if (n_leftover > n_inflight) begin + axi_mon_item t = actual_queue[s][i][0]; + bit [63:0] a = t.get_addr(); + `uvm_error("SCB_DRAIN_ERROR", + $sformatf("ERROR: %s (ID %h) responded without a manager request: %s addr=%h (%0d error, %0d in-flight)", + s, i, t.get_dir().name(), a, n_leftover - n_inflight, n_inflight)) + end + end +endfunction : check_phase diff --git a/hw/top_chip/dv/env/top_chip_dv_env.core b/hw/top_chip/dv/env/top_chip_dv_env.core index 2ed5b43ac..3aa57001b 100644 --- a/hw/top_chip/dv/env/top_chip_dv_env.core +++ b/hw/top_chip/dv/env/top_chip_dv_env.core @@ -11,6 +11,7 @@ filesets: - lowrisc:dv:dv_utils - lowrisc:dv:mem_bkdr_util - lowrisc:dv:uart_agent + - lowrisc:dv:axi_agent:0.1 - lowrisc:dv:common_ifs - lowrisc:mocha_dv:gpio_env - lowrisc:dv:i2c_env @@ -19,6 +20,7 @@ filesets: - mem_clear_util.sv: {is_include_file: true} - top_chip_dv_env_cfg.sv: {is_include_file: true} - top_chip_dv_env_cov.sv: {is_include_file: true} + - top_chip_dv_axi_scoreboard.sv: {is_include_file: true} - top_chip_dv_env.sv: {is_include_file: true} - top_chip_dv_virtual_sequencer.sv: {is_include_file: true} - seq_lib/top_chip_dv_vseq_list.sv: {is_include_file: true} diff --git a/hw/top_chip/dv/env/top_chip_dv_env.sv b/hw/top_chip/dv/env/top_chip_dv_env.sv index 8f9c3e950..d8f59bef1 100644 --- a/hw/top_chip/dv/env/top_chip_dv_env.sv +++ b/hw/top_chip/dv/env/top_chip_dv_env.sv @@ -12,8 +12,14 @@ class top_chip_dv_env extends uvm_env; mem_bkdr_util mem_bkdr_util_h[chip_mem_e]; // Agents - uart_agent m_uart_agent; i2c_agent m_i2c_agent; + uart_agent m_uart_agent; + // Passive AXI monitors on the xbar host (CVA6) + device ports; each self-gets its + // cfg (is_active=UVM_PASSIVE) from the config_db published by its axi_vip_if in the tb. + axi_mgr_agent m_mgr_axi[]; + axi_mgr_agent m_sub_axi[]; + + top_chip_dv_axi_scoreboard m_axi_scb; // Standard SV/UVM methods extern function new(string name = "", uvm_component parent = null); @@ -22,6 +28,9 @@ class top_chip_dv_env extends uvm_env; // Class specific methods extern task load_memories(); + + // Block until every AXI manager monitor reports no in-flight transactions. + extern task wait_for_axi_idle(); endclass : top_chip_dv_env @@ -76,6 +85,21 @@ function void top_chip_dv_env::build_phase(uvm_phase phase); m_uart_agent = uart_agent::type_id::create("m_uart_agent", this); uvm_config_db#(uart_agent_cfg)::set(this, "m_uart_agent*", "cfg", cfg.m_uart_agent_cfg); + m_mgr_axi = new[top_pkg::AxiXbarHosts]; + m_mgr_axi[top_pkg::CVA6] = axi_mgr_agent::type_id::create("m_mgr_axi_CVA6", this); + m_mgr_axi[top_pkg::DM_HOST] = axi_mgr_agent::type_id::create("m_mgr_axi_DM_HOST", this); + + m_sub_axi = new[top_pkg::AxiXbarDevices]; + m_sub_axi[top_pkg::RomCtrlMem] = axi_mgr_agent::type_id::create("m_sub_axi_RomCtrlMem", this); + m_sub_axi[top_pkg::SRAM] = axi_mgr_agent::type_id::create("m_sub_axi_SRAM", this); + m_sub_axi[top_pkg::DM_DEV] = axi_mgr_agent::type_id::create("m_sub_axi_DM_DEV", this); + m_sub_axi[top_pkg::Mailbox] = axi_mgr_agent::type_id::create("m_sub_axi_Mailbox", this); + m_sub_axi[top_pkg::RestOfChip] = axi_mgr_agent::type_id::create("m_sub_axi_RestOfChip", this); + m_sub_axi[top_pkg::TlCrossbar] = axi_mgr_agent::type_id::create("m_sub_axi_TlCrossbar", this); + m_sub_axi[top_pkg::DRAM] = axi_mgr_agent::type_id::create("m_sub_axi_DRAM", this); + + m_axi_scb = top_chip_dv_axi_scoreboard::type_id::create("m_axi_scb", this); + uvm_config_db#(top_chip_dv_env_cfg)::set(this, "", "cfg", cfg); top_vsqr = top_chip_dv_virtual_sequencer::type_id::create("top_vsqr", this); @@ -93,6 +117,23 @@ function void top_chip_dv_env::connect_phase(uvm_phase phase); // Connect monitor output to matching FIFO in the virtual sequencer. // Allows virtual sequences to check TX items. m_uart_agent.monitor.tx_analysis_port.connect(top_vsqr.uart_tx_fifo.analysis_export); + + m_mgr_axi[top_pkg::CVA6].get_monitor().tx_ap.connect(m_axi_scb.mgr0_cva6_imp); + m_mgr_axi[top_pkg::CVA6].get_monitor().aw_ap.connect(m_axi_scb.mgr0_cva6_req_imp); + m_mgr_axi[top_pkg::CVA6].get_monitor().ar_ap.connect(m_axi_scb.mgr0_cva6_req_imp); + m_mgr_axi[top_pkg::DM_HOST].get_monitor().tx_ap.connect(m_axi_scb.mgr1_dm_host_imp); + m_mgr_axi[top_pkg::DM_HOST].get_monitor().aw_ap.connect(m_axi_scb.mgr1_dm_host_req_imp); + m_mgr_axi[top_pkg::DM_HOST].get_monitor().ar_ap.connect(m_axi_scb.mgr1_dm_host_req_imp); + m_sub_axi[top_pkg::RomCtrlMem].get_monitor().tx_ap.connect(m_axi_scb.sub0_romctrlmem_imp); + m_sub_axi[top_pkg::SRAM].get_monitor().tx_ap.connect(m_axi_scb.sub1_sram_imp); + m_sub_axi[top_pkg::Mailbox].get_monitor().tx_ap.connect(m_axi_scb.sub2_mailbox_imp); + m_sub_axi[top_pkg::TlCrossbar].get_monitor().tx_ap.connect(m_axi_scb.sub3_tlcrossbar_imp); + m_sub_axi[top_pkg::DRAM].get_monitor().tx_ap.connect(m_axi_scb.sub4_dram_imp); + m_sub_axi[top_pkg::DM_DEV].get_monitor().tx_ap.connect(m_axi_scb.sub5_dm_dev_imp); + m_sub_axi[top_pkg::RestOfChip].get_monitor().tx_ap.connect(m_axi_scb.sub6_restofchip_imp); + + // Flush the scoreboard on AXI fabric reset (all taps share it, so one reset monitor suffices). + m_mgr_axi[top_pkg::CVA6].get_reset_monitor().m_analysis_port.connect(m_axi_scb.reset_imp); endfunction : connect_phase task top_chip_dv_env::load_memories(); @@ -104,3 +145,9 @@ task top_chip_dv_env::load_memories(); end end endtask : load_memories + +task top_chip_dv_env::wait_for_axi_idle(); + foreach (m_mgr_axi[i]) begin + if (m_mgr_axi[i] != null) m_mgr_axi[i].get_monitor().wait_for_idle(); + end +endtask : wait_for_axi_idle diff --git a/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv b/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv index 9195959da..92c5d878d 100644 --- a/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv +++ b/hw/top_chip/dv/env/top_chip_dv_env_pkg.sv @@ -11,6 +11,7 @@ package top_chip_dv_env_pkg; import uart_agent_pkg::*; import gpio_env_pkg::NUM_GPIOS; import i2c_reg_pkg::FifoDepth; + import axi_agent_pkg::*; // Macro includes `include "uvm_macros.svh" @@ -23,13 +24,20 @@ package top_chip_dv_env_pkg; typedef chip_mem_e chip_mem_list_t[$]; + // Local Address Map Struct + typedef struct { + string subordinate_name; + bit [63:0] start_addr; + bit [63:0] end_addr; + } axi_addr_range_t; + // Generate the list of all chip_mem_e values, this helps to simplify iterating over them with // foreach loops. const chip_mem_list_t CHIP_MEM_LIST = chip_mem_values(); function automatic chip_mem_list_t chip_mem_values; chip_mem_list_t list; - chip_mem_e tmp = tmp.first; + chip_mem_e tmp = tmp.first; do begin list.push_back(tmp); tmp = tmp.next; @@ -57,6 +65,7 @@ package top_chip_dv_env_pkg; `include "top_chip_dv_env_cfg.sv" `include "top_chip_dv_env_cov.sv" `include "top_chip_dv_virtual_sequencer.sv" + `include "top_chip_dv_axi_scoreboard.sv" `include "top_chip_dv_env.sv" `include "top_chip_dv_vseq_list.sv" endpackage : top_chip_dv_env_pkg diff --git a/hw/top_chip/dv/tb/chip_hier_macros.svh b/hw/top_chip/dv/tb/chip_hier_macros.svh index 27da0c95a..0fa6c9a16 100644 --- a/hw/top_chip/dv/tb/chip_hier_macros.svh +++ b/hw/top_chip/dv/tb/chip_hier_macros.svh @@ -8,6 +8,7 @@ `define SRAM_MEM_HIER `SYSTEM_HIER.u_axi_sram.u_ram.mem `define TAG_MEM_HIER `SYSTEM_HIER.u_axi_sram.u_tag_ram.mem `define ROM_MEM_HIER `SYSTEM_HIER.u_rom_ctrl.gen_rom_scramble_enabled.u_rom.u_rom.u_prim_rom.mem +`define AXI_XBAR_HIER `SYSTEM_HIER.u_axi_xbar // Testbench related `define SIM_SRAM_IF u_sim_sram.u_sim_sram_if diff --git a/hw/top_chip/dv/tb/tb.sv b/hw/top_chip/dv/tb/tb.sv index b675447ab..8afebcbd7 100644 --- a/hw/top_chip/dv/tb/tb.sv +++ b/hw/top_chip/dv/tb/tb.sv @@ -58,6 +58,54 @@ module tb; .sda_io(sda ) ); + // Passive AXI monitors on each AXI itf of the crossbar + wire axi_mon_rst_n = dut.rstmgr_resets.rst_main_n[rstmgr_pkg::DomainMainSel]; + + // ---- Host (manager) ports ---- + `define AXI_MON_MGR(u_inst, host, agent_name, id_str) \ + axi_vip_if #( \ + .req_t (top_pkg::axi_req_t), \ + .resp_t (top_pkg::axi_resp_t), \ + .IdWidth (top_pkg::AxiIdWidth), \ + .AddrWidth (top_pkg::AxiAddrWidth), \ + .DataWidth (top_pkg::AxiDataWidth), \ + .UserWidth (top_pkg::AxiUserWidth), \ + .IsActive (uvm_pkg::UVM_PASSIVE), \ + .InstId (id_str), \ + .CfgScope ({"*.", agent_name}) \ + ) u_inst (.clk_i(clk), .rst_ni(axi_mon_rst_n)); \ + assign u_inst.axi_req = `AXI_XBAR_HIER.slv_ports_req_i [host]; \ + assign u_inst.axi_resp = `AXI_XBAR_HIER.slv_ports_resp_o[host] + + // ---- Device (subordinate) ports ---- + `define AXI_MON_SUB(u_inst, dev, agent_name, id_str) \ + axi_vip_if #( \ + .req_t (top_pkg::axi_dev_req_t), \ + .resp_t (top_pkg::axi_dev_resp_t), \ + .IdWidth (top_pkg::AxiDevIdWidth), \ + .AddrWidth (top_pkg::AxiAddrWidth), \ + .DataWidth (top_pkg::AxiDataWidth), \ + .UserWidth (top_pkg::AxiUserWidth), \ + .IsActive (uvm_pkg::UVM_PASSIVE), \ + .InstId (id_str), \ + .CfgScope ({"*.", agent_name}) \ + ) u_inst (.clk_i(clk), .rst_ni(axi_mon_rst_n)); \ + assign u_inst.axi_req = `AXI_XBAR_HIER.mst_ports_req_o [dev]; \ + assign u_inst.axi_resp = `AXI_XBAR_HIER.mst_ports_resp_i[dev] + + `AXI_MON_MGR(u_axi_mon_cva6, top_pkg::CVA6, "m_mgr_axi_CVA6", "CVA6"); + `AXI_MON_MGR(u_axi_mon_dm_host, top_pkg::DM_HOST, "m_mgr_axi_DM_HOST", "DM_HOST"); + `AXI_MON_SUB(u_axi_mon_romctrlmem, top_pkg::RomCtrlMem, "m_sub_axi_RomCtrlMem", "RomCtrlMem"); + `AXI_MON_SUB(u_axi_mon_sram, top_pkg::SRAM, "m_sub_axi_SRAM", "SRAM"); + `AXI_MON_SUB(u_axi_mon_dm_dev, top_pkg::DM_DEV, "m_sub_axi_DM_DEV", "DM_DEV"); + `AXI_MON_SUB(u_axi_mon_mailbox, top_pkg::Mailbox, "m_sub_axi_Mailbox", "Mailbox"); + `AXI_MON_SUB(u_axi_mon_restofchip, top_pkg::RestOfChip, "m_sub_axi_RestOfChip", "RestOfChip"); + `AXI_MON_SUB(u_axi_mon_tlcrossbar, top_pkg::TlCrossbar, "m_sub_axi_TlCrossbar", "TlCrossbar"); + `AXI_MON_SUB(u_axi_mon_dram, top_pkg::DRAM, "m_sub_axi_DRAM", "DRAM"); + + `undef AXI_MON_MGR + `undef AXI_MON_SUB + // ------ Mock DRAM ------ top_pkg::axi_dram_req_t dram_req; top_pkg::axi_dram_resp_t dram_resp; diff --git a/hw/top_chip/dv/test/top_chip_dv_base_test.sv b/hw/top_chip/dv/test/top_chip_dv_base_test.sv index 4be12e3b9..fa254a448 100644 --- a/hw/top_chip/dv/test/top_chip_dv_base_test.sv +++ b/hw/top_chip/dv/test/top_chip_dv_base_test.sv @@ -54,6 +54,8 @@ task top_chip_dv_base_test::run_phase(uvm_phase phase); env.load_memories(); phase.raise_objection(this); run_test(); + // wait until the AXI managers have no in-flight transactions + env.wait_for_axi_idle(); phase.drop_objection(this); endtask : run_phase diff --git a/hw/top_chip/dv/test/top_chip_dv_test_pkg.sv b/hw/top_chip/dv/test/top_chip_dv_test_pkg.sv index 11f5b0b27..ee703d8cc 100644 --- a/hw/top_chip/dv/test/top_chip_dv_test_pkg.sv +++ b/hw/top_chip/dv/test/top_chip_dv_test_pkg.sv @@ -6,6 +6,7 @@ package top_chip_dv_test_pkg; import uvm_pkg::*; import dv_utils_pkg::*; import top_chip_dv_env_pkg::*; + import axi_agent_pkg::*; `include "uvm_macros.svh" `include "dv_macros.svh" diff --git a/hw/top_chip/dv/top_chip_sim.core b/hw/top_chip/dv/top_chip_sim.core index 259964876..b5af244b6 100644 --- a/hw/top_chip/dv/top_chip_sim.core +++ b/hw/top_chip/dv/top_chip_sim.core @@ -16,6 +16,7 @@ filesets: - lowrisc:dv:sw_test_status - lowrisc:dv:sw_logger_if - lowrisc:mocha_dv:top_chip_dv_test + - lowrisc:mocha_dv:axi_vip - lowrisc:dv:mem_bkdr_util - lowrisc:dv:pins_if - lowrisc:dv:common_ifs