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..1a620fd7a --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_agent.core @@ -0,0 +1,75 @@ +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 + - lowrisc:dv:common_ifs + 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 + + - 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} + - 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_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.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} + - 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} + + - 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} + - 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} + + file_type: systemVerilogSource + +targets: + default: + filesets: + - files_dv 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..3ec7d5267 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_agent_cfg.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 + +// 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) + + // 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; + virtual axi_read_request_if read_request_vif; + virtual axi_read_data_if read_data_vif; + + // 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 new file mode 100644 index 000000000..3ea5c4486 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_agent_pkg.sv @@ -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 + +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" + + `include "axi_write_data_item.svh" + `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" + + `include "axi_mgr_read_request_driver.svh" + `include "axi_mgr_read_data_driver.svh" + + `include "axi_reset_item.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" + + 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_response_router.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" + `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" + + `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_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/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/axi_mgr_agent.svh b/hw/ip/dv/axi_agent/axi_mgr_agent.svh new file mode 100644 index 000000000..6bd35bdf5 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_agent.svh @@ -0,0 +1,290 @@ +// 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) + + 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; + + // 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_mgr_write_request_driver m_write_request_driver; + local write_request_sequencer_t m_write_request_sequencer; + + // The write data channel (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_mgr_write_response_driver m_write_response_driver; + local write_response_sequencer_t m_write_response_sequencer; + + // The read request channel (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_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; + + // 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). + 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); + + // 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 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(); + + // 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(); + + // 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); + 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 + + // One reset monitor for the shared AXI clock/reset (ACLK/ARESETn). + m_reset_monitor = axi_reset_monitor::type_id::create("m_reset_monitor", this); + + // 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); + + 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 = + 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.set_vif(m_cfg.clk_rst_vif); + + // If the agent is active, connect drivers to interfaces and sequencers + 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); + + 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); + + // 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 + +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 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 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.") + 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 + +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_mgr_read_data_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh new file mode 100644 index 000000000..c6c0c323a --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_read_data_driver.svh @@ -0,0 +1,174 @@ +// 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; + local virtual clk_rst_if m_clk_rst_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); + + // 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(); + + // 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 + +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 || m_clk_rst_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_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; + forever begin + wait (m_clk_rst_vif.rst_n); + m_in_reset = 0; + wait (!m_clk_rst_vif.rst_n); + 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; + 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); + + 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 + + // 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; + 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; + + // 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 + 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..b606233af --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_read_request_driver.svh @@ -0,0 +1,195 @@ +// 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; + local virtual clk_rst_if m_clk_rst_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); + + // 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(); + + // 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 + +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 || m_clk_rst_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_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; + forever begin + wait (m_clk_rst_vif.rst_n); + m_in_reset = 0; + wait (!m_clk_rst_vif.rst_n); + 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_data_driver.svh b/hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh new file mode 100644 index 000000000..be3e69c02 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_write_data_driver.svh @@ -0,0 +1,177 @@ +// 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; + local virtual clk_rst_if m_clk_rst_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); + + // 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(); + + // 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 + +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 || m_clk_rst_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_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; + forever begin + wait (m_clk_rst_vif.rst_n); + m_in_reset = 0; + wait (!m_clk_rst_vif.rst_n); + 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..ae36d9267 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_write_request_driver.svh @@ -0,0 +1,195 @@ +// 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. +// +// 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) + + 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(). + // + // 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); + + // 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(); + + // 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 + +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 || m_clk_rst_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_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; + forever begin + wait (m_clk_rst_vif.rst_n); + m_in_reset = 0; + wait (!m_clk_rst_vif.rst_n); + 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..1784f0567 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_mgr_write_response_driver.svh @@ -0,0 +1,177 @@ +// 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; + local virtual clk_rst_if m_clk_rst_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); + + // 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(); + + // 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 + +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 || m_clk_rst_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_clk_rst_vif.rst_n)); + m_in_reset = !m_clk_rst_vif.rst_n; + forever begin + wait (m_clk_rst_vif.rst_n); + m_in_reset = 0; + wait (!m_clk_rst_vif.rst_n); + 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; + 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); + + 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 + + // 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); + response.m_user = m_vif.mgr_cb.buser; + + // Set rsp, which will pass the response back to the sequencer. + rsp = response; + + // 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 + disable fork; + end join +endtask 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 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_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_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_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/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.svh b/hw/ip/dv/axi_agent/axi_reset_monitor.svh new file mode 100644 index 000000000..a7f289c35 --- /dev/null +++ b/hw/ip/dv/axi_agent/axi_reset_monitor.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 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 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 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 clk_rst_if vif); +endclass + +function void axi_reset_monitor::set_vif(virtual clk_rst_if vif); + m_vif = vif; +endfunction + +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::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_n)); + forever begin + axi_reset_item rst_item; + 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_n === !rst_n_bit); + end +endtask 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; +} 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 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 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..78ef8c3dc --- /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 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 + // 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 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 = ""); + 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; +} 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 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_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_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 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 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..03cfa37f8 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_burst_vseq.svh @@ -0,0 +1,141 @@ +// 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; + + // AR request fields + bit [31:0] m_id; + bit [63:0] m_addr; + bit [2:0] m_size = 3'd3; // ARSIZE (log2 bytes per beat); 8 bytes by default + bit [7:0] m_len = 8'd0; // ARLEN (number of beats - 1) + burst_e m_burst = BurstIncr; // ARBURST + bit [3:0] m_region; + bit m_lock; + bit [3:0] m_cache; + bit [2:0] m_prot; + bit [3:0] m_qos; + bit [127:0] m_user; // ARUSER + + // 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); +endfunction + +task axi_mgr_read_burst_vseq::body(); + axi_mgr_txn_request_seq ar_seq; + int unsigned n_beats = 32'(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 + + // Send the read request (AR). All fields are pinned via the m_use_fixed_* mechanism. + 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_id; + ar_seq.m_use_fixed_addr = 1'b1; ar_seq.m_fixed_addr = m_addr; + ar_seq.m_use_fixed_region = 1'b1; ar_seq.m_fixed_region = m_region; + ar_seq.m_use_fixed_len = 1'b1; ar_seq.m_fixed_len = m_len; + ar_seq.m_use_fixed_size = 1'b1; ar_seq.m_fixed_size = m_size; + ar_seq.m_use_fixed_burst = 1'b1; ar_seq.m_fixed_burst = m_burst; + ar_seq.m_use_fixed_lock = 1'b1; ar_seq.m_fixed_lock = m_lock; + ar_seq.m_use_fixed_cache = 1'b1; ar_seq.m_fixed_cache = m_cache; + ar_seq.m_use_fixed_prot = 1'b1; ar_seq.m_fixed_prot = m_prot; + ar_seq.m_use_fixed_qos = 1'b1; ar_seq.m_fixed_qos = m_qos; + ar_seq.m_use_fixed_user = 1'b1; ar_seq.m_fixed_user = m_user; + + // 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)); + 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_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_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 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..72d8a796d --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_read_fixed_vseq.svh @@ -0,0 +1,130 @@ +// 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; + + // 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 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 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 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..9ba12387b --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_burst_vseq.svh @@ -0,0 +1,141 @@ +// 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; + + // AW request fields. Set these (as needed) before starting the sequence. + bit [31:0] m_id; + bit [63:0] m_addr; + bit [2:0] m_size = 3'd3; // AWSIZE (log2 bytes per beat); 8 bytes by default + burst_e m_burst = BurstIncr; // AWBURST + bit [3:0] m_region; + bit m_lock; + bit [3:0] m_cache; + bit [2:0] m_prot; + bit [3:0] m_qos; + bit [127:0] m_user; // AWUSER (the CHERI tag travels on WUSER, not here) + + // 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[$]; + + 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); +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). All fields are pinned via the m_use_fixed_* mechanism. + 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_id; + aw_seq.m_use_fixed_addr = 1'b1; aw_seq.m_fixed_addr = m_addr; + aw_seq.m_use_fixed_region = 1'b1; aw_seq.m_fixed_region = m_region; + aw_seq.m_use_fixed_len = 1'b1; aw_seq.m_fixed_len = 8'(m_data_items.size() - 1); + aw_seq.m_use_fixed_size = 1'b1; aw_seq.m_fixed_size = m_size; + aw_seq.m_use_fixed_burst = 1'b1; aw_seq.m_fixed_burst = m_burst; + aw_seq.m_use_fixed_lock = 1'b1; aw_seq.m_fixed_lock = m_lock; + aw_seq.m_use_fixed_cache = 1'b1; aw_seq.m_fixed_cache = m_cache; + aw_seq.m_use_fixed_prot = 1'b1; aw_seq.m_fixed_prot = m_prot; + aw_seq.m_use_fixed_qos = 1'b1; aw_seq.m_fixed_qos = m_qos; + aw_seq.m_use_fixed_user = 1'b1; aw_seq.m_fixed_user = m_user; + + // 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"); + 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_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 new file mode 100644 index 000000000..079e97162 --- /dev/null +++ b/hw/ip/dv/axi_agent/seq_lib/axi_mgr_write_data_seq.svh @@ -0,0 +1,67 @@ +// 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(); + + // 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 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. + 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); + 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 + // 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::populate_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_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 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_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 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..df28f7665 --- /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::populate_item, ensuring that the item that is sent + // matches m_write_data_item. + extern protected virtual function void populate_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::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); +endfunction + +constraint axi_mgr_write_single_data_seq::single_item_c { + m_number_of_items == 1; +} 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