From bc2ad40c9d1a8a3d2b2381c3e86ebabba41a970f Mon Sep 17 00:00:00 2001 From: Luca Colagrande Date: Tue, 9 Aug 2022 08:54:17 +0200 Subject: [PATCH] axi_mcast_xbar: Add testbench Passes tests when multicast probability is set to 0. When spill registers on AW are enabled we expect a non-negligible area overhead from registering slv_aw_addr_i and slv_aw_mask_i. --- Bender.yml | 4 +- scripts/run_vsim.sh | 36 ++- src/axi_mcast_demux.sv | 411 +++++++++++++------------ src/axi_mcast_xbar.sv | 269 ++++++++-------- src/axi_test.sv | 41 ++- test/tb_axi_mcast_xbar.sv | 460 ++++++++++++++++++++++++++++ test/tb_axi_mcast_xbar_pkg.sv | 557 ++++++++++++++++++++++++++++++++++ test/tb_axi_xbar.sv | 45 +-- 8 files changed, 1452 insertions(+), 371 deletions(-) create mode 100644 test/tb_axi_mcast_xbar.sv create mode 100644 test/tb_axi_mcast_xbar_pkg.sv diff --git a/Bender.yml b/Bender.yml index 8dceff16c..fc7498451 100644 --- a/Bender.yml +++ b/Bender.yml @@ -8,7 +8,7 @@ package: - "Wolfgang Roenninger " dependencies: - common_cells: { git: "https://github.com/pulp-platform/common_cells.git", rev: "6a2ccec070786c333809871a09bea84cd50dcce8" } + common_cells: { git: "https://github.com/pulp-platform/common_cells.git", rev: "c051072d2819d47df8120d8e499856c287033234" } common_verification: { git: "https://github.com/pulp-platform/common_verification.git", version: 0.2.0 } export_include_dirs: @@ -78,6 +78,7 @@ sources: # Level 0 - test/tb_axi_dw_pkg.sv - test/tb_axi_xbar_pkg.sv + - test/tb_axi_mcast_xbar_pkg.sv # Level 1 - test/tb_axi_addr_test.sv - test/tb_axi_atop_filter.sv @@ -97,3 +98,4 @@ sources: - test/tb_axi_sim_mem.sv - test/tb_axi_to_axi_lite.sv - test/tb_axi_xbar.sv + - test/tb_axi_mcast_xbar.sv diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index 5b279ddae..154d95ff1 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -160,22 +160,38 @@ exec_test() { done ;; axi_xbar) - for Multicast in 0 1; do - for NumMst in 1 6; do - for NumSlv in 1 8; do - for Atop in 0 1; do - for Exclusive in 0 1; do - for UniqueIds in 0 1; do - call_vsim tb_axi_xbar -gTbMulticast=$Multicast -gTbNumMst=$NumMst -gTbNumSlv=$NumSlv \ - -gTbEnAtop=$Atop -gTbEnExcl=$Exclusive \ - -gTbUniqueIds=$UniqueIds - done + for NumMst in 1 6; do + for NumSlv in 1 8; do + for Atop in 0 1; do + for Exclusive in 0 1; do + for UniqueIds in 0 1; do + call_vsim tb_axi_xbar -gTbNumMst=$NumMst -gTbNumSlv=$NumSlv \ + -gTbEnAtop=$Atop -gTbEnExcl=$Exclusive \ + -gTbUniqueIds=$UniqueIds done done done done done ;; + axi_mcast_xbar) + for NumMst in 1 6; do + for NumSlv in 1 8; do + # for Atop in 0 1; do + for Atop in 0; do + # for Exclusive in 0 1; do + for Exclusive in 0; do + for UniqueIds in 0 1; do + call_vsim tb_axi_mcast_xbar -gTbNumMst=$NumMst -gTbNumSlv=$NumSlv \ + -gTbEnAtop=$Atop -gTbEnExcl=$Exclusive \ + -gTbUniqueIds=$UniqueIds + done + done + done + done + done + ;; + *) call_vsim tb_$1 -t 1ns -coverage -voptargs="+acc +cover=bcesfx" ;; diff --git a/src/axi_mcast_demux.sv b/src/axi_mcast_demux.sv index d12e14455..28b6c1c84 100644 --- a/src/axi_mcast_demux.sv +++ b/src/axi_mcast_demux.sv @@ -1,4 +1,4 @@ -// Copyright (c) 2019 ETH Zurich and University of Bologna. +// Copyright (c) 2022 ETH Zurich and University of Bologna. // Copyright and related rights are licensed under the Solderpad Hardware // License, Version 0.51 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at @@ -9,9 +9,9 @@ // specific language governing permissions and limitations under the License. // // Authors: -// - Wolfgang Roenninger -// - Andreas Kurth // - Luca Colagrande +// Based on: +// - axi_demux.sv `include "common_cells/assertions.svh" `include "common_cells/registers.svh" @@ -22,42 +22,45 @@ `define TARGET_VSIM `endif -// axi_demux: Demultiplex an AXI bus from one slave port to multiple master ports. +// axi_mcast_demux: Demultiplex an AXI bus from one slave port to multiple master ports. // See `doc/axi_demux.md` for the documentation, including the definition of parameters and ports. module axi_mcast_demux #( - parameter int unsigned AxiIdWidth = 32'd0, - parameter bit AtopSupport = 1'b1, - parameter type aw_chan_t = logic, - parameter type w_chan_t = logic, - parameter type b_chan_t = logic, - parameter type ar_chan_t = logic, - parameter type r_chan_t = logic, - parameter type axi_req_t = logic, - parameter type axi_resp_t = logic, - parameter int unsigned NoMstPorts = 32'd0, - parameter int unsigned MaxTrans = 32'd8, - parameter int unsigned AxiLookBits = 32'd3, - parameter bit UniqueIds = 1'b0, - parameter bit FallThrough = 1'b0, - parameter bit SpillAw = 1'b1, - parameter bit SpillW = 1'b0, - parameter bit SpillB = 1'b0, - parameter bit SpillAr = 1'b1, - parameter bit SpillR = 1'b0, + parameter int unsigned AxiIdWidth = 32'd0, + parameter bit AtopSupport = 1'b1, + parameter type aw_addr_t = logic, + parameter type aw_chan_t = logic, + parameter type w_chan_t = logic, + parameter type b_chan_t = logic, + parameter type ar_chan_t = logic, + parameter type r_chan_t = logic, + parameter type axi_req_t = logic, + parameter type axi_resp_t = logic, + parameter int unsigned NoMstPorts = 32'd0, + parameter int unsigned MaxTrans = 32'd8, + parameter int unsigned AxiLookBits = 32'd3, + parameter bit UniqueIds = 1'b0, + parameter bit FallThrough = 1'b0, + parameter bit SpillAw = 1'b1, + parameter bit SpillW = 1'b0, + parameter bit SpillB = 1'b0, + parameter bit SpillAr = 1'b1, + parameter bit SpillR = 1'b0, // Dependent parameters, DO NOT OVERRIDE! - parameter type select_t = logic [NoMstPorts-1:0] + parameter type select_t = logic [NoMstPorts-1:0] ) ( - input logic clk_i, - input logic rst_ni, - input logic test_i, + input logic clk_i, + input logic rst_ni, + input logic test_i, // Slave Port - input axi_req_t slv_req_i, - input select_t slv_aw_select_i, - input select_t slv_ar_select_i, - output axi_resp_t slv_resp_o, + input axi_req_t slv_req_i, + input select_t slv_aw_select_i, + input select_t slv_ar_select_i, + input aw_addr_t [NoMstPorts-1:0] slv_aw_addr_i, + input aw_addr_t [NoMstPorts-1:0] slv_aw_mask_i, + output axi_resp_t slv_resp_o, // Master Ports - output axi_req_t [NoMstPorts-1:0] mst_reqs_o, - input axi_resp_t [NoMstPorts-1:0] mst_resps_i + output axi_req_t [NoMstPorts-1:0] mst_reqs_o, + input axi_resp_t [NoMstPorts-1:0] mst_resps_i ); localparam int unsigned IdCounterWidth = MaxTrans > 1 ? $clog2(MaxTrans) : 1; @@ -66,9 +69,11 @@ module axi_mcast_demux #( // Typedefs for the FIFOs / Queues //-------------------------------------- typedef struct packed { - aw_chan_t aw_chan; - select_t aw_select; - } aw_chan_select_t; + aw_chan_t aw_chan; + select_t aw_select; + aw_addr_t [NoMstPorts-1:0] aw_addr; + aw_addr_t [NoMstPorts-1:0] aw_mask; + } aw_chan_extended_t; typedef struct packed { ar_chan_t ar_chan; select_t ar_select; @@ -77,69 +82,69 @@ module axi_mcast_demux #( // pass through if only one master port if (NoMstPorts == 32'h1) begin : gen_no_demux spill_register #( - .T ( aw_chan_t ), - .Bypass ( ~SpillAw ) + .T (aw_chan_t), + .Bypass(~SpillAw) ) i_aw_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.aw_valid ), - .ready_o ( slv_resp_o.aw_ready ), - .data_i ( slv_req_i.aw ), - .valid_o ( mst_reqs_o[0].aw_valid ), - .ready_i ( mst_resps_i[0].aw_ready ), - .data_o ( mst_reqs_o[0].aw ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .valid_i(slv_req_i.aw_valid), + .ready_o(slv_resp_o.aw_ready), + .data_i (slv_req_i.aw), + .valid_o(mst_reqs_o[0].aw_valid), + .ready_i(mst_resps_i[0].aw_ready), + .data_o (mst_reqs_o[0].aw) ); spill_register #( - .T ( w_chan_t ), - .Bypass ( ~SpillW ) + .T (w_chan_t), + .Bypass(~SpillW) ) i_w_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.w_valid ), - .ready_o ( slv_resp_o.w_ready ), - .data_i ( slv_req_i.w ), - .valid_o ( mst_reqs_o[0].w_valid ), - .ready_i ( mst_resps_i[0].w_ready ), - .data_o ( mst_reqs_o[0].w ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .valid_i(slv_req_i.w_valid), + .ready_o(slv_resp_o.w_ready), + .data_i (slv_req_i.w), + .valid_o(mst_reqs_o[0].w_valid), + .ready_i(mst_resps_i[0].w_ready), + .data_o (mst_reqs_o[0].w) ); spill_register #( - .T ( b_chan_t ), - .Bypass ( ~SpillB ) + .T (b_chan_t), + .Bypass(~SpillB) ) i_b_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_resps_i[0].b_valid ), - .ready_o ( mst_reqs_o[0].b_ready ), - .data_i ( mst_resps_i[0].b ), - .valid_o ( slv_resp_o.b_valid ), - .ready_i ( slv_req_i.b_ready ), - .data_o ( slv_resp_o.b ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .valid_i(mst_resps_i[0].b_valid), + .ready_o(mst_reqs_o[0].b_ready), + .data_i (mst_resps_i[0].b), + .valid_o(slv_resp_o.b_valid), + .ready_i(slv_req_i.b_ready), + .data_o (slv_resp_o.b) ); spill_register #( - .T ( ar_chan_t ), - .Bypass ( ~SpillAr ) + .T (ar_chan_t), + .Bypass(~SpillAr) ) i_ar_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.ar_valid ), - .ready_o ( slv_resp_o.ar_ready ), - .data_i ( slv_req_i.ar ), - .valid_o ( mst_reqs_o[0].ar_valid ), - .ready_i ( mst_resps_i[0].ar_ready ), - .data_o ( mst_reqs_o[0].ar ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .valid_i(slv_req_i.ar_valid), + .ready_o(slv_resp_o.ar_ready), + .data_i (slv_req_i.ar), + .valid_o(mst_reqs_o[0].ar_valid), + .ready_i(mst_resps_i[0].ar_ready), + .data_o (mst_reqs_o[0].ar) ); spill_register #( - .T ( r_chan_t ), - .Bypass ( ~SpillR ) + .T (r_chan_t), + .Bypass(~SpillR) ) i_r_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_resps_i[0].r_valid ), - .ready_o ( mst_reqs_o[0].r_ready ), - .data_i ( mst_resps_i[0].r ), - .valid_o ( slv_resp_o.r_valid ), - .ready_i ( slv_req_i.r_ready ), - .data_o ( slv_resp_o.r ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .valid_i(mst_resps_i[0].r_valid), + .ready_o(mst_reqs_o[0].r_ready), + .data_i (mst_resps_i[0].r), + .valid_o(slv_resp_o.r_valid), + .ready_i(slv_req_i.r_ready), + .data_o (slv_resp_o.r) ); // other non degenerate cases @@ -155,7 +160,7 @@ module axi_mcast_demux #( // Write Transaction //-------------------------------------- // comes from spill register at input - aw_chan_select_t slv_aw_chan_select; + aw_chan_extended_t slv_aw_chan_extended; logic slv_aw_valid, slv_aw_ready; // AW/AR channels inputs to mux @@ -227,29 +232,29 @@ module axi_mcast_demux #( `ifdef TARGET_VSIM // Workaround for bug in Questa 2020.2 and 2021.1: Flatten the struct into a logic vector before // instantiating `spill_register`. - typedef logic [$bits(aw_chan_select_t)-1:0] aw_chan_select_flat_t; + typedef logic [$bits(aw_chan_extended_t)-1:0] aw_chan_extended_flat_t; `else // Other tools, such as VCS, have problems with `$bits()`, so the workaround cannot be used // generally. - typedef aw_chan_select_t aw_chan_select_flat_t; + typedef aw_chan_extended_t aw_chan_extended_flat_t; `endif - aw_chan_select_flat_t slv_aw_chan_select_in_flat, - slv_aw_chan_select_out_flat; - assign slv_aw_chan_select_in_flat = {slv_req_i.aw, slv_aw_select_i}; + aw_chan_extended_flat_t slv_aw_chan_extended_in_flat, + slv_aw_chan_extended_out_flat; + assign slv_aw_chan_extended_in_flat = {slv_req_i.aw, slv_aw_select_i, slv_aw_addr_i, slv_aw_mask_i}; spill_register #( - .T ( aw_chan_select_flat_t ), + .T ( aw_chan_extended_flat_t ), .Bypass ( ~SpillAw ) // because module param indicates if we want a spill reg ) i_aw_spill_reg ( .clk_i ( clk_i ), .rst_ni ( rst_ni ), .valid_i ( slv_req_i.aw_valid ), .ready_o ( slv_resp_o.aw_ready ), - .data_i ( slv_aw_chan_select_in_flat ), + .data_i ( slv_aw_chan_extended_in_flat ), .valid_o ( slv_aw_valid ), .ready_i ( slv_aw_ready ), - .data_o ( slv_aw_chan_select_out_flat ) + .data_o ( slv_aw_chan_extended_out_flat ) ); - assign slv_aw_chan_select = slv_aw_chan_select_out_flat; + assign slv_aw_chan_extended = slv_aw_chan_extended_out_flat; // Control of the AW handshake always_comb begin @@ -276,19 +281,18 @@ module axi_mcast_demux #( lock_aw_valid_d = 1'b0; load_aw_lock = 1'b1; // inject the ATOP if necessary - atop_inject = slv_aw_chan_select.aw_chan.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; + atop_inject = slv_aw_chan_extended.aw_chan.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; end end else begin // An AW can be handled if `i_aw_id_counter` and `i_w_fifo` are not full. An ATOP that // requires an R response can be handled if additionally `i_ar_id_counter` is not full (this // only applies if ATOPs are supported at all). if (!aw_id_cnt_full && !w_fifo_full && - (!(ar_id_cnt_full && slv_aw_chan_select.aw_chan.atop[axi_pkg::ATOP_R_RESP]) || + (!(ar_id_cnt_full && slv_aw_chan_extended.aw_chan.atop[axi_pkg::ATOP_R_RESP]) || !AtopSupport)) begin // there is a valid AW vector make the id lookup and go further, if it passes - // TODO colluca change to |(slv_aw_chan_select.aw_select & lookup_aw_select) if (slv_aw_valid && (!aw_select_occupied || - (slv_aw_chan_select.aw_select == lookup_aw_select))) begin + (slv_aw_chan_extended.aw_select == lookup_aw_select))) begin // connect the handshake aw_valid = 1'b1; // push arbitration to the W FIFO regardless, do not wait for the AW transaction @@ -296,7 +300,7 @@ module axi_mcast_demux #( // on AW transaction if (aw_ready) begin slv_aw_ready = 1'b1; - atop_inject = slv_aw_chan_select.aw_chan.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; + atop_inject = slv_aw_chan_extended.aw_chan.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; // no AW transaction this cycle, lock the decision end else begin lock_aw_valid_d = 1'b1; @@ -317,7 +321,7 @@ module axi_mcast_demux #( // master port as all write transactions with the same ID, or both. This means that the // signals that are driven by the ID counters if this parameter is not set can instead be // derived from existing signals. The ID counters can therefore be omitted. - assign lookup_aw_select = slv_aw_chan_select.aw_select; + assign lookup_aw_select = slv_aw_chan_extended.aw_select; assign aw_select_occupied = 1'b0; assign aw_id_cnt_full = 1'b0; end else begin : gen_aw_id_counter @@ -328,14 +332,14 @@ module axi_mcast_demux #( ) i_aw_id_counter ( .clk_i ( clk_i ), .rst_ni ( rst_ni ), - .lookup_axi_id_i ( slv_aw_chan_select.aw_chan.id[0+:AxiLookBits] ), + .lookup_axi_id_i ( slv_aw_chan_extended.aw_chan.id[0+:AxiLookBits] ), .lookup_mst_select_o ( lookup_aw_select ), .lookup_mst_select_occupied_o ( aw_select_occupied ), .full_o ( aw_id_cnt_full ), .inject_axi_id_i ( '0 ), .inject_i ( 1'b0 ), - .push_axi_id_i ( slv_aw_chan_select.aw_chan.id[0+:AxiLookBits] ), - .push_mst_select_i ( slv_aw_chan_select.aw_select ), + .push_axi_id_i ( slv_aw_chan_extended.aw_chan.id[0+:AxiLookBits] ), + .push_mst_select_i ( slv_aw_chan_extended.aw_select ), .push_i ( aw_push ), .pop_axi_id_i ( slv_b_chan.id[0+:AxiLookBits] ), .pop_i ( slv_b_valid & slv_b_ready ) @@ -356,7 +360,7 @@ module axi_mcast_demux #( .full_o ( w_fifo_full ), .empty_o ( w_fifo_empty ), .usage_o ( ), - .data_i ( slv_aw_chan_select.aw_select ), + .data_i ( slv_aw_chan_extended.aw_select ), .push_i ( aw_push ), // controlled from proc_aw_chan .data_o ( w_select ), // where the w beat should go .pop_i ( w_fifo_pop ) // controlled from proc_w_chan @@ -366,17 +370,17 @@ module axi_mcast_demux #( // W Channel //-------------------------------------- spill_register #( - .T ( w_chan_t ), - .Bypass ( ~SpillW ) + .T (w_chan_t), + .Bypass(~SpillW) ) i_w_spill_reg( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.w_valid ), - .ready_o ( slv_resp_o.w_ready ), - .data_i ( slv_req_i.w ), - .valid_o ( slv_w_valid ), - .ready_i ( slv_w_ready ), - .data_o ( slv_w_chan ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .valid_i(slv_req_i.w_valid), + .ready_o(slv_resp_o.w_ready), + .data_i (slv_req_i.w), + .valid_o(slv_w_valid), + .ready_i(slv_w_ready), + .data_o (slv_w_chan) ); //-------------------------------------- @@ -384,37 +388,37 @@ module axi_mcast_demux #( //-------------------------------------- // optional spill register spill_register #( - .T ( b_chan_t ), - .Bypass ( ~SpillB ) + .T (b_chan_t), + .Bypass(~SpillB) ) i_b_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_b_valid ), - .ready_o ( slv_b_ready ), - .data_i ( slv_b_chan ), - .valid_o ( slv_resp_o.b_valid ), - .ready_i ( slv_req_i.b_ready ), - .data_o ( slv_resp_o.b ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .valid_i(slv_b_valid), + .ready_o(slv_b_ready), + .data_i (slv_b_chan), + .valid_o(slv_resp_o.b_valid), + .ready_i(slv_req_i.b_ready), + .data_o (slv_resp_o.b) ); // Arbitration of the different B responses rr_arb_tree #( - .NumIn ( NoMstPorts ), - .DataType ( b_chan_t ), - .AxiVldRdy( 1'b1 ), - .LockIn ( 1'b1 ) + .NumIn (NoMstPorts), + .DataType (b_chan_t), + .AxiVldRdy(1'b1), + .LockIn (1'b1) ) i_b_mux ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .flush_i( 1'b0 ), - .rr_i ( '0 ), - .req_i ( mst_b_valids ), - .gnt_o ( mst_b_readies ), - .data_i ( mst_b_chans ), - .gnt_i ( slv_b_ready ), - .req_o ( slv_b_valid ), - .data_o ( slv_b_chan ), - .idx_o ( ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .flush_i(1'b0), + .rr_i ('0), + .req_i (mst_b_valids), + .gnt_o (mst_b_readies), + .data_i (mst_b_chans), + .gnt_i (slv_b_ready), + .req_o (slv_b_valid), + .data_o (slv_b_chan), + .idx_o () ); //-------------------------------------- @@ -430,17 +434,17 @@ module axi_mcast_demux #( slv_ar_chan_select_out_flat; assign slv_ar_chan_select_in_flat = {slv_req_i.ar, slv_ar_select_i}; spill_register #( - .T ( ar_chan_select_flat_t ), - .Bypass ( ~SpillAr ) + .T (ar_chan_select_flat_t), + .Bypass(~SpillAr) ) i_ar_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.ar_valid ), - .ready_o ( slv_resp_o.ar_ready ), - .data_i ( slv_ar_chan_select_in_flat ), - .valid_o ( slv_ar_valid ), - .ready_i ( slv_ar_ready ), - .data_o ( slv_ar_chan_select_out_flat ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .valid_i(slv_req_i.ar_valid), + .ready_o(slv_resp_o.ar_ready), + .data_i (slv_ar_chan_select_in_flat), + .valid_o(slv_ar_valid), + .ready_i(slv_ar_ready), + .data_o (slv_ar_chan_select_out_flat) ); assign slv_ar_chan_select = slv_ar_chan_select_out_flat; @@ -504,23 +508,23 @@ module axi_mcast_demux #( assign ar_id_cnt_full = 1'b0; end else begin : gen_ar_id_counter axi_demux_id_counters #( - .AxiIdBits ( AxiLookBits ), - .CounterWidth ( IdCounterWidth ), - .mst_port_select_t ( select_t ) + .AxiIdBits (AxiLookBits), + .CounterWidth (IdCounterWidth), + .mst_port_select_t(select_t) ) i_ar_id_counter ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .lookup_axi_id_i ( slv_ar_chan_select.ar_chan.id[0+:AxiLookBits] ), - .lookup_mst_select_o ( lookup_ar_select ), - .lookup_mst_select_occupied_o ( ar_select_occupied ), - .full_o ( ar_id_cnt_full ), - .inject_axi_id_i ( slv_aw_chan_select.aw_chan.id[0+:AxiLookBits] ), - .inject_i ( atop_inject ), - .push_axi_id_i ( slv_ar_chan_select.ar_chan.id[0+:AxiLookBits] ), - .push_mst_select_i ( slv_ar_chan_select.ar_select ), - .push_i ( ar_push ), - .pop_axi_id_i ( slv_r_chan.id[0+:AxiLookBits] ), - .pop_i ( slv_r_valid & slv_r_ready & slv_r_chan.last ) + .clk_i (clk_i), + .rst_ni (rst_ni), + .lookup_axi_id_i (slv_ar_chan_select.ar_chan.id[0+:AxiLookBits]), + .lookup_mst_select_o (lookup_ar_select), + .lookup_mst_select_occupied_o(ar_select_occupied), + .full_o (ar_id_cnt_full), + .inject_axi_id_i (slv_aw_chan_extended.aw_chan.id[0+:AxiLookBits]), + .inject_i (atop_inject), + .push_axi_id_i (slv_ar_chan_select.ar_chan.id[0+:AxiLookBits]), + .push_mst_select_i (slv_ar_chan_select.ar_select), + .push_i (ar_push), + .pop_axi_id_i (slv_r_chan.id[0+:AxiLookBits]), + .pop_i (slv_r_valid & slv_r_ready & slv_r_chan.last) ); end @@ -529,8 +533,8 @@ module axi_mcast_demux #( //-------------------------------------- // optional spill register spill_register #( - .T ( r_chan_t ), - .Bypass ( ~SpillR ) + .T (r_chan_t ), + .Bypass (~SpillR ) ) i_r_spill_reg ( .clk_i ( clk_i ), .rst_ni ( rst_ni ), @@ -564,7 +568,7 @@ module axi_mcast_demux #( // TODO colluca adapt with state machines assign ar_ready = ar_valid & |(mst_ar_readies & slv_ar_chan_select.ar_select); - assign aw_ready = aw_valid & |(mst_aw_readies & slv_aw_chan_select.aw_select); + assign aw_ready = aw_valid & |(mst_aw_readies & slv_aw_chan_extended.aw_select); // process that defines the individual demuxes and assignments for the arbitration // as mst_reqs_o has to be driven from the same always comb block! @@ -576,9 +580,11 @@ module axi_mcast_demux #( for (int unsigned i = 0; i < NoMstPorts; i++) begin // AW channel - mst_reqs_o[i].aw = slv_aw_chan_select.aw_chan; - mst_reqs_o[i].aw_valid = 1'b0; - if (aw_valid && slv_aw_chan_select.aw_select[i]) begin + mst_reqs_o[i].aw = slv_aw_chan_extended.aw_chan; + mst_reqs_o[i].aw.addr = slv_aw_chan_extended.aw_addr[i]; + mst_reqs_o[i].aw.user.mcast = slv_aw_chan_extended.aw_mask[i]; + mst_reqs_o[i].aw_valid = 1'b0; + if (aw_valid && slv_aw_chan_extended.aw_select[i]) begin mst_reqs_o[i].aw_valid = 1'b1; end @@ -625,6 +631,8 @@ module axi_mcast_demux #( $fatal(1, "The Number of slaves (NoMstPorts) has to be at least 1"); AXI_ID_BITS: assume (AxiIdWidth >= AxiLookBits) else $fatal(1, "AxiIdBits has to be equal or smaller than AxiIdWidth."); + aw_addr_bits: assume ($bits(slv_aw_addr_i[0]) == $bits(slv_req_i.aw.addr)) else + $fatal(1, "slv_aw_addr_i[*] must be of type aw_addr_t"); end default disable iff (!rst_ni); aw_valid_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) |=> aw_valid) else @@ -633,11 +641,11 @@ module axi_mcast_demux #( (ar_valid && !ar_ready) |=> ar_valid) else $fatal(1, "ar_valid was deasserted, when ar_ready = 0 in last cycle."); aw_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) - |=> $stable(slv_aw_chan_select)) else - $fatal(1, "slv_aw_chan_select unstable with valid set."); + |=> $stable(slv_aw_chan_extended)) else + $fatal(1, "slv_aw_chan_extended unstable with valid set."); ar_stable: assert property( @(posedge clk_i) (ar_valid && !ar_ready) |=> $stable(slv_ar_chan_select)) else - $fatal(1, "slv_aw_chan_select unstable with valid set."); + $fatal(1, "slv_aw_chan_extended unstable with valid set."); `ASSUME(NoAtopAllowed, !AtopSupport && slv_req_i.aw_valid |-> slv_req_i.aw.atop == '0) `endif `endif @@ -824,36 +832,37 @@ module axi_mcast_demux_intf #( end axi_mcast_demux #( - .AxiIdWidth ( AXI_ID_WIDTH ), // ID Width - .AtopSupport ( ATOP_SUPPORT ), - .aw_chan_t ( aw_chan_t ), // AW Channel Type - .w_chan_t ( w_chan_t ), // W Channel Type - .b_chan_t ( b_chan_t ), // B Channel Type - .ar_chan_t ( ar_chan_t ), // AR Channel Type - .r_chan_t ( r_chan_t ), // R Channel Type - .axi_req_t ( axi_req_t ), - .axi_resp_t ( axi_resp_t ), - .NoMstPorts ( NO_MST_PORTS ), - .MaxTrans ( MAX_TRANS ), - .AxiLookBits ( AXI_LOOK_BITS ), - .UniqueIds ( UNIQUE_IDS ), - .FallThrough ( FALL_THROUGH ), - .SpillAw ( SPILL_AW ), - .SpillW ( SPILL_W ), - .SpillB ( SPILL_B ), - .SpillAr ( SPILL_AR ), - .SpillR ( SPILL_R ) + .AxiIdWidth (AXI_ID_WIDTH), // ID Width + .AtopSupport(ATOP_SUPPORT), + .aw_addr_t (addr_t), // AW Address Type + .aw_chan_t (aw_chan_t), // AW Channel Type + .w_chan_t (w_chan_t), // W Channel Type + .b_chan_t (b_chan_t), // B Channel Type + .ar_chan_t (ar_chan_t), // AR Channel Type + .r_chan_t (r_chan_t), // R Channel Type + .axi_req_t (axi_req_t), + .axi_resp_t (axi_resp_t), + .NoMstPorts (NO_MST_PORTS), + .MaxTrans (MAX_TRANS), + .AxiLookBits(AXI_LOOK_BITS), + .UniqueIds (UNIQUE_IDS), + .FallThrough(FALL_THROUGH), + .SpillAw (SPILL_AW), + .SpillW (SPILL_W), + .SpillB (SPILL_B), + .SpillAr (SPILL_AR), + .SpillR (SPILL_R) ) i_axi_demux ( .clk_i, // Clock .rst_ni, // Asynchronous reset active low .test_i, // Testmode enable // slave port - .slv_req_i ( slv_req ), - .slv_aw_select_i ( slv_aw_select_i ), - .slv_ar_select_i ( slv_ar_select_i ), - .slv_resp_o ( slv_resp ), + .slv_req_i (slv_req), + .slv_aw_select_i(slv_aw_select_i), + .slv_ar_select_i(slv_ar_select_i), + .slv_resp_o (slv_resp), // master port - .mst_reqs_o ( mst_req ), - .mst_resps_i ( mst_resp ) + .mst_reqs_o (mst_req), + .mst_resps_i (mst_resp) ); endmodule diff --git a/src/axi_mcast_xbar.sv b/src/axi_mcast_xbar.sv index 53ff3b743..093e4cc3c 100644 --- a/src/axi_mcast_xbar.sv +++ b/src/axi_mcast_xbar.sv @@ -1,4 +1,4 @@ -// Copyright (c) 2019 ETH Zurich and University of Bologna. +// Copyright (c) 2022 ETH Zurich and University of Bologna. // Copyright and related rights are licensed under the Solderpad Hardware // License, Version 0.51 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at @@ -47,7 +47,7 @@ import cf_math_pkg::idx_width; input rule_t [Cfg.NoAddrRules-1:0] addr_map_i ); - typedef logic [Cfg.AxiAddrWidth-1:0] addr_t; + typedef logic [Cfg.AxiAddrWidth-1:0] addr_t; // to account for the decoding error slave typedef logic [(Cfg.NoMstPorts+1)-1:0] mst_port_mask_t; @@ -63,91 +63,102 @@ import cf_math_pkg::idx_width; slv_resp_t [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_resps; for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_slv_port_demux - logic [Cfg.NoMstPorts-1:0] dec_aw, dec_ar; - mst_port_mask_t slv_aw_select, slv_ar_select; - logic dec_aw_valid, dec_aw_error; - logic dec_ar_valid, dec_ar_error; + logic [Cfg.NoMstPorts-1:0] dec_aw_select, dec_ar_select; + addr_t [Cfg.NoMstPorts-1:0] dec_aw_addr, dec_aw_mask; + logic dec_aw_valid, dec_aw_error; + logic dec_ar_valid, dec_ar_error; + mst_port_mask_t slv_aw_select, slv_ar_select; + addr_t [Cfg.NoMstPorts:0] slv_aw_addr, slv_aw_mask; multiaddr_decode #( - .NoIndices ( Cfg.NoMstPorts ), - .NoRules ( Cfg.NoAddrRules ), - .addr_t ( addr_t ), - .rule_t ( rule_t ) + .NoRules(Cfg.NoMstPorts), + .addr_t (addr_t), + .rule_t (rule_t) ) i_axi_aw_decode ( - .addr_i ( slv_ports_req_i[i].aw.addr ), - .addr_map_i ( addr_map_i ), - .mask_o ( dec_aw ), - .dec_valid_o ( dec_aw_valid ), - .dec_error_o ( dec_aw_error ) + .addr_i (slv_ports_req_i[i].aw.addr), + .mask_i (slv_ports_req_i[i].aw.user.mcast), + .addr_map_i (addr_map_i), + .select_o (dec_aw_select), + .addr_o (dec_aw_addr), + .mask_o (dec_aw_mask), + .dec_valid_o(dec_aw_valid), + .dec_error_o(dec_aw_error) ); multiaddr_decode #( - .NoIndices ( Cfg.NoMstPorts ), - .addr_t ( addr_t ), - .NoRules ( Cfg.NoAddrRules ), - .rule_t ( rule_t ) + .NoRules(Cfg.NoMstPorts), + .addr_t (addr_t), + .rule_t (rule_t) ) i_axi_ar_decode ( - .addr_i ( slv_ports_req_i[i].ar.addr ), - .addr_map_i ( addr_map_i ), - .mask_o ( dec_ar ), - .dec_valid_o ( dec_ar_valid ), - .dec_error_o ( dec_ar_error ) + .addr_i (slv_ports_req_i[i].ar.addr), + .mask_i ('0), + .addr_map_i (addr_map_i), + .select_o (dec_ar_select), + .addr_o (/* Unused */), + .mask_o (/* Unused */), + .dec_valid_o(dec_ar_valid), + .dec_error_o(dec_ar_error) ); + // decerror slave assign slv_aw_select = (dec_aw_error) ? - {1'b1, {Cfg.NoMstPorts{1'b0}}} : {1'b0, dec_aw}; + {1'b1, {Cfg.NoMstPorts{1'b0}}} : {1'b0, dec_aw_select}; assign slv_ar_select = (dec_ar_error) ? - {1'b1, {Cfg.NoMstPorts{1'b0}}} : {1'b0, dec_ar}; - + {1'b1, {Cfg.NoMstPorts{1'b0}}} : {1'b0, dec_ar_select}; + assign slv_aw_addr = {'0, dec_aw_addr}; + assign slv_aw_mask = {'0, dec_aw_mask}; axi_mcast_demux #( - .AxiIdWidth ( Cfg.AxiIdWidthSlvPorts ), // ID Width - .AtopSupport ( ATOPs ), - .aw_chan_t ( slv_aw_chan_t ), // AW Channel Type - .w_chan_t ( w_chan_t ), // W Channel Type - .b_chan_t ( slv_b_chan_t ), // B Channel Type - .ar_chan_t ( slv_ar_chan_t ), // AR Channel Type - .r_chan_t ( slv_r_chan_t ), // R Channel Type - .axi_req_t ( slv_req_t ), - .axi_resp_t ( slv_resp_t ), - .NoMstPorts ( Cfg.NoMstPorts + 1 ), - .MaxTrans ( Cfg.MaxMstTrans ), - .AxiLookBits ( Cfg.AxiIdUsedSlvPorts ), - .UniqueIds ( Cfg.UniqueIds ), - .FallThrough ( Cfg.FallThrough ), - .SpillAw ( Cfg.LatencyMode[9] ), - .SpillW ( Cfg.LatencyMode[8] ), - .SpillB ( Cfg.LatencyMode[7] ), - .SpillAr ( Cfg.LatencyMode[6] ), - .SpillR ( Cfg.LatencyMode[5] ) + .AxiIdWidth (Cfg.AxiIdWidthSlvPorts), // ID Width + .AtopSupport(ATOPs), + .aw_addr_t (addr_t), // AW Address Type + .aw_chan_t (slv_aw_chan_t), // AW Channel Type + .w_chan_t (w_chan_t), // W Channel Type + .b_chan_t (slv_b_chan_t), // B Channel Type + .ar_chan_t (slv_ar_chan_t), // AR Channel Type + .r_chan_t (slv_r_chan_t), // R Channel Type + .axi_req_t (slv_req_t), + .axi_resp_t (slv_resp_t), + .NoMstPorts (Cfg.NoMstPorts + 1), + .MaxTrans (Cfg.MaxMstTrans), + .AxiLookBits(Cfg.AxiIdUsedSlvPorts), + .UniqueIds (Cfg.UniqueIds), + .FallThrough(Cfg.FallThrough), + .SpillAw (Cfg.LatencyMode[9]), + .SpillW (Cfg.LatencyMode[8]), + .SpillB (Cfg.LatencyMode[7]), + .SpillAr (Cfg.LatencyMode[6]), + .SpillR (Cfg.LatencyMode[5]) ) i_axi_demux ( .clk_i, // Clock .rst_ni, // Asynchronous reset active low .test_i, // Testmode enable - .slv_req_i ( slv_ports_req_i[i] ), - .slv_aw_select_i ( slv_aw_select ), - .slv_ar_select_i ( slv_ar_select ), - .slv_resp_o ( slv_ports_resp_o[i] ), - .mst_reqs_o ( slv_reqs[i] ), - .mst_resps_i ( slv_resps[i] ) + .slv_req_i (slv_ports_req_i[i]), + .slv_aw_select_i(slv_aw_select), + .slv_ar_select_i(slv_ar_select), + .slv_aw_addr_i (slv_aw_addr), + .slv_aw_mask_i (slv_aw_mask), + .slv_resp_o (slv_ports_resp_o[i]), + .mst_reqs_o (slv_reqs[i]), + .mst_resps_i (slv_resps[i]) ); axi_err_slv #( - .AxiIdWidth ( Cfg.AxiIdWidthSlvPorts ), - .axi_req_t ( slv_req_t ), - .axi_resp_t ( slv_resp_t ), - .Resp ( axi_pkg::RESP_DECERR ), - .ATOPs ( ATOPs ), - .MaxTrans ( 4 ) // Transactions terminate at this slave, so minimize - // resource consumption by accepting only a few - // transactions at a time. + .AxiIdWidth(Cfg.AxiIdWidthSlvPorts ), + .axi_req_t (slv_req_t), + .axi_resp_t(slv_resp_t), + .Resp (axi_pkg::RESP_DECERR), + .ATOPs (ATOPs), + .MaxTrans (4) // Transactions terminate at this slave, so minimize + // resource consumption by accepting only a few + // transactions at a time. ) i_axi_err_slv ( .clk_i, // Clock .rst_ni, // Asynchronous reset active low .test_i, // Testmode enable // slave port - .slv_req_i ( slv_reqs[i][Cfg.NoMstPorts] ), - .slv_resp_o ( slv_resps[i][cfg_NoMstPorts] ) + .slv_req_i (slv_reqs[i][Cfg.NoMstPorts]), + .slv_resp_o(slv_resps[i][cfg_NoMstPorts]) ); end @@ -161,18 +172,18 @@ import cf_math_pkg::idx_width; end else begin : gen_no_connection assign mst_reqs[j][i] = '0; axi_err_slv #( - .AxiIdWidth ( Cfg.AxiIdWidthSlvPorts ), - .axi_req_t ( slv_req_t ), - .axi_resp_t ( slv_resp_t ), - .Resp ( axi_pkg::RESP_DECERR ), - .ATOPs ( ATOPs ), - .MaxTrans ( 1 ) + .AxiIdWidth(Cfg.AxiIdWidthSlvPorts), + .axi_req_t (slv_req_t), + .axi_resp_t(slv_resp_t), + .Resp (axi_pkg::RESP_DECERR), + .ATOPs (ATOPs), + .MaxTrans (1) ) i_axi_err_slv ( .clk_i, .rst_ni, .test_i, - .slv_req_i ( slv_reqs[i][j] ), - .slv_resp_o ( slv_resps[i][j] ) + .slv_req_i (slv_reqs[i][j]), + .slv_resp_o(slv_resps[i][j]) ); end end @@ -180,36 +191,36 @@ import cf_math_pkg::idx_width; for (genvar i = 0; i < Cfg.NoMstPorts; i++) begin : gen_mst_port_mux axi_mux #( - .SlvAxiIDWidth ( Cfg.AxiIdWidthSlvPorts ), // ID width of the slave ports - .slv_aw_chan_t ( slv_aw_chan_t ), // AW Channel Type, slave ports - .mst_aw_chan_t ( mst_aw_chan_t ), // AW Channel Type, master port - .w_chan_t ( w_chan_t ), // W Channel Type, all ports - .slv_b_chan_t ( slv_b_chan_t ), // B Channel Type, slave ports - .mst_b_chan_t ( mst_b_chan_t ), // B Channel Type, master port - .slv_ar_chan_t ( slv_ar_chan_t ), // AR Channel Type, slave ports - .mst_ar_chan_t ( mst_ar_chan_t ), // AR Channel Type, master port - .slv_r_chan_t ( slv_r_chan_t ), // R Channel Type, slave ports - .mst_r_chan_t ( mst_r_chan_t ), // R Channel Type, master port - .slv_req_t ( slv_req_t ), - .slv_resp_t ( slv_resp_t ), - .mst_req_t ( mst_req_t ), - .mst_resp_t ( mst_resp_t ), - .NoSlvPorts ( Cfg.NoSlvPorts ), // Number of Masters for the module - .MaxWTrans ( Cfg.MaxSlvTrans ), - .FallThrough ( Cfg.FallThrough ), - .SpillAw ( Cfg.LatencyMode[4] ), - .SpillW ( Cfg.LatencyMode[3] ), - .SpillB ( Cfg.LatencyMode[2] ), - .SpillAr ( Cfg.LatencyMode[1] ), - .SpillR ( Cfg.LatencyMode[0] ) + .SlvAxiIDWidth(Cfg.AxiIdWidthSlvPorts ), // ID width of the slave ports + .slv_aw_chan_t(slv_aw_chan_t), // AW Channel Type, slave ports + .mst_aw_chan_t(mst_aw_chan_t), // AW Channel Type, master port + .w_chan_t (w_chan_t), // W Channel Type, all ports + .slv_b_chan_t (slv_b_chan_t), // B Channel Type, slave ports + .mst_b_chan_t (mst_b_chan_t), // B Channel Type, master port + .slv_ar_chan_t(slv_ar_chan_t), // AR Channel Type, slave ports + .mst_ar_chan_t(mst_ar_chan_t), // AR Channel Type, master port + .slv_r_chan_t (slv_r_chan_t), // R Channel Type, slave ports + .mst_r_chan_t (mst_r_chan_t), // R Channel Type, master port + .slv_req_t (slv_req_t), + .slv_resp_t (slv_resp_t), + .mst_req_t (mst_req_t), + .mst_resp_t (mst_resp_t), + .NoSlvPorts (Cfg.NoSlvPorts), // Number of Masters for the module + .MaxWTrans (Cfg.MaxSlvTrans), + .FallThrough (Cfg.FallThrough), + .SpillAw (Cfg.LatencyMode[4]), + .SpillW (Cfg.LatencyMode[3]), + .SpillB (Cfg.LatencyMode[2]), + .SpillAr (Cfg.LatencyMode[1]), + .SpillR (Cfg.LatencyMode[0]) ) i_axi_mux ( .clk_i, // Clock .rst_ni, // Asynchronous reset active low .test_i, // Test Mode enable - .slv_reqs_i ( mst_reqs[i] ), - .slv_resps_o ( mst_resps[i] ), - .mst_req_o ( mst_ports_req_o[i] ), - .mst_resp_i ( mst_ports_resp_i[i] ) + .slv_reqs_i (mst_reqs[i]), + .slv_resps_o(mst_resps[i]), + .mst_req_o (mst_ports_req_o[i]), + .mst_resp_i (mst_ports_resp_i[i]) ); end @@ -221,6 +232,10 @@ import cf_math_pkg::idx_width; $fatal(1, $sformatf("Slv_req and aw_chan id width not equal.")); id_slv_resp_ports: assert ($bits(slv_ports_resp_o[0].r.id) == Cfg.AxiIdWidthSlvPorts) else $fatal(1, $sformatf("Slv_req and aw_chan id width not equal.")); + addr_slv_req_ports: assert ($bits(slv_ports_req_i[0].aw.addr) == Cfg.AxiAddrWidth) else + $fatal(1, $sformatf("Slv_req and aw_addr width not equal.")); + addr_mst_req_ports: assert ($bits(mst_ports_req_o[0].aw.addr) == Cfg.AxiAddrWidth) else + $fatal(1, $sformatf("Mst_req and aw_addr width not equal.")); end `endif `endif @@ -239,12 +254,12 @@ import cf_math_pkg::idx_width; parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] CONNECTIVITY = '1, parameter type rule_t = axi_pkg::xbar_rule_64_t ) ( - input logic clk_i, - input logic rst_ni, - input logic test_i, - AXI_BUS.Slave slv_ports [Cfg.NoSlvPorts-1:0], - AXI_BUS.Master mst_ports [Cfg.NoMstPorts-1:0], - input rule_t [Cfg.NoAddrRules-1:0] addr_map_i + input logic clk_i, + input logic rst_ni, + input logic test_i, + AXI_BUS.Slave slv_ports [Cfg.NoSlvPorts-1:0], + AXI_BUS.Master mst_ports [Cfg.NoMstPorts-1:0], + input rule_t [Cfg.NoAddrRules-1:0] addr_map_i ); localparam int unsigned AxiIdWidthMstPorts = Cfg.AxiIdWidthSlvPorts + $clog2(Cfg.NoSlvPorts); @@ -255,9 +270,13 @@ import cf_math_pkg::idx_width; typedef logic [Cfg.AxiDataWidth -1:0] data_t; typedef logic [Cfg.AxiDataWidth/8 -1:0] strb_t; typedef logic [AXI_USER_WIDTH -1:0] user_t; + // AW channel adds multicast mask to USER signals + typedef struct packed { + addr_t mcast; + } aw_user_t; - `AXI_TYPEDEF_AW_CHAN_T(mst_aw_chan_t, addr_t, id_mst_t, user_t) - `AXI_TYPEDEF_AW_CHAN_T(slv_aw_chan_t, addr_t, id_slv_t, user_t) + `AXI_TYPEDEF_AW_CHAN_T(mst_aw_chan_t, addr_t, id_mst_t, aw_user_t) + `AXI_TYPEDEF_AW_CHAN_T(slv_aw_chan_t, addr_t, id_slv_t, aw_user_t) `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) `AXI_TYPEDEF_B_CHAN_T(mst_b_chan_t, id_mst_t, user_t) `AXI_TYPEDEF_B_CHAN_T(slv_b_chan_t, id_slv_t, user_t) @@ -286,31 +305,31 @@ import cf_math_pkg::idx_width; end axi_mcast_xbar #( - .Cfg (Cfg), - .ATOPs ( ATOPS ), - .Connectivity ( CONNECTIVITY ), - .slv_aw_chan_t ( slv_aw_chan_t ), - .mst_aw_chan_t ( mst_aw_chan_t ), - .w_chan_t ( w_chan_t ), - .slv_b_chan_t ( slv_b_chan_t ), - .mst_b_chan_t ( mst_b_chan_t ), - .slv_ar_chan_t ( slv_ar_chan_t ), - .mst_ar_chan_t ( mst_ar_chan_t ), - .slv_r_chan_t ( slv_r_chan_t ), - .mst_r_chan_t ( mst_r_chan_t ), - .slv_req_t ( slv_req_t ), - .slv_resp_t ( slv_resp_t ), - .mst_req_t ( mst_req_t ), - .mst_resp_t ( mst_resp_t ), - .rule_t ( rule_t ) + .Cfg (Cfg), + .ATOPs (ATOPS), + .Connectivity (CONNECTIVITY), + .slv_aw_chan_t(slv_aw_chan_t), + .mst_aw_chan_t(mst_aw_chan_t), + .w_chan_t (w_chan_t), + .slv_b_chan_t (slv_b_chan_t), + .mst_b_chan_t (mst_b_chan_t), + .slv_ar_chan_t(slv_ar_chan_t), + .mst_ar_chan_t(mst_ar_chan_t), + .slv_r_chan_t (slv_r_chan_t), + .mst_r_chan_t (mst_r_chan_t), + .slv_req_t (slv_req_t), + .slv_resp_t (slv_resp_t), + .mst_req_t (mst_req_t), + .mst_resp_t (mst_resp_t), + .rule_t (rule_t) ) i_xbar ( .clk_i, .rst_ni, .test_i, - .slv_ports_req_i (slv_reqs ), - .slv_ports_resp_o (slv_resps), - .mst_ports_req_o (mst_reqs ), - .mst_ports_resp_i (mst_resps), + .slv_ports_req_i (slv_reqs), + .slv_ports_resp_o(slv_resps), + .mst_ports_req_o (mst_reqs), + .mst_ports_resp_i(mst_resps), .addr_map_i ); diff --git a/src/axi_test.sv b/src/axi_test.sv index 28e7b2b90..45b81c6e2 100644 --- a/src/axi_test.sv +++ b/src/axi_test.sv @@ -15,6 +15,7 @@ // - Fabian Schuiki // - Florian Zaruba // - Matheus Cavalcante +// - Luca Colagrande /// A set of testbench utilities for AXI interfaces. @@ -709,6 +710,8 @@ package axi_test; parameter bit UNIQUE_IDS = 1'b0, // guarantee that the ID of each transaction is // unique among all in-flight transactions in the // same direction + // Custom features + parameter bit ENABLE_MULTICAST = 1'b0, // Dependent parameters, do not override. parameter int AXI_STRB_WIDTH = DW/8, parameter int N_AXI_IDS = 2**IW @@ -765,6 +768,8 @@ package axi_test; } traffic_shape[$]; int unsigned max_cprob; + real mcast_prob; + function new( virtual AXI_BUS_DV #( .AXI_ADDR_WIDTH(AW), @@ -803,6 +808,11 @@ package axi_test; atop_resp_r = '0; endfunction + // Sets the probability to generate a transaction with a non-zero multicast mask + function void set_multicast_probability(real prob); + mcast_prob = prob; + endfunction + function void add_memory_region(input addr_t addr_begin, input addr_t addr_end, input mem_type_t mem_type); mem_map.push_back({addr_begin, addr_end, mem_type}); endfunction @@ -829,6 +839,8 @@ package axi_test; automatic int unsigned mem_region_idx; automatic mem_region_t mem_region; automatic int cprob; + automatic bit mcast; + automatic addr_t mcast_mask; // No memory regions defined if (mem_map.size() == 0) begin @@ -854,6 +866,7 @@ package axi_test; // Determine memory type. ax_beat.ax_cache = is_read ? axi_pkg::get_arcache(mem_region.mem_type) : axi_pkg::get_awcache(mem_region.mem_type); // Randomize beat size. + // TODO colluca: how do we handle traffic shaping w/ multicast? if (TRAFFIC_SHAPING) begin rand_success = std::randomize(cprob) with { cprob >= 0; cprob < max_cprob; @@ -930,6 +943,15 @@ package axi_test; ax_beat.ax_addr = addr; rand_success = std::randomize(id); assert(rand_success); rand_success = std::randomize(qos); assert(rand_success); + // Randomize multicast mask. + if (ENABLE_MULTICAST && !is_read) begin + rand_success = std::randomize(mcast, mcast_mask) with { + mcast dist {0 := (1 - mcast_prob), 1 := mcast_prob}; + !mcast -> mcast_mask == 0; + mcast -> mcast_mask != 0; + }; assert(rand_success); + ax_beat.ax_user = mcast_mask; + end // The random ID *must* be legalized with `legalize_id()` before the beat is sent! This is // currently done in the functions `create_aws()` and `send_ars()`. ax_beat.ax_id = id; @@ -1162,6 +1184,7 @@ package axi_test; aw_beat = excl_queue.pop_front(); end else begin aw_beat = new_rand_burst(1'b0); + // TODO colluca if (AXI_ATOPS) rand_atop_burst(aw_beat); end while (tot_w_flight_cnt >= MAX_WRITE_TXNS) begin @@ -2199,7 +2222,8 @@ module axi_chan_logger #( parameter type w_chan_t = logic, // axi W type parameter type b_chan_t = logic, // axi B type parameter type ar_chan_t = logic, // axi AR type - parameter type r_chan_t = logic // axi R type + parameter type r_chan_t = logic, // axi R type + parameter bit ENABLE_MULTICAST = 1'b0 ) ( input logic clk_i, // Clock input logic rst_ni, // Asynchronous reset active low, when `1'b0` no sampling @@ -2229,6 +2253,10 @@ module axi_chan_logger #( localparam int unsigned IdWidth = $bits(aw_chan_i.id); localparam int unsigned NoIds = 2**IdWidth; + // addr width from channel + localparam int unsigned AddrWidth = $bits(aw_chan_i.addr); + typedef logic [AddrWidth-1:0] addr_t; + // queues for writes and reads aw_chan_t aw_queue[$]; w_chan_t w_queue[$]; @@ -2242,6 +2270,8 @@ module axi_chan_logger #( automatic int fd; automatic string log_file; automatic string log_str; + automatic addr_t aw_mcast_mask; + automatic addr_t aw_addr; // only execute when reset is high if (rst_ni) begin // AW channel @@ -2250,8 +2280,13 @@ module axi_chan_logger #( log_file = $sformatf("./axi_log/%s/write.log", LoggerName); fd = $fopen(log_file, "a"); if (fd) begin - log_str = $sformatf("%0t> ID: %h AW on channel: LEN: %d, ATOP: %b", - $time, aw_chan_i.id, aw_chan_i.len, aw_chan_i.atop); + aw_addr = aw_chan_i.addr; + if (ENABLE_MULTICAST) begin + aw_mcast_mask = aw_chan_i.user[AddrWidth-1:0]; + for (int i = 0; i < AddrWidth; i++) if (aw_mcast_mask[i]) aw_addr[i] = 1'bx; + end + log_str = $sformatf("%0t> ID: %h AW on channel: LEN: %d, ATOP: %b, ADDR: %b", + $time, aw_chan_i.id, aw_chan_i.len, aw_chan_i.atop, aw_addr); $fdisplay(fd, log_str); $fclose(fd); end diff --git a/test/tb_axi_mcast_xbar.sv b/test/tb_axi_mcast_xbar.sv new file mode 100644 index 000000000..66ea1b61b --- /dev/null +++ b/test/tb_axi_mcast_xbar.sv @@ -0,0 +1,460 @@ +// Copyright (c) 2022 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Luca Colagrande +// Based on: +// - tb_axi_xbar.sv + +// Directed Random Verification Testbench for `axi_xbar`: The crossbar is instantiated with +// a number of random axi master and slave modules. Each random master executes a fixed number of +// writes and reads over the whole addess map. All masters simultaneously issue transactions +// through the crossbar, thereby saturating it. A monitor, which snoops the transactions of each +// master and slave port and models the crossbar with a network of FIFOs, checks whether each +// transaction follows the expected route. + +`include "axi/typedef.svh" +`include "axi/assign.svh" + +module tb_axi_mcast_xbar #( + parameter bit TbEnAtop = 1'b1, // enable atomic operations (ATOPs) + parameter bit TbEnExcl = 1'b0, // enable exclusive accesses + parameter bit TbUniqueIds = 1'b0, // restrict to only unique IDs + parameter int unsigned TbNumMst = 32'd6, // how many AXI masters there are + parameter int unsigned TbNumSlv = 32'd8 // how many AXI slaves there are +); + // Random master no Transactions + localparam int unsigned NoWrites = 80; // How many writes per master + localparam int unsigned NoReads = 80; // How many reads per master + // timing parameters + localparam time CyclTime = 10ns; + localparam time ApplTime = 2ns; + localparam time TestTime = 8ns; + + // axi configuration + localparam int unsigned AxiIdWidthMasters = 4; + localparam int unsigned AxiIdUsed = 3; // Has to be <= AxiIdWidthMasters + localparam int unsigned AxiIdWidthSlaves = AxiIdWidthMasters + $clog2(TbNumMst); + localparam int unsigned AxiAddrWidth = 32; // Axi Address Width + localparam int unsigned AxiDataWidth = 64; // Axi Data Width + localparam int unsigned AxiStrbWidth = AxiDataWidth / 8; + localparam int unsigned AxiUserWidth = AxiAddrWidth; + // in the bench can change this variables which are set here freely + localparam axi_pkg::xbar_cfg_t xbar_cfg = '{ + NoSlvPorts: TbNumMst, + NoMstPorts: TbNumSlv, + MaxMstTrans: 10, + MaxSlvTrans: 6, + FallThrough: 1'b0, + LatencyMode: axi_pkg::CUT_ALL_AX, + AxiIdWidthSlvPorts: AxiIdWidthMasters, + AxiIdUsedSlvPorts: AxiIdUsed, + UniqueIds: TbUniqueIds, + AxiAddrWidth: AxiAddrWidth, + AxiDataWidth: AxiDataWidth, + NoAddrRules: TbNumSlv + }; + typedef logic [AxiIdWidthMasters-1:0] id_mst_t; + typedef logic [AxiIdWidthSlaves-1:0] id_slv_t; + typedef logic [AxiAddrWidth-1:0] addr_t; + typedef struct packed { + addr_t addr; + addr_t mask; + } rule_t; + typedef logic [AxiDataWidth-1:0] data_t; + typedef logic [AxiStrbWidth-1:0] strb_t; + typedef logic [AxiUserWidth-1:0] user_t; + + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_mst_t, addr_t, id_mst_t, user_t) + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_slv_t, addr_t, id_slv_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_mst_t, id_mst_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_slv_t, id_slv_t, user_t) + + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_mst_t, addr_t, id_mst_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_slv_t, addr_t, id_slv_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_mst_t, data_t, id_mst_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_slv_t, data_t, id_slv_t, user_t) + + `AXI_TYPEDEF_REQ_T(mst_req_t, aw_chan_mst_t, w_chan_t, ar_chan_mst_t) + `AXI_TYPEDEF_RESP_T(mst_resp_t, b_chan_mst_t, r_chan_mst_t) + `AXI_TYPEDEF_REQ_T(slv_req_t, aw_chan_slv_t, w_chan_t, ar_chan_slv_t) + `AXI_TYPEDEF_RESP_T(slv_resp_t, b_chan_slv_t, r_chan_slv_t) + + localparam rule_t [15:0] __AddrMap = '{ + '{addr: 32'h0001_E000, mask: 32'h0000_1FFF}, + '{addr: 32'h0001_C000, mask: 32'h0000_1FFF}, + '{addr: 32'h0001_A000, mask: 32'h0000_1FFF}, + '{addr: 32'h0001_8000, mask: 32'h0000_1FFF}, + '{addr: 32'h0001_6000, mask: 32'h0000_1FFF}, + '{addr: 32'h0001_4000, mask: 32'h0000_1FFF}, + '{addr: 32'h0001_2000, mask: 32'h0000_1FFF}, + '{addr: 32'h0001_0000, mask: 32'h0000_1FFF}, + '{addr: 32'h0000_E000, mask: 32'h0000_1FFF}, + '{addr: 32'h0000_C000, mask: 32'h0000_1FFF}, + '{addr: 32'h0000_A000, mask: 32'h0000_1FFF}, + '{addr: 32'h0000_8000, mask: 32'h0000_1FFF}, + '{addr: 32'h0000_6000, mask: 32'h0000_1FFF}, + '{addr: 32'h0000_4000, mask: 32'h0000_1FFF}, + '{addr: 32'h0000_2000, mask: 32'h0000_1FFF}, + '{addr: 32'h0000_0000, mask: 32'h0000_1FFF} + }; + localparam rule_t [xbar_cfg.NoAddrRules-1:0] AddrMap = __AddrMap[xbar_cfg.NoAddrRules-1:0]; + + typedef axi_test::axi_rand_master #( + // AXI interface parameters + .AW(AxiAddrWidth), + .DW(AxiDataWidth), + .IW(AxiIdWidthMasters), + .UW(AxiUserWidth), + // Stimuli application and test time + .TA(ApplTime), + .TT(TestTime), + // Maximum number of read and write transactions in flight + .MAX_READ_TXNS (20), + .MAX_WRITE_TXNS (20), + .AXI_EXCLS (TbEnExcl), + .AXI_ATOPS (TbEnAtop), + .UNIQUE_IDS (TbUniqueIds), + .ENABLE_MULTICAST(1) + ) axi_rand_master_t; + typedef axi_test::axi_rand_slave #( + // AXI interface parameters + .AW(AxiAddrWidth), + .DW(AxiDataWidth), + .IW(AxiIdWidthSlaves), + .UW(AxiUserWidth), + // Stimuli application and test time + .TA(ApplTime), + .TT(TestTime) + ) axi_rand_slave_t; + + // ------------- + // DUT signals + // ------------- + logic clk; + // DUT signals + logic rst_n; + logic [TbNumMst-1:0] end_of_sim; + + // master structs + mst_req_t [TbNumMst-1:0] masters_req; + mst_resp_t [TbNumMst-1:0] masters_resp; + + // slave structs + slv_req_t [TbNumSlv-1:0] slaves_req; + slv_resp_t [TbNumSlv-1:0] slaves_resp; + + // ------------------------------- + // AXI Interfaces + // ------------------------------- + AXI_BUS #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthMasters ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) master [TbNumMst-1:0] (); + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthMasters ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) master_dv [TbNumMst-1:0] (clk); + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthMasters ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) master_monitor_dv [TbNumMst-1:0] (clk); + for (genvar i = 0; i < TbNumMst; i++) begin : gen_conn_dv_masters + `AXI_ASSIGN (master[i], master_dv[i]) + `AXI_ASSIGN_TO_REQ(masters_req[i], master[i]) + `AXI_ASSIGN_TO_RESP(masters_resp[i], master[i]) + end + + AXI_BUS #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthSlaves ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) slave [TbNumSlv-1:0] (); + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthSlaves ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) slave_dv [TbNumSlv-1:0](clk); + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthSlaves ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) slave_monitor_dv [TbNumSlv-1:0](clk); + for (genvar i = 0; i < TbNumSlv; i++) begin : gen_conn_dv_slaves + `AXI_ASSIGN(slave_dv[i], slave[i]) + `AXI_ASSIGN_TO_REQ(slaves_req[i], slave[i]) + `AXI_ASSIGN_TO_RESP(slaves_resp[i], slave[i]) + end + // ------------------------------- + // AXI Rand Masters and Slaves + // ------------------------------- + // Masters control simulation run time + axi_rand_master_t axi_rand_master [TbNumMst]; + for (genvar i = 0; i < TbNumMst; i++) begin : gen_rand_master + initial begin + axi_rand_master[i] = new( master_dv[i] ); + end_of_sim[i] <= 1'b0; + // TODO colluca + axi_rand_master[i].add_memory_region(0, + xbar_cfg.NoAddrRules * 32'h0000_2000, + axi_pkg::DEVICE_NONBUFFERABLE); + axi_rand_master[i].set_multicast_probability(0); + axi_rand_master[i].reset(); + @(posedge rst_n); + axi_rand_master[i].run(NoReads, NoWrites); + end_of_sim[i] <= 1'b1; + end + end + + axi_rand_slave_t axi_rand_slave [TbNumSlv]; + for (genvar i = 0; i < TbNumSlv; i++) begin : gen_rand_slave + initial begin + axi_rand_slave[i] = new( slave_dv[i] ); + axi_rand_slave[i].reset(); + @(posedge rst_n); + axi_rand_slave[i].run(); + end + end + + initial begin : proc_monitor + static tb_axi_mcast_xbar_pkg::axi_mcast_xbar_monitor #( + .AxiAddrWidth ( AxiAddrWidth ), + .AxiDataWidth ( AxiDataWidth ), + .AxiIdWidthMasters ( AxiIdWidthMasters ), + .AxiIdWidthSlaves ( AxiIdWidthSlaves ), + .AxiUserWidth ( AxiUserWidth ), + .NoMasters ( TbNumMst ), + .NoSlaves ( TbNumSlv ), + .NoAddrRules ( xbar_cfg.NoAddrRules ), + .rule_t ( rule_t ), + .AddrMap ( AddrMap ), + .TimeTest ( TestTime ) + ) monitor = new( master_monitor_dv, slave_monitor_dv ); + fork + monitor.run(); + do begin + #TestTime; + if(end_of_sim == '1) begin + monitor.print_result(); + $stop(); + end + @(posedge clk); + end while (1'b1); + join + end + + //----------------------------------- + // Clock generator + //----------------------------------- + clk_rst_gen #( + .ClkPeriod ( CyclTime ), + .RstClkCycles ( 5 ) + ) i_clk_gen ( + .clk_o (clk), + .rst_no(rst_n) + ); + + //----------------------------------- + // DUT + //----------------------------------- + + axi_mcast_xbar_intf #( + .AXI_USER_WIDTH ( AxiUserWidth ), + .Cfg ( xbar_cfg ), + .rule_t ( rule_t ) + ) i_xbar_dut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .test_i ( 1'b0 ), + .slv_ports ( master ), + .mst_ports ( slave ), + .addr_map_i ( AddrMap ) + ); + + // logger for master modules + for (genvar i = 0; i < TbNumMst; i++) begin : gen_master_logger + axi_chan_logger #( + .TestTime ( TestTime ), // Time after clock, where sampling happens + .LoggerName( $sformatf("axi_logger_master_%0d", i)), + .aw_chan_t ( aw_chan_mst_t ), // axi AW type + .w_chan_t ( w_chan_t ), // axi W type + .b_chan_t ( b_chan_mst_t ), // axi B type + .ar_chan_t ( ar_chan_mst_t ), // axi AR type + .r_chan_t ( r_chan_mst_t ), // axi R type + .ENABLE_MULTICAST(1) + ) i_mst_channel_logger ( + .clk_i ( clk ), // Clock + .rst_ni ( rst_n ), // Asynchronous reset active low, when `1'b0` no sampling + .end_sim_i ( &end_of_sim ), + // AW channel + .aw_chan_i ( masters_req[i].aw ), + .aw_valid_i ( masters_req[i].aw_valid ), + .aw_ready_i ( masters_resp[i].aw_ready ), + // W channel + .w_chan_i ( masters_req[i].w ), + .w_valid_i ( masters_req[i].w_valid ), + .w_ready_i ( masters_resp[i].w_ready ), + // B channel + .b_chan_i ( masters_resp[i].b ), + .b_valid_i ( masters_resp[i].b_valid ), + .b_ready_i ( masters_req[i].b_ready ), + // AR channel + .ar_chan_i ( masters_req[i].ar ), + .ar_valid_i ( masters_req[i].ar_valid ), + .ar_ready_i ( masters_resp[i].ar_ready ), + // R channel + .r_chan_i ( masters_resp[i].r ), + .r_valid_i ( masters_resp[i].r_valid ), + .r_ready_i ( masters_req[i].r_ready ) + ); + end + // logger for slave modules + for (genvar i = 0; i < TbNumSlv; i++) begin : gen_slave_logger + axi_chan_logger #( + .TestTime ( TestTime ), // Time after clock, where sampling happens + .LoggerName( $sformatf("axi_logger_slave_%0d",i)), + .aw_chan_t ( aw_chan_slv_t ), // axi AW type + .w_chan_t ( w_chan_t ), // axi W type + .b_chan_t ( b_chan_slv_t ), // axi B type + .ar_chan_t ( ar_chan_slv_t ), // axi AR type + .r_chan_t ( r_chan_slv_t ) // axi R type + ) i_slv_channel_logger ( + .clk_i ( clk ), // Clock + .rst_ni ( rst_n ), // Asynchronous reset active low, when `1'b0` no sampling + .end_sim_i ( &end_of_sim ), + // AW channel + .aw_chan_i ( slaves_req[i].aw ), + .aw_valid_i ( slaves_req[i].aw_valid ), + .aw_ready_i ( slaves_resp[i].aw_ready ), + // W channel + .w_chan_i ( slaves_req[i].w ), + .w_valid_i ( slaves_req[i].w_valid ), + .w_ready_i ( slaves_resp[i].w_ready ), + // B channel + .b_chan_i ( slaves_resp[i].b ), + .b_valid_i ( slaves_resp[i].b_valid ), + .b_ready_i ( slaves_req[i].b_ready ), + // AR channel + .ar_chan_i ( slaves_req[i].ar ), + .ar_valid_i ( slaves_req[i].ar_valid ), + .ar_ready_i ( slaves_resp[i].ar_ready ), + // R channel + .r_chan_i ( slaves_resp[i].r ), + .r_valid_i ( slaves_resp[i].r_valid ), + .r_ready_i ( slaves_req[i].r_ready ) + ); + end + + + for (genvar i = 0; i < TbNumMst; i++) begin : gen_connect_master_monitor + assign master_monitor_dv[i].aw_id = master[i].aw_id ; + assign master_monitor_dv[i].aw_addr = master[i].aw_addr ; + assign master_monitor_dv[i].aw_len = master[i].aw_len ; + assign master_monitor_dv[i].aw_size = master[i].aw_size ; + assign master_monitor_dv[i].aw_burst = master[i].aw_burst ; + assign master_monitor_dv[i].aw_lock = master[i].aw_lock ; + assign master_monitor_dv[i].aw_cache = master[i].aw_cache ; + assign master_monitor_dv[i].aw_prot = master[i].aw_prot ; + assign master_monitor_dv[i].aw_qos = master[i].aw_qos ; + assign master_monitor_dv[i].aw_region = master[i].aw_region; + assign master_monitor_dv[i].aw_atop = master[i].aw_atop ; + assign master_monitor_dv[i].aw_user = master[i].aw_user ; + assign master_monitor_dv[i].aw_valid = master[i].aw_valid ; + assign master_monitor_dv[i].aw_ready = master[i].aw_ready ; + assign master_monitor_dv[i].w_data = master[i].w_data ; + assign master_monitor_dv[i].w_strb = master[i].w_strb ; + assign master_monitor_dv[i].w_last = master[i].w_last ; + assign master_monitor_dv[i].w_user = master[i].w_user ; + assign master_monitor_dv[i].w_valid = master[i].w_valid ; + assign master_monitor_dv[i].w_ready = master[i].w_ready ; + assign master_monitor_dv[i].b_id = master[i].b_id ; + assign master_monitor_dv[i].b_resp = master[i].b_resp ; + assign master_monitor_dv[i].b_user = master[i].b_user ; + assign master_monitor_dv[i].b_valid = master[i].b_valid ; + assign master_monitor_dv[i].b_ready = master[i].b_ready ; + assign master_monitor_dv[i].ar_id = master[i].ar_id ; + assign master_monitor_dv[i].ar_addr = master[i].ar_addr ; + assign master_monitor_dv[i].ar_len = master[i].ar_len ; + assign master_monitor_dv[i].ar_size = master[i].ar_size ; + assign master_monitor_dv[i].ar_burst = master[i].ar_burst ; + assign master_monitor_dv[i].ar_lock = master[i].ar_lock ; + assign master_monitor_dv[i].ar_cache = master[i].ar_cache ; + assign master_monitor_dv[i].ar_prot = master[i].ar_prot ; + assign master_monitor_dv[i].ar_qos = master[i].ar_qos ; + assign master_monitor_dv[i].ar_region = master[i].ar_region; + assign master_monitor_dv[i].ar_user = master[i].ar_user ; + assign master_monitor_dv[i].ar_valid = master[i].ar_valid ; + assign master_monitor_dv[i].ar_ready = master[i].ar_ready ; + assign master_monitor_dv[i].r_id = master[i].r_id ; + assign master_monitor_dv[i].r_data = master[i].r_data ; + assign master_monitor_dv[i].r_resp = master[i].r_resp ; + assign master_monitor_dv[i].r_last = master[i].r_last ; + assign master_monitor_dv[i].r_user = master[i].r_user ; + assign master_monitor_dv[i].r_valid = master[i].r_valid ; + assign master_monitor_dv[i].r_ready = master[i].r_ready ; + end + for (genvar i = 0; i < TbNumSlv; i++) begin : gen_connect_slave_monitor + assign slave_monitor_dv[i].aw_id = slave[i].aw_id ; + assign slave_monitor_dv[i].aw_addr = slave[i].aw_addr ; + assign slave_monitor_dv[i].aw_len = slave[i].aw_len ; + assign slave_monitor_dv[i].aw_size = slave[i].aw_size ; + assign slave_monitor_dv[i].aw_burst = slave[i].aw_burst ; + assign slave_monitor_dv[i].aw_lock = slave[i].aw_lock ; + assign slave_monitor_dv[i].aw_cache = slave[i].aw_cache ; + assign slave_monitor_dv[i].aw_prot = slave[i].aw_prot ; + assign slave_monitor_dv[i].aw_qos = slave[i].aw_qos ; + assign slave_monitor_dv[i].aw_region = slave[i].aw_region; + assign slave_monitor_dv[i].aw_atop = slave[i].aw_atop ; + assign slave_monitor_dv[i].aw_user = slave[i].aw_user ; + assign slave_monitor_dv[i].aw_valid = slave[i].aw_valid ; + assign slave_monitor_dv[i].aw_ready = slave[i].aw_ready ; + assign slave_monitor_dv[i].w_data = slave[i].w_data ; + assign slave_monitor_dv[i].w_strb = slave[i].w_strb ; + assign slave_monitor_dv[i].w_last = slave[i].w_last ; + assign slave_monitor_dv[i].w_user = slave[i].w_user ; + assign slave_monitor_dv[i].w_valid = slave[i].w_valid ; + assign slave_monitor_dv[i].w_ready = slave[i].w_ready ; + assign slave_monitor_dv[i].b_id = slave[i].b_id ; + assign slave_monitor_dv[i].b_resp = slave[i].b_resp ; + assign slave_monitor_dv[i].b_user = slave[i].b_user ; + assign slave_monitor_dv[i].b_valid = slave[i].b_valid ; + assign slave_monitor_dv[i].b_ready = slave[i].b_ready ; + assign slave_monitor_dv[i].ar_id = slave[i].ar_id ; + assign slave_monitor_dv[i].ar_addr = slave[i].ar_addr ; + assign slave_monitor_dv[i].ar_len = slave[i].ar_len ; + assign slave_monitor_dv[i].ar_size = slave[i].ar_size ; + assign slave_monitor_dv[i].ar_burst = slave[i].ar_burst ; + assign slave_monitor_dv[i].ar_lock = slave[i].ar_lock ; + assign slave_monitor_dv[i].ar_cache = slave[i].ar_cache ; + assign slave_monitor_dv[i].ar_prot = slave[i].ar_prot ; + assign slave_monitor_dv[i].ar_qos = slave[i].ar_qos ; + assign slave_monitor_dv[i].ar_region = slave[i].ar_region; + assign slave_monitor_dv[i].ar_user = slave[i].ar_user ; + assign slave_monitor_dv[i].ar_valid = slave[i].ar_valid ; + assign slave_monitor_dv[i].ar_ready = slave[i].ar_ready ; + assign slave_monitor_dv[i].r_id = slave[i].r_id ; + assign slave_monitor_dv[i].r_data = slave[i].r_data ; + assign slave_monitor_dv[i].r_resp = slave[i].r_resp ; + assign slave_monitor_dv[i].r_last = slave[i].r_last ; + assign slave_monitor_dv[i].r_user = slave[i].r_user ; + assign slave_monitor_dv[i].r_valid = slave[i].r_valid ; + assign slave_monitor_dv[i].r_ready = slave[i].r_ready ; + end +endmodule diff --git a/test/tb_axi_mcast_xbar_pkg.sv b/test/tb_axi_mcast_xbar_pkg.sv new file mode 100644 index 000000000..f63864a18 --- /dev/null +++ b/test/tb_axi_mcast_xbar_pkg.sv @@ -0,0 +1,557 @@ +// Copyright (c) 2019 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Luca Colagrande +// Based on: +// - tb_axi_xbar_pkg.sv + +// `axi_mcast_xbar_monitor` implements an AXI bus monitor that is tuned for the AXI multicast +// crossbar. It snoops on each of the slaves and master ports of the crossbar and +// populates FIFOs and ID queues to validate that no AXI beats get +// lost or sent to the wrong destination. + +package tb_axi_mcast_xbar_pkg; + class axi_mcast_xbar_monitor #( + parameter int unsigned AxiAddrWidth, + parameter int unsigned AxiDataWidth, + parameter int unsigned AxiIdWidthMasters, + parameter int unsigned AxiIdWidthSlaves, + parameter int unsigned AxiUserWidth, + parameter int unsigned NoMasters, + parameter int unsigned NoSlaves, + parameter int unsigned NoAddrRules, + parameter type rule_t, + parameter rule_t [NoAddrRules-1:0] AddrMap, + // Stimuli application and test time + parameter time TimeTest + ); + typedef logic [AxiIdWidthMasters-1:0] mst_axi_id_t; + typedef logic [AxiIdWidthSlaves-1:0] slv_axi_id_t; + typedef logic [AxiAddrWidth-1:0] axi_addr_t; + + typedef logic [$clog2(NoMasters)-1:0] idx_mst_t; + typedef int unsigned idx_slv_t; // from rule_t + + typedef struct packed { + mst_axi_id_t mst_axi_id; + logic last; + } master_exp_t; + typedef struct packed { + slv_axi_id_t slv_axi_id; + axi_addr_t slv_axi_addr; + axi_addr_t slv_axi_mcast; + axi_pkg::len_t slv_axi_len; + } exp_ax_t; + typedef struct packed { + slv_axi_id_t slv_axi_id; + logic last; + } slave_exp_t; + + typedef rand_id_queue_pkg::rand_id_queue #( + .data_t ( master_exp_t ), + .ID_WIDTH ( AxiIdWidthMasters ) + ) master_exp_queue_t; + typedef rand_id_queue_pkg::rand_id_queue #( + .data_t ( exp_ax_t ), + .ID_WIDTH ( AxiIdWidthSlaves ) + ) ax_queue_t; + + typedef rand_id_queue_pkg::rand_id_queue #( + .data_t ( slave_exp_t ), + .ID_WIDTH ( AxiIdWidthSlaves ) + ) slave_exp_queue_t; + + //----------------------------------------- + // Monitoring virtual interfaces + //----------------------------------------- + virtual AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthMasters ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) masters_axi [NoMasters-1:0]; + virtual AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthSlaves ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) slaves_axi [NoSlaves-1:0]; + //----------------------------------------- + // Queues and FIFOs to hold the expected ids + //----------------------------------------- + // Write transactions + ax_queue_t exp_aw_queue [NoSlaves-1:0]; + slave_exp_t exp_w_fifo [NoSlaves-1:0][$]; + slave_exp_t act_w_fifo [NoSlaves-1:0][$]; + master_exp_queue_t exp_b_queue [NoMasters-1:0]; + + // Read transactions + ax_queue_t exp_ar_queue [NoSlaves-1:0]; + master_exp_queue_t exp_r_queue [NoMasters-1:0]; + + //----------------------------------------- + // Bookkeeping + //----------------------------------------- + longint unsigned tests_expected; + longint unsigned tests_conducted; + longint unsigned tests_failed; + semaphore cnt_sem; + + //----------------------------------------- + // Constructor + //----------------------------------------- + function new( + virtual AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthMasters ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) axi_masters_vif [NoMasters-1:0], + virtual AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidthSlaves ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) axi_slaves_vif [NoSlaves-1:0] + ); + begin + this.masters_axi = axi_masters_vif; + this.slaves_axi = axi_slaves_vif; + this.tests_expected = 0; + this.tests_conducted = 0; + this.tests_failed = 0; + for (int unsigned i = 0; i < NoMasters; i++) begin + this.exp_b_queue[i] = new; + this.exp_r_queue[i] = new; + end + for (int unsigned i = 0; i < NoSlaves; i++) begin + this.exp_aw_queue[i] = new; + this.exp_ar_queue[i] = new; + end + this.cnt_sem = new(1); + end + endfunction + + // when start the testing + task cycle_start; + #TimeTest; + endtask + + // when is cycle finished + task cycle_end; + @(posedge masters_axi[0].clk_i); + endtask + + // This task monitors a slave port of the crossbar. Every time an AW beat is seen + // it populates an id queue at the right master port(s) (if there is no expected decode error), + // populates the expected b response in its own id_queue and in case the atomic bit [5] + // is set it also injects an expected response in the R channel. + task automatic monitor_mst_aw(input int unsigned i); + axi_addr_t aw_addr; + axi_addr_t aw_mcast; + axi_addr_t aw_addr_masked; + axi_addr_t addrmap_masked; + idx_slv_t to_slave_idx[$]; + int unsigned num_slaves_matched; + axi_addr_t addr_to_slave[$]; + axi_addr_t mask_to_slave[$]; + bit decerr; + exp_ax_t exp_aw; + slv_axi_id_t exp_aw_id; + string slaves_str; + + master_exp_t exp_b; + + if (masters_axi[i].aw_valid && masters_axi[i].aw_ready) begin + + // Check to which slaves the transaction is directed or if it should go to a decerror. + // Store the indices of the selected slaves (to_slave_idx) and the filtered address + // sets {addr, mask} to be forwarded to each slave (addr_to_slave, mask_to_slave). + aw_addr = masters_axi[i].aw_addr; + aw_mcast = masters_axi[i].aw_user[AxiAddrWidth-1:0]; + for (int k = 0; k < AxiAddrWidth; k++) aw_addr_masked[k] = aw_mcast[k] ? 1'bx : aw_addr[k]; + $display("Trying to match: %b", aw_addr_masked); + for (int unsigned j = 0; j < NoSlaves; j++) begin + // request goes to the slave if all bits match, which are neither masked in the + // request nor in the addrmap rule + for (int k = 0; k < AxiAddrWidth; k++) addrmap_masked[k] = AddrMap[j].mask[k] ? 1'bx : AddrMap[j].addr[k]; + $display("With slave %0d : %b", j, addrmap_masked); + if (&(~(aw_addr ^ AddrMap[j].addr) | AddrMap[j].mask | aw_mcast)) begin + to_slave_idx.push_back(idx_slv_t'(j)); + mask_to_slave.push_back(aw_mcast & AddrMap[j].mask); + addr_to_slave.push_back((~aw_mcast & aw_addr) | (aw_mcast & AddrMap[j].addr)); + $display("Pushing mask : %b", aw_mcast & AddrMap[j].mask); + $display("Pushing address: %b", (~aw_mcast & aw_addr) | (aw_mcast & AddrMap[j].addr)); + end + end + num_slaves_matched = to_slave_idx.size(); + decerr = num_slaves_matched == 0; + if (num_slaves_matched > 1 || decerr) begin + $display("MULTICAST occur: %b, %b", aw_addr, aw_mcast); + for (int j = 0; j < NoSlaves; j++) begin + $display("Slave %0d AddrMap: %b, %b", j, AddrMap[j].addr, AddrMap[j].mask); + end + end + + // send the exp aw beats down into the queues of the selected slaves + // when no decerror + if (decerr) begin + $display("%0tns > Master %0d: AW to Decerror: Axi ID: %b", + $time, i, masters_axi[i].aw_id); + end else begin + exp_aw_id = {idx_mst_t'(i), masters_axi[i].aw_id}; + for (int j = 0; j < to_slave_idx.size(); j++) begin + automatic idx_slv_t slave_idx = to_slave_idx.pop_front(); + // $display("Test exp aw_id: %b",exp_aw_id); + exp_aw = '{slv_axi_id: exp_aw_id, + slv_axi_addr: addr_to_slave.pop_front(), + slv_axi_mcast: mask_to_slave.pop_front(), + slv_axi_len: masters_axi[i].aw_len}; + this.exp_aw_queue[slave_idx].push(exp_aw_id, exp_aw); + incr_expected_tests(4); + if (j == 0) slaves_str = $sformatf("%0d", slave_idx); + else slaves_str = $sformatf("%s, %0d", slaves_str, slave_idx); + end + $display("%0tns > Master %0d: AW to Slaves [%s]: Axi ID: %b", + $time, i, slaves_str, masters_axi[i].aw_id); + end + + // populate the expected b queue anyway + exp_b = '{mst_axi_id: masters_axi[i].aw_id, last: 1'b1}; + this.exp_b_queue[i].push(masters_axi[i].aw_id, exp_b); + incr_expected_tests(1); + $display(" Expect B response."); + + // inject expected r beats on this id, if it is an atop + // throw an error if a multicast atop is attempted (not supported) + if(masters_axi[i].aw_atop[5]) begin + if (num_slaves_matched > 1) $fatal("Multicast ATOPs are not supported"); + // push the required r beats into the right fifo (reuse the exp_b variable) + $display(" Expect R response, len: %0d.", masters_axi[i].aw_len); + for (int unsigned j = 0; j <= masters_axi[i].aw_len; j++) begin + exp_b = (j == masters_axi[i].aw_len) ? + '{mst_axi_id: masters_axi[i].aw_id, last: 1'b1} : + '{mst_axi_id: masters_axi[i].aw_id, last: 1'b0}; + this.exp_r_queue[i].push(masters_axi[i].aw_id, exp_b); + incr_expected_tests(1); + end + end + end + endtask : monitor_mst_aw + + // This task monitors a master port of the crossbar. Every time there is an AW vector it + // gets checked for its contents and if it was expected. The task then pushes an expected + // amount of W beats in the respective fifo. Emphasis of the last flag. + task automatic monitor_slv_aw(input int unsigned i); + exp_ax_t exp_aw; + slave_exp_t exp_slv_w; + // $display("%0t > Was triggered: aw_valid %b, aw_ready: %b", + // $time(), slaves_axi[i].aw_valid, slaves_axi[i].aw_ready); + if (slaves_axi[i].aw_valid && slaves_axi[i].aw_ready) begin + // test if the aw beat was expected + exp_aw = this.exp_aw_queue[i].pop_id(slaves_axi[i].aw_id); + $display("%0tns > Slave %0d: AW Axi ID: %b", + $time, i, slaves_axi[i].aw_id); + if (exp_aw.slv_axi_id != slaves_axi[i].aw_id) begin + incr_failed_tests(1); + $warning("Slave %0d: Unexpected AW with ID: %b", i, slaves_axi[i].aw_id); + end + if (exp_aw.slv_axi_addr != slaves_axi[i].aw_addr) begin + incr_failed_tests(1); + $warning("Slave %0d: Unexpected AW with ID: %b and ADDR: %h, exp: %h", + i, slaves_axi[i].aw_id, slaves_axi[i].aw_addr, exp_aw.slv_axi_addr); + end + if (exp_aw.slv_axi_mcast != slaves_axi[i].aw_user[AxiAddrWidth-1:0]) begin + incr_failed_tests(1); + $warning("Slave %0d: Unexpected AW with ID: %b and MCAST: %h, exp: %h", + i, slaves_axi[i].aw_id, slaves_axi[i].aw_user[AxiAddrWidth-1:0], + exp_aw.slv_axi_mcast); + end + if (exp_aw.slv_axi_len != slaves_axi[i].aw_len) begin + incr_failed_tests(1); + $warning("Slave %0d: Unexpected AW with ID: %b and LEN: %h, exp: %h", + i, slaves_axi[i].aw_id, slaves_axi[i].aw_len, exp_aw.slv_axi_len); + end + incr_conducted_tests(4); + + // push the required w beats into the right fifo + incr_expected_tests(slaves_axi[i].aw_len + 1); + for (int unsigned j = 0; j <= slaves_axi[i].aw_len; j++) begin + exp_slv_w = (j == slaves_axi[i].aw_len) ? + '{slv_axi_id: slaves_axi[i].aw_id, last: 1'b1} : + '{slv_axi_id: slaves_axi[i].aw_id, last: 1'b0}; + this.exp_w_fifo[i].push_back(exp_slv_w); + end + end + endtask : monitor_slv_aw + + // This task just pushes every W beat that gets sent on a master port in its respective fifo. + task automatic monitor_slv_w(input int unsigned i); + slave_exp_t act_slv_w; + if (slaves_axi[i].w_valid && slaves_axi[i].w_ready) begin + // $display("%0t > W beat on Slave %0d, last flag: %b", $time, i, slaves_axi[i].w_last); + act_slv_w = '{last: slaves_axi[i].w_last , default:'0}; + this.act_w_fifo[i].push_back(act_slv_w); + end + endtask : monitor_slv_w + + // This task compares the expected and actual W beats on a master port. The reason that + // this is not done in `monitor_slv_w` is that there can be per protocol W beats on the + // channel, before AW is sent to the slave. + task automatic check_slv_w(input int unsigned i); + slave_exp_t exp_w, act_w; + while (this.exp_w_fifo[i].size() != 0 && this.act_w_fifo[i].size() != 0) begin + + exp_w = this.exp_w_fifo[i].pop_front(); + act_w = this.act_w_fifo[i].pop_front(); + // do the check + incr_conducted_tests(1); + if(exp_w.last != act_w.last) begin + incr_failed_tests(1); + $warning("Slave %d: unexpected W beat last flag %b, expected: %b.", + i, act_w.last, exp_w.last); + end + end + endtask : check_slv_w + + // This task checks if a B response is allowed on a slave port of the crossbar. + task automatic monitor_mst_b(input int unsigned i); + master_exp_t exp_b; + mst_axi_id_t axi_b_id; + if (masters_axi[i].b_valid && masters_axi[i].b_ready) begin + incr_conducted_tests(1); + axi_b_id = masters_axi[i].b_id; + $display("%0tns > Master %0d: Got last B with id: %b", + $time, i, axi_b_id); + if (this.exp_b_queue[i].empty()) begin + incr_failed_tests(1); + $warning("Master %d: unexpected B beat with ID: %b detected!", i, axi_b_id); + end else begin + exp_b = this.exp_b_queue[i].pop_id(axi_b_id); + if (axi_b_id != exp_b.mst_axi_id) begin + incr_failed_tests(1); + $warning("Master: %d got unexpected B with ID: %b", i, axi_b_id); + end + end + end + endtask : monitor_mst_b + + // This task monitors the AR channel of a slave port of the crossbar. For each AR it populates + // the corresponding ID queue with the number of r beats indicated on the `ar_len` field. + // Emphasis on the last flag. We will detect reordering, if the last flags do not match, + // as each `random` burst tend to have a different length. + task automatic monitor_mst_ar(input int unsigned i); + mst_axi_id_t mst_axi_id; + axi_addr_t mst_axi_addr; + axi_pkg::len_t mst_axi_len; + + idx_slv_t exp_slv_idx; + slv_axi_id_t exp_slv_axi_id; + exp_ax_t exp_slv_ar; + master_exp_t exp_mst_r; + + logic exp_decerr; + + if (masters_axi[i].ar_valid && masters_axi[i].ar_ready) begin + exp_decerr = 1'b1; + mst_axi_id = masters_axi[i].ar_id; + mst_axi_addr = masters_axi[i].ar_addr; + mst_axi_len = masters_axi[i].ar_len; + exp_slv_axi_id = {idx_mst_t'(i), mst_axi_id}; + exp_slv_idx = '0; + + for (int unsigned j = 0; j < NoSlaves; j++) begin + if (&(~(mst_axi_addr ^ AddrMap[j].addr) | AddrMap[j].mask)) begin + if (!exp_decerr) $fatal("Multicast reads are not supported"); + exp_slv_idx = j; + exp_decerr = 1'b0; + end + end + if (exp_decerr) begin + $display("%0tns > Master %0d: AR to Decerror: Axi ID: %b", + $time, i, mst_axi_id); + end else begin + $display("%0tns > Master %0d: AR to Slave %0d: Axi ID: %b", + $time, i, exp_slv_idx, mst_axi_id); + // push the expected vectors AW for exp_slv + exp_slv_ar = '{slv_axi_id: exp_slv_axi_id, + slv_axi_addr: mst_axi_addr, + slv_axi_mcast: '0, + slv_axi_len: mst_axi_len}; + //$display("Expected Slv Axi Id is: %b", exp_slv_axi_id); + this.exp_ar_queue[exp_slv_idx].push(exp_slv_axi_id, exp_slv_ar); + incr_expected_tests(1); + end + // push the required r beats into the right fifo + $display(" Expect R response, len: %0d.", masters_axi[i].ar_len); + for (int unsigned j = 0; j <= mst_axi_len; j++) begin + exp_mst_r = (j == mst_axi_len) ? '{mst_axi_id: mst_axi_id, last: 1'b1} : + '{mst_axi_id: mst_axi_id, last: 1'b0}; + this.exp_r_queue[i].push(mst_axi_id, exp_mst_r); + incr_expected_tests(1); + end + end + endtask : monitor_mst_ar + + // This task monitors a master port of the crossbar and checks if a transmitted AR beat was + // expected. + task automatic monitor_slv_ar(input int unsigned i); + exp_ax_t exp_slv_ar; + slv_axi_id_t slv_axi_id; + if (slaves_axi[i].ar_valid && slaves_axi[i].ar_ready) begin + incr_conducted_tests(1); + slv_axi_id = slaves_axi[i].ar_id; + if (this.exp_ar_queue[i].empty()) begin + incr_failed_tests(1); + end else begin + // check that the ids are the same + exp_slv_ar = this.exp_ar_queue[i].pop_id(slv_axi_id); + $display("%0tns > Slave %0d: AR Axi ID: %b", $time, i, slv_axi_id); + if (exp_slv_ar.slv_axi_id != slv_axi_id) begin + incr_failed_tests(1); + $warning("Slave %d: Unexpected AR with ID: %b", i, slv_axi_id); + end + end + end + endtask : monitor_slv_ar + + // This task does the R channel monitoring on a slave port. It compares the last flags, + // which are determined by the sequence of previously sent AR vectors. + task automatic monitor_mst_r(input int unsigned i); + master_exp_t exp_mst_r; + mst_axi_id_t mst_axi_r_id; + logic mst_axi_r_last; + if (masters_axi[i].r_valid && masters_axi[i].r_ready) begin + incr_conducted_tests(1); + mst_axi_r_id = masters_axi[i].r_id; + mst_axi_r_last = masters_axi[i].r_last; + if (mst_axi_r_last) begin + $display("%0tns > Master %0d: Got last R with id: %b", + $time, i, mst_axi_r_id); + end + if (this.exp_r_queue[i].empty()) begin + incr_failed_tests(1); + $warning("Master %d: unexpected R beat with ID: %b detected!", i, mst_axi_r_id); + end else begin + exp_mst_r = this.exp_r_queue[i].pop_id(mst_axi_r_id); + if (mst_axi_r_id != exp_mst_r.mst_axi_id) begin + incr_failed_tests(1); + $warning("Master: %d got unexpected R with ID: %b", i, mst_axi_r_id); + end + if (mst_axi_r_last != exp_mst_r.last) begin + incr_failed_tests(1); + $warning("Master: %d got unexpected R with ID: %b and last flag: %b", + i, mst_axi_r_id, mst_axi_r_last); + end + end + end + endtask : monitor_mst_r + + // Some tasks to manage bookkeeping of the tests conducted. + task incr_expected_tests(input int unsigned times); + cnt_sem.get(); + this.tests_expected += times; + cnt_sem.put(); + endtask : incr_expected_tests + + task incr_conducted_tests(input int unsigned times); + cnt_sem.get(); + this.tests_conducted += times; + cnt_sem.put(); + endtask : incr_conducted_tests + + task incr_failed_tests(input int unsigned times); + cnt_sem.get(); + this.tests_failed += times; + cnt_sem.put(); + endtask : incr_failed_tests + + // This task invokes the various monitoring tasks. It first forks in two, spitting + // the tasks that should continuously run and the ones that get invoked every clock cycle. + // For the tasks every clock cycle all processes that only push something in the fifo's and + // Queues get run. When they are finished the processes that pop something get run. + task run(); + Continous: fork + begin + do begin + cycle_start(); + // at every cycle span some monitoring processes + // execute all processes that put something into the queues + PushMon: fork + proc_mst_aw: begin + for (int unsigned i = 0; i < NoMasters; i++) begin + monitor_mst_aw(i); + end + end + proc_mst_ar: begin + for (int unsigned i = 0; i < NoMasters; i++) begin + monitor_mst_ar(i); + end + end + join : PushMon + // this one pops and pushes something + proc_slv_aw: begin + for (int unsigned i = 0; i < NoSlaves; i++) begin + monitor_slv_aw(i); + end + end + proc_slv_w: begin + for (int unsigned i = 0; i < NoSlaves; i++) begin + monitor_slv_w(i); + end + end + // These only pop somethong from the queses + PopMon: fork + proc_mst_b: begin + for (int unsigned i = 0; i < NoMasters; i++) begin + monitor_mst_b(i); + end + end + proc_slv_ar: begin + for (int unsigned i = 0; i < NoSlaves; i++) begin + monitor_slv_ar(i); + end + end + proc_mst_r: begin + for (int unsigned i = 0; i < NoMasters; i++) begin + monitor_mst_r(i); + end + end + join : PopMon + // check the slave W fifos last + proc_check_slv_w: begin + for (int unsigned i = 0; i < NoSlaves; i++) begin + check_slv_w(i); + end + end + cycle_end(); + end while (1'b1); + end + join + endtask : run + + task print_result(); + $info("Simulation has ended!"); + $display("Tests Expected: %d", this.tests_expected); + $display("Tests Conducted: %d", this.tests_conducted); + $display("Tests Failed: %d", this.tests_failed); + if(tests_failed > 0) begin + $error("Simulation encountered unexpected Transactions!!!!!!"); + end + if(tests_conducted == 0) begin + $error("Simulation did not conduct any tests!"); + end + if (tests_conducted < tests_expected) begin + $error("Some of the expected tests were not conducted!"); + end + endtask : print_result + endclass : axi_mcast_xbar_monitor +endpackage diff --git a/test/tb_axi_xbar.sv b/test/tb_axi_xbar.sv index 793879cf9..d8d75900d 100644 --- a/test/tb_axi_xbar.sv +++ b/test/tb_axi_xbar.sv @@ -26,7 +26,6 @@ module tb_axi_xbar #( parameter bit TbEnAtop = 1'b1, // enable atomic operations (ATOPs) parameter bit TbEnExcl = 1'b0, // enable exclusive accesses parameter bit TbUniqueIds = 1'b0, // restrict to only unique IDs - parameter bit TbMulticast = 1'b0, // test the multicast XBAR parameter int unsigned TbNumMst = 32'd6, // how many AXI masters there are parameter int unsigned TbNumSlv = 32'd8 // how many AXI slaves there are ); @@ -259,36 +258,20 @@ module tb_axi_xbar #( //----------------------------------- // DUT //----------------------------------- - - if (TbMulticast) begin : g_multicast_xbar - axi_mcast_xbar_intf #( - .AXI_USER_WIDTH ( AxiUserWidth ), - .Cfg ( xbar_cfg ), - .rule_t ( rule_t ) - ) i_xbar_dut ( - .clk_i ( clk ), - .rst_ni ( rst_n ), - .test_i ( 1'b0 ), - .slv_ports ( master ), - .mst_ports ( slave ), - .addr_map_i ( AddrMap ) - ); - end else begin : g_standard_xbar - axi_xbar_intf #( - .AXI_USER_WIDTH ( AxiUserWidth ), - .Cfg ( xbar_cfg ), - .rule_t ( rule_t ) - ) i_xbar_dut ( - .clk_i ( clk ), - .rst_ni ( rst_n ), - .test_i ( 1'b0 ), - .slv_ports ( master ), - .mst_ports ( slave ), - .addr_map_i ( AddrMap ), - .en_default_mst_port_i ( '0 ), - .default_mst_port_i ( '0 ) - ); - end + axi_xbar_intf #( + .AXI_USER_WIDTH ( AxiUserWidth ), + .Cfg ( xbar_cfg ), + .rule_t ( rule_t ) + ) i_xbar_dut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .test_i ( 1'b0 ), + .slv_ports ( master ), + .mst_ports ( slave ), + .addr_map_i ( AddrMap ), + .en_default_mst_port_i ( '0 ), + .default_mst_port_i ( '0 ) + ); // logger for master modules for (genvar i = 0; i < TbNumMst; i++) begin : gen_master_logger