diff --git a/.ci/Memora.yml b/.ci/Memora.yml index a4504d448..72ee87b62 100644 --- a/.ci/Memora.yml +++ b/.ci/Memora.yml @@ -328,5 +328,23 @@ artifacts: - src/axi_xbar_unmuxed.sv - src/axi_xbar.sv - test/tb_axi_xbar.sv + - test/tb_axi_xbar_pkg.sv outputs: - build/axi_xbar-%.tested + + axi_mcast_xbar-%: + inputs: + - Bender.yml + - include + - scripts/run_vsim.sh + - src/axi_pkg.sv + - src/axi_intf.sv + - src/axi_test.sv + - src/axi_mcast_demux.sv + - src/axi_err_slv.sv + - src/axi_mcast_mux.sv + - src/axi_mcast_xbar.sv + - test/tb_axi_mcast_xbar.sv + - test/tb_axi_xbar_pkg.sv + outputs: + - build/axi_mcast_xbar-%.tested diff --git a/.github/workflows/gitlab-ci.yml b/.github/workflows/gitlab-ci.yml index 96456f43e..b6041ec1e 100644 --- a/.github/workflows/gitlab-ci.yml +++ b/.github/workflows/gitlab-ci.yml @@ -9,7 +9,7 @@ on: jobs: gitlab-ci: runs-on: ubuntu-latest - timeout-minutes: 310 + timeout-minutes: 360 steps: - name: Check Gitlab CI uses: pulp-platform/pulp-actions/gitlab-ci@v2 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7419b1af5..761348b14 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -188,3 +188,10 @@ axi_xbar: <<: *run_vsim variables: TEST_MODULE: axi_xbar + timeout: 6h 00m + +axi_mcast_xbar: + <<: *run_vsim + variables: + TEST_MODULE: axi_mcast_xbar + timeout: 6h 00m diff --git a/Bender.yml b/Bender.yml index a53a54dab..4fdd4511b 100644 --- a/Bender.yml +++ b/Bender.yml @@ -5,6 +5,7 @@ package: - "Thomas Benz " # current maintainer - "Michael Rogenmoser " # current maintainer - "Matheus Cavalcante " + - "Luca Colagrande " - "Tim Fischer " - "Noah Huetter " - "Cyril Koenig " @@ -19,7 +20,7 @@ package: - "Florian Zaruba " dependencies: - common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: 1.37.0 } + common_cells: { git: "https://github.com/pulp-platform/common_cells.git", rev: "multicast-xbar" } common_verification: { git: "https://github.com/pulp-platform/common_verification.git", version: 0.2.5 } tech_cells_generic: { git: "https://github.com/pulp-platform/tech_cells_generic.git", version: 0.2.2 } @@ -33,6 +34,7 @@ sources: # Level 0 - src/axi_pkg.sv # Level 1 + - src/axi_demux_id_counters.sv - src/axi_intf.sv # Level 2 - src/axi_atop_filter.sv @@ -63,6 +65,8 @@ sources: - src/axi_lite_regs.sv - src/axi_lite_to_apb.sv - src/axi_lite_to_axi.sv + - src/axi_mcast_demux_simple.sv + - src/axi_mcast_mux.sv - src/axi_modify_address.sv - src/axi_mux.sv - src/axi_rw_join.sv @@ -80,6 +84,7 @@ sources: - src/axi_from_mem.sv - src/axi_id_serialize.sv - src/axi_lfsr.sv + - src/axi_mcast_demux_mapped.sv - src/axi_multicut.sv - src/axi_to_axi_lite.sv - src/axi_to_mem.sv @@ -88,11 +93,13 @@ sources: - src/axi_interleaved_xbar.sv - src/axi_iw_converter.sv - src/axi_lite_xbar.sv + - src/axi_mcast_xbar_unmuxed.sv - src/axi_xbar_unmuxed.sv - src/axi_to_mem_banked.sv - src/axi_to_mem_interleaved.sv - src/axi_to_mem_split.sv # Level 5 + - src/axi_mcast_xbar.sv - src/axi_xbar.sv # Level 6 - src/axi_xp.sv @@ -130,6 +137,7 @@ sources: - test/tb_axi_lite_to_apb.sv - test/tb_axi_lite_to_axi.sv - test/tb_axi_lite_xbar.sv + - test/tb_axi_mcast_xbar.sv - test/tb_axi_modify_address.sv - test/tb_axi_serializer.sv - test/tb_axi_sim_mem.sv diff --git a/doc/axi_demux.md b/doc/axi_demux.md index 4b8b964ed..c7bf1679b 100644 --- a/doc/axi_demux.md +++ b/doc/axi_demux.md @@ -71,7 +71,7 @@ Setting the `UniqueIds` parameter to `1'b1` reduces the area complexity of the d `2 * 2^AxiLookBits` counters track the number of [in-flight](../doc#in-flight) transactions. That is, for each ID in the (potentially) reduced set of IDs of `AxiLookBits` bits, there is one counter for write transactions and one for read transactions. Each counter can count up to (and including) `MaxTrans`, and there is a register that holds the index of the master port to which a counter is assigned. -When the demultiplexer gets an AW or an AR, it indexes the counters with the AXI ID. If the indexed counter has a value greater than zero and its master port index register is not equal to the index to which the AW or AR is to be sent, a transaction with the same direction and ID is already in flight to another master port. The demultiplexer then stalls the AW or AR. In all other cases, the demultiplexer forwards the AW or AR, increments the value of the indexed counter, and sets the master port index of the counter. A counter is decremented upon a handshake a B respectively last R beat at a slave port. +When the demultiplexer gets an AW or an AR, it indexes the counters with the AXI ID. If the indexed counter has a value greater than zero and its master port index register is not equal to the index to which the AW or AR is to be sent, a transaction with the same direction and ID is already in flight to another master port. The demultiplexer then stalls the AW or AR. In all other cases, the demultiplexer forwards the AW or AR, increments the value of the indexed counter, and sets the master port index of the counter. A counter associated with the AW or AR channel is decremented upon a handshake on the slave port respectively on the B channel or on the R channel in correspondence of the last beat. W beats are routed to the master port defined by the value of `slv_aw_select_i` for the corresponding AW. As the order of the W bursts is given by the order of the AWs, the select signals are stored in a FIFO queue. This FIFO is pushed upon a handshake on the AW slave channel and popped upon a handshake of the last W beat of a burst on a W master channel. diff --git a/doc/axi_mux.md b/doc/axi_mux.md index fc257feb0..0c3641267 100644 --- a/doc/axi_mux.md +++ b/doc/axi_mux.md @@ -4,7 +4,7 @@ The opposite function to the AXI demultiplexer is performed by the AXI Multiplex ![Block-diagram of the AXI 4 Multiplexer Module.](axi_mux.png "Block-diagram of the AXI 4 Multiplexer Module.") -The Multiplexer module is has a simpler structure than the demultiplexer introduced in the previous section. The requests on the AW and AR channels get merged with the same round robin arbitration used for merging the responses in the demultiplexer. One key difference however is the mechanism how the multiplexer determines from which slave port a request came. It uses for this the higher bits of the `axi_id` field of a request. The number of bits can be calculated with: +The Multiplexer module has a simpler structure than the demultiplexer introduced in the previous section. The requests on the AW and AR channels get merged with the same round robin arbitration used for merging the responses in the demultiplexer. One key difference however is the mechanism how the multiplexer determines from which slave port a request came. It uses for this the higher bits of the `axi_id` field of a request. The number of bits can be calculated with: ```systemverilog $clog2(NoSlavePorts) diff --git a/doc/axi_xbar.md b/doc/axi_xbar.md index ca0c12108..c9d89745d 100644 --- a/doc/axi_xbar.md +++ b/doc/axi_xbar.md @@ -5,7 +5,7 @@ ## Design Overview -`axi_xbar` is a fully-connected crossbar, which means that each master module that is connected to a *slave port* for of the crossbar has direct wires to all slave modules that are connected to the *master ports* of the crossbar. +`axi_xbar` is a fully-connected crossbar, which means that each master module that is connected to a *slave port* of the crossbar has direct wires to all slave modules that are connected to the *master ports* of the crossbar. A block-diagram of the crossbar is shown below: ![Block-diagram showing the design of the full AXI4 Crossbar.](axi_xbar.png "Block-diagram showing the design of the full AXI4 Crossbar.") @@ -49,7 +49,7 @@ The crossbar is configured through the `Cfg` parameter with a `axi_pkg::xbar_cfg | `LatencyMode` | `enum logic [9:0]` | Latency on the individual channels, defined in detail in section *Pipelining and Latency* below. | | `AxiIdWidthSlvPorts` | `int unsigned` | The AXI ID width of the slave ports. | | `AxiIdUsedSlvPorts` | `int unsigned` | The number of slave port ID bits (starting at the least significant) the crossbar uses to determine the uniqueness of an AXI ID (see section *Ordering and Stalls* below). This value has to be less or equal than `AxiIdWidthSlvPorts`. | -| `UniqueIds` | `bit` | If you can guarantee that the ID of each transaction is always unique among all in-flight transactions in the same direction, setting this parameter to `1'b1` simplifies the crossbar. See the [`axi_demux` documentation](axi_demux#ordering-and-stalls) for details. | +| `UniqueIds` | `bit` | If you can guarantee that the ID of each transaction is always unique among all in-flight transactions in the same direction, setting this parameter to `1'b1` simplifies the crossbar. See the [`axi_demux` documentation](axi_demux.md#ordering-and-stalls) for details. | | `AxiAddrWidth` | `int unsigned` | The AXI address width. | | `AxiDataWidth` | `int unsigned` | The AXI data width. | | `NoAddrRules` | `int unsigned` | The number of address map rules. | diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index aaf8c3f22..e5d5112ed 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -175,21 +175,6 @@ exec_test() { done done ;; - axi_xbar) - 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 -gTbNumMasters=$NumMst -gTbNumSlaves=$NumSlv \ - -gTbEnAtop=$Atop -gTbEnExcl=$Exclusive \ - -gTbUniqueIds=$UniqueIds - done - done - done - done - done - ;; axi_to_mem_banked) for MEM_LAT in 1 2; do for BANK_FACTOR in 1 2; do @@ -219,7 +204,7 @@ exec_test() { MST_ID=5 for DATA_WIDTH in 64 256; do for PIPE in 0 1; do - call_vsim tb_axi_xbar -t 1ns -voptargs="+acc" \ + call_vsim tb_axi_xbar -t 1ns \ -gTbNumMasters=$NUM_MST \ -gTbNumSlaves=$NUM_SLV \ -gTbAxiIdWidthMasters=$MST_ID \ @@ -241,6 +226,32 @@ exec_test() { done done ;; + axi_mcast_xbar) + for GEN_ATOP in 0 1; do + for NUM_MST in 1 6; do + for NUM_SLV in 2 9; do + for MST_ID_USE in 3 5; do + MST_ID=5 + for DATA_WIDTH in 64 256; do + for PIPE in 0; do + for UNIQUE_IDS in 0; do + call_vsim tb_axi_mcast_xbar -t 1ns \ + -gTbNumMasters=$NUM_MST \ + -gTbNumMcastSlaves=$NUM_SLV \ + -gTbAxiIdWidthMasters=$MST_ID \ + -gTbAxiIdUsed=$MST_ID_USE \ + -gTbAxiDataWidth=$DATA_WIDTH \ + -gTbPipeline=$PIPE \ + -gTbEnAtop=$GEN_ATOP \ + -gTbUniqueIds=$UNIQUE_IDS + done + done + done + done + done + done + done + ;; *) call_vsim tb_$1 -t 1ns -coverage -voptargs="+acc +cover=bcesfx" ;; diff --git a/src/axi_demux_id_counters.sv b/src/axi_demux_id_counters.sv new file mode 100644 index 000000000..7e8c88da5 --- /dev/null +++ b/src/axi_demux_id_counters.sv @@ -0,0 +1,146 @@ +// 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: +// - Wolfgang Roenninger +// - Michael Rogenmoser +// - Thomas Benz +// - Andreas Kurth + +`include "common_cells/registers.svh" + +module axi_demux_id_counters #( + // the lower bits of the AXI ID that should be considered, results in 2**AXI_ID_BITS counters + parameter int unsigned AxiIdBits = 2, + parameter int unsigned CounterWidth = 4, + parameter type mst_port_select_t = logic +) ( + input logic clk_i, // Clock + input logic rst_ni, // Asynchronous reset active low + // lookup + input logic [AxiIdBits-1:0] lookup_axi_id_i, + output mst_port_select_t lookup_mst_select_o, + output logic lookup_mst_select_occupied_o, + // push + output logic full_o, + input logic [AxiIdBits-1:0] push_axi_id_i, + input mst_port_select_t push_mst_select_i, + input logic push_i, + // inject ATOPs in AR channel + input logic [AxiIdBits-1:0] inject_axi_id_i, + input logic inject_i, + // pop + input logic [AxiIdBits-1:0] pop_axi_id_i, + input logic pop_i, + // outstanding transactions + output logic any_outstanding_trx_o +); + localparam int unsigned NoCounters = 2**AxiIdBits; + typedef logic [CounterWidth-1:0] cnt_t; + + // registers, each gets loaded when push_en[i] + mst_port_select_t [NoCounters-1:0] mst_select_q; + + // counter signals + logic [NoCounters-1:0] push_en, inject_en, pop_en, occupied, cnt_full; + + //----------------------------------- + // Lookup + //----------------------------------- + assign lookup_mst_select_o = mst_select_q[lookup_axi_id_i]; + assign lookup_mst_select_occupied_o = occupied[lookup_axi_id_i]; + //----------------------------------- + // Push and Pop + //----------------------------------- + assign push_en = (push_i) ? (1 << push_axi_id_i) : '0; + assign inject_en = (inject_i) ? (1 << inject_axi_id_i) : '0; + assign pop_en = (pop_i) ? (1 << pop_axi_id_i) : '0; + assign full_o = |cnt_full; + //----------------------------------- + // Status + //----------------------------------- + assign any_outstanding_trx_o = |occupied; + + // counters + for (genvar i = 0; i < NoCounters; i++) begin : gen_counters + logic cnt_en, cnt_down, overflow; + cnt_t cnt_delta, in_flight; + always_comb begin + unique case ({push_en[i], inject_en[i], pop_en[i]}) + 3'b001 : begin // pop_i = -1 + cnt_en = 1'b1; + cnt_down = 1'b1; + cnt_delta = cnt_t'(1); + end + 3'b010 : begin // inject_i = +1 + cnt_en = 1'b1; + cnt_down = 1'b0; + cnt_delta = cnt_t'(1); + end + // 3'b011, inject_i & pop_i = 0 --> use default + 3'b100 : begin // push_i = +1 + cnt_en = 1'b1; + cnt_down = 1'b0; + cnt_delta = cnt_t'(1); + end + // 3'b101, push_i & pop_i = 0 --> use default + 3'b110 : begin // push_i & inject_i = +2 + cnt_en = 1'b1; + cnt_down = 1'b0; + cnt_delta = cnt_t'(2); + end + 3'b111 : begin // push_i & inject_i & pop_i = +1 + cnt_en = 1'b1; + cnt_down = 1'b0; + cnt_delta = cnt_t'(1); + end + default : begin // do nothing to the counters + cnt_en = 1'b0; + cnt_down = 1'b0; + cnt_delta = cnt_t'(0); + end + endcase + end + + delta_counter #( + .WIDTH ( CounterWidth ), + .STICKY_OVERFLOW ( 1'b0 ) + ) i_in_flight_cnt ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .clear_i ( 1'b0 ), + .en_i ( cnt_en ), + .load_i ( 1'b0 ), + .down_i ( cnt_down ), + .delta_i ( cnt_delta ), + .d_i ( '0 ), + .q_o ( in_flight ), + .overflow_o ( overflow ) + ); + assign occupied[i] = |in_flight; + assign cnt_full[i] = overflow | (&in_flight); + + // holds the selection signal for this id + `FFLARN(mst_select_q[i], push_mst_select_i, push_en[i], '0, clk_i, rst_ni) + +// pragma translate_off +`ifndef VERILATOR +`ifndef XSIM + // Validate parameters. + cnt_underflow: assert property( + @(posedge clk_i) disable iff (~rst_ni) (pop_en[i] |=> !overflow)) else + $fatal(1, "axi_demux_id_counters > Counter: %0d underflowed.\ + The reason is probably a faulty AXI response.", i); +`endif +`endif +// pragma translate_on + end +endmodule + diff --git a/src/axi_demux_simple.sv b/src/axi_demux_simple.sv index c7ba39f32..81e731fae 100644 --- a/src/axi_demux_simple.sv +++ b/src/axi_demux_simple.sv @@ -66,568 +66,35 @@ module axi_demux_simple #( input axi_resp_t [NoMstPorts-1:0] mst_resps_i ); - localparam int unsigned IdCounterWidth = cf_math_pkg::idx_width(MaxTrans); - typedef logic [IdCounterWidth-1:0] id_cnt_t; + logic [NoMstPorts-1:0] aw_select_mask; + + assign aw_select_mask = 1'b1 << slv_aw_select_i; + + axi_mcast_demux_simple #( + .AxiIdWidth (AxiIdWidth), + .AtopSupport (AtopSupport), + .axi_req_t (axi_req_t), + .axi_resp_t (axi_resp_t), + .NoMstPorts (NoMstPorts), + .MaxTrans (MaxTrans), + .AxiLookBits (AxiLookBits), + .UniqueIds (UniqueIds), + .NoMulticastPorts (0), + .MaxMcastTrans (1) + ) i_axi_mcast_demux_simple ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .test_i (test_i), + .slv_req_i (slv_req_i), + .slv_aw_select_i (aw_select_mask), + .slv_aw_addr_i ('0), + .slv_aw_mask_i ('0), + .slv_ar_select_i (slv_ar_select_i), + .slv_resp_o (slv_resp_o), + .mst_reqs_o (mst_reqs_o), + .mst_resps_i (mst_resps_i), + .mst_is_mcast_o (), + .mst_aw_commit_o () + ); - // pass through if only one master port - if (NoMstPorts == 32'h1) begin : gen_no_demux - `AXI_ASSIGN_REQ_STRUCT(mst_reqs_o[0], slv_req_i) - `AXI_ASSIGN_RESP_STRUCT(slv_resp_o, mst_resps_i[0]) - end else begin - - //-------------------------------------- - //-------------------------------------- - // Signal Declarations - //-------------------------------------- - //-------------------------------------- - - //-------------------------------------- - // Write Transaction - //-------------------------------------- - - // Register which locks the AW valid signal - logic lock_aw_valid_d, lock_aw_valid_q, load_aw_lock; - logic aw_valid, aw_ready; - - // AW ID counter - select_t lookup_aw_select; - logic aw_select_occupied, aw_id_cnt_full; - // Upon an ATOP load, inject IDs from the AW into the AR channel - logic atop_inject; - - // W select counter: stores the decision to which master W beats should go - select_t w_select, w_select_q; - logic w_select_valid; - id_cnt_t w_open; - logic w_cnt_up, w_cnt_down; - - // B channles input into the arbitration - logic [NoMstPorts-1:0] mst_b_valids, mst_b_readies; - - //-------------------------------------- - // Read Transaction - //-------------------------------------- - - // AR ID counter - select_t lookup_ar_select; - logic ar_select_occupied, ar_id_cnt_full; - logic ar_push; - - // Register which locks the AR valid signel - logic lock_ar_valid_d, lock_ar_valid_q, load_ar_lock; - logic ar_valid, ar_ready; - - logic [NoMstPorts-1:0] mst_r_valids, mst_r_readies; - - - - - - - - //-------------------------------------- - // Channel Control - //-------------------------------------- - //-------------------------------------- - - //-------------------------------------- - // AW Channel - //-------------------------------------- - - // Control of the AW handshake - always_comb begin - // AXI Handshakes - slv_resp_o.aw_ready = 1'b0; - aw_valid = 1'b0; - // `lock_aw_valid`, used to be protocol conform as it is not allowed to deassert - // a valid if there was no corresponding ready. As this process has to be able to inject - // an AXI ID into the counter of the AR channel on an ATOP, there could be a case where - // this process waits on `aw_ready` but in the mean time on the AR channel the counter gets - // full. - lock_aw_valid_d = lock_aw_valid_q; - load_aw_lock = 1'b0; - // AW ID counter and W FIFO - w_cnt_up = 1'b0; - // ATOP injection into ar counter - atop_inject = 1'b0; - // we had an arbitration decision, the valid is locked, wait for the transaction - if (lock_aw_valid_q) begin - aw_valid = 1'b1; - // transaction - if (aw_ready) begin - slv_resp_o.aw_ready = 1'b1; - lock_aw_valid_d = 1'b0; - load_aw_lock = 1'b1; - // inject the ATOP if necessary - atop_inject = slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; - end - end else begin - // An AW can be handled if `i_aw_id_counter` and `i_counter_open_w` 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_open != {IdCounterWidth{1'b1}}) && - (!(ar_id_cnt_full && slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP]) || - !AtopSupport)) begin - // There is a valid AW vector make the id lookup and go further, if it passes. - // Also stall if previous transmitted AWs still have active W's in flight. - // This prevents deadlocking of the W channel. The counters are there for the - // Handling of the B responses. - if (slv_req_i.aw_valid && - ((w_open == '0) || (w_select == slv_aw_select_i)) && - (!aw_select_occupied || (slv_aw_select_i == 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 - w_cnt_up = 1'b1; - // on AW transaction - if (aw_ready) begin - slv_resp_o.aw_ready = 1'b1; - atop_inject = slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; - // no AW transaction this cycle, lock the decision - end else begin - lock_aw_valid_d = 1'b1; - load_aw_lock = 1'b1; - end - end - end - end - end - - // lock the valid signal, as the selection gets pushed into the W FIFO on first assertion, - // prevent further pushing - `FFLARN(lock_aw_valid_q, lock_aw_valid_d, load_aw_lock, '0, clk_i, rst_ni) - - if (UniqueIds) begin : gen_unique_ids_aw - // If the `UniqueIds` parameter is set, each write transaction has an ID that is unique among - // all in-flight write transactions, or all write transactions with a given ID target the same - // 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_select_i; - assign aw_select_occupied = 1'b0; - assign aw_id_cnt_full = 1'b0; - end else begin : gen_aw_id_counter - axi_demux_id_counters #( - .AxiIdBits ( AxiLookBits ), - .CounterWidth ( IdCounterWidth ), - .mst_port_select_t ( select_t ) - ) i_aw_id_counter ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .lookup_axi_id_i ( slv_req_i.aw.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_req_i.aw.id[0+:AxiLookBits] ), - .push_mst_select_i ( slv_aw_select_i ), - .push_i ( w_cnt_up ), - .pop_axi_id_i ( slv_resp_o.b.id[0+:AxiLookBits] ), - .pop_i ( slv_resp_o.b_valid & slv_req_i.b_ready ) - ); - // pop from ID counter on outward transaction - end - - // This counter steers the demultiplexer of the W channel. - // `w_select` determines, which handshaking is connected. - // AWs are only forwarded, if the counter is empty, or `w_select_q` is the same as - // `slv_aw_select_i`. - counter #( - .WIDTH ( IdCounterWidth ), - .STICKY_OVERFLOW ( 1'b0 ) - ) i_counter_open_w ( - .clk_i, - .rst_ni, - .clear_i ( 1'b0 ), - .en_i ( w_cnt_up ^ w_cnt_down ), - .load_i ( 1'b0 ), - .down_i ( w_cnt_down ), - .d_i ( '0 ), - .q_o ( w_open ), - .overflow_o ( /*not used*/ ) - ); - - `FFLARN(w_select_q, slv_aw_select_i, w_cnt_up, select_t'(0), clk_i, rst_ni) - assign w_select = (|w_open) ? w_select_q : slv_aw_select_i; - assign w_select_valid = w_cnt_up | (|w_open); - - //-------------------------------------- - // W Channel - //-------------------------------------- - - //-------------------------------------- - // B Channel - //-------------------------------------- - logic [cf_math_pkg::idx_width(NoMstPorts)-1:0] b_idx; - - // Arbitration of the different B responses - rr_arb_tree #( - .NumIn ( NoMstPorts ), - .DataType ( logic ), - .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 ( '0 ), - .gnt_i ( slv_req_i.b_ready ), - .req_o ( slv_resp_o.b_valid ), - .data_o ( ), - .idx_o ( b_idx ) - ); - - always_comb begin - if (slv_resp_o.b_valid) begin - `AXI_SET_B_STRUCT(slv_resp_o.b, mst_resps_i[b_idx].b) - end else begin - slv_resp_o.b = '0; - end - end - - //-------------------------------------- - // AR Channel - //-------------------------------------- - - // control of the AR handshake - always_comb begin - // AXI Handshakes - slv_resp_o.ar_ready = 1'b0; - ar_valid = 1'b0; - // `lock_ar_valid`: Used to be protocol conform as it is not allowed to deassert `ar_valid` - // if there was no corresponding `ar_ready`. There is the possibility that an injection - // of a R response from an `atop` from the AW channel can change the occupied flag of the - // `i_ar_id_counter`, even if it was previously empty. This FF prevents the deassertion. - lock_ar_valid_d = lock_ar_valid_q; - load_ar_lock = 1'b0; - // AR id counter - ar_push = 1'b0; - // The process had an arbitration decision in a previous cycle, the valid is locked, - // wait for the AR transaction. - if (lock_ar_valid_q) begin - ar_valid = 1'b1; - // transaction - if (ar_ready) begin - slv_resp_o.ar_ready = 1'b1; - ar_push = 1'b1; - lock_ar_valid_d = 1'b0; - load_ar_lock = 1'b1; - end - end else begin - // The process can start handling AR transaction if `i_ar_id_counter` has space. - if (!ar_id_cnt_full) begin - // There is a valid AR, so look the ID up. - if (slv_req_i.ar_valid && (!ar_select_occupied || - (slv_ar_select_i == lookup_ar_select))) begin - // connect the AR handshake - ar_valid = 1'b1; - // on transaction - if (ar_ready) begin - slv_resp_o.ar_ready = 1'b1; - ar_push = 1'b1; - // no transaction this cycle, lock the valid decision! - end else begin - lock_ar_valid_d = 1'b1; - load_ar_lock = 1'b1; - end - end - end - end - end - - // this ff is needed so that ar does not get de-asserted if an atop gets injected - `FFLARN(lock_ar_valid_q, lock_ar_valid_d, load_ar_lock, '0, clk_i, rst_ni) - - if (UniqueIds) begin : gen_unique_ids_ar - // If the `UniqueIds` parameter is set, each read transaction has an ID that is unique among - // all in-flight read transactions, or all read transactions with a given ID target the same - // master port as all read 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_ar_select = slv_ar_select_i; - assign ar_select_occupied = 1'b0; - 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 ) - ) i_ar_id_counter ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .lookup_axi_id_i ( slv_req_i.ar.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_req_i.aw.id[0+:AxiLookBits] ), - .inject_i ( atop_inject ), - .push_axi_id_i ( slv_req_i.ar.id[0+:AxiLookBits] ), - .push_mst_select_i ( slv_ar_select_i ), - .push_i ( ar_push ), - .pop_axi_id_i ( slv_resp_o.r.id[0+:AxiLookBits] ), - .pop_i ( slv_resp_o.r_valid & slv_req_i.r_ready & slv_resp_o.r.last ) - ); - end - - //-------------------------------------- - // R Channel - //-------------------------------------- - - logic [cf_math_pkg::idx_width(NoMstPorts)-1:0] r_idx; - - // Arbitration of the different r responses - rr_arb_tree #( - .NumIn ( NoMstPorts ), - .DataType ( logic ), - .AxiVldRdy( 1'b1 ), - .LockIn ( 1'b1 ) - ) i_r_mux ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .flush_i( 1'b0 ), - .rr_i ( '0 ), - .req_i ( mst_r_valids ), - .gnt_o ( mst_r_readies ), - .data_i ( '0 ), - .gnt_i ( slv_req_i.r_ready ), - .req_o ( slv_resp_o.r_valid ), - .data_o (), - .idx_o ( r_idx ) - ); - - always_comb begin - if (slv_resp_o.r_valid) begin - `AXI_SET_R_STRUCT(slv_resp_o.r, mst_resps_i[r_idx].r) - end else begin - slv_resp_o.r = '0; - end - end - - assign ar_ready = ar_valid & mst_resps_i[slv_ar_select_i].ar_ready; - assign aw_ready = aw_valid & mst_resps_i[slv_aw_select_i].aw_ready; - - // process that defines the individual demuxes and assignments for the arbitration - // as mst_reqs_o has to be drivem from the same always comb block! - always_comb begin - // default assignments - mst_reqs_o = '0; - slv_resp_o.w_ready = 1'b0; - w_cnt_down = 1'b0; - - for (int unsigned i = 0; i < NoMstPorts; i++) begin - // AW channel - mst_reqs_o[i].aw = slv_req_i.aw; - mst_reqs_o[i].aw_valid = 1'b0; - if (aw_valid && (slv_aw_select_i == i)) begin - mst_reqs_o[i].aw_valid = 1'b1; - end - - // W channel - mst_reqs_o[i].w = slv_req_i.w; - mst_reqs_o[i].w_valid = 1'b0; - if (w_select_valid && (w_select == i)) begin - mst_reqs_o[i].w_valid = slv_req_i.w_valid; - slv_resp_o.w_ready = mst_resps_i[i].w_ready; - w_cnt_down = slv_req_i.w_valid & mst_resps_i[i].w_ready & slv_req_i.w.last; - end - - // B channel - mst_reqs_o[i].b_ready = mst_b_readies[i]; - - // AR channel - mst_reqs_o[i].ar = slv_req_i.ar; - mst_reqs_o[i].ar_valid = 1'b0; - if (ar_valid && (slv_ar_select_i == i)) begin - mst_reqs_o[i].ar_valid = 1'b1; - end - - // R channel - mst_reqs_o[i].r_ready = mst_r_readies[i]; - end - end - // unpack the response B and R channels for the arbitration - for (genvar i = 0; i < NoMstPorts; i++) begin : gen_b_channels - // assign mst_b_chans[i] = mst_resps_i[i].b; - assign mst_b_valids[i] = mst_resps_i[i].b_valid; - // assign mst_r_chans[i] = mst_resps_i[i].r; - assign mst_r_valids[i] = mst_resps_i[i].r_valid; - end - -// Validate parameters. -// pragma translate_off -`ifndef VERILATOR -`ifndef XSIM - initial begin: validate_params - no_mst_ports: assume (NoMstPorts > 0) else - $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."); - end - default disable iff (!rst_ni); - aw_select: assume property( @(posedge clk_i) (slv_req_i.aw_valid |-> - (slv_aw_select_i < NoMstPorts))) else - $fatal(1, "slv_aw_select_i is %d: AW has selected a slave that is not defined.\ - NoMstPorts: %d", slv_aw_select_i, NoMstPorts); - ar_select: assume property( @(posedge clk_i) (slv_req_i.ar_valid |-> - (slv_ar_select_i < NoMstPorts))) else - $fatal(1, "slv_ar_select_i is %d: AR has selected a slave that is not defined.\ - NoMstPorts: %d", slv_ar_select_i, NoMstPorts); - aw_valid_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) |=> aw_valid) else - $fatal(1, "aw_valid was deasserted, when aw_ready = 0 in last cycle."); - ar_valid_stable: assert property( @(posedge clk_i) - (ar_valid && !ar_ready) |=> ar_valid) else - $fatal(1, "ar_valid was deasserted, when ar_ready = 0 in last cycle."); - slv_aw_chan_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) - |=> $stable(slv_req_i.aw)) else - $fatal(1, "slv_aw_chan unstable with valid set."); - slv_aw_select_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) - |=> $stable(slv_aw_select_i)) else - $fatal(1, "slv_aw_select_i unstable with valid set."); - slv_ar_chan_stable: assert property( @(posedge clk_i) (ar_valid && !ar_ready) - |=> $stable(slv_req_i.ar)) else - $fatal(1, "slv_ar_chan unstable with valid set."); - slv_ar_select_stable: assert property( @(posedge clk_i) (ar_valid && !ar_ready) - |=> $stable(slv_ar_select_i)) else - $fatal(1, "slv_ar_select_i unstable with valid set."); - internal_ar_select: assert property( @(posedge clk_i) - (ar_valid |-> slv_ar_select_i < NoMstPorts)) - else $fatal(1, "slv_ar_select_i illegal while ar_valid."); - internal_aw_select: assert property( @(posedge clk_i) - (aw_valid |-> slv_aw_select_i < NoMstPorts)) - else $fatal(1, "slv_aw_select_i illegal while aw_valid."); - w_underflow: assert property( @(posedge clk_i) - ((w_open == '0) && (w_cnt_up ^ w_cnt_down) |-> !w_cnt_down)) else - $fatal(1, "W counter underflowed!"); - `ASSUME(NoAtopAllowed, !AtopSupport && slv_req_i.aw_valid |-> slv_req_i.aw.atop == '0) -`endif -`endif -// pragma translate_on - end endmodule - - -module axi_demux_id_counters #( - // the lower bits of the AXI ID that should be considered, results in 2**AXI_ID_BITS counters - parameter int unsigned AxiIdBits = 2, - parameter int unsigned CounterWidth = 4, - parameter type mst_port_select_t = logic -) ( - input logic clk_i, // Clock - input logic rst_ni, // Asynchronous reset active low - // lookup - input logic [AxiIdBits-1:0] lookup_axi_id_i, - output mst_port_select_t lookup_mst_select_o, - output logic lookup_mst_select_occupied_o, - // push - output logic full_o, - input logic [AxiIdBits-1:0] push_axi_id_i, - input mst_port_select_t push_mst_select_i, - input logic push_i, - // inject ATOPs in AR channel - input logic [AxiIdBits-1:0] inject_axi_id_i, - input logic inject_i, - // pop - input logic [AxiIdBits-1:0] pop_axi_id_i, - input logic pop_i -); - localparam int unsigned NoCounters = 2**AxiIdBits; - typedef logic [CounterWidth-1:0] cnt_t; - - // registers, each gets loaded when push_en[i] - mst_port_select_t [NoCounters-1:0] mst_select_q; - - // counter signals - logic [NoCounters-1:0] push_en, inject_en, pop_en, occupied, cnt_full; - - //----------------------------------- - // Lookup - //----------------------------------- - assign lookup_mst_select_o = mst_select_q[lookup_axi_id_i]; - assign lookup_mst_select_occupied_o = occupied[lookup_axi_id_i]; - //----------------------------------- - // Push and Pop - //----------------------------------- - assign push_en = (push_i) ? (1 << push_axi_id_i) : '0; - assign inject_en = (inject_i) ? (1 << inject_axi_id_i) : '0; - assign pop_en = (pop_i) ? (1 << pop_axi_id_i) : '0; - assign full_o = |cnt_full; - // counters - for (genvar i = 0; i < NoCounters; i++) begin : gen_counters - logic cnt_en, cnt_down, overflow; - cnt_t cnt_delta, in_flight; - always_comb begin - unique case ({push_en[i], inject_en[i], pop_en[i]}) - 3'b001 : begin // pop_i = -1 - cnt_en = 1'b1; - cnt_down = 1'b1; - cnt_delta = cnt_t'(1); - end - 3'b010 : begin // inject_i = +1 - cnt_en = 1'b1; - cnt_down = 1'b0; - cnt_delta = cnt_t'(1); - end - // 3'b011, inject_i & pop_i = 0 --> use default - 3'b100 : begin // push_i = +1 - cnt_en = 1'b1; - cnt_down = 1'b0; - cnt_delta = cnt_t'(1); - end - // 3'b101, push_i & pop_i = 0 --> use default - 3'b110 : begin // push_i & inject_i = +2 - cnt_en = 1'b1; - cnt_down = 1'b0; - cnt_delta = cnt_t'(2); - end - 3'b111 : begin // push_i & inject_i & pop_i = +1 - cnt_en = 1'b1; - cnt_down = 1'b0; - cnt_delta = cnt_t'(1); - end - default : begin // do nothing to the counters - cnt_en = 1'b0; - cnt_down = 1'b0; - cnt_delta = cnt_t'(0); - end - endcase - end - - delta_counter #( - .WIDTH ( CounterWidth ), - .STICKY_OVERFLOW ( 1'b0 ) - ) i_in_flight_cnt ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .clear_i ( 1'b0 ), - .en_i ( cnt_en ), - .load_i ( 1'b0 ), - .down_i ( cnt_down ), - .delta_i ( cnt_delta ), - .d_i ( '0 ), - .q_o ( in_flight ), - .overflow_o ( overflow ) - ); - assign occupied[i] = |in_flight; - assign cnt_full[i] = overflow | (&in_flight); - - // holds the selection signal for this id - `FFLARN(mst_select_q[i], push_mst_select_i, push_en[i], '0, clk_i, rst_ni) - -// pragma translate_off -`ifndef VERILATOR -`ifndef XSIM - // Validate parameters. - cnt_underflow: assert property( - @(posedge clk_i) disable iff (~rst_ni) (pop_en[i] |=> !overflow)) else - $fatal(1, "axi_demux_id_counters > Counter: %0d underflowed.\ - The reason is probably a faulty AXI response.", i); -`endif -`endif -// pragma translate_on - end -endmodule - diff --git a/src/axi_mcast_demux_mapped.sv b/src/axi_mcast_demux_mapped.sv new file mode 100644 index 000000000..c4d1f105d --- /dev/null +++ b/src/axi_mcast_demux_mapped.sv @@ -0,0 +1,547 @@ +// 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: +// - Michael Rogenmoser +// - Wolfgang Roenninger +// - Thomas Benz +// - Andreas Kurth +// - Luca Colagrande + +`include "common_cells/assertions.svh" +`include "common_cells/registers.svh" + +`ifdef QUESTA +// Derive `TARGET_VSIM`, which is used for tool-specific workarounds in this file, from `QUESTA`, +// which is automatically set in Questa. +`define TARGET_VSIM +`endif + +/// Demultiplex one AXI4+ATOP slave port to multiple AXI4+ATOP master ports. +/// +/// The module internally decodes Ax requests, determining the master port route based on the +/// address of the request. To this end an address map, and additional inputs required by the +/// address decoding modules, are provided. +/// +/// ## Design overview +/// +/// ![Block diagram](module.axi_demux.png "Block diagram") +/// +/// Beats on the W channel are routed by demultiplexer according to the selection for the +/// corresponding AW beat. This relies on the AXI property that W bursts must be sent in the same +/// order as AW beats and beats from different W bursts may not be interleaved. +/// +/// Beats on the B and R channel are multiplexed from the master ports to the slave port with +/// a round-robin arbitration tree. +module axi_mcast_demux_mapped #( + parameter int unsigned AxiIdWidth = 32'd0, + parameter int unsigned AxiAddrWidth = 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 SpillAw = 1'b1, + parameter bit SpillW = 1'b0, + parameter bit SpillB = 1'b0, + parameter bit SpillAr = 1'b1, + parameter bit SpillR = 1'b0, + parameter bit [NoMstPorts-1:0] Connectivity = '1, + parameter bit [NoMstPorts-1:0] MulticastConnectivity = '1, + parameter type rule_t = logic, + parameter int unsigned NoAddrRules = 32'd0, + parameter int unsigned NoMulticastRules = 32'd0, + parameter int unsigned NoMulticastPorts = 32'd0, + parameter int unsigned MaxMcastTrans = 32'd7 +) ( + input logic clk_i, + input logic rst_ni, + input logic test_i, + // Addressing rules + input rule_t [NoAddrRules-1:0] addr_map_i, + input logic en_default_mst_port_i, + input rule_t default_mst_port_i, + // Slave Port + input axi_req_t slv_req_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 logic [NoMstPorts-1:0] mst_is_mcast_o, + output logic [NoMstPorts-1:0] mst_aw_commit_o +); + + // Account for additional error slave + localparam int unsigned NoMstPortsExt = NoMstPorts + 1; + + localparam int unsigned IdxSelectWidth = cf_math_pkg::idx_width(NoMstPorts); + localparam int unsigned IdxSelectWidthExt = cf_math_pkg::idx_width(NoMstPortsExt); + typedef logic [IdxSelectWidth-1:0] idx_select_t; + typedef logic [IdxSelectWidthExt-1:0] idx_select_ext_t; + + typedef logic [NoMstPortsExt-1:0] mask_select_t; + + typedef logic [AxiAddrWidth-1:0] addr_t; + + typedef struct packed { + int unsigned idx; + addr_t addr; + addr_t mask; + } mask_rule_t; + + // ---------------- + // Spill registers + // ---------------- + + axi_req_t slv_req_cut; + axi_resp_t slv_resp_cut; + + spill_register #( + .T ( aw_chan_t ), + .Bypass ( ~SpillAw ) + ) i_aw_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.aw_valid ), + .ready_o ( slv_resp_o.aw_ready ), + .data_i ( slv_req_i.aw ), + .valid_o ( slv_req_cut.aw_valid ), + .ready_i ( slv_resp_cut.aw_ready ), + .data_o ( slv_req_cut.aw ) + ); + spill_register #( + .T ( w_chan_t ), + .Bypass ( ~SpillW ) + ) i_w_spill_reg ( + .clk_i, + .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_req_cut.w_valid ), + .ready_i ( slv_resp_cut.w_ready ), + .data_o ( slv_req_cut.w ) + ); + spill_register #( + .T ( ar_chan_t ), + .Bypass ( ~SpillAr ) + ) i_ar_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.ar_valid ), + .ready_o ( slv_resp_o.ar_ready ), + .data_i ( slv_req_i.ar ), + .valid_o ( slv_req_cut.ar_valid ), + .ready_i ( slv_resp_cut.ar_ready ), + .data_o ( slv_req_cut.ar ) + ); + spill_register #( + .T ( b_chan_t ), + .Bypass ( ~SpillB ) + ) i_b_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_resp_cut.b_valid ), + .ready_o ( slv_req_cut.b_ready ), + .data_i ( slv_resp_cut.b ), + .valid_o ( slv_resp_o.b_valid ), + .ready_i ( slv_req_i.b_ready ), + .data_o ( slv_resp_o.b ) + ); + spill_register #( + .T ( r_chan_t ), + .Bypass ( ~SpillR ) + ) i_r_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_resp_cut.r_valid ), + .ready_o ( slv_req_cut.r_ready ), + .data_i ( slv_resp_cut.r ), + .valid_o ( slv_resp_o.r_valid ), + .ready_i ( slv_req_i.r_ready ), + .data_o ( slv_resp_o.r ) + ); + + // ----------------- + // AR address decoding + // ----------------- + + idx_select_t dec_ar_select_idx; + logic dec_ar_error; + idx_select_ext_t ar_select_idx; + + // Address decoding for unicast requests + addr_decode #( + .NoIndices (NoMstPorts), + .NoRules (NoAddrRules), + .addr_t (addr_t), + .rule_t (rule_t) + ) i_axi_ar_decode ( + .addr_i (slv_req_cut.ar.addr), + .addr_map_i (addr_map_i), + .idx_o (dec_ar_select_idx), + .dec_valid_o (), + .dec_error_o (dec_ar_error), + .en_default_idx_i (en_default_mst_port_i), + .default_idx_i (idx_select_t'(default_mst_port_i.idx)) + ); + + assign ar_select_idx = dec_ar_error ? NoMstPorts : idx_select_ext_t'(dec_ar_select_idx); + + // ----------------- + // AW address decoding + // ----------------- + + // AW decoder inputs + mask_rule_t [axi_pkg::iomsb(NoMulticastRules):0] multicast_rules; + mask_rule_t default_rule; + + // AW unicast decoder outputs + idx_select_t dec_aw_unicast_select_idx; + logic [NoMstPorts-1:0] dec_aw_unicast_select_mask; + logic dec_aw_unicast_valid; + logic dec_aw_unicast_error; + + // AW multicast decoder outputs + logic [NoMulticastPorts-1:0] dec_aw_multicast_select_mask; + addr_t [NoMulticastPorts-1:0] dec_aw_multicast_addr; + addr_t [NoMulticastPorts-1:0] dec_aw_multicast_mask; + logic dec_aw_multicast_valid; + logic dec_aw_multicast_error; + + // Decoding outputs (merged from unicast and multicast paths) + mask_select_t dec_aw_select_mask; + addr_t [NoMstPortsExt-1:0] dec_aw_addr; + addr_t [NoMstPortsExt-1:0] dec_aw_mask; + + // Address decoding for unicast requests + addr_decode #( + .NoIndices (NoMstPorts), + .NoRules (NoAddrRules), + .addr_t (addr_t), + .rule_t (rule_t) + ) i_axi_aw_unicast_decode ( + .addr_i (slv_req_cut.aw.addr), + .addr_map_i (addr_map_i), + .idx_o (dec_aw_unicast_select_idx), + .dec_valid_o (dec_aw_unicast_valid), + .dec_error_o (dec_aw_unicast_error), + .en_default_idx_i (en_default_mst_port_i), + .default_idx_i (idx_select_t'(default_mst_port_i.idx)) + ); + + // Generate the output mask from the index + assign dec_aw_unicast_select_mask = 1'b1 << dec_aw_unicast_select_idx; + + // If the address decoding doesn't produce any match, the request + // is routed to the error slave, which lies at the highest index. + mask_select_t select_error_slave; + assign select_error_slave = 1'b1 << NoMstPorts; + + // Disable multicast only if NoMulticastPorts == 0. In some instances you may want to + // match the multicast decoder's default port, even if NoMulticastRules == 0. + if (NoMulticastPorts > 0) begin : gen_multicast + + // Convert multicast rules to mask (NAPOT) form, see https://arxiv.org/pdf/2502.19215 + for (genvar i = 0; i < NoMulticastRules; i++) begin : gen_multicast_rules + assign multicast_rules[i].idx = addr_map_i[i].idx; + assign multicast_rules[i].mask = addr_map_i[i].end_addr - addr_map_i[i].start_addr - 1; + assign multicast_rules[i].addr = addr_map_i[i].start_addr; + end + assign default_rule.idx = default_mst_port_i.idx; + assign default_rule.mask = default_mst_port_i.end_addr - default_mst_port_i.start_addr - 1; + assign default_rule.addr = default_mst_port_i.start_addr; + + if (NoMulticastRules > 0) begin : gen_multiaddr_decode + // Address decoding for multicast requests. + multiaddr_decode #( + .NoIndices (NoMulticastPorts), + .NoRules (NoMulticastRules), + .addr_t (addr_t), + .rule_t (mask_rule_t) + ) i_axi_aw_multicast_decode ( + .addr_map_i (multicast_rules), + .addr_i (slv_req_cut.aw.addr), + .mask_i (slv_req_cut.aw.user.collective_mask), + .select_o (dec_aw_multicast_select_mask), + .addr_o (dec_aw_multicast_addr), + .mask_o (dec_aw_multicast_mask), + .dec_valid_o (dec_aw_multicast_valid), + .dec_error_o (dec_aw_multicast_error), + .en_default_idx_i (en_default_mst_port_i), + .default_idx_i (default_rule) + ); + end else begin : gen_no_multiaddr_decode + assign dec_aw_multicast_select_mask = 1'b1 << default_rule.idx; + assign dec_aw_multicast_addr = slv_req_cut.aw.addr; + assign dec_aw_multicast_mask = slv_req_cut.aw.user.collective_mask; + assign dec_aw_multicast_valid = 1'b1; + assign dec_aw_multicast_error = 1'b0; + end + + // Mux the multicast and unicast decoding outputs. + always_comb begin + dec_aw_select_mask = '0; + dec_aw_addr = '0; + dec_aw_mask = '0; + + if (slv_req_cut.aw.user.collective_mask == '0) begin + dec_aw_addr = {'0, {NoMstPorts{slv_req_cut.aw.addr}}}; + if (dec_aw_unicast_error) begin + dec_aw_select_mask = select_error_slave; + end else begin + dec_aw_select_mask = dec_aw_unicast_select_mask & Connectivity; + end + end else begin + dec_aw_addr = {'0, {(NoMstPorts-NoMulticastPorts){slv_req_cut.aw.addr}}, dec_aw_multicast_addr}; + dec_aw_mask = {'0, dec_aw_multicast_mask}; + if (dec_aw_multicast_error) begin + dec_aw_select_mask = select_error_slave; + end else begin + dec_aw_select_mask = {'0, dec_aw_multicast_select_mask} & MulticastConnectivity; + end + end + end + + end else begin : gen_no_multicast + assign dec_aw_addr = {'0, {NoMstPorts{slv_req_cut.aw.addr}}}; + assign dec_aw_mask = '0; + assign dec_aw_select_mask = (dec_aw_unicast_error) ? select_error_slave : + (dec_aw_unicast_select_mask & Connectivity); + end + + // ----------------- + // Demux + // ----------------- + + axi_req_t errslv_req; + axi_resp_t errslv_resp; + logic errslv_is_mcast; + logic errslv_aw_commit; + + axi_mcast_demux_simple #( + .AxiIdWidth ( AxiIdWidth ), + .AtopSupport ( AtopSupport ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .NoMstPorts ( NoMstPortsExt ), + .MaxTrans ( MaxTrans ), + .AxiLookBits ( AxiLookBits ), + .UniqueIds ( UniqueIds ), + .aw_addr_t ( addr_t ), + .NoMulticastPorts ( NoMulticastPorts ), + .MaxMcastTrans ( MaxMcastTrans ) + ) i_mcast_demux_simple ( + .clk_i, + .rst_ni, + .test_i, + .slv_req_i ( slv_req_cut ), + .slv_aw_select_i ( dec_aw_select_mask ), + .slv_aw_addr_i ( dec_aw_addr ), + .slv_aw_mask_i ( dec_aw_mask ), + .slv_ar_select_i ( ar_select_idx ), + .slv_resp_o ( slv_resp_cut ), + .mst_reqs_o ( {errslv_req, mst_reqs_o} ), + .mst_resps_i ( {errslv_resp, mst_resps_i} ), + .mst_is_mcast_o ( {errslv_is_mcast, mst_is_mcast_o} ), + .mst_aw_commit_o ( {errslv_aw_commit, mst_aw_commit_o} ) + ); + + axi_err_slv #( + .AxiIdWidth ( AxiIdWidth ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .Resp ( axi_pkg::RESP_DECERR ), + .ATOPs ( AtopSupport ), + // Transactions terminate at this slave, so minimize resource consumption by accepting + // only a few transactions at a time. + .MaxTrans ( 4 ) + ) i_axi_err_slv ( + .clk_i, + .rst_ni, + .test_i, + .slv_req_i ( errslv_req ), + .slv_resp_o ( errslv_resp ) + ); + + // ----------------- + // Assertions + // ----------------- + +// pragma translate_off +`ifndef VERILATOR +`ifndef XSIM + + // Check that multicast address map rules expressed in interval form can be converted to + // mask form, see https://arxiv.org/pdf/2502.19215 + for (genvar i = 0; i < NoMulticastRules; i++) begin : gen_multicast_rule_assertion + addr_t size; + assign size = addr_map_i[i].end_addr - addr_map_i[i].start_addr; + `ASSERT(MulticastRuleSize, + ((size & (size - 1)) == 0), clk_i, !rst_ni, + $sformatf("Size %0d of rule %0d is not a power of 2", size, i)) + `ASSERT(MulticastRuleAlignment, + (addr_map_i[i].start_addr % size) == 0, clk_i, !rst_ni, + $sformatf("Rule %0d, starting at 0x%0x, is not aligned to its size (%0d)", + i, addr_map_i[i].start_addr, size)) + end + // Default rule is only converted to mask form if there are any other multicast rules + if (NoMulticastPorts > 0) begin : gen_multicast_default_rule_assertion + addr_t size; + assign size = default_mst_port_i.end_addr - default_mst_port_i.start_addr; + `ASSERT(DefaultRuleSize, + !en_default_mst_port_i || ((size & (size - 1)) == 0), clk_i, !rst_ni, + $sformatf("Size %0d of default rule is not a power of 2", size)) + `ASSERT(DefaultRuleAlignment, + !en_default_mst_port_i || ((default_mst_port_i.start_addr % size) == 0), clk_i, !rst_ni, + $sformatf("Default rule, starting at 0x%0x, is not aligned to its size (%0d)", + default_mst_port_i.start_addr, size)) + end + + // Check that addrmap and default slave do not change while there is an unserved Ax + `ASSERT(default_mst_port_en_aw_stable, + (slv_req_cut.aw_valid && !slv_resp_cut.aw_ready) |=> $stable(en_default_mst_port_i), + clk_i, !rst_ni, + "It is not allowed to change the default mst port enable when there is an unserved Aw beat.") + `ASSERT(default_mst_port_aw_stable, + (slv_req_cut.aw_valid && !slv_resp_cut.aw_ready) |=> $stable(default_mst_port_i), + clk_i, !rst_ni, + "It is not allowed to change the default mst port when there is an unserved Aw beat.") + `ASSERT(addrmap_aw_stable, + (slv_req_cut.aw_valid && !slv_resp_cut.aw_ready) |=> $stable(addr_map_i), + clk_i, !rst_ni, + "It is not allowed to change the address map when there is an unserved Aw beat.") + `ASSERT(default_mst_port_en_ar_stable, + (slv_req_cut.ar_valid && !slv_resp_cut.ar_ready) |=> $stable(en_default_mst_port_i), + clk_i, !rst_ni, + "It is not allowed to change the default mst port enable when there is an unserved Ar beat.") + `ASSERT(default_mst_port_ar_stable, + (slv_req_cut.ar_valid && !slv_resp_cut.ar_ready) |=> $stable(default_mst_port_i), + clk_i, !rst_ni, + "It is not allowed to change the default mst port when there is an unserved Ar beat.") + `ASSERT(addrmap_ar_stable, + (slv_req_cut.ar_valid && !slv_resp_cut.ar_ready) |=> $stable(addr_map_i), + clk_i, !rst_ni, + "It is not allowed to change the address map when there is an unserved Ar beat.") + +`endif +`endif +// pragma translate_on + +endmodule + +// interface wrapper +`include "axi/assign.svh" +`include "axi/typedef.svh" +module axi_mcast_demux_intf #( + parameter int unsigned AXI_ID_WIDTH = 32'd0, // Synopsys DC requires default value for params + parameter bit ATOP_SUPPORT = 1'b1, + parameter int unsigned AXI_ADDR_WIDTH = 32'd0, + parameter int unsigned AXI_DATA_WIDTH = 32'd0, + parameter int unsigned AXI_USER_WIDTH = 32'd0, + parameter int unsigned NO_MST_PORTS = 32'd3, + parameter int unsigned MAX_TRANS = 32'd8, + parameter int unsigned AXI_LOOK_BITS = 32'd3, + parameter bit UNIQUE_IDS = 1'b0, + parameter bit SPILL_AW = 1'b1, + parameter bit SPILL_W = 1'b0, + parameter bit SPILL_B = 1'b0, + parameter bit SPILL_AR = 1'b1, + parameter bit SPILL_R = 1'b0, + parameter type rule_t = logic, + // Dependent parameters, DO NOT OVERRIDE! + parameter int unsigned SELECT_WIDTH = (NO_MST_PORTS > 32'd1) ? $clog2(NO_MST_PORTS) : 32'd1, + parameter type idx_select_t = logic [SELECT_WIDTH-1:0] // MST port select type +) ( + input logic clk_i, // Clock + input logic rst_ni, // Asynchronous reset active low + input logic test_i, // Testmode enable + input rule_t [NO_MST_PORTS-2:0] addr_map_i, + input idx_select_t slv_ar_select_i, // has to be stable, when ar_valid + input logic en_default_mst_port_i, + input rule_t default_mst_port_i, + AXI_BUS.Slave slv, // slave port + AXI_BUS.Master mst [NO_MST_PORTS-1:0], // master ports + output logic [NO_MST_PORTS-1:0] mst_is_mcast_o, + output logic [NO_MST_PORTS-1:0] mst_aw_commit_o +); + + typedef logic [AXI_ID_WIDTH-1:0] id_t; + typedef logic [AXI_ADDR_WIDTH-1:0] addr_t; + typedef logic [AXI_DATA_WIDTH-1:0] data_t; + typedef logic [AXI_DATA_WIDTH/8-1:0] strb_t; + typedef logic [AXI_USER_WIDTH-1:0] user_t; + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_REQ_T(axi_req_t, aw_chan_t, w_chan_t, ar_chan_t) + `AXI_TYPEDEF_RESP_T(axi_resp_t, b_chan_t, r_chan_t) + + axi_req_t slv_req; + axi_resp_t slv_resp; + axi_req_t [NO_MST_PORTS-1:0] mst_req; + axi_resp_t [NO_MST_PORTS-1:0] mst_resp; + + `AXI_ASSIGN_TO_REQ(slv_req, slv) + `AXI_ASSIGN_FROM_RESP(slv, slv_resp) + + for (genvar i = 0; i < NO_MST_PORTS; i++) begin : gen_assign_mst_ports + `AXI_ASSIGN_FROM_REQ(mst[i], mst_req[i]) + `AXI_ASSIGN_TO_RESP(mst_resp[i], mst[i]) + end + + axi_mcast_demux #( + .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 ), + .SpillAw ( SPILL_AW ), + .SpillW ( SPILL_W ), + .SpillB ( SPILL_B ), + .SpillAr ( SPILL_AR ), + .SpillR ( SPILL_R ), + .rule_t ( rule_t ) + ) i_axi_demux ( + .clk_i, // Clock + .rst_ni, // Asynchronous reset active low + .test_i, // Testmode enable + .addr_map_i ( addr_map_i ), + .en_default_mst_port_i ( en_default_mst_port_i ), + .default_mst_port_i ( default_mst_port_i ), + // slave port + .slv_req_i ( slv_req ), + .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_is_mcast_o ( mst_is_mcast_o ), + .mst_aw_commit_o ( mst_aw_commit_o ) + ); +endmodule diff --git a/src/axi_mcast_demux_simple.sv b/src/axi_mcast_demux_simple.sv new file mode 100644 index 000000000..c88e00e05 --- /dev/null +++ b/src/axi_mcast_demux_simple.sv @@ -0,0 +1,739 @@ +// 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: +// - Wolfgang Roenninger +// - Michael Rogenmoser +// - Thomas Benz +// - Andreas Kurth +// - Luca Colagrande + +`include "common_cells/assertions.svh" +`include "common_cells/registers.svh" +`include "axi/assign.svh" + +`ifdef QUESTA +// Derive `TARGET_VSIM`, which is used for tool-specific workarounds in this file, from `QUESTA`, +// which is automatically set in Questa. +`define TARGET_VSIM +`endif + +/// Demultiplex one AXI4+ATOP slave port to multiple AXI4+ATOP master ports. +/// +/// The AW and AR slave channels each have a `select` input to determine to which master port the +/// current request is sent. The `select` can, for example, be driven by an address decoding module +/// to map address ranges to different AXI slaves. +/// +/// ## Design overview +/// +/// ![Block diagram](module.axi_demux.png "Block diagram") +/// +/// Beats on the W channel are routed by demultiplexer according to the selection for the +/// corresponding AW beat. This relies on the AXI property that W bursts must be sent in the same +/// order as AW beats and beats from different W bursts may not be interleaved. +/// +/// Beats on the B and R channel are multiplexed from the master ports to the slave port with +/// a round-robin arbitration tree. +module axi_mcast_demux_simple #( + parameter int unsigned AxiIdWidth = 32'd0, + parameter bit AtopSupport = 1'b1, + 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 type aw_addr_t = logic, + parameter int unsigned NoMulticastPorts = 32'd0, + parameter int unsigned MaxMcastTrans = 32'd7, + // Dependent parameters, DO NOT OVERRIDE! + parameter int unsigned IdxSelectWidth = (NoMstPorts > 32'd1) ? $clog2(NoMstPorts) : 32'd1, + parameter type idx_select_t = logic [IdxSelectWidth-1:0], + parameter type mask_select_t = logic [NoMstPorts-1:0] +) ( + input logic clk_i, + input logic rst_ni, + input logic test_i, + // Slave Port + input axi_req_t slv_req_i, + input mask_select_t slv_aw_select_i, + input aw_addr_t [NoMstPorts-1:0] slv_aw_addr_i, + input aw_addr_t [NoMstPorts-1:0] slv_aw_mask_i, + input idx_select_t slv_ar_select_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 logic [NoMstPorts-1:0] mst_is_mcast_o, + output logic [NoMstPorts-1:0] mst_aw_commit_o +); + + localparam int unsigned IdCounterWidth = cf_math_pkg::idx_width(MaxTrans); + typedef logic [IdCounterWidth-1:0] id_cnt_t; + + localparam int unsigned McastCounterWidth = (MaxMcastTrans > 32'd1) ? $clog2(MaxMcastTrans+1) : 32'd1; + typedef logic [McastCounterWidth-1:0] mcast_cnt_t; + + // pass through if only one master port + if (NoMstPorts == 32'h1) begin : gen_no_demux + `AXI_ASSIGN_REQ_STRUCT(mst_reqs_o[0], slv_req_i) + `AXI_ASSIGN_RESP_STRUCT(slv_resp_o, mst_resps_i[0]) + end else begin : gen_demux + + //-------------------------------------- + //-------------------------------------- + // Signal Declarations + //-------------------------------------- + //-------------------------------------- + + //-------------------------------------- + // Write Transaction + //-------------------------------------- + + // Index-form AW select signal for unicast requests + idx_select_t slv_aw_select; + + // AW channel to slave ports + logic [NoMstPorts-1:0] mst_aw_valids, mst_aw_readies; + + // Multicast control signals + logic aw_is_multicast; + logic outstanding_multicast; + logic multicast_stall; + mask_select_t multicast_select_q, multicast_select_d; + mcast_cnt_t outstanding_mcast_cnt_q, outstanding_mcast_cnt_d; + logic [$clog2(NoMstPorts)+1-1:0] aw_select_popcount; + logic accept_aw; + logic mcast_aw_hs_in_progress; + + // Register which locks the AW valid signal + logic lock_aw_valid_d, lock_aw_valid_q, load_aw_lock; + logic aw_valid, aw_ready; + + // AW ID counter + idx_select_t lookup_aw_select; + logic aw_select_occupied, aw_id_cnt_full; + logic aw_any_outstanding_unicast_trx; + // Upon an ATOP load, inject IDs from the AW into the AR channel + logic atop_inject; + + // W select counter: stores the decision to which master(s) W beats should go + logic [NoMstPorts-1:0] mst_w_valids, mst_w_readies; + mask_select_t w_select, w_select_q; + logic w_select_valid; + id_cnt_t w_open; + logic w_cnt_up, w_cnt_down; + + // B channels input into the arbitration (unicast transactions) + // or join module (multicast transactions) + axi_pkg::resp_t [NoMstPorts-1:0] mst_b_resps; + idx_select_t mst_b_valids_tzc; + logic [NoMstPorts-1:0] mst_b_valids, mst_b_readies; + logic [NoMstPorts-1:0] mst_b_readies_arb, mst_b_readies_join; + + // B channel to spill register + logic slv_b_valid_arb, slv_b_valid_join; + + //-------------------------------------- + // Read Transaction + //-------------------------------------- + + // AR ID counter + idx_select_t lookup_ar_select; + logic ar_select_occupied, ar_id_cnt_full; + logic ar_push; + + // Register which locks the AR valid signel + logic lock_ar_valid_d, lock_ar_valid_q, load_ar_lock; + logic ar_valid, ar_ready; + + logic [NoMstPorts-1:0] mst_r_valids, mst_r_readies; + + + + + + + + //-------------------------------------- + // Channel Control + //-------------------------------------- + //-------------------------------------- + + //-------------------------------------- + // AW Channel + //-------------------------------------- + + // Control of the AW handshake + always_comb begin + // AXI Handshakes + slv_resp_o.aw_ready = 1'b0; + aw_valid = 1'b0; + // `lock_aw_valid`, used to be protocol conform as it is not allowed to deassert + // a valid if there was no corresponding ready. As this process has to be able to inject + // an AXI ID into the counter of the AR channel on an ATOP, there could be a case where + // this process waits on `aw_ready` but in the mean time on the AR channel the counter gets + // full. + lock_aw_valid_d = lock_aw_valid_q; + load_aw_lock = 1'b0; + // AW ID counter and W FIFO + w_cnt_up = 1'b0; + // ATOP injection into ar counter + atop_inject = 1'b0; + // we had an arbitration decision, the valid is locked, wait for the transaction + if (lock_aw_valid_q) begin + aw_valid = 1'b1; + // transaction + if (aw_ready) begin + slv_resp_o.aw_ready = 1'b1; + lock_aw_valid_d = 1'b0; + load_aw_lock = 1'b1; + // inject the ATOP if necessary + atop_inject = slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; + end + end else begin + // An AW can be handled if `i_aw_id_counter` and `i_counter_open_w` 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_open != {IdCounterWidth{1'b1}}) && + (!(ar_id_cnt_full && slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP]) || + !AtopSupport)) begin + // There is a valid AW vector make the id lookup and go further, if it passes. + // Also stall if previous transmitted AWs still have active W's in flight. + // This prevents deadlocking of the W channel. The counters are there for the + // Handling of the B responses. + if (slv_req_i.aw_valid && + ((w_open == '0) || (w_select == slv_aw_select_i)) && + (!aw_select_occupied || (slv_aw_select == lookup_aw_select)) && + !multicast_stall) begin + // connect the handshake + aw_valid = 1'b1; + // push arbitration to the W FIFO regardless, do not wait for the AW transaction + w_cnt_up = 1'b1; + // on AW transaction + if (aw_ready) begin + slv_resp_o.aw_ready = 1'b1; + atop_inject = slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; + // no AW transaction this cycle, lock the decision + end else begin + lock_aw_valid_d = 1'b1; + load_aw_lock = 1'b1; + end + end + end + end + end + + // lock the valid signal, as the selection gets pushed into the W FIFO on first assertion, + // prevent further pushing + `FFLARN(lock_aw_valid_q, lock_aw_valid_d, load_aw_lock, '0, clk_i, rst_ni) + + //-------------------------------------- + // Multicast logic + //-------------------------------------- + + // Convert AW select mask to select index since the indices + // are smaller than the masks and thus cheaper to store in + // the ID counters. We anyways don't use the ID counters on + // multicast transactions... + + onehot_to_bin #( + .ONEHOT_WIDTH(NoMstPorts) + ) i_onehot_to_bin ( + .onehot(slv_aw_select_i & {NoMstPorts{!aw_is_multicast}}), + .bin (slv_aw_select) + ); + + // Popcount to identify multicast requests + popcount #(NoMstPorts) i_aw_select_popcount ( + .data_i (slv_aw_select_i), + .popcount_o(aw_select_popcount) + ); + + // While there can be multiple outstanding write transactions, i.e. + // multiple AWs can be accepted before the corresponding Bs are returned, + // in the case of multicast transactions this would require the need + // for additional (possibly expensive) hardware. + // The reason is that multicast transactions require merging multiple B + // responses arriving on different master ports. To know which master ports + // need to be merged we need to register the select signal of the + // write transaction. And if we allow multiple outstanding transactions + // we need to register the select signal of each, and we need a big + // associative (by ID) table for this. And actually this still allows + // deadlocks to occur, e.g. if two multicast transactions A and B are partially + // reordered, i.e. some masters respond to A first and some to B. + // If we restrict the outstanding transactions to a single ID then ordering is + // guaranteed, but we anyways need a FIFO to store the selects. + // So for the moment we disallow multiple outstanding transactions in presence + // of a multicast. This means stall an AW request if: + // - there is an outstanding multicast transaction or + // - if the request is a multicast, until there are no more outstanding transactions + // We can slightly loosen this constraint, in the case of successive multicast + // requests going to the same slaves. In this case, we don't need to buffer any + // additional select signals. + assign aw_is_multicast = aw_select_popcount > 1; + assign outstanding_multicast = outstanding_mcast_cnt_q != '0; + assign multicast_stall = (outstanding_multicast && (slv_aw_select_i != multicast_select_q)) || + (aw_is_multicast && aw_any_outstanding_unicast_trx) || + (outstanding_mcast_cnt_q == MaxMcastTrans); + // We can send this signal to all slaves since we will only have one outstanding aw + assign mst_is_mcast_o = {NoMstPorts{aw_is_multicast}}; + + // Keep track of which B responses need to be returned to complete the multicast + `FFARN(multicast_select_q, multicast_select_d, '0, clk_i, rst_ni) + `FFARN(outstanding_mcast_cnt_q, outstanding_mcast_cnt_d, '0, clk_i, rst_ni) + + // Logic to update number of outstanding multicast transactions and current multicast + // transactions' select mask. Counter is incremented upon the AW handshake of a multicast + // transaction and decremented upon the "joined" B handshake. + always_comb begin + multicast_select_d = multicast_select_q; + outstanding_mcast_cnt_d = outstanding_mcast_cnt_q; + + // Written as separate if statements as they may both be valid at the same time + // For the same reason the right hand side uses outstanding_mcast_cnt_d + // instead of outstanding_mcast_cnt_q + if (aw_is_multicast && aw_valid && aw_ready) begin + outstanding_mcast_cnt_d = outstanding_mcast_cnt_d + (|slv_aw_select_i); + multicast_select_d = slv_aw_select_i; + end + if (outstanding_multicast && slv_resp_o.b_valid && slv_req_i.b_ready) begin + outstanding_mcast_cnt_d = outstanding_mcast_cnt_d - 1; + end + end + + // Multicast AW transaction handshake state + typedef enum logic {MCastAwHandshakeIdle, MCastAwHandshakeInProgress} mcast_aw_hs_state_e; + mcast_aw_hs_state_e mcast_aw_hs_state_q, mcast_aw_hs_state_d; + + // Update of the multicast AW handshake state + always_comb begin + mcast_aw_hs_state_d = mcast_aw_hs_state_q; + unique case (mcast_aw_hs_state_q) + // Waiting for all selected master ports to be ready + MCastAwHandshakeIdle: + if (accept_aw && aw_is_multicast) begin + mcast_aw_hs_state_d = MCastAwHandshakeInProgress; + end + // Commit is asserted and handshake takes place in the current cycle. + // In the next cycle we are again idle + MCastAwHandshakeInProgress: + mcast_aw_hs_state_d = MCastAwHandshakeIdle; + default: ; + endcase + end + + assign mcast_aw_hs_in_progress = mcast_aw_hs_state_q == MCastAwHandshakeInProgress; + + `FFARN(mcast_aw_hs_state_q, mcast_aw_hs_state_d, MCastAwHandshakeIdle, clk_i, rst_ni) + + // When a multicast occurs, the upstream valid signals need to + // be forwarded to multiple master ports. + // Proper stream forking is necessary to avoid protocol violations. + // We must also require that downstream handshakes occur + // simultaneously on all addressed master ports, otherwise deadlocks + // may occur. To achieve this we modify the ready-valid protocol by adding + // a third signal, called commit, which is asserted only when we have all + // downstream handshakes. This signal notifies the slaves that the + // handshake can now actually take place. + // Using commit, instead of valid, to this end ensures that we don't have + // any combinational loops. + assign mst_aw_valids = {NoMstPorts{aw_valid}} & slv_aw_select_i; + assign accept_aw = &((mst_aw_valids & mst_aw_readies) | ~slv_aw_select_i); + assign aw_ready = aw_is_multicast ? mcast_aw_hs_in_progress : accept_aw; + assign mst_aw_commit_o = {NoMstPorts{mcast_aw_hs_in_progress}} & slv_aw_select_i; + + // If multicast is enabled, we need to count the number of outstanding transactions + if (UniqueIds) begin : gen_unique_ids_aw + // If the `UniqueIds` parameter is set, each write transaction has an ID that is unique among + // all in-flight write transactions, or all write transactions with a given ID target the same + // 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_select; + assign aw_select_occupied = 1'b0; + assign aw_id_cnt_full = 1'b0; + assign aw_any_outstanding_unicast_trx = 1'b0; + + end else begin : gen_aw_id_counter + + axi_demux_id_counters #( + .AxiIdBits ( AxiLookBits ), + .CounterWidth ( IdCounterWidth ), + .mst_port_select_t ( idx_select_t ) + ) i_aw_id_counter ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .lookup_axi_id_i ( slv_req_i.aw.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_req_i.aw.id[0+:AxiLookBits] ), + .push_mst_select_i ( slv_aw_select ), + .push_i ( w_cnt_up && !aw_is_multicast ), + .pop_axi_id_i ( slv_resp_o.b.id[0+:AxiLookBits] ), + .pop_i ( slv_resp_o.b_valid & slv_req_i.b_ready & ~outstanding_multicast ), + .any_outstanding_trx_o ( aw_any_outstanding_unicast_trx ) + ); + // pop from ID counter on outward transaction + end + + // This counter steers the demultiplexer of the W channel. + // `w_select` determines, which handshaking is connected. + // AWs are only forwarded, if the counter is empty, or `w_select_q` is the same as + // `slv_aw_select_i`. + counter #( + .WIDTH ( IdCounterWidth ), + .STICKY_OVERFLOW ( 1'b0 ) + ) i_counter_open_w ( + .clk_i, + .rst_ni, + .clear_i ( 1'b0 ), + .en_i ( w_cnt_up ^ w_cnt_down ), + .load_i ( 1'b0 ), + .down_i ( w_cnt_down ), + .d_i ( '0 ), + .q_o ( w_open ), + .overflow_o ( /*not used*/ ) + ); + + `FFLARN(w_select_q, slv_aw_select_i, w_cnt_up, mask_select_t'(0), clk_i, rst_ni) + assign w_select = (|w_open) ? w_select_q : slv_aw_select_i; + assign w_select_valid = w_cnt_up | (|w_open); + assign w_cnt_down = slv_req_i.w_valid & slv_resp_o.w_ready & slv_req_i.w.last; + + //-------------------------------------- + // W Channel + //-------------------------------------- + + // When a multicast occurs, the upstream valid signals need to + // be forwarded to multiple master ports. + // Proper stream forking is necessary to avoid protocol violations + stream_fork_dynamic #( + .N_OUP(NoMstPorts) + ) i_w_stream_fork_dynamic ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .valid_i (slv_req_i.w_valid), + .ready_o (slv_resp_o.w_ready), + .sel_i (w_select), + .sel_valid_i(w_select_valid), + .sel_ready_o(), + .valid_o (mst_w_valids), + .ready_i (mst_w_readies) + ); + + //-------------------------------------- + // B Channel + //-------------------------------------- + logic [cf_math_pkg::idx_width(NoMstPorts)-1:0] b_idx; + + // Arbitration of the different B responses + rr_arb_tree #( + .NumIn ( NoMstPorts ), + .DataType ( logic ), + .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 & {NoMstPorts{!outstanding_multicast}} ), + .gnt_o ( mst_b_readies_arb ), + .data_i ( '0 ), + .gnt_i ( slv_req_i.b_ready ), + .req_o ( slv_b_valid_arb ), + .data_o ( ), + .idx_o ( b_idx ) + ); + + // Streams must be joined instead of arbitrated when multicast + stream_join_dynamic #(NoMstPorts) i_b_stream_join ( + .inp_valid_i(mst_b_valids & {NoMstPorts{outstanding_multicast}}), + .inp_ready_o(mst_b_readies_join), + .sel_i (multicast_select_q), + .oup_valid_o(slv_b_valid_join), + .oup_ready_i(slv_req_i.b_ready) + ); + for (genvar i=0; i 0) else + $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."); + no_multicast_ports: assume (NoMstPorts >= NoMulticastPorts) else + $fatal(1, "NoMstPorts needs to be >= NoMulticastPorts."); + unique_ids_multicast: assume (!UniqueIds || (NoMulticastPorts == 0)) else + $fatal(1, "UniqueIds not supported when multicast is enabled (NoMulticastPorts > 0)."); + end + default disable iff (!rst_ni); + ar_select: assume property( @(posedge clk_i) (slv_req_i.ar_valid |-> + (slv_ar_select_i < NoMstPorts))) else + $fatal(1, "slv_ar_select_i is %d: AR has selected a slave that is not defined.\ + NoMstPorts: %d", slv_ar_select_i, NoMstPorts); + aw_valid_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) |=> aw_valid) else + $fatal(1, "aw_valid was deasserted, when aw_ready = 0 in last cycle."); + ar_valid_stable: assert property( @(posedge clk_i) + (ar_valid && !ar_ready) |=> ar_valid) else + $fatal(1, "ar_valid was deasserted, when ar_ready = 0 in last cycle."); + slv_aw_chan_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) + |=> $stable(slv_req_i.aw)) else + $fatal(1, "slv_aw_chan unstable with valid set."); + slv_aw_select_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) + |=> $stable(slv_aw_select_i)) else + $fatal(1, "slv_aw_select_i unstable with valid set."); + slv_ar_chan_stable: assert property( @(posedge clk_i) (ar_valid && !ar_ready) + |=> $stable(slv_req_i.ar)) else + $fatal(1, "slv_ar_chan unstable with valid set."); + slv_ar_select_stable: assert property( @(posedge clk_i) (ar_valid && !ar_ready) + |=> $stable(slv_ar_select_i)) else + $fatal(1, "slv_ar_select_i unstable with valid set."); + internal_ar_select: assert property( @(posedge clk_i) + (ar_valid |-> slv_ar_select_i < NoMstPorts)) + else $fatal(1, "slv_ar_select_i illegal while ar_valid."); + w_underflow: assert property( @(posedge clk_i) + ((w_open == '0) && (w_cnt_up ^ w_cnt_down) |-> !w_cnt_down)) else + $fatal(1, "W counter underflowed!"); + `ASSUME(NoAtopAllowed, !AtopSupport && slv_req_i.aw_valid |-> slv_req_i.aw.atop == '0) +`endif +`endif +// pragma translate_on + end +endmodule diff --git a/src/axi_mcast_mux.sv b/src/axi_mcast_mux.sv new file mode 100644 index 000000000..e17cbf40e --- /dev/null +++ b/src/axi_mcast_mux.sv @@ -0,0 +1,665 @@ +// Copyright (c) 2019 ETH Zurich, 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: +// - Wolfgang Roenninger +// - Andreas Kurth + +// AXI Multiplexer: This module multiplexes the AXI4 slave ports down to one master port. +// The AXI IDs from the slave ports get extended with the respective slave port index. +// The extension width can be calculated with `$clog2(NoSlvPorts)`. This means the AXI +// ID for the master port has to be this `$clog2(NoSlvPorts)` wider than the ID for the +// slave ports. +// Responses are switched based on these bits. For example, with 4 slave ports +// a response with ID `6'b100110` will be forwarded to slave port 2 (`2'b10`). + +// register macros +`include "common_cells/assertions.svh" +`include "common_cells/registers.svh" + +module axi_mcast_mux #( + // AXI parameter and channel types + parameter int unsigned SlvAxiIDWidth = 32'd0, // AXI ID width, slave ports + parameter type slv_aw_chan_t = logic, // AW Channel Type, slave ports + parameter type mst_aw_chan_t = logic, // AW Channel Type, master port + parameter type w_chan_t = logic, // W Channel Type, all ports + parameter type slv_b_chan_t = logic, // B Channel Type, slave ports + parameter type mst_b_chan_t = logic, // B Channel Type, master port + parameter type slv_ar_chan_t = logic, // AR Channel Type, slave ports + parameter type mst_ar_chan_t = logic, // AR Channel Type, master port + parameter type slv_r_chan_t = logic, // R Channel Type, slave ports + parameter type mst_r_chan_t = logic, // R Channel Type, master port + parameter type slv_req_t = logic, // Slave port request type + parameter type slv_resp_t = logic, // Slave port response type + parameter type mst_req_t = logic, // Master ports request type + parameter type mst_resp_t = logic, // Master ports response type + parameter int unsigned NoSlvPorts = 32'd0, // Number of slave ports + // Maximum number of outstanding transactions per write + parameter int unsigned MaxWTrans = 32'd8, + // If enabled, this multiplexer is purely combinatorial + parameter bit FallThrough = 1'b0, + // add spill register on write master ports, adds a cycle latency on write channels + parameter bit SpillAw = 1'b1, + parameter bit SpillW = 1'b0, + parameter bit SpillB = 1'b0, + // add spill register on read master ports, adds a cycle latency on read channels + parameter bit SpillAr = 1'b1, + parameter bit SpillR = 1'b0 +) ( + input logic clk_i, // Clock + input logic rst_ni, // Asynchronous reset active low + input logic test_i, // Test Mode enable + // slave ports (AXI inputs), connect master modules here + input logic [NoSlvPorts-1:0] slv_is_mcast_i, + input logic [NoSlvPorts-1:0] slv_aw_commit_i, + input slv_req_t [NoSlvPorts-1:0] slv_reqs_i, + output slv_resp_t [NoSlvPorts-1:0] slv_resps_o, + // master port (AXI outputs), connect slave modules here + output mst_req_t mst_req_o, + input mst_resp_t mst_resp_i +); + + localparam int unsigned MstIdxBits = $clog2(NoSlvPorts); + localparam int unsigned MstAxiIDWidth = SlvAxiIDWidth + MstIdxBits; + + // pass through if only one slave port + if (NoSlvPorts == 32'h1) begin : gen_no_mux + spill_register #( + .T ( mst_aw_chan_t ), + .Bypass ( ~SpillAw ) + ) i_aw_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( slv_is_mcast_i[0] ? + slv_aw_commit_i[0] : + slv_reqs_i[0].aw_valid ), + .ready_o ( slv_resps_o[0].aw_ready ), + .data_i ( slv_reqs_i[0].aw ), + .valid_o ( mst_req_o.aw_valid ), + .ready_i ( mst_resp_i.aw_ready ), + .data_o ( mst_req_o.aw ) + ); + spill_register #( + .T ( w_chan_t ), + .Bypass ( ~SpillW ) + ) i_w_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( slv_reqs_i[0].w_valid ), + .ready_o ( slv_resps_o[0].w_ready ), + .data_i ( slv_reqs_i[0].w ), + .valid_o ( mst_req_o.w_valid ), + .ready_i ( mst_resp_i.w_ready ), + .data_o ( mst_req_o.w ) + ); + spill_register #( + .T ( mst_b_chan_t ), + .Bypass ( ~SpillB ) + ) i_b_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( mst_resp_i.b_valid ), + .ready_o ( mst_req_o.b_ready ), + .data_i ( mst_resp_i.b ), + .valid_o ( slv_resps_o[0].b_valid ), + .ready_i ( slv_reqs_i[0].b_ready ), + .data_o ( slv_resps_o[0].b ) + ); + spill_register #( + .T ( mst_ar_chan_t ), + .Bypass ( ~SpillAr ) + ) i_ar_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( slv_reqs_i[0].ar_valid ), + .ready_o ( slv_resps_o[0].ar_ready ), + .data_i ( slv_reqs_i[0].ar ), + .valid_o ( mst_req_o.ar_valid ), + .ready_i ( mst_resp_i.ar_ready ), + .data_o ( mst_req_o.ar ) + ); + spill_register #( + .T ( mst_r_chan_t ), + .Bypass ( ~SpillR ) + ) i_r_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( mst_resp_i.r_valid ), + .ready_o ( mst_req_o.r_ready ), + .data_i ( mst_resp_i.r ), + .valid_o ( slv_resps_o[0].r_valid ), + .ready_i ( slv_reqs_i[0].r_ready ), + .data_o ( slv_resps_o[0].r ) + ); +// Validate parameters. +// pragma translate_off + `ASSERT_INIT(CorrectIdWidthSlvAw, $bits(slv_reqs_i[0].aw.id) == SlvAxiIDWidth) + `ASSERT_INIT(CorrectIdWidthSlvB, $bits(slv_resps_o[0].b.id) == SlvAxiIDWidth) + `ASSERT_INIT(CorrectIdWidthSlvAr, $bits(slv_reqs_i[0].ar.id) == SlvAxiIDWidth) + `ASSERT_INIT(CorrectIdWidthSlvR, $bits(slv_resps_o[0].r.id) == SlvAxiIDWidth) + `ASSERT_INIT(CorrectIdWidthMstAw, $bits(mst_req_o.aw.id) == SlvAxiIDWidth) + `ASSERT_INIT(CorrectIdWidthMstB, $bits(mst_resp_i.b.id) == SlvAxiIDWidth) + `ASSERT_INIT(CorrectIdWidthMstAr, $bits(mst_req_o.ar.id) == SlvAxiIDWidth) + `ASSERT_INIT(CorrectIdWidthMstR, $bits(mst_resp_i.r.id) == SlvAxiIDWidth) +// pragma translate_on + + // other non degenerate cases + end else begin : gen_mux + + typedef logic [MstIdxBits-1:0] switch_id_t; + + // AXI channels between the ID prepend unit and the rest of the multiplexer + mst_aw_chan_t [NoSlvPorts-1:0] slv_aw_chans; + logic [NoSlvPorts-1:0] slv_aw_valids, slv_aw_readies; + w_chan_t [NoSlvPorts-1:0] slv_w_chans; + logic [NoSlvPorts-1:0] slv_w_valids, slv_w_readies; + mst_b_chan_t [NoSlvPorts-1:0] slv_b_chans; + logic [NoSlvPorts-1:0] slv_b_valids, slv_b_readies; + mst_ar_chan_t [NoSlvPorts-1:0] slv_ar_chans; + logic [NoSlvPorts-1:0] slv_ar_valids, slv_ar_readies; + mst_r_chan_t [NoSlvPorts-1:0] slv_r_chans; + logic [NoSlvPorts-1:0] slv_r_valids, slv_r_readies; + + // These signals are all ID prepended + // AW channel + mst_aw_chan_t mst_aw_chan; + logic mst_aw_valid, mst_aw_ready; + + // AW arbiter signals + mst_aw_chan_t ucast_aw_chan, mcast_aw_chan; + logic ucast_aw_valid, ucast_aw_ready; + logic mcast_aw_valid, mcast_aw_ready, mcast_aw_commit; + logic mcast_not_aw_valid, mcast_aw_priority; + logic [MstIdxBits-1:0] mcast_sel_q, mcast_sel_d; + logic [NoSlvPorts-1:0] mcast_sel_mask; + logic [NoSlvPorts-1:0] ucast_aw_readies, mcast_aw_readies; + + // AW master handshake internal, so that we are able to stall, if w_fifo is full + logic aw_valid, aw_ready; + + // FF to lock the AW valid signal, when a new arbitration decision is made the decision + // gets pushed into the W FIFO, when it now stalls prevent subsequent pushing + // This FF removes AW to W dependency + logic lock_aw_valid_d, lock_aw_valid_q; + logic lock_unicast_d, lock_unicast_q; + logic load_aw_lock; + + // signals for the FIFO that holds the last switching decision of the AW channel + logic w_fifo_full, w_fifo_empty; + logic w_fifo_push, w_fifo_pop; + switch_id_t w_fifo_data; + + // W channel spill reg + w_chan_t mst_w_chan; + logic mst_w_valid, mst_w_ready; + + // master ID in the b_id + switch_id_t switch_b_id; + + // B channel spill reg + mst_b_chan_t mst_b_chan; + logic mst_b_valid; + + // AR channel for when spill is enabled + mst_ar_chan_t mst_ar_chan; + logic ar_valid, ar_ready; + + // master ID in the r_id + switch_id_t switch_r_id; + + // R channel spill reg + mst_r_chan_t mst_r_chan; + logic mst_r_valid; + + //-------------------------------------- + // ID prepend for all slave ports + //-------------------------------------- + for (genvar i = 0; i < NoSlvPorts; i++) begin : gen_id_prepend + axi_id_prepend #( + .NoBus ( 32'd1 ), // one AXI bus per slave port + .AxiIdWidthSlvPort( SlvAxiIDWidth ), + .AxiIdWidthMstPort( MstAxiIDWidth ), + .slv_aw_chan_t ( slv_aw_chan_t ), + .slv_w_chan_t ( w_chan_t ), + .slv_b_chan_t ( slv_b_chan_t ), + .slv_ar_chan_t ( slv_ar_chan_t ), + .slv_r_chan_t ( slv_r_chan_t ), + .mst_aw_chan_t ( mst_aw_chan_t ), + .mst_w_chan_t ( w_chan_t ), + .mst_b_chan_t ( mst_b_chan_t ), + .mst_ar_chan_t ( mst_ar_chan_t ), + .mst_r_chan_t ( mst_r_chan_t ) + ) i_id_prepend ( + .pre_id_i ( switch_id_t'(i) ), + .slv_aw_chans_i ( slv_reqs_i[i].aw ), + .slv_aw_valids_i ( slv_reqs_i[i].aw_valid ), + .slv_aw_readies_o ( slv_resps_o[i].aw_ready ), + .slv_w_chans_i ( slv_reqs_i[i].w ), + .slv_w_valids_i ( slv_reqs_i[i].w_valid ), + .slv_w_readies_o ( slv_resps_o[i].w_ready ), + .slv_b_chans_o ( slv_resps_o[i].b ), + .slv_b_valids_o ( slv_resps_o[i].b_valid ), + .slv_b_readies_i ( slv_reqs_i[i].b_ready ), + .slv_ar_chans_i ( slv_reqs_i[i].ar ), + .slv_ar_valids_i ( slv_reqs_i[i].ar_valid ), + .slv_ar_readies_o ( slv_resps_o[i].ar_ready ), + .slv_r_chans_o ( slv_resps_o[i].r ), + .slv_r_valids_o ( slv_resps_o[i].r_valid ), + .slv_r_readies_i ( slv_reqs_i[i].r_ready ), + .mst_aw_chans_o ( slv_aw_chans[i] ), + .mst_aw_valids_o ( slv_aw_valids[i] ), + .mst_aw_readies_i ( slv_aw_readies[i] ), + .mst_w_chans_o ( slv_w_chans[i] ), + .mst_w_valids_o ( slv_w_valids[i] ), + .mst_w_readies_i ( slv_w_readies[i] ), + .mst_b_chans_i ( slv_b_chans[i] ), + .mst_b_valids_i ( slv_b_valids[i] ), + .mst_b_readies_o ( slv_b_readies[i] ), + .mst_ar_chans_o ( slv_ar_chans[i] ), + .mst_ar_valids_o ( slv_ar_valids[i] ), + .mst_ar_readies_i ( slv_ar_readies[i] ), + .mst_r_chans_i ( slv_r_chans[i] ), + .mst_r_valids_i ( slv_r_valids[i] ), + .mst_r_readies_o ( slv_r_readies[i] ) + ); + end + + //-------------------------------------- + // AW Channel + //-------------------------------------- + // Arbitrate unicast requests in round-robin fashion + rr_arb_tree #( + .NumIn ( NoSlvPorts ), + .DataType ( mst_aw_chan_t ), + .AxiVldRdy( 1'b1 ), + .LockIn ( 1'b1 ) + ) i_aw_ucast_arbiter ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .flush_i( 1'b0 ), + .rr_i ( '0 ), + .req_i ( slv_aw_valids & ~slv_is_mcast_i ), + .gnt_o ( ucast_aw_readies ), + .data_i ( slv_aw_chans ), + .gnt_i ( ucast_aw_ready ), + .req_o ( ucast_aw_valid ), + .data_o ( ucast_aw_chan ), + .idx_o ( ) + ); + + // Arbitrate multicast requests in priority encoder fashion + lzc #( + .WIDTH ( NoSlvPorts ), + .MODE ( 1'b0 ) // Trailing zero mode + ) i_aw_mcast_lzc ( + .in_i ( slv_aw_valids & slv_is_mcast_i ), + .cnt_o ( mcast_sel_d ), + .empty_o ( mcast_not_aw_valid ) + ); + assign mcast_sel_mask = mcast_not_aw_valid ? '0 : 1 << mcast_sel_d; + assign mcast_aw_chan = slv_aw_chans[mcast_sel_q]; + assign mcast_aw_valid = !mcast_not_aw_valid; + assign mcast_aw_commit = |slv_aw_commit_i; + assign mcast_aw_readies = {NoSlvPorts{mcast_aw_ready}} & mcast_sel_mask; + + `FFL(mcast_sel_q, mcast_sel_d, mcast_aw_valid && mcast_aw_ready, '0, clk_i, rst_ni) + + // Arbitrate "winners" of unicast and multicast arbitrations, + // giving priority to multicast. + // On a multicast, the downstream valid is gated until we get the commit signal. + // But the commit signal is only received after we have received the downstream + // ready and propagated it upstream. So the downstream ready will be kept high + // for an additional cycle after there was an upstream handshake. We must thus + // gate it upstream for that additional cycle (with !mcast_aw_commit). + // If we already locked the AW on a unicast transaction, we must wait for it to + // complete before accepting a multicast transaction, so we give priority to the + // unicast. + assign mcast_aw_priority = mcast_aw_valid && !lock_unicast_q; + assign mcast_aw_ready = aw_ready && !mcast_aw_commit && mcast_aw_priority; + assign ucast_aw_ready = aw_ready && !mcast_aw_commit && !mcast_aw_priority; + assign mst_aw_chan = mcast_aw_commit ? mcast_aw_chan : ucast_aw_chan; + assign slv_aw_readies = mcast_aw_readies | ucast_aw_readies; + // In case of a multicast transaction, downstream valid should only be asserted + // on commit. However, a multicast transaction is already accepted on the cycle + // before, when we have mcast_aw_valid. We must prevent a unicast transaction + // from raising the downstream valid in that time (with !mcast_aw_valid). + assign aw_valid = mcast_aw_commit || (ucast_aw_valid && !mcast_aw_valid); + + // control of the AW channel + always_comb begin + // default assignments + lock_aw_valid_d = lock_aw_valid_q; + lock_unicast_d = lock_unicast_q; + load_aw_lock = 1'b0; + w_fifo_push = 1'b0; + mst_aw_valid = 1'b0; + aw_ready = 1'b0; + // had a downstream stall, be valid and send the AW along + if (lock_aw_valid_q) begin + mst_aw_valid = 1'b1; + // transaction + if (mst_aw_ready) begin + aw_ready = 1'b1; + lock_aw_valid_d = 1'b0; + lock_unicast_d = 1'b0; + load_aw_lock = 1'b1; + w_fifo_push = 1'b1; + end + end else begin + if (!w_fifo_full) begin + if (mst_aw_ready) begin + aw_ready = 1'b1; + end + if (aw_valid) begin + mst_aw_valid = 1'b1; + if (!mst_aw_ready) begin + // go to lock if transaction not in this cycle + lock_aw_valid_d = 1'b1; + load_aw_lock = 1'b1; + // remember if it was a unicast transaction + lock_unicast_d = !mcast_aw_commit; + end else begin + w_fifo_push = 1'b1; + end + end + end + end + end + + `FFL(lock_aw_valid_q, lock_aw_valid_d, load_aw_lock, '0, clk_i, rst_ni) + `FFL(lock_unicast_q, lock_unicast_d, load_aw_lock, '0, clk_i, rst_ni) + + fifo_v3 #( + .FALL_THROUGH ( FallThrough ), + .DEPTH ( MaxWTrans ), + .dtype ( switch_id_t ) + ) i_w_fifo ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .flush_i ( 1'b0 ), + .testmode_i( test_i ), + .full_o ( w_fifo_full ), + .empty_o ( w_fifo_empty ), + .usage_o ( ), + .data_i ( mst_aw_chan.id[SlvAxiIDWidth+:MstIdxBits] ), + .push_i ( w_fifo_push ), + .data_o ( w_fifo_data ), + .pop_i ( w_fifo_pop ) + ); + + spill_register #( + .T ( mst_aw_chan_t ), + .Bypass ( ~SpillAw ) // Param indicated that we want a spill reg + ) i_aw_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( mst_aw_valid ), + .ready_o ( mst_aw_ready ), + .data_i ( mst_aw_chan ), + .valid_o ( mst_req_o.aw_valid ), + .ready_i ( mst_resp_i.aw_ready ), + .data_o ( mst_req_o.aw ) + ); + + //-------------------------------------- + // W Channel + //-------------------------------------- + // multiplexer + assign mst_w_chan = slv_w_chans[w_fifo_data]; + always_comb begin + // default assignments + mst_w_valid = 1'b0; + slv_w_readies = '0; + w_fifo_pop = 1'b0; + // control + if (!w_fifo_empty) begin + // connect the handshake + mst_w_valid = slv_w_valids[w_fifo_data]; + slv_w_readies[w_fifo_data] = mst_w_ready; + // pop FIFO on a last transaction + w_fifo_pop = slv_w_valids[w_fifo_data] & mst_w_ready & mst_w_chan.last; + end + end + + spill_register #( + .T ( w_chan_t ), + .Bypass ( ~SpillW ) + ) i_w_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( mst_w_valid ), + .ready_o ( mst_w_ready ), + .data_i ( mst_w_chan ), + .valid_o ( mst_req_o.w_valid ), + .ready_i ( mst_resp_i.w_ready ), + .data_o ( mst_req_o.w ) + ); + + //-------------------------------------- + // B Channel + //-------------------------------------- + // replicate B channels + assign slv_b_chans = {NoSlvPorts{mst_b_chan}}; + // control B channel handshake + assign switch_b_id = mst_b_chan.id[SlvAxiIDWidth+:MstIdxBits]; + assign slv_b_valids = (mst_b_valid) ? (1 << switch_b_id) : '0; + + spill_register #( + .T ( mst_b_chan_t ), + .Bypass ( ~SpillB ) + ) i_b_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( mst_resp_i.b_valid ), + .ready_o ( mst_req_o.b_ready ), + .data_i ( mst_resp_i.b ), + .valid_o ( mst_b_valid ), + .ready_i ( slv_b_readies[switch_b_id] ), + .data_o ( mst_b_chan ) + ); + + //-------------------------------------- + // AR Channel + //-------------------------------------- + rr_arb_tree #( + .NumIn ( NoSlvPorts ), + .DataType ( mst_ar_chan_t ), + .AxiVldRdy( 1'b1 ), + .LockIn ( 1'b1 ) + ) i_ar_arbiter ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .flush_i( 1'b0 ), + .rr_i ( '0 ), + .req_i ( slv_ar_valids ), + .gnt_o ( slv_ar_readies ), + .data_i ( slv_ar_chans ), + .gnt_i ( ar_ready ), + .req_o ( ar_valid ), + .data_o ( mst_ar_chan ), + .idx_o ( ) + ); + + spill_register #( + .T ( mst_ar_chan_t ), + .Bypass ( ~SpillAr ) + ) i_ar_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( ar_valid ), + .ready_o ( ar_ready ), + .data_i ( mst_ar_chan ), + .valid_o ( mst_req_o.ar_valid ), + .ready_i ( mst_resp_i.ar_ready ), + .data_o ( mst_req_o.ar ) + ); + + //-------------------------------------- + // R Channel + //-------------------------------------- + // replicate R channels + assign slv_r_chans = {NoSlvPorts{mst_r_chan}}; + // R channel handshake control + assign switch_r_id = mst_r_chan.id[SlvAxiIDWidth+:MstIdxBits]; + assign slv_r_valids = (mst_r_valid) ? (1 << switch_r_id) : '0; + + spill_register #( + .T ( mst_r_chan_t ), + .Bypass ( ~SpillR ) + ) i_r_spill_reg ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( mst_resp_i.r_valid ), + .ready_o ( mst_req_o.r_ready ), + .data_i ( mst_resp_i.r ), + .valid_o ( mst_r_valid ), + .ready_i ( slv_r_readies[switch_r_id] ), + .data_o ( mst_r_chan ) + ); + end + +// pragma translate_off +`ifndef VERILATOR + initial begin + assert (SlvAxiIDWidth > 0) else $fatal(1, "AXI ID width of slave ports must be non-zero!"); + assert (NoSlvPorts > 0) else $fatal(1, "Number of slave ports must be non-zero!"); + assert (MaxWTrans > 0) + else $fatal(1, "Maximum number of outstanding writes must be non-zero!"); + assert (MstAxiIDWidth >= SlvAxiIDWidth + $clog2(NoSlvPorts)) + else $fatal(1, "AXI ID width of master ports must be wide enough to identify slave ports!"); + // Assert ID widths (one slave is sufficient since they all have the same type). + assert ($unsigned($bits(slv_reqs_i[0].aw.id)) == SlvAxiIDWidth) + else $fatal(1, "ID width of AW channel of slave ports does not match parameter!"); + assert ($unsigned($bits(slv_reqs_i[0].ar.id)) == SlvAxiIDWidth) + else $fatal(1, "ID width of AR channel of slave ports does not match parameter!"); + assert ($unsigned($bits(slv_resps_o[0].b.id)) == SlvAxiIDWidth) + else $fatal(1, "ID width of B channel of slave ports does not match parameter!"); + assert ($unsigned($bits(slv_resps_o[0].r.id)) == SlvAxiIDWidth) + else $fatal(1, "ID width of R channel of slave ports does not match parameter!"); + assert ($unsigned($bits(mst_req_o.aw.id)) == MstAxiIDWidth) + else $fatal(1, "ID width of AW channel of master port is wrong!"); + assert ($unsigned($bits(mst_req_o.ar.id)) == MstAxiIDWidth) + else $fatal(1, "ID width of AR channel of master port is wrong!"); + assert ($unsigned($bits(mst_resp_i.b.id)) == MstAxiIDWidth) + else $fatal(1, "ID width of B channel of master port is wrong!"); + assert ($unsigned($bits(mst_resp_i.r.id)) == MstAxiIDWidth) + else $fatal(1, "ID width of R channel of master port is wrong!"); + end +`endif +// pragma translate_on +endmodule + +// interface wrap +`include "axi/assign.svh" +`include "axi/typedef.svh" +module axi_mcast_mux_intf #( + parameter int unsigned SLV_AXI_ID_WIDTH = 32'd0, // Synopsys DC requires default value for params + parameter int unsigned MST_AXI_ID_WIDTH = 32'd0, + parameter int unsigned AXI_ADDR_WIDTH = 32'd0, + parameter int unsigned AXI_DATA_WIDTH = 32'd0, + parameter int unsigned AXI_USER_WIDTH = 32'd0, + parameter int unsigned NO_SLV_PORTS = 32'd0, // Number of slave ports + // Maximum number of outstanding transactions per write + parameter int unsigned MAX_W_TRANS = 32'd8, + // if enabled, this multiplexer is purely combinatorial + parameter bit FALL_THROUGH = 1'b0, + // add spill register on write master ports, adds a cycle latency on write channels + parameter bit SPILL_AW = 1'b1, + parameter bit SPILL_W = 1'b0, + parameter bit SPILL_B = 1'b0, + // add spill register on read master ports, adds a cycle latency on read channels + parameter bit SPILL_AR = 1'b1, + parameter bit SPILL_R = 1'b0 +) ( + input logic clk_i, // Clock + input logic rst_ni, // Asynchronous reset active low + input logic test_i, // Testmode enable + input logic [NO_SLV_PORTS-1:0] slv_is_mcast_i, + input logic [NO_SLV_PORTS-1:0] slv_aw_commit_i, + AXI_BUS.Slave slv [NO_SLV_PORTS-1:0], // slave ports + AXI_BUS.Master mst // master port +); + + typedef logic [SLV_AXI_ID_WIDTH-1:0] slv_id_t; + typedef logic [MST_AXI_ID_WIDTH-1:0] mst_id_t; + typedef logic [AXI_ADDR_WIDTH -1:0] addr_t; + typedef logic [AXI_DATA_WIDTH-1:0] data_t; + typedef logic [AXI_DATA_WIDTH/8-1:0] strb_t; + typedef logic [AXI_USER_WIDTH-1:0] user_t; + // channels typedef + `AXI_TYPEDEF_AW_CHAN_T(slv_aw_chan_t, addr_t, slv_id_t, user_t) + `AXI_TYPEDEF_AW_CHAN_T(mst_aw_chan_t, addr_t, mst_id_t, user_t) + + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + + `AXI_TYPEDEF_B_CHAN_T(slv_b_chan_t, slv_id_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(mst_b_chan_t, mst_id_t, user_t) + + `AXI_TYPEDEF_AR_CHAN_T(slv_ar_chan_t, addr_t, slv_id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(mst_ar_chan_t, addr_t, mst_id_t, user_t) + + `AXI_TYPEDEF_R_CHAN_T(slv_r_chan_t, data_t, slv_id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(mst_r_chan_t, data_t, mst_id_t, user_t) + + `AXI_TYPEDEF_REQ_T(slv_req_t, slv_aw_chan_t, w_chan_t, slv_ar_chan_t) + `AXI_TYPEDEF_RESP_T(slv_resp_t, slv_b_chan_t, slv_r_chan_t) + + `AXI_TYPEDEF_REQ_T(mst_req_t, mst_aw_chan_t, w_chan_t, mst_ar_chan_t) + `AXI_TYPEDEF_RESP_T(mst_resp_t, mst_b_chan_t, mst_r_chan_t) + + slv_req_t [NO_SLV_PORTS-1:0] slv_reqs; + slv_resp_t [NO_SLV_PORTS-1:0] slv_resps; + mst_req_t mst_req; + mst_resp_t mst_resp; + + for (genvar i = 0; i < NO_SLV_PORTS; i++) begin : gen_assign_slv_ports + `AXI_ASSIGN_TO_REQ(slv_reqs[i], slv[i]) + `AXI_ASSIGN_FROM_RESP(slv[i], slv_resps[i]) + end + + `AXI_ASSIGN_FROM_REQ(mst, mst_req) + `AXI_ASSIGN_TO_RESP(mst_resp, mst) + + axi_mcast_mux #( + .SlvAxiIDWidth ( SLV_AXI_ID_WIDTH ), + .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 ( NO_SLV_PORTS ), // Number of slave ports + .MaxWTrans ( MAX_W_TRANS ), + .FallThrough ( FALL_THROUGH ), + .SpillAw ( SPILL_AW ), + .SpillW ( SPILL_W ), + .SpillB ( SPILL_B ), + .SpillAr ( SPILL_AR ), + .SpillR ( SPILL_R ) + ) i_axi_mux ( + .clk_i ( clk_i ), // Clock + .rst_ni ( rst_ni ), // Asynchronous reset active low + .test_i ( test_i ), // Test Mode enable + .slv_is_mcast_i, + .slv_aw_commit_i, + .slv_reqs_i ( slv_reqs ), + .slv_resps_o ( slv_resps ), + .mst_req_o ( mst_req ), + .mst_resp_i ( mst_resp ) + ); +endmodule diff --git a/src/axi_mcast_xbar.sv b/src/axi_mcast_xbar.sv new file mode 100644 index 000000000..0dd385ecc --- /dev/null +++ b/src/axi_mcast_xbar.sv @@ -0,0 +1,265 @@ +// 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: +// - Wolfgang Roenninger +// - Andreas Kurth +// - Florian Zaruba +// - Luca Colagrande + +/// axi_xbar: Fully-connected AXI4+ATOP crossbar with an arbitrary number of slave and master ports. +/// See `doc/axi_xbar.md` for the documentation, including the definition of parameters and ports. +module axi_mcast_xbar +import cf_math_pkg::idx_width; +#( + /// Configuration struct for the crossbar see `axi_pkg` for fields and definitions. + parameter axi_pkg::xbar_cfg_t Cfg = '0, + /// Enable atomic operations support. + parameter bit ATOPs = 1'b1, + /// Connectivity matrix + parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] Connectivity = '1, + /// Connectivity matrix for multicast transactions + parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] MulticastConnectivity = '1, + /// AXI4+ATOP AW channel struct type for the slave ports. + parameter type slv_aw_chan_t = logic, + /// AXI4+ATOP AW channel struct type for the master ports. + parameter type mst_aw_chan_t = logic, + /// AXI4+ATOP W channel struct type for all ports. + parameter type w_chan_t = logic, + /// AXI4+ATOP B channel struct type for the slave ports. + parameter type slv_b_chan_t = logic, + /// AXI4+ATOP B channel struct type for the master ports. + parameter type mst_b_chan_t = logic, + /// AXI4+ATOP AR channel struct type for the slave ports. + parameter type slv_ar_chan_t = logic, + /// AXI4+ATOP AR channel struct type for the master ports. + parameter type mst_ar_chan_t = logic, + /// AXI4+ATOP R channel struct type for the slave ports. + parameter type slv_r_chan_t = logic, + /// AXI4+ATOP R channel struct type for the master ports. + parameter type mst_r_chan_t = logic, + /// AXI4+ATOP request struct type for the slave ports. + parameter type slv_req_t = logic, + /// AXI4+ATOP response struct type for the slave ports. + parameter type slv_resp_t = logic, + /// AXI4+ATOP request struct type for the master ports. + parameter type mst_req_t = logic, + /// AXI4+ATOP response struct type for the master ports + parameter type mst_resp_t = logic, + /// Address rule type for the address decoders from `common_cells:addr_decode`. + /// Example types are provided in `axi_pkg`. + /// Required struct fields: + /// ``` + /// typedef struct packed { + /// int unsigned idx; + /// axi_addr_t start_addr; + /// axi_addr_t end_addr; + /// } rule_t; + /// ``` + parameter type rule_t = axi_pkg::xbar_rule_64_t +) ( + /// Clock, positive edge triggered. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// Testmode enable, active high. + input logic test_i, + /// AXI4+ATOP requests to the slave ports. + input slv_req_t [Cfg.NoSlvPorts-1:0] slv_ports_req_i, + /// AXI4+ATOP responses of the slave ports. + output slv_resp_t [Cfg.NoSlvPorts-1:0] slv_ports_resp_o, + /// AXI4+ATOP requests of the master ports. + output mst_req_t [Cfg.NoMstPorts-1:0] mst_ports_req_o, + /// AXI4+ATOP responses to the master ports. + input mst_resp_t [Cfg.NoMstPorts-1:0] mst_ports_resp_i, + /// Address map array input for the crossbar. This map is global for the whole module. + /// It is used for routing the transactions to the respective master ports. + /// Each master port can have multiple different rules. + input rule_t [Cfg.NoAddrRules-1:0] addr_map_i, + /// Enable default master port. + input logic [Cfg.NoSlvPorts-1:0] en_default_mst_port_i, + /// Enables a default master port for each slave port. When this is enabled unmapped + /// transactions get issued at the master port given by `default_mst_port_i`. + /// When not used, tie to `'0`. + input rule_t [Cfg.NoSlvPorts-1:0] default_mst_port_i +); + + // signals into the axi_muxes, are of type slave as the multiplexer extends the ID + slv_req_t [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_reqs; + slv_resp_t [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_resps; + + logic [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_is_mcast; + logic [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_aw_commit; + + axi_mcast_xbar_unmuxed #( + .Cfg (Cfg), + .ATOPs (ATOPs), + .Connectivity (Connectivity), + .MulticastConnectivity (MulticastConnectivity), + .aw_chan_t (slv_aw_chan_t), + .w_chan_t (w_chan_t), + .b_chan_t (slv_b_chan_t), + .ar_chan_t (slv_ar_chan_t), + .r_chan_t (slv_r_chan_t), + .req_t (slv_req_t), + .resp_t (slv_resp_t), + .rule_t (rule_t) + ) i_xbar_unmuxed ( + .clk_i, + .rst_ni, + .test_i, + .slv_ports_req_i, + .slv_ports_resp_o, + .mst_ports_req_o (mst_reqs), + .mst_ports_resp_i (mst_resps), + .mst_is_mcast_o (mst_is_mcast), + .mst_aw_commit_o (mst_aw_commit), + .addr_map_i, + .en_default_mst_port_i, + .default_mst_port_i + ); + + for (genvar i = 0; i < Cfg.NoMstPorts; i++) begin : gen_mst_port_mux + axi_mcast_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] ) + ) i_axi_mux ( + .clk_i, // Clock + .rst_ni, // Asynchronous reset active low + .test_i, // Test Mode enable + .slv_is_mcast_i ( mst_is_mcast[i] ), + .slv_aw_commit_i ( mst_aw_commit[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 + +endmodule + +`include "axi/assign.svh" +`include "axi/typedef.svh" + +module axi_mcast_xbar_intf +import cf_math_pkg::idx_width; +#( + parameter int unsigned AXI_USER_WIDTH = 0, + parameter axi_pkg::xbar_cfg_t Cfg = '0, + parameter bit ATOPS = 1'b1, + parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] CONNECTIVITY = '1, + parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] MULTICAST_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 [Cfg.NoSlvPorts-1:0] en_default_mst_port_i, + input rule_t [Cfg.NoSlvPorts-1:0] default_mst_port_i +); + + localparam int unsigned AxiIdWidthMstPorts = Cfg.AxiIdWidthSlvPorts + $clog2(Cfg.NoSlvPorts); + + typedef logic [AxiIdWidthMstPorts -1:0] id_mst_t; + typedef logic [Cfg.AxiIdWidthSlvPorts -1:0] id_slv_t; + typedef logic [Cfg.AxiAddrWidth -1:0] addr_t; + 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 collective mask to USER signals + typedef struct packed { + addr_t collective_mask; + } aw_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) + `AXI_TYPEDEF_AR_CHAN_T(mst_ar_chan_t, addr_t, id_mst_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(slv_ar_chan_t, addr_t, id_slv_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(mst_r_chan_t, data_t, id_mst_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(slv_r_chan_t, data_t, id_slv_t, user_t) + `AXI_TYPEDEF_REQ_T(mst_req_t, mst_aw_chan_t, w_chan_t, mst_ar_chan_t) + `AXI_TYPEDEF_REQ_T(slv_req_t, slv_aw_chan_t, w_chan_t, slv_ar_chan_t) + `AXI_TYPEDEF_RESP_T(mst_resp_t, mst_b_chan_t, mst_r_chan_t) + `AXI_TYPEDEF_RESP_T(slv_resp_t, slv_b_chan_t, slv_r_chan_t) + + mst_req_t [Cfg.NoMstPorts-1:0] mst_reqs; + mst_resp_t [Cfg.NoMstPorts-1:0] mst_resps; + slv_req_t [Cfg.NoSlvPorts-1:0] slv_reqs; + slv_resp_t [Cfg.NoSlvPorts-1:0] slv_resps; + + for (genvar i = 0; i < Cfg.NoMstPorts; i++) begin : gen_assign_mst + `AXI_ASSIGN_FROM_REQ(mst_ports[i], mst_reqs[i]) + `AXI_ASSIGN_TO_RESP(mst_resps[i], mst_ports[i]) + end + + for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_assign_slv + `AXI_ASSIGN_TO_REQ(slv_reqs[i], slv_ports[i]) + `AXI_ASSIGN_FROM_RESP(slv_ports[i], slv_resps[i]) + end + + axi_mcast_xbar #( + .Cfg (Cfg), + .ATOPs ( ATOPS ), + .Connectivity ( CONNECTIVITY ), + .MulticastConnectivity ( MULTICAST_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), + .addr_map_i, + .en_default_mst_port_i, + .default_mst_port_i + ); + +endmodule diff --git a/src/axi_mcast_xbar_unmuxed.sv b/src/axi_mcast_xbar_unmuxed.sv new file mode 100644 index 000000000..d720f2dfe --- /dev/null +++ b/src/axi_mcast_xbar_unmuxed.sv @@ -0,0 +1,291 @@ +// 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: +// - Wolfgang Roenninger +// - Andreas Kurth +// - Florian Zaruba + +/// axi_xbar: Fully-connected AXI4+ATOP crossbar with an arbitrary number of slave and master ports. +/// See `doc/axi_xbar.md` for the documentation, including the definition of parameters and ports. +module axi_mcast_xbar_unmuxed +import cf_math_pkg::idx_width; +#( + /// Configuration struct for the crossbar see `axi_pkg` for fields and definitions. + parameter axi_pkg::xbar_cfg_t Cfg = '0, + /// Enable atomic operations support. + parameter bit ATOPs = 1'b1, + /// Connectivity matrix + parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] Connectivity = '1, + /// Connectivity matrix for multicast transactions + parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] MulticastConnectivity = '1, + /// AXI4+ATOP AW channel struct type for the slave ports. + parameter type aw_chan_t = logic, + /// AXI4+ATOP W channel struct type for all ports. + parameter type w_chan_t = logic, + /// AXI4+ATOP B channel struct type for the slave ports. + parameter type b_chan_t = logic, + /// AXI4+ATOP AR channel struct type for the slave ports. + parameter type ar_chan_t = logic, + /// AXI4+ATOP R channel struct type for the slave ports. + parameter type r_chan_t = logic, + /// AXI4+ATOP request struct type for the slave ports. + parameter type req_t = logic, + /// AXI4+ATOP response struct type for the slave ports. + parameter type resp_t = logic, + /// Address rule type for the address decoders from `common_cells:addr_decode`. + /// Example types are provided in `axi_pkg`. + /// Required struct fields: + /// ``` + /// typedef struct packed { + /// int unsigned idx; + /// axi_addr_t start_addr; + /// axi_addr_t end_addr; + /// } rule_t; + /// ``` + parameter type rule_t = axi_pkg::xbar_rule_64_t +) ( + /// Clock, positive edge triggered. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// Testmode enable, active high. + input logic test_i, + /// AXI4+ATOP requests to the slave ports. + input req_t [Cfg.NoSlvPorts-1:0] slv_ports_req_i, + /// AXI4+ATOP responses of the slave ports. + output resp_t [Cfg.NoSlvPorts-1:0] slv_ports_resp_o, + /// AXI4+ATOP requests of the master ports. + output req_t [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_ports_req_o, + /// AXI4+ATOP responses to the master ports. + input resp_t [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_ports_resp_i, + /// Additional multicast signals to the master ports. + output logic [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_is_mcast_o, + output logic [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_aw_commit_o, + /// Address map array input for the crossbar. This map is global for the whole module. + /// It is used for routing the transactions to the respective master ports. + /// Each master port can have multiple different rules. + input rule_t [Cfg.NoAddrRules-1:0] addr_map_i, + /// Enable default master port. + input logic [Cfg.NoSlvPorts-1:0] en_default_mst_port_i, + /// Enables a default master port for each slave port. When this is enabled unmapped + /// transactions get issued at the master port given by `default_mst_port_i`. + /// When not used, tie to `'0`. + input rule_t [Cfg.NoSlvPorts-1:0] default_mst_port_i +); + + localparam bit EnableMulticast = (Cfg.NoMulticastPorts > 0) || (Cfg.NoMulticastRules > 0); + + // signals from the axi_demuxes + req_t [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] slv_reqs; + resp_t [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] slv_resps; + logic [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] slv_is_mcast; + logic [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] slv_aw_commit; + + for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_slv_port_demux + + axi_mcast_demux_mapped #( + .AxiIdWidth ( Cfg.AxiIdWidthSlvPorts ), // ID Width + .AxiAddrWidth ( Cfg.AxiAddrWidth ), + .AtopSupport ( ATOPs ), + .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 ( req_t ), + .axi_resp_t ( resp_t ), + .NoMstPorts ( Cfg.NoMstPorts ), + .MaxTrans ( Cfg.MaxMstTrans ), + .AxiLookBits ( Cfg.AxiIdUsedSlvPorts ), + .UniqueIds ( Cfg.UniqueIds ), + .SpillAw ( Cfg.LatencyMode[9] ), + .SpillW ( Cfg.LatencyMode[8] ), + .SpillB ( Cfg.LatencyMode[7] ), + .SpillAr ( Cfg.LatencyMode[6] ), + .SpillR ( Cfg.LatencyMode[5] ), + .Connectivity ( Connectivity[i] ), + .MulticastConnectivity ( MulticastConnectivity[i] ), + .rule_t ( rule_t ), + .NoAddrRules ( Cfg.NoAddrRules ), + .NoMulticastRules ( Cfg.NoMulticastRules ), + .NoMulticastPorts ( Cfg.NoMulticastPorts ) + ) i_axi_demux ( + .clk_i, // Clock + .rst_ni, // Asynchronous reset active low + .test_i, // Testmode enable + .addr_map_i ( addr_map_i ), + .en_default_mst_port_i ( en_default_mst_port_i[i] ), + .default_mst_port_i ( default_mst_port_i[i] ), + .slv_req_i ( slv_ports_req_i[i] ), + .slv_resp_o ( slv_ports_resp_o[i] ), + .mst_reqs_o ( slv_reqs[i] ), + .mst_resps_i ( slv_resps[i] ), + .mst_is_mcast_o ( slv_is_mcast[i] ), + .mst_aw_commit_o ( slv_aw_commit[i] ) + ); + + end + + // cross all channels + for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_xbar_slv_cross + for (genvar j = 0; j < Cfg.NoMstPorts; j++) begin : gen_xbar_mst_cross + if (Connectivity[i][j]) begin : gen_connection + + assign mst_is_mcast_o[j][i] = slv_is_mcast[i][j]; + assign mst_aw_commit_o[j][i] = slv_aw_commit[i][j]; + + axi_multicut #( + .NoCuts ( Cfg.PipelineStages ), + .aw_chan_t ( aw_chan_t ), + .w_chan_t ( w_chan_t ), + .b_chan_t ( b_chan_t ), + .ar_chan_t ( ar_chan_t ), + .r_chan_t ( r_chan_t ), + .axi_req_t ( req_t ), + .axi_resp_t ( resp_t ) + ) i_axi_multicut_xbar_pipeline ( + .clk_i, + .rst_ni, + .slv_req_i ( slv_reqs[i][j] ), + .slv_resp_o ( slv_resps[i][j] ), + .mst_req_o ( mst_ports_req_o[j][i] ), + .mst_resp_i ( mst_ports_resp_i[j][i] ) + ); + + end else begin : gen_no_connection + + assign mst_is_mcast_o[j][i] = 1'b0; + assign mst_aw_commit_o[j][i] = 1'b0; + + assign mst_ports_req_o[j][i] = '0; + axi_err_slv #( + .AxiIdWidth ( Cfg.AxiIdWidthSlvPorts ), + .axi_req_t ( req_t ), + .axi_resp_t ( 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] ) + ); + end + end + end + + // pragma translate_off + `ifndef VERILATOR + `ifndef XSIM + initial begin : check_params + id_slv_req_ports: assert ($bits(slv_ports_req_i[0].aw.id ) == Cfg.AxiIdWidthSlvPorts) else + $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][0].aw.addr) == Cfg.AxiAddrWidth) else + $fatal(1, $sformatf("Mst_req and aw_addr width not equal.")); + no_cuts_if_mcast: assert (!EnableMulticast || (Cfg.PipelineStages == 0)) else + $fatal(1, $sformatf("Multicast XBAR currently does not support pipeline stages.")); + end + `endif + `endif + // pragma translate_on +endmodule + +`ifndef VCS +// As of now, VCS does not support multi-dimensional array of interfaces. +`include "axi/assign.svh" +`include "axi/typedef.svh" + +module axi_mcast_xbar_unmuxed_intf +import cf_math_pkg::idx_width; +#( + parameter int unsigned AXI_USER_WIDTH = 0, + parameter axi_pkg::xbar_cfg_t Cfg = '0, + parameter bit ATOPS = 1'b1, + parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] CONNECTIVITY = '1, + parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] MULTICAST_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][Cfg.NoSlvPorts-1:0], + input rule_t [Cfg.NoAddrRules-1:0] addr_map_i, + input logic [Cfg.NoSlvPorts-1:0] en_default_mst_port_i, + input logic [Cfg.NoSlvPorts-1:0][idx_width(Cfg.NoMstPorts)-1:0] default_mst_port_i +); + + typedef logic [Cfg.AxiIdWidthSlvPorts -1:0] id_t; + typedef logic [Cfg.AxiAddrWidth -1:0] addr_t; + 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; + + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_REQ_T(req_t, aw_chan_t, w_chan_t, ar_chan_t) + `AXI_TYPEDEF_RESP_T(resp_t, b_chan_t, r_chan_t) + + req_t [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_reqs; + resp_t [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_resps; + req_t [Cfg.NoSlvPorts-1:0] slv_reqs; + resp_t [Cfg.NoSlvPorts-1:0] slv_resps; + + for (genvar i = 0; i < Cfg.NoMstPorts; i++) begin : gen_assign_mst + for (genvar j = 0; j < Cfg.NoSlvPorts; j++) begin : gen_assign_mst_inner + `AXI_ASSIGN_FROM_REQ(mst_ports[i][j], mst_reqs[i][j]) + `AXI_ASSIGN_TO_RESP(mst_resps[i][j], mst_ports[i][j]) + end + end + + for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_assign_slv + `AXI_ASSIGN_TO_REQ(slv_reqs[i], slv_ports[i]) + `AXI_ASSIGN_FROM_RESP(slv_ports[i], slv_resps[i]) + end + + axi_mcast_xbar_unmuxed #( + .Cfg ( Cfg ), + .ATOPs ( ATOPS ), + .Connectivity ( CONNECTIVITY ), + .MulticastConnectivity ( MULTICAST_CONNECTIVITY ), + .aw_chan_t ( aw_chan_t ), + .w_chan_t ( w_chan_t ), + .b_chan_t ( b_chan_t ), + .ar_chan_t ( ar_chan_t ), + .r_chan_t ( r_chan_t ), + .req_t ( req_t ), + .resp_t ( 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), + .addr_map_i, + .en_default_mst_port_i, + .default_mst_port_i + ); + +endmodule + +`endif diff --git a/src/axi_mux.sv b/src/axi_mux.sv index da17e2b8c..a0c16b708 100644 --- a/src/axi_mux.sv +++ b/src/axi_mux.sv @@ -65,433 +65,41 @@ module axi_mux #( input mst_resp_t mst_resp_i ); - localparam int unsigned MstIdxBits = $clog2(NoSlvPorts); - localparam int unsigned MstAxiIDWidth = SlvAxiIDWidth + MstIdxBits; - - // pass through if only one slave port - if (NoSlvPorts == 32'h1) begin : gen_no_mux - spill_register #( - .T ( mst_aw_chan_t ), - .Bypass ( ~SpillAw ) - ) i_aw_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_reqs_i[0].aw_valid ), - .ready_o ( slv_resps_o[0].aw_ready ), - .data_i ( slv_reqs_i[0].aw ), - .valid_o ( mst_req_o.aw_valid ), - .ready_i ( mst_resp_i.aw_ready ), - .data_o ( mst_req_o.aw ) - ); - spill_register #( - .T ( w_chan_t ), - .Bypass ( ~SpillW ) - ) i_w_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_reqs_i[0].w_valid ), - .ready_o ( slv_resps_o[0].w_ready ), - .data_i ( slv_reqs_i[0].w ), - .valid_o ( mst_req_o.w_valid ), - .ready_i ( mst_resp_i.w_ready ), - .data_o ( mst_req_o.w ) - ); - spill_register #( - .T ( mst_b_chan_t ), - .Bypass ( ~SpillB ) - ) i_b_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_resp_i.b_valid ), - .ready_o ( mst_req_o.b_ready ), - .data_i ( mst_resp_i.b ), - .valid_o ( slv_resps_o[0].b_valid ), - .ready_i ( slv_reqs_i[0].b_ready ), - .data_o ( slv_resps_o[0].b ) - ); - spill_register #( - .T ( mst_ar_chan_t ), - .Bypass ( ~SpillAr ) - ) i_ar_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_reqs_i[0].ar_valid ), - .ready_o ( slv_resps_o[0].ar_ready ), - .data_i ( slv_reqs_i[0].ar ), - .valid_o ( mst_req_o.ar_valid ), - .ready_i ( mst_resp_i.ar_ready ), - .data_o ( mst_req_o.ar ) - ); - spill_register #( - .T ( mst_r_chan_t ), - .Bypass ( ~SpillR ) - ) i_r_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_resp_i.r_valid ), - .ready_o ( mst_req_o.r_ready ), - .data_i ( mst_resp_i.r ), - .valid_o ( slv_resps_o[0].r_valid ), - .ready_i ( slv_reqs_i[0].r_ready ), - .data_o ( slv_resps_o[0].r ) - ); -// Validate parameters. -// pragma translate_off - `ASSERT_INIT(CorrectIdWidthSlvAw, $bits(slv_reqs_i[0].aw.id) == SlvAxiIDWidth) - `ASSERT_INIT(CorrectIdWidthSlvB, $bits(slv_resps_o[0].b.id) == SlvAxiIDWidth) - `ASSERT_INIT(CorrectIdWidthSlvAr, $bits(slv_reqs_i[0].ar.id) == SlvAxiIDWidth) - `ASSERT_INIT(CorrectIdWidthSlvR, $bits(slv_resps_o[0].r.id) == SlvAxiIDWidth) - `ASSERT_INIT(CorrectIdWidthMstAw, $bits(mst_req_o.aw.id) == SlvAxiIDWidth) - `ASSERT_INIT(CorrectIdWidthMstB, $bits(mst_resp_i.b.id) == SlvAxiIDWidth) - `ASSERT_INIT(CorrectIdWidthMstAr, $bits(mst_req_o.ar.id) == SlvAxiIDWidth) - `ASSERT_INIT(CorrectIdWidthMstR, $bits(mst_resp_i.r.id) == SlvAxiIDWidth) -// pragma translate_on - - // other non degenerate cases - end else begin : gen_mux - - typedef logic [MstIdxBits-1:0] switch_id_t; - - // AXI channels between the ID prepend unit and the rest of the multiplexer - mst_aw_chan_t [NoSlvPorts-1:0] slv_aw_chans; - logic [NoSlvPorts-1:0] slv_aw_valids, slv_aw_readies; - w_chan_t [NoSlvPorts-1:0] slv_w_chans; - logic [NoSlvPorts-1:0] slv_w_valids, slv_w_readies; - mst_b_chan_t [NoSlvPorts-1:0] slv_b_chans; - logic [NoSlvPorts-1:0] slv_b_valids, slv_b_readies; - mst_ar_chan_t [NoSlvPorts-1:0] slv_ar_chans; - logic [NoSlvPorts-1:0] slv_ar_valids, slv_ar_readies; - mst_r_chan_t [NoSlvPorts-1:0] slv_r_chans; - logic [NoSlvPorts-1:0] slv_r_valids, slv_r_readies; - - // These signals are all ID prepended - // AW channel - mst_aw_chan_t mst_aw_chan; - logic mst_aw_valid, mst_aw_ready; - - // AW master handshake internal, so that we are able to stall, if w_fifo is full - logic aw_valid, aw_ready; - - // FF to lock the AW valid signal, when a new arbitration decision is made the decision - // gets pushed into the W FIFO, when it now stalls prevent subsequent pushing - // This FF removes AW to W dependency - logic lock_aw_valid_d, lock_aw_valid_q; - logic load_aw_lock; - - // signals for the FIFO that holds the last switching decision of the AW channel - logic w_fifo_full, w_fifo_empty; - logic w_fifo_push, w_fifo_pop; - switch_id_t w_fifo_data; - - // W channel spill reg - w_chan_t mst_w_chan; - logic mst_w_valid, mst_w_ready; - - // master ID in the b_id - switch_id_t switch_b_id; - - // B channel spill reg - mst_b_chan_t mst_b_chan; - logic mst_b_valid; - - // AR channel for when spill is enabled - mst_ar_chan_t mst_ar_chan; - logic ar_valid, ar_ready; - - // master ID in the r_id - switch_id_t switch_r_id; - - // R channel spill reg - mst_r_chan_t mst_r_chan; - logic mst_r_valid; - - //-------------------------------------- - // ID prepend for all slave ports - //-------------------------------------- - for (genvar i = 0; i < NoSlvPorts; i++) begin : gen_id_prepend - axi_id_prepend #( - .NoBus ( 32'd1 ), // one AXI bus per slave port - .AxiIdWidthSlvPort( SlvAxiIDWidth ), - .AxiIdWidthMstPort( MstAxiIDWidth ), - .slv_aw_chan_t ( slv_aw_chan_t ), - .slv_w_chan_t ( w_chan_t ), - .slv_b_chan_t ( slv_b_chan_t ), - .slv_ar_chan_t ( slv_ar_chan_t ), - .slv_r_chan_t ( slv_r_chan_t ), - .mst_aw_chan_t ( mst_aw_chan_t ), - .mst_w_chan_t ( w_chan_t ), - .mst_b_chan_t ( mst_b_chan_t ), - .mst_ar_chan_t ( mst_ar_chan_t ), - .mst_r_chan_t ( mst_r_chan_t ) - ) i_id_prepend ( - .pre_id_i ( switch_id_t'(i) ), - .slv_aw_chans_i ( slv_reqs_i[i].aw ), - .slv_aw_valids_i ( slv_reqs_i[i].aw_valid ), - .slv_aw_readies_o ( slv_resps_o[i].aw_ready ), - .slv_w_chans_i ( slv_reqs_i[i].w ), - .slv_w_valids_i ( slv_reqs_i[i].w_valid ), - .slv_w_readies_o ( slv_resps_o[i].w_ready ), - .slv_b_chans_o ( slv_resps_o[i].b ), - .slv_b_valids_o ( slv_resps_o[i].b_valid ), - .slv_b_readies_i ( slv_reqs_i[i].b_ready ), - .slv_ar_chans_i ( slv_reqs_i[i].ar ), - .slv_ar_valids_i ( slv_reqs_i[i].ar_valid ), - .slv_ar_readies_o ( slv_resps_o[i].ar_ready ), - .slv_r_chans_o ( slv_resps_o[i].r ), - .slv_r_valids_o ( slv_resps_o[i].r_valid ), - .slv_r_readies_i ( slv_reqs_i[i].r_ready ), - .mst_aw_chans_o ( slv_aw_chans[i] ), - .mst_aw_valids_o ( slv_aw_valids[i] ), - .mst_aw_readies_i ( slv_aw_readies[i] ), - .mst_w_chans_o ( slv_w_chans[i] ), - .mst_w_valids_o ( slv_w_valids[i] ), - .mst_w_readies_i ( slv_w_readies[i] ), - .mst_b_chans_i ( slv_b_chans[i] ), - .mst_b_valids_i ( slv_b_valids[i] ), - .mst_b_readies_o ( slv_b_readies[i] ), - .mst_ar_chans_o ( slv_ar_chans[i] ), - .mst_ar_valids_o ( slv_ar_valids[i] ), - .mst_ar_readies_i ( slv_ar_readies[i] ), - .mst_r_chans_i ( slv_r_chans[i] ), - .mst_r_valids_i ( slv_r_valids[i] ), - .mst_r_readies_o ( slv_r_readies[i] ) - ); - end - - //-------------------------------------- - // AW Channel - //-------------------------------------- - rr_arb_tree #( - .NumIn ( NoSlvPorts ), - .DataType ( mst_aw_chan_t ), - .AxiVldRdy( 1'b1 ), - .LockIn ( 1'b1 ) - ) i_aw_arbiter ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .flush_i( 1'b0 ), - .rr_i ( '0 ), - .req_i ( slv_aw_valids ), - .gnt_o ( slv_aw_readies ), - .data_i ( slv_aw_chans ), - .gnt_i ( aw_ready ), - .req_o ( aw_valid ), - .data_o ( mst_aw_chan ), - .idx_o ( ) - ); - - // control of the AW channel - always_comb begin - // default assignments - lock_aw_valid_d = lock_aw_valid_q; - load_aw_lock = 1'b0; - w_fifo_push = 1'b0; - mst_aw_valid = 1'b0; - aw_ready = 1'b0; - // had a downstream stall, be valid and send the AW along - if (lock_aw_valid_q) begin - mst_aw_valid = 1'b1; - // transaction - if (mst_aw_ready) begin - aw_ready = 1'b1; - lock_aw_valid_d = 1'b0; - load_aw_lock = 1'b1; - end - end else begin - if (!w_fifo_full && aw_valid) begin - mst_aw_valid = 1'b1; - w_fifo_push = 1'b1; - if (mst_aw_ready) begin - aw_ready = 1'b1; - end else begin - // go to lock if transaction not in this cycle - lock_aw_valid_d = 1'b1; - load_aw_lock = 1'b1; - end - end - end - end - - `FFLARN(lock_aw_valid_q, lock_aw_valid_d, load_aw_lock, '0, clk_i, rst_ni) - - fifo_v3 #( - .FALL_THROUGH ( FallThrough ), - .DEPTH ( MaxWTrans ), - .dtype ( switch_id_t ) - ) i_w_fifo ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .flush_i ( 1'b0 ), - .testmode_i( test_i ), - .full_o ( w_fifo_full ), - .empty_o ( w_fifo_empty ), - .usage_o ( ), - .data_i ( mst_aw_chan.id[SlvAxiIDWidth+:MstIdxBits] ), - .push_i ( w_fifo_push ), - .data_o ( w_fifo_data ), - .pop_i ( w_fifo_pop ) - ); - - spill_register #( - .T ( mst_aw_chan_t ), - .Bypass ( ~SpillAw ) // Param indicated that we want a spill reg - ) i_aw_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_aw_valid ), - .ready_o ( mst_aw_ready ), - .data_i ( mst_aw_chan ), - .valid_o ( mst_req_o.aw_valid ), - .ready_i ( mst_resp_i.aw_ready ), - .data_o ( mst_req_o.aw ) - ); - - //-------------------------------------- - // W Channel - //-------------------------------------- - // multiplexer - assign mst_w_chan = slv_w_chans[w_fifo_data]; - always_comb begin - // default assignments - mst_w_valid = 1'b0; - slv_w_readies = '0; - w_fifo_pop = 1'b0; - // control - if (!w_fifo_empty) begin - // connect the handshake - mst_w_valid = slv_w_valids[w_fifo_data]; - slv_w_readies[w_fifo_data] = mst_w_ready; - // pop FIFO on a last transaction - w_fifo_pop = slv_w_valids[w_fifo_data] & mst_w_ready & mst_w_chan.last; - end - end - - spill_register #( - .T ( w_chan_t ), - .Bypass ( ~SpillW ) - ) i_w_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_w_valid ), - .ready_o ( mst_w_ready ), - .data_i ( mst_w_chan ), - .valid_o ( mst_req_o.w_valid ), - .ready_i ( mst_resp_i.w_ready ), - .data_o ( mst_req_o.w ) - ); - - //-------------------------------------- - // B Channel - //-------------------------------------- - // replicate B channels - assign slv_b_chans = {NoSlvPorts{mst_b_chan}}; - // control B channel handshake - assign switch_b_id = mst_b_chan.id[SlvAxiIDWidth+:MstIdxBits]; - assign slv_b_valids = (mst_b_valid) ? (1 << switch_b_id) : '0; - - spill_register #( - .T ( mst_b_chan_t ), - .Bypass ( ~SpillB ) - ) i_b_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_resp_i.b_valid ), - .ready_o ( mst_req_o.b_ready ), - .data_i ( mst_resp_i.b ), - .valid_o ( mst_b_valid ), - .ready_i ( slv_b_readies[switch_b_id] ), - .data_o ( mst_b_chan ) - ); - - //-------------------------------------- - // AR Channel - //-------------------------------------- - rr_arb_tree #( - .NumIn ( NoSlvPorts ), - .DataType ( mst_ar_chan_t ), - .AxiVldRdy( 1'b1 ), - .LockIn ( 1'b1 ) - ) i_ar_arbiter ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .flush_i( 1'b0 ), - .rr_i ( '0 ), - .req_i ( slv_ar_valids ), - .gnt_o ( slv_ar_readies ), - .data_i ( slv_ar_chans ), - .gnt_i ( ar_ready ), - .req_o ( ar_valid ), - .data_o ( mst_ar_chan ), - .idx_o ( ) - ); - - spill_register #( - .T ( mst_ar_chan_t ), - .Bypass ( ~SpillAr ) - ) i_ar_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( ar_valid ), - .ready_o ( ar_ready ), - .data_i ( mst_ar_chan ), - .valid_o ( mst_req_o.ar_valid ), - .ready_i ( mst_resp_i.ar_ready ), - .data_o ( mst_req_o.ar ) - ); - - //-------------------------------------- - // R Channel - //-------------------------------------- - // replicate R channels - assign slv_r_chans = {NoSlvPorts{mst_r_chan}}; - // R channel handshake control - assign switch_r_id = mst_r_chan.id[SlvAxiIDWidth+:MstIdxBits]; - assign slv_r_valids = (mst_r_valid) ? (1 << switch_r_id) : '0; - - spill_register #( - .T ( mst_r_chan_t ), - .Bypass ( ~SpillR ) - ) i_r_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_resp_i.r_valid ), - .ready_o ( mst_req_o.r_ready ), - .data_i ( mst_resp_i.r ), - .valid_o ( mst_r_valid ), - .ready_i ( slv_r_readies[switch_r_id] ), - .data_o ( mst_r_chan ) - ); - end + axi_mcast_mux #( + .SlvAxiIDWidth(SlvAxiIDWidth), + .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), + .NoSlvPorts (NoSlvPorts), + .MaxWTrans (MaxWTrans), + .FallThrough (FallThrough), + .SpillAw (SpillAw), + .SpillW (SpillW), + .SpillB (SpillB), + .SpillAr (SpillAr), + .SpillR (SpillR) + ) i_axi_mcast_mux ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .test_i (test_i), + .slv_is_mcast_i ('0), + .slv_aw_commit_i('0), + .slv_reqs_i (slv_reqs_i), + .slv_resps_o (slv_resps_o), + .mst_req_o (mst_req_o), + .mst_resp_i (mst_resp_i) + ); -// pragma translate_off -`ifndef VERILATOR - initial begin - assert (SlvAxiIDWidth > 0) else $fatal(1, "AXI ID width of slave ports must be non-zero!"); - assert (NoSlvPorts > 0) else $fatal(1, "Number of slave ports must be non-zero!"); - assert (MaxWTrans > 0) - else $fatal(1, "Maximum number of outstanding writes must be non-zero!"); - assert (MstAxiIDWidth >= SlvAxiIDWidth + $clog2(NoSlvPorts)) - else $fatal(1, "AXI ID width of master ports must be wide enough to identify slave ports!"); - // Assert ID widths (one slave is sufficient since they all have the same type). - assert ($unsigned($bits(slv_reqs_i[0].aw.id)) == SlvAxiIDWidth) - else $fatal(1, "ID width of AW channel of slave ports does not match parameter!"); - assert ($unsigned($bits(slv_reqs_i[0].ar.id)) == SlvAxiIDWidth) - else $fatal(1, "ID width of AR channel of slave ports does not match parameter!"); - assert ($unsigned($bits(slv_resps_o[0].b.id)) == SlvAxiIDWidth) - else $fatal(1, "ID width of B channel of slave ports does not match parameter!"); - assert ($unsigned($bits(slv_resps_o[0].r.id)) == SlvAxiIDWidth) - else $fatal(1, "ID width of R channel of slave ports does not match parameter!"); - assert ($unsigned($bits(mst_req_o.aw.id)) == MstAxiIDWidth) - else $fatal(1, "ID width of AW channel of master port is wrong!"); - assert ($unsigned($bits(mst_req_o.ar.id)) == MstAxiIDWidth) - else $fatal(1, "ID width of AR channel of master port is wrong!"); - assert ($unsigned($bits(mst_resp_i.b.id)) == MstAxiIDWidth) - else $fatal(1, "ID width of B channel of master port is wrong!"); - assert ($unsigned($bits(mst_resp_i.r.id)) == MstAxiIDWidth) - else $fatal(1, "ID width of R channel of master port is wrong!"); - end -`endif -// pragma translate_on endmodule // interface wrap diff --git a/src/axi_pkg.sv b/src/axi_pkg.sv index f14d6c36e..d3937433c 100644 --- a/src/axi_pkg.sv +++ b/src/axi_pkg.sv @@ -519,6 +519,12 @@ package axi_pkg; /// Each master port can have multiple rules, should have however at least one. /// If a transaction can not be routed the xbar will answer with an `axi_pkg::RESP_DECERR`. int unsigned NoAddrRules; + /// The number of address rules to be considered for multicasting, + /// assumed to be at the start of `addr_map_i`. + int unsigned NoMulticastRules; + /// Number of master ports of the crossbar which can be targets of a multicast request. + /// These are assumed to be connected at the lower indices. + int unsigned NoMulticastPorts; } xbar_cfg_t; /// Commonly used rule types for `axi_xbar` (64-bit addresses). diff --git a/src/axi_test.sv b/src/axi_test.sv index 5300a86b2..601bcb66e 100644 --- a/src/axi_test.sv +++ b/src/axi_test.sv @@ -15,6 +15,7 @@ // - Fabian Schuiki // - Thomas Benz // - Matheus Cavalcante +// - Luca Colagrande /// A set of testbench utilities for AXI interfaces. @@ -710,6 +711,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, @@ -770,6 +773,8 @@ package axi_test; } traffic_shape[$]; int unsigned max_cprob; + int unsigned mcast_prob; + function new( virtual AXI_BUS_DV #( .AXI_ADDR_WIDTH(AW), @@ -808,6 +813,13 @@ package axi_test; atop_resp_r = '0; endfunction + // Sets the probability to generate a transaction with a non-zero multicast mask. + // `prob` should be a percentage, as Questa 2022.3 doesn't support real types + // in SystemVerilog `dist` constraints. + function void set_multicast_probability(int unsigned 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 @@ -867,6 +879,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 @@ -964,11 +978,21 @@ package axi_test; ax_beat.ax_addr = axi_pkg::aligned_addr(addr, axi_pkg::size_t'(SIZE_ALIGN) ); 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 := (100 - mcast_prob), 1 := mcast_prob}; + !mcast -> mcast_mask == 0; + mcast -> mcast_mask != 0; + }; assert(rand_success); + ax_beat.ax_user = mcast_mask; + end else begin + ax_beat.ax_user = user; + 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; ax_beat.ax_qos = qos; - ax_beat.ax_user = user; return ax_beat; endfunction @@ -980,6 +1004,13 @@ package axi_test; // We can emit ATOPs only if INCR bursts are allowed. $warning("ATOP suppressed because INCR bursts are disabled!"); beat.ax_atop[5:4] = 2'b00; + break; + end + if (ENABLE_MULTICAST && beat.ax_atop[5:4] != 2'b00 && beat.ax_user != '0) begin + // We can emit ATOPs only if current burst is not a multicast. + $warning("ATOP suppressed because burst is a multicast!"); + beat.ax_atop[5:4] = 2'b00; + break; end if (beat.ax_atop[5:4] != 2'b00) begin // ATOP // Determine `ax_atop`. @@ -1300,7 +1331,8 @@ package axi_test; aw_done = 1'b0; fork // Cache-Partition: randomize the patid - automatic user_t ax_user = rand_user(AX_USER_RANGE, AX_USER_RAND); + // Multicast: randomize each burst separately + automatic user_t ax_user = ENABLE_MULTICAST ? '0 : rand_user(AX_USER_RANGE, AX_USER_RAND); begin send_ars(n_reads, ax_user); ar_done = 1'b1; @@ -2613,7 +2645,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 @@ -2643,6 +2676,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[$]; @@ -2656,6 +2693,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 @@ -2664,8 +2703,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/src/axi_xbar.sv b/src/axi_xbar.sv index f41e9a660..926d1ebc9 100644 --- a/src/axi_xbar.sv +++ b/src/axi_xbar.sv @@ -90,82 +90,47 @@ import cf_math_pkg::idx_width; input logic [Cfg.NoSlvPorts-1:0][MstPortsIdxWidth-1:0] default_mst_port_i ); - // signals into the axi_muxes, are of type slave as the multiplexer extends the ID - slv_req_t [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_reqs; - slv_resp_t [Cfg.NoMstPorts-1:0][Cfg.NoSlvPorts-1:0] mst_resps; + rule_t [Cfg.NoSlvPorts-1:0] default_rules; + for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_default_rules + assign default_rules[i] = '{ + idx: default_mst_port_i[i], + start_addr: '0, + end_addr: '0 + }; + end - axi_xbar_unmuxed #( - .Cfg (Cfg), - .ATOPs (ATOPs), - .Connectivity (Connectivity), - .aw_chan_t (slv_aw_chan_t), - .w_chan_t (w_chan_t), - .b_chan_t (slv_b_chan_t), - .ar_chan_t (slv_ar_chan_t), - .r_chan_t (slv_r_chan_t), - .req_t (slv_req_t), - .resp_t (slv_resp_t), - .rule_t (rule_t) - ) i_xbar_unmuxed ( - .clk_i, - .rst_ni, - .test_i, - .slv_ports_req_i, - .slv_ports_resp_o, - .mst_ports_req_o (mst_reqs), - .mst_ports_resp_i (mst_resps), - .addr_map_i, - .en_default_mst_port_i, - .default_mst_port_i + axi_mcast_xbar #( + .Cfg (Cfg), + .ATOPs (ATOPs), + .Connectivity (Connectivity), + .MulticastConnectivity('1), + .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_axi_mcast_xbar ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .test_i (test_i), + .slv_ports_req_i (slv_ports_req_i), + .slv_ports_resp_o (slv_ports_resp_o), + .mst_ports_req_o (mst_ports_req_o), + .mst_ports_resp_i (mst_ports_resp_i), + .addr_map_i (addr_map_i), + .en_default_mst_port_i(en_default_mst_port_i), + .default_mst_port_i (default_rules) ); - 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] ) - ) 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] ) - ); - end - - // pragma translate_off - `ifndef VERILATOR - `ifndef XSIM - initial begin : check_params - id_slv_req_ports: assert ($bits(slv_ports_req_i[0].aw.id ) == Cfg.AxiIdWidthSlvPorts) else - $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.")); - end - `endif - `endif - // pragma translate_on endmodule `include "axi/assign.svh" diff --git a/src/axi_xbar_unmuxed.sv b/src/axi_xbar_unmuxed.sv index 3d7065444..fa92fc2c8 100644 --- a/src/axi_xbar_unmuxed.sv +++ b/src/axi_xbar_unmuxed.sv @@ -69,202 +69,49 @@ import cf_math_pkg::idx_width; /// Address map array input for the crossbar. This map is global for the whole module. /// It is used for routing the transactions to the respective master ports. /// Each master port can have multiple different rules. - input rule_t [Cfg.NoAddrRules-1:0] addr_map_i, + input rule_t [Cfg.NoAddrRules-1:0] addr_map_i, /// Enable default master port. - input logic [Cfg.NoSlvPorts-1:0] en_default_mst_port_i, + input logic [Cfg.NoSlvPorts-1:0] en_default_mst_port_i, /// Enables a default master port for each slave port. When this is enabled unmapped /// transactions get issued at the master port given by `default_mst_port_i`. /// When not used, tie to `'0`. - input logic [Cfg.NoSlvPorts-1:0][MstPortsIdxWidth-1:0] default_mst_port_i + input logic [Cfg.NoSlvPorts-1:0][MstPortsIdxWidth-1:0] default_mst_port_i ); - // Address tpye for inidvidual address signals - typedef logic [Cfg.AxiAddrWidth-1:0] addr_t; - // to account for the decoding error slave - localparam int unsigned MstPortsIdxWidthOne = - (Cfg.NoMstPorts == 32'd1) ? 32'd1 : unsigned'($clog2(Cfg.NoMstPorts + 1)); - typedef logic [MstPortsIdxWidthOne-1:0] mst_port_idx_t; - - // signals from the axi_demuxes, one index more for decode error - req_t [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts:0] slv_reqs; - resp_t [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts:0] slv_resps; - - // workaround for issue #133 (problem with vsim 10.6c) - localparam int unsigned cfg_NoMstPorts = Cfg.NoMstPorts; - - for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_slv_port_demux - logic [MstPortsIdxWidth-1:0] dec_aw, dec_ar; - mst_port_idx_t slv_aw_select, slv_ar_select; - logic dec_aw_valid, dec_aw_error; - logic dec_ar_valid, dec_ar_error; - - addr_decode #( - .NoIndices ( Cfg.NoMstPorts ), - .NoRules ( Cfg.NoAddrRules ), - .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 ), - .idx_o ( dec_aw ), - .dec_valid_o ( dec_aw_valid ), - .dec_error_o ( dec_aw_error ), - .en_default_idx_i ( en_default_mst_port_i[i] ), - .default_idx_i ( default_mst_port_i[i] ) - ); - - addr_decode #( - .NoIndices ( Cfg.NoMstPorts ), - .addr_t ( addr_t ), - .NoRules ( Cfg.NoAddrRules ), - .rule_t ( rule_t ) - ) i_axi_ar_decode ( - .addr_i ( slv_ports_req_i[i].ar.addr ), - .addr_map_i ( addr_map_i ), - .idx_o ( dec_ar ), - .dec_valid_o ( dec_ar_valid ), - .dec_error_o ( dec_ar_error ), - .en_default_idx_i ( en_default_mst_port_i[i] ), - .default_idx_i ( default_mst_port_i[i] ) - ); - - assign slv_aw_select = (dec_aw_error) ? - mst_port_idx_t'(Cfg.NoMstPorts) : mst_port_idx_t'(dec_aw); - assign slv_ar_select = (dec_ar_error) ? - mst_port_idx_t'(Cfg.NoMstPorts) : mst_port_idx_t'(dec_ar); - - // make sure that the default slave does not get changed, if there is an unserved Ax - // pragma translate_off - `ifndef VERILATOR - `ifndef XSIM - default disable iff (~rst_ni); - default_aw_mst_port_en: assert property( - @(posedge clk_i) (slv_ports_req_i[i].aw_valid && !slv_ports_resp_o[i].aw_ready) - |=> $stable(en_default_mst_port_i[i])) - else $fatal (1, $sformatf("It is not allowed to change the default mst port\ - enable, when there is an unserved Aw beat. Slave Port: %0d", i)); - default_aw_mst_port: assert property( - @(posedge clk_i) (slv_ports_req_i[i].aw_valid && !slv_ports_resp_o[i].aw_ready) - |=> $stable(default_mst_port_i[i])) - else $fatal (1, $sformatf("It is not allowed to change the default mst port\ - when there is an unserved Aw beat. Slave Port: %0d", i)); - default_ar_mst_port_en: assert property( - @(posedge clk_i) (slv_ports_req_i[i].ar_valid && !slv_ports_resp_o[i].ar_ready) - |=> $stable(en_default_mst_port_i[i])) - else $fatal (1, $sformatf("It is not allowed to change the enable, when\ - there is an unserved Ar beat. Slave Port: %0d", i)); - default_ar_mst_port: assert property( - @(posedge clk_i) (slv_ports_req_i[i].ar_valid && !slv_ports_resp_o[i].ar_ready) - |=> $stable(default_mst_port_i[i])) - else $fatal (1, $sformatf("It is not allowed to change the default mst port\ - when there is an unserved Ar beat. Slave Port: %0d", i)); - `endif - `endif - // pragma translate_on - axi_demux #( - .AxiIdWidth ( Cfg.AxiIdWidthSlvPorts ), // ID Width - .AtopSupport ( ATOPs ), - .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 ( req_t ), - .axi_resp_t ( resp_t ), - .NoMstPorts ( Cfg.NoMstPorts + 1 ), - .MaxTrans ( Cfg.MaxMstTrans ), - .AxiLookBits ( Cfg.AxiIdUsedSlvPorts ), - .UniqueIds ( Cfg.UniqueIds ), - .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] ) - ); - - axi_err_slv #( - .AxiIdWidth ( Cfg.AxiIdWidthSlvPorts ), - .axi_req_t ( req_t ), - .axi_resp_t ( 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] ) - ); + rule_t [Cfg.NoSlvPorts-1:0] default_rules; + for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_default_rules + assign default_rules[i] = '{ + idx: default_mst_port_i[i], + start_addr: '0, + end_addr: '0 + }; end - // cross all channels - for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_xbar_slv_cross - for (genvar j = 0; j < Cfg.NoMstPorts; j++) begin : gen_xbar_mst_cross - if (Connectivity[i][j]) begin : gen_connection - axi_multicut #( - .NoCuts ( Cfg.PipelineStages ), - .aw_chan_t ( aw_chan_t ), - .w_chan_t ( w_chan_t ), - .b_chan_t ( b_chan_t ), - .ar_chan_t ( ar_chan_t ), - .r_chan_t ( r_chan_t ), - .axi_req_t ( req_t ), - .axi_resp_t ( resp_t ) - ) i_axi_multicut_xbar_pipeline ( - .clk_i, - .rst_ni, - .slv_req_i ( slv_reqs[i][j] ), - .slv_resp_o ( slv_resps[i][j] ), - .mst_req_o ( mst_ports_req_o[j][i] ), - .mst_resp_i ( mst_ports_resp_i[j][i] ) - ); - - end else begin : gen_no_connection - assign mst_ports_req_o[j][i] = '0; - axi_err_slv #( - .AxiIdWidth ( Cfg.AxiIdWidthSlvPorts ), - .axi_req_t ( req_t ), - .axi_resp_t ( 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] ) - ); - end - end - end + axi_mcast_xbar_unmuxed #( + .Cfg (Cfg), + .ATOPs (ATOPs), + .Connectivity (Connectivity), + .aw_chan_t (aw_chan_t), + .w_chan_t (w_chan_t), + .b_chan_t (b_chan_t), + .ar_chan_t (ar_chan_t), + .r_chan_t (r_chan_t), + .req_t (req_t), + .resp_t (resp_t), + .rule_t (rule_t) + ) i_axi_mcast_xbar_unmuxed ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .test_i (test_i), + .slv_ports_req_i (slv_ports_req_i), + .slv_ports_resp_o (slv_ports_resp_o), + .mst_ports_req_o (mst_ports_req_o), + .mst_ports_resp_i (mst_ports_resp_i), + .addr_map_i (addr_map_i), + .en_default_mst_port_i(en_default_mst_port_i), + .default_mst_port_i (default_rules) + ); - // pragma translate_off - `ifndef VERILATOR - `ifndef XSIM - initial begin : check_params - id_slv_req_ports: assert ($bits(slv_ports_req_i[0].aw.id ) == Cfg.AxiIdWidthSlvPorts) else - $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.")); - end - `endif - `endif - // pragma translate_on endmodule `ifndef VCS diff --git a/test/axi_synth_bench.sv b/test/axi_synth_bench.sv index 334846abc..a6a358fa1 100644 --- a/test/axi_synth_bench.sv +++ b/test/axi_synth_bench.sv @@ -13,6 +13,7 @@ // - Andreas Kurth // - Fabian Schuiki // - Michael Rogenmoser +// - Luca Colagrande /// A synthesis test bench which instantiates various adapter variants. module axi_synth_bench ( @@ -793,3 +794,1015 @@ module synth_axi_lite_dw_converter #( ); endmodule + + +module synth_axi_xbar #( + parameter int unsigned NoSlvMst = 32'd8, // Max 16, as the addr rules defined below + parameter bit EnableMulticast = 0, + parameter bit UniqueIds = 0, + parameter axi_pkg::xbar_latency_e LatencyMode = axi_pkg::NO_LATENCY, + // axi configuration + parameter int unsigned AxiIdWidthMasters = 4, + parameter int unsigned AxiIdUsed = 3, // Has to be <= AxiIdWidthMasters + parameter int unsigned AxiIdWidthSlaves = AxiIdWidthMasters + $clog2(NoSlvMst), + parameter int unsigned AxiAddrWidth = 32, // Axi Address Width + parameter int unsigned AxiDataWidth = 32, // Axi Data Width + parameter int unsigned AxiStrbWidth = AxiDataWidth / 8, + parameter int unsigned AxiUserWidth = 1, + parameter int unsigned AxiAwUserWidth = EnableMulticast ? AxiAddrWidth : 1, + // axi types + parameter type id_mst_t = logic [AxiIdWidthSlaves-1:0], + parameter type id_slv_t = logic [AxiIdWidthMasters-1:0], + parameter type addr_t = logic [AxiAddrWidth-1:0], + parameter type data_t = logic [AxiDataWidth-1:0], + parameter type strb_t = logic [AxiStrbWidth-1:0], + parameter type user_t = logic [AxiUserWidth-1:0], + parameter type aw_user_t = struct packed {logic [AxiAwUserWidth-1:0] mcast;} +) ( + input logic clk_i, + input logic rst_ni, + + /*********************************** + /* Slave ports request inputs + ***********************************/ + + // AW + input id_slv_t [NoSlvMst-1:0] slv_aw_id, + input addr_t [NoSlvMst-1:0] slv_aw_addr, + input axi_pkg::len_t [NoSlvMst-1:0] slv_aw_len, + input axi_pkg::size_t [NoSlvMst-1:0] slv_aw_size, + input axi_pkg::burst_t [NoSlvMst-1:0] slv_aw_burst, + input logic [NoSlvMst-1:0] slv_aw_lock, + input axi_pkg::cache_t [NoSlvMst-1:0] slv_aw_cache, + input axi_pkg::prot_t [NoSlvMst-1:0] slv_aw_prot, + input axi_pkg::qos_t [NoSlvMst-1:0] slv_aw_qos, + input axi_pkg::region_t [NoSlvMst-1:0] slv_aw_region, + input axi_pkg::atop_t [NoSlvMst-1:0] slv_aw_atop, + input aw_user_t [NoSlvMst-1:0] slv_aw_user, + input logic [NoSlvMst-1:0] slv_aw_valid, + // W + input data_t [NoSlvMst-1:0] slv_w_data, + input strb_t [NoSlvMst-1:0] slv_w_strb, + input logic [NoSlvMst-1:0] slv_w_last, + input user_t [NoSlvMst-1:0] slv_w_user, + input logic [NoSlvMst-1:0] slv_w_valid, + // B + input logic [NoSlvMst-1:0] slv_b_ready, + // AR + input id_slv_t [NoSlvMst-1:0] slv_ar_id, + input addr_t [NoSlvMst-1:0] slv_ar_addr, + input axi_pkg::len_t [NoSlvMst-1:0] slv_ar_len, + input axi_pkg::size_t [NoSlvMst-1:0] slv_ar_size, + input axi_pkg::burst_t [NoSlvMst-1:0] slv_ar_burst, + input logic [NoSlvMst-1:0] slv_ar_lock, + input axi_pkg::cache_t [NoSlvMst-1:0] slv_ar_cache, + input axi_pkg::prot_t [NoSlvMst-1:0] slv_ar_prot, + input axi_pkg::qos_t [NoSlvMst-1:0] slv_ar_qos, + input axi_pkg::region_t [NoSlvMst-1:0] slv_ar_region, + input user_t [NoSlvMst-1:0] slv_ar_user, + input logic [NoSlvMst-1:0] slv_ar_valid, + // R + input logic [NoSlvMst-1:0] slv_r_ready, + + /*********************************** + /* Slave ports response outputs + ***********************************/ + + // AW + output logic [NoSlvMst-1:0] slv_aw_ready, + // AR + output logic [NoSlvMst-1:0] slv_ar_ready, + // W + output logic [NoSlvMst-1:0] slv_w_ready, + // B + output logic [NoSlvMst-1:0] slv_b_valid, + output id_slv_t [NoSlvMst-1:0] slv_b_id, + output axi_pkg::resp_t [NoSlvMst-1:0] slv_b_resp, + output user_t [NoSlvMst-1:0] slv_b_user, + // R + output logic [NoSlvMst-1:0] slv_r_valid, + output id_slv_t [NoSlvMst-1:0] slv_r_id, + output data_t [NoSlvMst-1:0] slv_r_data, + output axi_pkg::resp_t [NoSlvMst-1:0] slv_r_resp, + output logic [NoSlvMst-1:0] slv_r_last, + output user_t [NoSlvMst-1:0] slv_r_user, + + /*********************************** + /* Master ports request outputs + ***********************************/ + + // AW + output id_mst_t [NoSlvMst-1:0] mst_aw_id, + output addr_t [NoSlvMst-1:0] mst_aw_addr, + output axi_pkg::len_t [NoSlvMst-1:0] mst_aw_len, + output axi_pkg::size_t [NoSlvMst-1:0] mst_aw_size, + output axi_pkg::burst_t [NoSlvMst-1:0] mst_aw_burst, + output logic [NoSlvMst-1:0] mst_aw_lock, + output axi_pkg::cache_t [NoSlvMst-1:0] mst_aw_cache, + output axi_pkg::prot_t [NoSlvMst-1:0] mst_aw_prot, + output axi_pkg::qos_t [NoSlvMst-1:0] mst_aw_qos, + output axi_pkg::region_t [NoSlvMst-1:0] mst_aw_region, + output axi_pkg::atop_t [NoSlvMst-1:0] mst_aw_atop, + output aw_user_t [NoSlvMst-1:0] mst_aw_user, + output logic [NoSlvMst-1:0] mst_aw_valid, + // W + output data_t [NoSlvMst-1:0] mst_w_data, + output strb_t [NoSlvMst-1:0] mst_w_strb, + output logic [NoSlvMst-1:0] mst_w_last, + output user_t [NoSlvMst-1:0] mst_w_user, + output logic [NoSlvMst-1:0] mst_w_valid, + // B + output logic [NoSlvMst-1:0] mst_b_ready, + // AR + output id_mst_t [NoSlvMst-1:0] mst_ar_id, + output addr_t [NoSlvMst-1:0] mst_ar_addr, + output axi_pkg::len_t [NoSlvMst-1:0] mst_ar_len, + output axi_pkg::size_t [NoSlvMst-1:0] mst_ar_size, + output axi_pkg::burst_t [NoSlvMst-1:0] mst_ar_burst, + output logic [NoSlvMst-1:0] mst_ar_lock, + output axi_pkg::cache_t [NoSlvMst-1:0] mst_ar_cache, + output axi_pkg::prot_t [NoSlvMst-1:0] mst_ar_prot, + output axi_pkg::qos_t [NoSlvMst-1:0] mst_ar_qos, + output axi_pkg::region_t [NoSlvMst-1:0] mst_ar_region, + output user_t [NoSlvMst-1:0] mst_ar_user, + output logic [NoSlvMst-1:0] mst_ar_valid, + // R + output logic [NoSlvMst-1:0] mst_r_ready, + + /*********************************** + /* Master ports response inputs + ***********************************/ + + // AW + input logic [NoSlvMst-1:0] mst_aw_ready, + // AR + input logic [NoSlvMst-1:0] mst_ar_ready, + // W + input logic [NoSlvMst-1:0] mst_w_ready, + // B + input logic [NoSlvMst-1:0] mst_b_valid, + input id_mst_t [NoSlvMst-1:0] mst_b_id, + input axi_pkg::resp_t [NoSlvMst-1:0] mst_b_resp, + input user_t [NoSlvMst-1:0] mst_b_user, + // R + input logic [NoSlvMst-1:0] mst_r_valid, + input id_mst_t [NoSlvMst-1:0] mst_r_id, + input data_t [NoSlvMst-1:0] mst_r_data, + input axi_pkg::resp_t [NoSlvMst-1:0] mst_r_resp, + input logic [NoSlvMst-1:0] mst_r_last, + input user_t [NoSlvMst-1:0] mst_r_user + +); + + localparam axi_pkg::xbar_cfg_t xbar_cfg = '{ + NoSlvPorts: NoSlvMst, + NoMstPorts: NoSlvMst, + MaxMstTrans: 10, + MaxSlvTrans: 6, + FallThrough: 1'b0, + LatencyMode: LatencyMode, + PipelineStages: 0, + AxiIdWidthSlvPorts: AxiIdWidthMasters, + AxiIdUsedSlvPorts: AxiIdUsed, + UniqueIds: UniqueIds, + AxiAddrWidth: AxiAddrWidth, + AxiDataWidth: AxiDataWidth, + NoAddrRules: NoSlvMst, + NoMulticastRules: EnableMulticast ? NoSlvMst : 0, + NoMulticastPorts: EnableMulticast ? NoSlvMst : 0 + }; + + typedef axi_pkg::xbar_rule_32_t rule_t; // Has to be the same width as axi addr + + `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) + + `AXI_TYPEDEF_AR_CHAN_T(mst_ar_chan_t, addr_t, id_mst_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(slv_ar_chan_t, addr_t, id_slv_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(mst_r_chan_t, data_t, id_mst_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(slv_r_chan_t, data_t, id_slv_t, user_t) + + `AXI_TYPEDEF_REQ_T(mst_req_t, mst_aw_chan_t, w_chan_t, mst_ar_chan_t) + `AXI_TYPEDEF_RESP_T(mst_resp_t, mst_b_chan_t, mst_r_chan_t) + `AXI_TYPEDEF_REQ_T(slv_req_t, slv_aw_chan_t, w_chan_t, slv_ar_chan_t) + `AXI_TYPEDEF_RESP_T(slv_resp_t, slv_b_chan_t, slv_r_chan_t) + + // Each slave has its own address range: + localparam rule_t [xbar_cfg.NoAddrRules-1:0] addr_map = addr_map_gen(); + + function rule_t [xbar_cfg.NoAddrRules-1:0] addr_map_gen (); + for (int unsigned i = 0; i < xbar_cfg.NoAddrRules; i++) begin + addr_map_gen[i] = rule_t'{ + idx: unsigned'(i), + start_addr: i * 32'h0000_2000, + end_addr: (i+1) * 32'h0000_2000, + default: '0 + }; + end + endfunction + + slv_req_t [NoSlvMst-1:0] slv_reqs; + mst_req_t [NoSlvMst-1:0] mst_reqs; + slv_resp_t [NoSlvMst-1:0] slv_resps; + mst_resp_t [NoSlvMst-1:0] mst_resps; + + // Connect XBAR interfaces + generate + for (genvar i = 0; i < NoSlvMst; i++) begin : g_connect_slv_port + // Request + assign slv_reqs[i].aw.id = slv_aw_id[i]; + assign slv_reqs[i].aw.addr = slv_aw_addr[i]; + assign slv_reqs[i].aw.len = slv_aw_len[i]; + assign slv_reqs[i].aw.size = slv_aw_size[i]; + assign slv_reqs[i].aw.burst = slv_aw_burst[i]; + assign slv_reqs[i].aw.lock = slv_aw_lock[i]; + assign slv_reqs[i].aw.cache = slv_aw_cache[i]; + assign slv_reqs[i].aw.prot = slv_aw_prot[i]; + assign slv_reqs[i].aw.qos = slv_aw_qos[i]; + assign slv_reqs[i].aw.region = slv_aw_region[i]; + assign slv_reqs[i].aw.atop = slv_aw_atop[i]; + assign slv_reqs[i].aw.user = slv_aw_user[i]; + assign slv_reqs[i].aw_valid = slv_aw_valid[i]; + assign slv_reqs[i].w.data = slv_w_data[i]; + assign slv_reqs[i].w.strb = slv_w_strb[i]; + assign slv_reqs[i].w.last = slv_w_last[i]; + assign slv_reqs[i].w.user = slv_w_user[i]; + assign slv_reqs[i].w_valid = slv_w_valid[i]; + assign slv_reqs[i].b_ready = slv_b_ready[i]; + assign slv_reqs[i].ar.id = slv_ar_id[i]; + assign slv_reqs[i].ar.addr = slv_ar_addr[i]; + assign slv_reqs[i].ar.len = slv_ar_len[i]; + assign slv_reqs[i].ar.size = slv_ar_size[i]; + assign slv_reqs[i].ar.burst = slv_ar_burst[i]; + assign slv_reqs[i].ar.lock = slv_ar_lock[i]; + assign slv_reqs[i].ar.cache = slv_ar_cache[i]; + assign slv_reqs[i].ar.prot = slv_ar_prot[i]; + assign slv_reqs[i].ar.qos = slv_ar_qos[i]; + assign slv_reqs[i].ar.region = slv_ar_region[i]; + assign slv_reqs[i].ar.user = slv_ar_user[i]; + assign slv_reqs[i].ar_valid = slv_ar_valid[i]; + assign slv_reqs[i].r_ready = slv_r_ready[i]; + // Response + assign slv_aw_ready[i] = slv_resps[i].aw_ready; + assign slv_ar_ready[i] = slv_resps[i].ar_ready; + assign slv_w_ready[i] = slv_resps[i].w_ready; + assign slv_b_valid[i] = slv_resps[i].b_valid; + assign slv_b_id[i] = slv_resps[i].b.id; + assign slv_b_resp[i] = slv_resps[i].b.resp; + assign slv_b_user[i] = slv_resps[i].b.user; + assign slv_r_valid[i] = slv_resps[i].r_valid; + assign slv_r_id[i] = slv_resps[i].r.id; + assign slv_r_data[i] = slv_resps[i].r.data; + assign slv_r_resp[i] = slv_resps[i].r.resp; + assign slv_r_last[i] = slv_resps[i].r.last; + assign slv_r_user[i] = slv_resps[i].r.user; + end + + for (genvar i = 0; i < NoSlvMst; i++) begin : g_connect_mst_port + // Request + assign mst_aw_id[i] = mst_reqs[i].aw.id; + assign mst_aw_addr[i] = mst_reqs[i].aw.addr; + assign mst_aw_len[i] = mst_reqs[i].aw.len; + assign mst_aw_size[i] = mst_reqs[i].aw.size; + assign mst_aw_burst[i] = mst_reqs[i].aw.burst; + assign mst_aw_lock[i] = mst_reqs[i].aw.lock; + assign mst_aw_cache[i] = mst_reqs[i].aw.cache; + assign mst_aw_prot[i] = mst_reqs[i].aw.prot; + assign mst_aw_qos[i] = mst_reqs[i].aw.qos; + assign mst_aw_region[i] = mst_reqs[i].aw.region; + assign mst_aw_atop[i] = mst_reqs[i].aw.atop; + assign mst_aw_user[i] = mst_reqs[i].aw.user; + assign mst_aw_valid[i] = mst_reqs[i].aw_valid; + assign mst_w_data[i] = mst_reqs[i].w.data; + assign mst_w_strb[i] = mst_reqs[i].w.strb; + assign mst_w_last[i] = mst_reqs[i].w.last; + assign mst_w_user[i] = mst_reqs[i].w.user; + assign mst_w_valid[i] = mst_reqs[i].w_valid; + assign mst_b_ready[i] = mst_reqs[i].b_ready; + assign mst_ar_id[i] = mst_reqs[i].ar.id; + assign mst_ar_addr[i] = mst_reqs[i].ar.addr; + assign mst_ar_len[i] = mst_reqs[i].ar.len; + assign mst_ar_size[i] = mst_reqs[i].ar.size; + assign mst_ar_burst[i] = mst_reqs[i].ar.burst; + assign mst_ar_lock[i] = mst_reqs[i].ar.lock; + assign mst_ar_cache[i] = mst_reqs[i].ar.cache; + assign mst_ar_prot[i] = mst_reqs[i].ar.prot; + assign mst_ar_qos[i] = mst_reqs[i].ar.qos; + assign mst_ar_region[i] = mst_reqs[i].ar.region; + assign mst_ar_user[i] = mst_reqs[i].ar.user; + assign mst_ar_valid[i] = mst_reqs[i].ar_valid; + assign mst_r_ready[i] = mst_reqs[i].r_ready; + // Response + assign mst_resps[i].aw_ready = mst_aw_ready[i]; + assign mst_resps[i].ar_ready = mst_ar_ready[i]; + assign mst_resps[i].w_ready = mst_w_ready[i]; + assign mst_resps[i].b_valid = mst_b_valid[i]; + assign mst_resps[i].b.id = mst_b_id[i]; + assign mst_resps[i].b.resp = mst_b_resp[i]; + assign mst_resps[i].b.user = mst_b_user[i]; + assign mst_resps[i].r_valid = mst_r_valid[i]; + assign mst_resps[i].r.id = mst_r_id[i]; + assign mst_resps[i].r.data = mst_r_data[i]; + assign mst_resps[i].r.resp = mst_r_resp[i]; + assign mst_resps[i].r.last = mst_r_last[i]; + assign mst_resps[i].r.user = mst_r_user[i]; + end + endgenerate + + if (EnableMulticast) begin : g_multicast + axi_mcast_xbar #( + .Cfg (xbar_cfg), + .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_dut ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .test_i ('0), + .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 (addr_map), + .en_default_mst_port_i('0), + .default_mst_port_i ('0) + ); + end else begin : g_no_multicast + axi_xbar #( + .Cfg (xbar_cfg), + .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_dut ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .test_i ('0), + .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 (addr_map), + .en_default_mst_port_i('0), + .default_mst_port_i ('0) + ); + end + +endmodule + +module synth_axi_demux import axi_pkg::*; #( + parameter int unsigned NoMstPorts = 32'd8, + parameter bit UniqueIds = 0, + parameter xbar_latency_e LatencyMode = NO_LATENCY, + // axi configuration + parameter int unsigned AxiIdWidthMasters = 4, + parameter int unsigned AxiIdUsed = 3, // Has to be <= AxiIdWidthMasters + parameter int unsigned AxiAddrWidth = 32, // Axi Address Width + parameter int unsigned AxiDataWidth = 32, // Axi Data Width + parameter int unsigned AxiStrbWidth = AxiDataWidth / 8, + parameter int unsigned AxiUserWidth = 1, + // axi types + parameter type id_t = logic [AxiIdWidthMasters-1:0], + parameter type addr_t = logic [AxiAddrWidth-1:0], + parameter type data_t = logic [AxiDataWidth-1:0], + parameter type strb_t = logic [AxiStrbWidth-1:0], + parameter type user_t = logic [AxiUserWidth-1:0], + // select signal types + parameter int unsigned SelectWidth = (NoMstPorts > 32'd1) ? $clog2(NoMstPorts) : 32'd1, + parameter type select_t = logic [SelectWidth-1:0] +) ( + input logic clk_i, + input logic rst_ni, + + // Select signals + input select_t slv_aw_select_i, + input select_t slv_ar_select_i, + + /*********************************** + /* Slave ports request inputs + ***********************************/ + + // AW + input id_t slv_aw_id, + input addr_t slv_aw_addr, + input axi_pkg::len_t slv_aw_len, + input axi_pkg::size_t slv_aw_size, + input axi_pkg::burst_t slv_aw_burst, + input logic slv_aw_lock, + input axi_pkg::cache_t slv_aw_cache, + input axi_pkg::prot_t slv_aw_prot, + input axi_pkg::qos_t slv_aw_qos, + input axi_pkg::region_t slv_aw_region, + input axi_pkg::atop_t slv_aw_atop, + input user_t slv_aw_user, + input logic slv_aw_valid, + // W + input data_t slv_w_data, + input strb_t slv_w_strb, + input logic slv_w_last, + input user_t slv_w_user, + input logic slv_w_valid, + // B + input logic slv_b_ready, + // AR + input id_t slv_ar_id, + input addr_t slv_ar_addr, + input axi_pkg::len_t slv_ar_len, + input axi_pkg::size_t slv_ar_size, + input axi_pkg::burst_t slv_ar_burst, + input logic slv_ar_lock, + input axi_pkg::cache_t slv_ar_cache, + input axi_pkg::prot_t slv_ar_prot, + input axi_pkg::qos_t slv_ar_qos, + input axi_pkg::region_t slv_ar_region, + input user_t slv_ar_user, + input logic slv_ar_valid, + // R + input logic slv_r_ready, + + /*********************************** + /* Slave ports response outputs + ***********************************/ + + // AW + output logic slv_aw_ready, + // AR + output logic slv_ar_ready, + // W + output logic slv_w_ready, + // B + output logic slv_b_valid, + output id_t slv_b_id, + output axi_pkg::resp_t slv_b_resp, + output user_t slv_b_user, + // R + output logic slv_r_valid, + output id_t slv_r_id, + output data_t slv_r_data, + output axi_pkg::resp_t slv_r_resp, + output logic slv_r_last, + output user_t slv_r_user, + + /*********************************** + /* Master ports request outputs + ***********************************/ + + // AW + output id_t [NoMstPorts-1:0] mst_aw_id, + output addr_t [NoMstPorts-1:0] mst_aw_addr, + output axi_pkg::len_t [NoMstPorts-1:0] mst_aw_len, + output axi_pkg::size_t [NoMstPorts-1:0] mst_aw_size, + output axi_pkg::burst_t [NoMstPorts-1:0] mst_aw_burst, + output logic [NoMstPorts-1:0] mst_aw_lock, + output axi_pkg::cache_t [NoMstPorts-1:0] mst_aw_cache, + output axi_pkg::prot_t [NoMstPorts-1:0] mst_aw_prot, + output axi_pkg::qos_t [NoMstPorts-1:0] mst_aw_qos, + output axi_pkg::region_t [NoMstPorts-1:0] mst_aw_region, + output axi_pkg::atop_t [NoMstPorts-1:0] mst_aw_atop, + output user_t [NoMstPorts-1:0] mst_aw_user, + output logic [NoMstPorts-1:0] mst_aw_valid, + // W + output data_t [NoMstPorts-1:0] mst_w_data, + output strb_t [NoMstPorts-1:0] mst_w_strb, + output logic [NoMstPorts-1:0] mst_w_last, + output user_t [NoMstPorts-1:0] mst_w_user, + output logic [NoMstPorts-1:0] mst_w_valid, + // B + output logic [NoMstPorts-1:0] mst_b_ready, + // AR + output id_t [NoMstPorts-1:0] mst_ar_id, + output addr_t [NoMstPorts-1:0] mst_ar_addr, + output axi_pkg::len_t [NoMstPorts-1:0] mst_ar_len, + output axi_pkg::size_t [NoMstPorts-1:0] mst_ar_size, + output axi_pkg::burst_t [NoMstPorts-1:0] mst_ar_burst, + output logic [NoMstPorts-1:0] mst_ar_lock, + output axi_pkg::cache_t [NoMstPorts-1:0] mst_ar_cache, + output axi_pkg::prot_t [NoMstPorts-1:0] mst_ar_prot, + output axi_pkg::qos_t [NoMstPorts-1:0] mst_ar_qos, + output axi_pkg::region_t [NoMstPorts-1:0] mst_ar_region, + output user_t [NoMstPorts-1:0] mst_ar_user, + output logic [NoMstPorts-1:0] mst_ar_valid, + // R + output logic [NoMstPorts-1:0] mst_r_ready, + + /*********************************** + /* Master ports response inputs + ***********************************/ + + // AW + input logic [NoMstPorts-1:0] mst_aw_ready, + // AR + input logic [NoMstPorts-1:0] mst_ar_ready, + // W + input logic [NoMstPorts-1:0] mst_w_ready, + // B + input logic [NoMstPorts-1:0] mst_b_valid, + input id_t [NoMstPorts-1:0] mst_b_id, + input axi_pkg::resp_t [NoMstPorts-1:0] mst_b_resp, + input user_t [NoMstPorts-1:0] mst_b_user, + // R + input logic [NoMstPorts-1:0] mst_r_valid, + input id_t [NoMstPorts-1:0] mst_r_id, + input data_t [NoMstPorts-1:0] mst_r_data, + input axi_pkg::resp_t [NoMstPorts-1:0] mst_r_resp, + input logic [NoMstPorts-1:0] mst_r_last, + input user_t [NoMstPorts-1:0] mst_r_user + +); + + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t) + + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t) + + `AXI_TYPEDEF_REQ_T(req_t, aw_chan_t, w_chan_t, ar_chan_t) + `AXI_TYPEDEF_RESP_T(resp_t, b_chan_t, r_chan_t) + + req_t slv_req; + req_t [NoMstPorts-1:0] mst_reqs; + resp_t slv_resp; + resp_t [NoMstPorts-1:0] mst_resps; + + // Connect XBAR interfaces + assign slv_req.aw.id = slv_aw_id; + assign slv_req.aw.addr = slv_aw_addr; + assign slv_req.aw.len = slv_aw_len; + assign slv_req.aw.size = slv_aw_size; + assign slv_req.aw.burst = slv_aw_burst; + assign slv_req.aw.lock = slv_aw_lock; + assign slv_req.aw.cache = slv_aw_cache; + assign slv_req.aw.prot = slv_aw_prot; + assign slv_req.aw.qos = slv_aw_qos; + assign slv_req.aw.region = slv_aw_region; + assign slv_req.aw.atop = slv_aw_atop; + assign slv_req.aw.user = slv_aw_user; + assign slv_req.aw_valid = slv_aw_valid; + assign slv_req.w.data = slv_w_data; + assign slv_req.w.strb = slv_w_strb; + assign slv_req.w.last = slv_w_last; + assign slv_req.w.user = slv_w_user; + assign slv_req.w_valid = slv_w_valid; + assign slv_req.b_ready = slv_b_ready; + assign slv_req.ar.id = slv_ar_id; + assign slv_req.ar.addr = slv_ar_addr; + assign slv_req.ar.len = slv_ar_len; + assign slv_req.ar.size = slv_ar_size; + assign slv_req.ar.burst = slv_ar_burst; + assign slv_req.ar.lock = slv_ar_lock; + assign slv_req.ar.cache = slv_ar_cache; + assign slv_req.ar.prot = slv_ar_prot; + assign slv_req.ar.qos = slv_ar_qos; + assign slv_req.ar.region = slv_ar_region; + assign slv_req.ar.user = slv_ar_user; + assign slv_req.ar_valid = slv_ar_valid; + assign slv_req.r_ready = slv_r_ready; + // Response + assign slv_aw_ready = slv_resp.aw_ready; + assign slv_ar_ready = slv_resp.ar_ready; + assign slv_w_ready = slv_resp.w_ready; + assign slv_b_valid = slv_resp.b_valid; + assign slv_b_id = slv_resp.b.id; + assign slv_b_resp = slv_resp.b.resp; + assign slv_b_user = slv_resp.b.user; + assign slv_r_valid = slv_resp.r_valid; + assign slv_r_id = slv_resp.r.id; + assign slv_r_data = slv_resp.r.data; + assign slv_r_resp = slv_resp.r.resp; + assign slv_r_last = slv_resp.r.last; + assign slv_r_user = slv_resp.r.user; + + generate + for (genvar i = 0; i < NoMstPorts; i++) begin : g_connect_mst_port + // Request + assign mst_aw_id[i] = mst_reqs[i].aw.id; + assign mst_aw_addr[i] = mst_reqs[i].aw.addr; + assign mst_aw_len[i] = mst_reqs[i].aw.len; + assign mst_aw_size[i] = mst_reqs[i].aw.size; + assign mst_aw_burst[i] = mst_reqs[i].aw.burst; + assign mst_aw_lock[i] = mst_reqs[i].aw.lock; + assign mst_aw_cache[i] = mst_reqs[i].aw.cache; + assign mst_aw_prot[i] = mst_reqs[i].aw.prot; + assign mst_aw_qos[i] = mst_reqs[i].aw.qos; + assign mst_aw_region[i] = mst_reqs[i].aw.region; + assign mst_aw_atop[i] = mst_reqs[i].aw.atop; + assign mst_aw_user[i] = mst_reqs[i].aw.user; + assign mst_aw_valid[i] = mst_reqs[i].aw_valid; + assign mst_w_data[i] = mst_reqs[i].w.data; + assign mst_w_strb[i] = mst_reqs[i].w.strb; + assign mst_w_last[i] = mst_reqs[i].w.last; + assign mst_w_user[i] = mst_reqs[i].w.user; + assign mst_w_valid[i] = mst_reqs[i].w_valid; + assign mst_b_ready[i] = mst_reqs[i].b_ready; + assign mst_ar_id[i] = mst_reqs[i].ar.id; + assign mst_ar_addr[i] = mst_reqs[i].ar.addr; + assign mst_ar_len[i] = mst_reqs[i].ar.len; + assign mst_ar_size[i] = mst_reqs[i].ar.size; + assign mst_ar_burst[i] = mst_reqs[i].ar.burst; + assign mst_ar_lock[i] = mst_reqs[i].ar.lock; + assign mst_ar_cache[i] = mst_reqs[i].ar.cache; + assign mst_ar_prot[i] = mst_reqs[i].ar.prot; + assign mst_ar_qos[i] = mst_reqs[i].ar.qos; + assign mst_ar_region[i] = mst_reqs[i].ar.region; + assign mst_ar_user[i] = mst_reqs[i].ar.user; + assign mst_ar_valid[i] = mst_reqs[i].ar_valid; + assign mst_r_ready[i] = mst_reqs[i].r_ready; + // Response + assign mst_resps[i].aw_ready = mst_aw_ready[i]; + assign mst_resps[i].ar_ready = mst_ar_ready[i]; + assign mst_resps[i].w_ready = mst_w_ready[i]; + assign mst_resps[i].b_valid = mst_b_valid[i]; + assign mst_resps[i].b.id = mst_b_id[i]; + assign mst_resps[i].b.resp = mst_b_resp[i]; + assign mst_resps[i].b.user = mst_b_user[i]; + assign mst_resps[i].r_valid = mst_r_valid[i]; + assign mst_resps[i].r.id = mst_r_id[i]; + assign mst_resps[i].r.data = mst_r_data[i]; + assign mst_resps[i].r.resp = mst_r_resp[i]; + assign mst_resps[i].r.last = mst_r_last[i]; + assign mst_resps[i].r.user = mst_r_user[i]; + end + endgenerate + + axi_demux #( + .AxiIdWidth (AxiIdWidthMasters), + .AtopSupport(1'b0), + .aw_chan_t (aw_chan_t), + .w_chan_t (w_chan_t), + .b_chan_t (b_chan_t), + .ar_chan_t (ar_chan_t), + .r_chan_t (r_chan_t), + .axi_req_t (req_t), + .axi_resp_t (resp_t), + .NoMstPorts (NoMstPorts), + .MaxTrans (10), + .AxiLookBits(AxiIdUsed), + .UniqueIds (UniqueIds), + .SpillAw (LatencyMode[9]), + .SpillW (LatencyMode[8]), + .SpillB (LatencyMode[7]), + .SpillAr (LatencyMode[6]), + .SpillR (LatencyMode[5]) + ) i_axi_demux ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .test_i ('0), + .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), + .mst_reqs_o (mst_reqs), + .mst_resps_i (mst_resps) + ); +endmodule + +module synth_axi_mcast_demux import axi_pkg::*; #( + parameter int unsigned NoMstPorts = 32'd8, + parameter bit UniqueIds = 0, + parameter xbar_latency_e LatencyMode = NO_LATENCY, + // axi configuration + parameter int unsigned AxiIdWidthMasters = 4, + parameter int unsigned AxiIdUsed = 3, // Has to be <= AxiIdWidthMasters + parameter int unsigned AxiAddrWidth = 32, // Axi Address Width + parameter int unsigned AxiDataWidth = 32, // Axi Data Width + parameter int unsigned AxiStrbWidth = AxiDataWidth / 8, + parameter int unsigned AxiUserWidth = 32, + // axi types + parameter type id_t = logic [AxiIdWidthMasters-1:0], + parameter type addr_t = logic [AxiAddrWidth-1:0], + parameter type data_t = logic [AxiDataWidth-1:0], + parameter type strb_t = logic [AxiStrbWidth-1:0], + parameter type user_t = logic [AxiUserWidth-1:0], + // select signal types + parameter int unsigned IdxSelectWidth = (NoMstPorts > 32'd1) ? $clog2(NoMstPorts) : 32'd1, + parameter type idx_select_t = logic [IdxSelectWidth-1:0], + parameter type mask_select_t = logic [NoMstPorts-1:0], + parameter type multi_addr_t = struct packed { + addr_t aw_addr; + addr_t aw_mask; + } +) ( + input logic clk_i, + input logic rst_ni, + + // Address decoder signals + input mask_select_t slv_aw_select_i, + input idx_select_t slv_ar_select_i, + input multi_addr_t [NoMstPorts-1:0] slv_aw_mcast_i, + + /*********************************** + /* Slave ports request inputs + ***********************************/ + + // AW + input id_t slv_aw_id, + input addr_t slv_aw_addr, + input axi_pkg::len_t slv_aw_len, + input axi_pkg::size_t slv_aw_size, + input axi_pkg::burst_t slv_aw_burst, + input logic slv_aw_lock, + input axi_pkg::cache_t slv_aw_cache, + input axi_pkg::prot_t slv_aw_prot, + input axi_pkg::qos_t slv_aw_qos, + input axi_pkg::region_t slv_aw_region, + input axi_pkg::atop_t slv_aw_atop, + input user_t slv_aw_user, + input logic slv_aw_valid, + // W + input data_t slv_w_data, + input strb_t slv_w_strb, + input logic slv_w_last, + input user_t slv_w_user, + input logic slv_w_valid, + // B + input logic slv_b_ready, + // AR + input id_t slv_ar_id, + input addr_t slv_ar_addr, + input axi_pkg::len_t slv_ar_len, + input axi_pkg::size_t slv_ar_size, + input axi_pkg::burst_t slv_ar_burst, + input logic slv_ar_lock, + input axi_pkg::cache_t slv_ar_cache, + input axi_pkg::prot_t slv_ar_prot, + input axi_pkg::qos_t slv_ar_qos, + input axi_pkg::region_t slv_ar_region, + input user_t slv_ar_user, + input logic slv_ar_valid, + // R + input logic slv_r_ready, + + /*********************************** + /* Slave ports response outputs + ***********************************/ + + // AW + output logic slv_aw_ready, + // AR + output logic slv_ar_ready, + // W + output logic slv_w_ready, + // B + output logic slv_b_valid, + output id_t slv_b_id, + output axi_pkg::resp_t slv_b_resp, + output user_t slv_b_user, + // R + output logic slv_r_valid, + output id_t slv_r_id, + output data_t slv_r_data, + output axi_pkg::resp_t slv_r_resp, + output logic slv_r_last, + output user_t slv_r_user, + + /*********************************** + /* Master ports request outputs + ***********************************/ + + // AW + output id_t [NoMstPorts-1:0] mst_aw_id, + output addr_t [NoMstPorts-1:0] mst_aw_addr, + output axi_pkg::len_t [NoMstPorts-1:0] mst_aw_len, + output axi_pkg::size_t [NoMstPorts-1:0] mst_aw_size, + output axi_pkg::burst_t [NoMstPorts-1:0] mst_aw_burst, + output logic [NoMstPorts-1:0] mst_aw_lock, + output axi_pkg::cache_t [NoMstPorts-1:0] mst_aw_cache, + output axi_pkg::prot_t [NoMstPorts-1:0] mst_aw_prot, + output axi_pkg::qos_t [NoMstPorts-1:0] mst_aw_qos, + output axi_pkg::region_t [NoMstPorts-1:0] mst_aw_region, + output axi_pkg::atop_t [NoMstPorts-1:0] mst_aw_atop, + output user_t [NoMstPorts-1:0] mst_aw_user, + output logic [NoMstPorts-1:0] mst_aw_valid, + // W + output data_t [NoMstPorts-1:0] mst_w_data, + output strb_t [NoMstPorts-1:0] mst_w_strb, + output logic [NoMstPorts-1:0] mst_w_last, + output user_t [NoMstPorts-1:0] mst_w_user, + output logic [NoMstPorts-1:0] mst_w_valid, + // B + output logic [NoMstPorts-1:0] mst_b_ready, + // AR + output id_t [NoMstPorts-1:0] mst_ar_id, + output addr_t [NoMstPorts-1:0] mst_ar_addr, + output axi_pkg::len_t [NoMstPorts-1:0] mst_ar_len, + output axi_pkg::size_t [NoMstPorts-1:0] mst_ar_size, + output axi_pkg::burst_t [NoMstPorts-1:0] mst_ar_burst, + output logic [NoMstPorts-1:0] mst_ar_lock, + output axi_pkg::cache_t [NoMstPorts-1:0] mst_ar_cache, + output axi_pkg::prot_t [NoMstPorts-1:0] mst_ar_prot, + output axi_pkg::qos_t [NoMstPorts-1:0] mst_ar_qos, + output axi_pkg::region_t [NoMstPorts-1:0] mst_ar_region, + output user_t [NoMstPorts-1:0] mst_ar_user, + output logic [NoMstPorts-1:0] mst_ar_valid, + // R + output logic [NoMstPorts-1:0] mst_r_ready, + + /*********************************** + /* Master ports response inputs + ***********************************/ + + // AW + input logic [NoMstPorts-1:0] mst_aw_ready, + // AR + input logic [NoMstPorts-1:0] mst_ar_ready, + // W + input logic [NoMstPorts-1:0] mst_w_ready, + // B + input logic [NoMstPorts-1:0] mst_b_valid, + input id_t [NoMstPorts-1:0] mst_b_id, + input axi_pkg::resp_t [NoMstPorts-1:0] mst_b_resp, + input user_t [NoMstPorts-1:0] mst_b_user, + // R + input logic [NoMstPorts-1:0] mst_r_valid, + input id_t [NoMstPorts-1:0] mst_r_id, + input data_t [NoMstPorts-1:0] mst_r_data, + input axi_pkg::resp_t [NoMstPorts-1:0] mst_r_resp, + input logic [NoMstPorts-1:0] mst_r_last, + input user_t [NoMstPorts-1:0] mst_r_user + +); + + typedef struct packed { + logic [AxiUserWidth-1:0] mcast; + } aw_user_t; + + `AXI_TYPEDEF_AW_CHAN_T(mst_aw_chan_t, addr_t, id_t, aw_user_t) + `AXI_TYPEDEF_AW_CHAN_T(slv_aw_chan_t, addr_t, id_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_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(slv_b_chan_t, id_t, user_t) + + `AXI_TYPEDEF_AR_CHAN_T(mst_ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(slv_ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(mst_r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(slv_r_chan_t, data_t, id_t, user_t) + + `AXI_TYPEDEF_REQ_T(mst_req_t, mst_aw_chan_t, w_chan_t, mst_ar_chan_t) + `AXI_TYPEDEF_RESP_T(mst_resp_t, mst_b_chan_t, mst_r_chan_t) + `AXI_TYPEDEF_REQ_T(slv_req_t, slv_aw_chan_t, w_chan_t, slv_ar_chan_t) + `AXI_TYPEDEF_RESP_T(slv_resp_t, slv_b_chan_t, slv_r_chan_t) + + slv_req_t slv_req; + mst_req_t [NoMstPorts-1:0] mst_reqs; + slv_resp_t slv_resp; + mst_resp_t [NoMstPorts-1:0] mst_resps; + + // Connect XBAR interfaces + assign slv_req.aw.id = slv_aw_id; + assign slv_req.aw.addr = slv_aw_addr; + assign slv_req.aw.len = slv_aw_len; + assign slv_req.aw.size = slv_aw_size; + assign slv_req.aw.burst = slv_aw_burst; + assign slv_req.aw.lock = slv_aw_lock; + assign slv_req.aw.cache = slv_aw_cache; + assign slv_req.aw.prot = slv_aw_prot; + assign slv_req.aw.qos = slv_aw_qos; + assign slv_req.aw.region = slv_aw_region; + assign slv_req.aw.atop = slv_aw_atop; + assign slv_req.aw.user = slv_aw_user; + assign slv_req.aw_valid = slv_aw_valid; + assign slv_req.w.data = slv_w_data; + assign slv_req.w.strb = slv_w_strb; + assign slv_req.w.last = slv_w_last; + assign slv_req.w.user = slv_w_user; + assign slv_req.w_valid = slv_w_valid; + assign slv_req.b_ready = slv_b_ready; + assign slv_req.ar.id = slv_ar_id; + assign slv_req.ar.addr = slv_ar_addr; + assign slv_req.ar.len = slv_ar_len; + assign slv_req.ar.size = slv_ar_size; + assign slv_req.ar.burst = slv_ar_burst; + assign slv_req.ar.lock = slv_ar_lock; + assign slv_req.ar.cache = slv_ar_cache; + assign slv_req.ar.prot = slv_ar_prot; + assign slv_req.ar.qos = slv_ar_qos; + assign slv_req.ar.region = slv_ar_region; + assign slv_req.ar.user = slv_ar_user; + assign slv_req.ar_valid = slv_ar_valid; + assign slv_req.r_ready = slv_r_ready; + // Response + assign slv_aw_ready = slv_resp.aw_ready; + assign slv_ar_ready = slv_resp.ar_ready; + assign slv_w_ready = slv_resp.w_ready; + assign slv_b_valid = slv_resp.b_valid; + assign slv_b_id = slv_resp.b.id; + assign slv_b_resp = slv_resp.b.resp; + assign slv_b_user = slv_resp.b.user; + assign slv_r_valid = slv_resp.r_valid; + assign slv_r_id = slv_resp.r.id; + assign slv_r_data = slv_resp.r.data; + assign slv_r_resp = slv_resp.r.resp; + assign slv_r_last = slv_resp.r.last; + assign slv_r_user = slv_resp.r.user; + + generate + for (genvar i = 0; i < NoMstPorts; i++) begin : g_connect_mst_port + // Request + assign mst_aw_id[i] = mst_reqs[i].aw.id; + assign mst_aw_addr[i] = mst_reqs[i].aw.addr; + assign mst_aw_len[i] = mst_reqs[i].aw.len; + assign mst_aw_size[i] = mst_reqs[i].aw.size; + assign mst_aw_burst[i] = mst_reqs[i].aw.burst; + assign mst_aw_lock[i] = mst_reqs[i].aw.lock; + assign mst_aw_cache[i] = mst_reqs[i].aw.cache; + assign mst_aw_prot[i] = mst_reqs[i].aw.prot; + assign mst_aw_qos[i] = mst_reqs[i].aw.qos; + assign mst_aw_region[i] = mst_reqs[i].aw.region; + assign mst_aw_atop[i] = mst_reqs[i].aw.atop; + assign mst_aw_user[i] = mst_reqs[i].aw.user; + assign mst_aw_valid[i] = mst_reqs[i].aw_valid; + assign mst_w_data[i] = mst_reqs[i].w.data; + assign mst_w_strb[i] = mst_reqs[i].w.strb; + assign mst_w_last[i] = mst_reqs[i].w.last; + assign mst_w_user[i] = mst_reqs[i].w.user; + assign mst_w_valid[i] = mst_reqs[i].w_valid; + assign mst_b_ready[i] = mst_reqs[i].b_ready; + assign mst_ar_id[i] = mst_reqs[i].ar.id; + assign mst_ar_addr[i] = mst_reqs[i].ar.addr; + assign mst_ar_len[i] = mst_reqs[i].ar.len; + assign mst_ar_size[i] = mst_reqs[i].ar.size; + assign mst_ar_burst[i] = mst_reqs[i].ar.burst; + assign mst_ar_lock[i] = mst_reqs[i].ar.lock; + assign mst_ar_cache[i] = mst_reqs[i].ar.cache; + assign mst_ar_prot[i] = mst_reqs[i].ar.prot; + assign mst_ar_qos[i] = mst_reqs[i].ar.qos; + assign mst_ar_region[i] = mst_reqs[i].ar.region; + assign mst_ar_user[i] = mst_reqs[i].ar.user; + assign mst_ar_valid[i] = mst_reqs[i].ar_valid; + assign mst_r_ready[i] = mst_reqs[i].r_ready; + // Response + assign mst_resps[i].aw_ready = mst_aw_ready[i]; + assign mst_resps[i].ar_ready = mst_ar_ready[i]; + assign mst_resps[i].w_ready = mst_w_ready[i]; + assign mst_resps[i].b_valid = mst_b_valid[i]; + assign mst_resps[i].b.id = mst_b_id[i]; + assign mst_resps[i].b.resp = mst_b_resp[i]; + assign mst_resps[i].b.user = mst_b_user[i]; + assign mst_resps[i].r_valid = mst_r_valid[i]; + assign mst_resps[i].r.id = mst_r_id[i]; + assign mst_resps[i].r.data = mst_r_data[i]; + assign mst_resps[i].r.resp = mst_r_resp[i]; + assign mst_resps[i].r.last = mst_r_last[i]; + assign mst_resps[i].r.user = mst_r_user[i]; + end + endgenerate + + axi_mcast_demux #( + .AxiIdWidth (AxiIdWidthMasters), + .AtopSupport(1'b0), + .aw_addr_t (addr_t), + .aw_chan_t (slv_aw_chan_t), + .w_chan_t (w_chan_t), + .b_chan_t (slv_b_chan_t), + .ar_chan_t (slv_ar_chan_t), + .r_chan_t (slv_r_chan_t), + .axi_req_t (slv_req_t), + .axi_resp_t (slv_resp_t), + .NoMstPorts (NoMstPorts), + .MaxTrans (10), + .AxiLookBits(AxiIdUsed), + .UniqueIds (UniqueIds), + .SpillAw (LatencyMode[9]), + .SpillW (LatencyMode[8]), + .SpillB (LatencyMode[7]), + .SpillAr (LatencyMode[6]), + .SpillR (LatencyMode[5]) + ) i_axi_mcast_demux ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .test_i ('0), + .slv_req_i (slv_req), + .slv_aw_select_i(slv_aw_select_i), + .slv_ar_select_i(slv_ar_select_i), + .slv_aw_mcast_i (slv_aw_mcast_i), + .slv_resp_o (slv_resp), + .mst_reqs_o (mst_reqs), + .mst_resps_i (mst_resps) + ); + +endmodule diff --git a/test/tb_axi_mcast_xbar.sv b/test/tb_axi_mcast_xbar.sv new file mode 100644 index 000000000..565c4382c --- /dev/null +++ b/test/tb_axi_mcast_xbar.sv @@ -0,0 +1,492 @@ +// 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: +// - Wolfgang Roenninger +// - Florian Zaruba +// - Andreas Kurth +// - Luca Colagrande + +// 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" + +/// Testbench for the module `axi_mcast_xbar`. +module tb_axi_mcast_xbar #( + /// Number of AXI masters connected to the xbar. (Number of slave ports) + parameter int unsigned TbNumMasters = 32'd6, + /// Number of AXI slaves connected to the xbar which can be targeted by multicast + /// transactions. (Number of multicast-enabled master ports) + parameter int unsigned TbNumMcastSlaves = 32'd8, + /// Number of write transactions per master. + parameter int unsigned TbNumWrites = 32'd200, + /// Number of read transactions per master. + parameter int unsigned TbNumReads = 32'd200, + /// AXI4+ATOP ID width of the masters connected to the slave ports of the DUT. + /// The ID width of the slaves is calculated depending on the xbar configuration. + parameter int unsigned TbAxiIdWidthMasters = 32'd5, + /// The used ID width of the DUT. + /// Has to be `TbAxiIdWidthMasters >= TbAxiIdUsed`. + parameter int unsigned TbAxiIdUsed = 32'd3, + /// Data width of the AXI channels. + parameter int unsigned TbAxiDataWidth = 32'd64, + /// Pipeline stages in the xbar itself (between demux and mux). + parameter int unsigned TbPipeline = 32'd1, + /// Enable ATOP generation + parameter bit TbEnAtop = 1'b1, + /// Enable exclusive accesses + parameter bit TbEnExcl = 1'b0, + /// Restrict to only unique IDs + parameter bit TbUniqueIds = 1'b0 +); + + // TB timing parameters + localparam time CyclTime = 10ns; + localparam time ApplTime = 2ns; + localparam time TestTime = 8ns; + + // AXI configuration which is automatically derived. + localparam int unsigned TbAxiIdWidthSlaves = TbAxiIdWidthMasters + $clog2(TbNumMasters); + localparam int unsigned TbAxiAddrWidth = 32'd32; + localparam int unsigned TbAxiStrbWidth = TbAxiDataWidth / 8; + localparam int unsigned TbAxiUserWidth = TbAxiAddrWidth; + // In the bench can change this variables which are set here freely, + localparam TbNumSlaves = TbNumMcastSlaves + 1; + localparam axi_pkg::xbar_cfg_t xbar_cfg = '{ + NoSlvPorts: TbNumMasters, + NoMstPorts: TbNumSlaves, + MaxMstTrans: 10, + MaxSlvTrans: 6, + FallThrough: 1'b0, + LatencyMode: axi_pkg::CUT_ALL_PORTS, + PipelineStages: TbPipeline, + AxiIdWidthSlvPorts: TbAxiIdWidthMasters, + AxiIdUsedSlvPorts: TbAxiIdUsed, + UniqueIds: TbUniqueIds, + AxiAddrWidth: TbAxiAddrWidth, + AxiDataWidth: TbAxiDataWidth, + NoAddrRules: TbNumMcastSlaves * 2 + 1, + NoMulticastRules: TbNumMcastSlaves * 2, + NoMulticastPorts: TbNumMcastSlaves + }; + typedef logic [TbAxiIdWidthMasters-1:0] id_mst_t; + typedef logic [TbAxiIdWidthSlaves-1:0] id_slv_t; + typedef logic [TbAxiAddrWidth-1:0] addr_t; + typedef struct packed { + int unsigned idx; + addr_t start_addr; + addr_t end_addr; + } rule_t; + typedef logic [TbAxiDataWidth-1:0] data_t; + typedef logic [TbAxiStrbWidth-1:0] strb_t; + typedef logic [TbAxiUserWidth-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) + + // Each slave has its own address range: + localparam rule_t [xbar_cfg.NoAddrRules-1:0] AddrMap = { + rule_t'{ + idx: TbNumMcastSlaves, + start_addr: 32'h7000_0000, + end_addr: 32'h7008_0000 + }, + addr_map_gen(32'h1000_0000, 32'h10_0000), + addr_map_gen(32'h0b00_0000, 32'h1_0000) + }; + + function rule_t [xbar_cfg.NoMulticastPorts-1:0] addr_map_gen (addr_t base, addr_t offset); + for (int unsigned i = 0; i < xbar_cfg.NoMulticastPorts; i++) begin + addr_map_gen[i] = rule_t'{ + idx: unsigned'(i), + start_addr: base + offset * i, + end_addr: base + offset * (i + 1), + default: '0 + }; + end + endfunction + + typedef axi_test::axi_rand_master #( + // AXI interface parameters + .AW ( TbAxiAddrWidth ), + .DW ( TbAxiDataWidth ), + .IW ( TbAxiIdWidthMasters ), + .UW ( TbAxiUserWidth ), + // 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 ( TbAxiAddrWidth ), + .DW ( TbAxiDataWidth ), + .IW ( TbAxiIdWidthSlaves ), + .UW ( TbAxiUserWidth ), + // Stimuli application and test time + .TA ( ApplTime ), + .TT ( TestTime ) + ) axi_rand_slave_t; + + // ------------- + // DUT signals + // ------------- + logic clk; + // DUT signals + logic rst_n; + logic [TbNumMasters-1:0] end_of_sim; + + // master structs + mst_req_t [TbNumMasters-1:0] masters_req; + mst_resp_t [TbNumMasters-1:0] masters_resp; + + // slave structs + slv_req_t [TbNumSlaves-1:0] slaves_req; + slv_resp_t [TbNumSlaves-1:0] slaves_resp; + + // ------------------------------- + // AXI Interfaces + // ------------------------------- + AXI_BUS #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthMasters ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) master [TbNumMasters-1:0] (); + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthMasters ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) master_dv [TbNumMasters-1:0] (clk); + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthMasters ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) master_monitor_dv [TbNumMasters-1:0] (clk); + for (genvar i = 0; i < TbNumMasters; 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 ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthSlaves ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) slave [TbNumSlaves-1:0] (); + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthSlaves ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) slave_dv [TbNumSlaves-1:0](clk); + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthSlaves ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) slave_monitor_dv [TbNumSlaves-1:0](clk); + for (genvar i = 0; i < TbNumSlaves; 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 [TbNumMasters]; + for (genvar i = 0; i < TbNumMasters; i++) begin : gen_rand_master + initial begin + axi_rand_master[i] = new( master_dv[i] ); + end_of_sim[i] <= 1'b0; + axi_rand_master[i].add_memory_region(AddrMap[0].start_addr, + AddrMap[xbar_cfg.NoMulticastPorts-1].end_addr, + axi_pkg::DEVICE_NONBUFFERABLE); + axi_rand_master[i].add_memory_region(AddrMap[xbar_cfg.NoMulticastPorts].start_addr, + AddrMap[xbar_cfg.NoMulticastRules-1].end_addr, + axi_pkg::DEVICE_NONBUFFERABLE); + axi_rand_master[i].add_memory_region(AddrMap[xbar_cfg.NoMulticastRules].start_addr, + AddrMap[xbar_cfg.NoAddrRules-1].end_addr, + axi_pkg::DEVICE_NONBUFFERABLE); + axi_rand_master[i].set_multicast_probability(50); + axi_rand_master[i].reset(); + @(posedge rst_n); + axi_rand_master[i].run(TbNumReads, TbNumWrites); + end_of_sim[i] <= 1'b1; + end + end + + axi_rand_slave_t axi_rand_slave [TbNumSlaves]; + for (genvar i = 0; i < TbNumSlaves; 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_xbar_pkg::axi_xbar_monitor #( + .AxiAddrWidth ( TbAxiAddrWidth ), + .AxiDataWidth ( TbAxiDataWidth ), + .AxiIdWidthMasters ( TbAxiIdWidthMasters ), + .AxiIdWidthSlaves ( TbAxiIdWidthSlaves ), + .AxiUserWidth ( TbAxiUserWidth ), + .NoMasters ( TbNumMasters ), + .NoSlaves ( TbNumSlaves ), + .NoAddrRules ( xbar_cfg.NoAddrRules ), + .NoMulticastRules ( xbar_cfg.NoMulticastRules ), + .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 ( TbAxiUserWidth ), + .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 < TbNumMasters; 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 < TbNumSlaves; 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 < TbNumMasters; 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 < TbNumSlaves; 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_xbar.sv b/test/tb_axi_xbar.sv index 6056be919..6607f39c7 100644 --- a/test/tb_axi_xbar.sv +++ b/test/tb_axi_xbar.sv @@ -76,7 +76,9 @@ module tb_axi_xbar #( UniqueIds: TbUniqueIds, AxiAddrWidth: TbAxiAddrWidth, AxiDataWidth: TbAxiDataWidth, - NoAddrRules: TbNumSlaves + NoAddrRules: TbNumSlaves, + NoMulticastRules: 0, + NoMulticastPorts: 0 }; typedef logic [TbAxiIdWidthMasters-1:0] id_mst_t; typedef logic [TbAxiIdWidthSlaves-1:0] id_slv_t; diff --git a/test/tb_axi_xbar.wave.do b/test/tb_axi_xbar.wave.do index 23a9095a2..f25ad7a41 100644 --- a/test/tb_axi_xbar.wave.do +++ b/test/tb_axi_xbar.wave.do @@ -5,11 +5,11 @@ add wave -noupdate -label Clock /tb_axi_xbar/i_xbar_dut/clk_i add wave -noupdate -label Reset /tb_axi_xbar/i_xbar_dut/rst_ni add wave -noupdate -label {Test Mode} /tb_axi_xbar/i_xbar_dut/test_i add wave -noupdate -divider {Slave Ports} -add wave -noupdate /tb_axi_xbar/i_xbar_dut/slv_ports_req_i -add wave -noupdate /tb_axi_xbar/i_xbar_dut/slv_ports_resp_o +add wave -noupdate /tb_axi_xbar/i_xbar_dut/slv_reqs +add wave -noupdate /tb_axi_xbar/i_xbar_dut/slv_resps add wave -noupdate -divider {Master Ports} -add wave -noupdate /tb_axi_xbar/i_xbar_dut/mst_ports_req_o -add wave -noupdate /tb_axi_xbar/i_xbar_dut/mst_ports_resp_i +add wave -noupdate /tb_axi_xbar/i_xbar_dut/mst_reqs +add wave -noupdate /tb_axi_xbar/i_xbar_dut/mst_resps add wave -noupdate -divider {Address Mapping} add wave -noupdate /tb_axi_xbar/i_xbar_dut/addr_map_i add wave -noupdate /tb_axi_xbar/i_xbar_dut/en_default_mst_port_i diff --git a/test/tb_axi_xbar_pkg.sv b/test/tb_axi_xbar_pkg.sv index 3c90fcca1..5343960b6 100644 --- a/test/tb_axi_xbar_pkg.sv +++ b/test/tb_axi_xbar_pkg.sv @@ -11,9 +11,10 @@ // Authors: // - Florian Zaruba // - Wolfgang Roenninger +// - Luca Colagrande -// `axi_xbar_monitor` implements an AXI bus monitor that is tuned for the AXI crossbar. -// It snoops on each of the slaves and master ports of the crossbar and +// `axi_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. @@ -27,6 +28,7 @@ package tb_axi_xbar_pkg; parameter int unsigned NoMasters, parameter int unsigned NoSlaves, parameter int unsigned NoAddrRules, + parameter int unsigned NoMulticastRules = 0, parameter type rule_t, parameter rule_t [NoAddrRules-1:0] AddrMap, // Stimuli application and test time @@ -46,6 +48,7 @@ package tb_axi_xbar_pkg; 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 { @@ -148,50 +151,139 @@ package tb_axi_xbar_pkg; @(posedge masters_axi[0].clk_i); endtask - // This task monitors a slave ports of the crossbar. Every time an AW beat is seen - // it populates an id queue at the right master port (if there is no expected decode error), - // populates the expected b response in its own id_queue and in case when the atomic bit [5] + // 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); - idx_slv_t to_slave_idx; - exp_ax_t exp_aw; - slv_axi_id_t exp_aw_id; - bit decerr; + axi_addr_t aw_addr; + axi_addr_t aw_mcast; + axi_addr_t rule_addr; + axi_addr_t rule_mask; + axi_addr_t aw_addr_masked; + axi_addr_t addrmap_masked; + idx_slv_t to_slave_idx[$]; + axi_addr_t addr_to_slave[$]; + axi_addr_t mask_to_slave[$]; + bit [NoSlaves-1:0] matched_slaves; + int unsigned num_slaves_matched; + bit decerr; + exp_ax_t exp_aw; + slv_axi_id_t exp_aw_id; + string slaves_str; master_exp_t exp_b; + // TODO colluca: add check that multicast requests only arrive on multicast ports + // (lower NoMulticastPorts) and that multicast requests only originate + // from multicast rules (lower NoMulticastRules) + if (masters_axi[i].aw_valid && masters_axi[i].aw_ready) begin - // check if it should go to a decerror - decerr = 1'b1; - for (int unsigned j = 0; j < NoAddrRules; j++) begin - if ((masters_axi[i].aw_addr >= AddrMap[j].start_addr) && - (masters_axi[i].aw_addr < AddrMap[j].end_addr)) begin - to_slave_idx = idx_slv_t'(AddrMap[j].idx); - decerr = 1'b0; + + // 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_queue, mask_queue). + + // Get address information from request + aw_addr = masters_axi[i].aw_addr; + aw_mcast = masters_axi[i].aw_user[AxiAddrWidth-1:0]; + + // Set decode error by default, reset if a rule is matched in the following + decerr = 1; + + // When a mask is present, multicast rules are used, otherwise the unicast rules are used. + if (aw_mcast != '0) begin + + // Log masked address + for (int k = 0; k < AxiAddrWidth; k++) + aw_addr_masked[k] = aw_mcast[k] ? 1'bx : aw_addr[k]; + + $display("Trying to match: %x", aw_addr_masked); + + // Compare request against each multicast rule. We look at the rules starting from the + // last ones. In case of multiple rules matching for the same slave, we want only + // the last rule to have effect + for (int j = (NoMulticastRules - 1); j >= 0; j--) begin + + // Convert address rule to mask (NAPOT) form + rule_mask = AddrMap[j].end_addr - AddrMap[j].start_addr - 1; + rule_addr = AddrMap[j].start_addr; + for (int k = 0; k < AxiAddrWidth; k++) + addrmap_masked[k] = rule_mask[k] ? 1'bx : rule_addr[k]; + $display("With slave %3d : %b", AddrMap[j].idx, addrmap_masked); + + // Request goes to the slave if all bits match, out of those which are neither masked + // in the request nor in the addrmap rule + if (&(~(aw_addr ^ rule_addr) | rule_mask | aw_mcast)) begin + int unsigned slave_idx = AddrMap[j].idx; + decerr = 0; + + // Only push the request if we haven't already matched it with a previous rule + // for the same slave + if (!matched_slaves[slave_idx]) begin + matched_slaves[slave_idx] = 1'b1; + to_slave_idx.push_back(slave_idx); + mask_to_slave.push_back(aw_mcast & rule_mask); + addr_to_slave.push_back((~aw_mcast & aw_addr) | (aw_mcast & rule_addr)); + $display(" Push mask : %32b", aw_mcast & rule_mask); + $display(" Push address : %32b", (~aw_mcast & aw_addr) | (aw_mcast & rule_addr)); + end + end end - end - // send the exp aw beat down into the queue of the slave when no decerror - if (!decerr) begin - exp_aw_id = {idx_mst_t'(i), masters_axi[i].aw_id}; - // $display("Test exp aw_id: %b",exp_aw_id); - exp_aw = '{slv_axi_id: exp_aw_id, - slv_axi_addr: masters_axi[i].aw_addr, - slv_axi_len: masters_axi[i].aw_len }; - this.exp_aw_queue[to_slave_idx].push(exp_aw_id, exp_aw); - incr_expected_tests(3); - $display("%0tns > Master %0d: AW to Slave %0d: Axi ID: %b", - $time, i, to_slave_idx, masters_axi[i].aw_id); + end else begin + + // Compare request against each interval-form rule. We look at the rules starting from + // the last ones. We ignore the case of multiple rules matching for the same slave + $display("Trying to match: %x", aw_addr); + for (int j = (NoAddrRules - 1); j >= 0; j--) begin + if ((aw_addr >= AddrMap[j].start_addr) && + (aw_addr < AddrMap[j].end_addr)) begin + decerr = 0; + to_slave_idx.push_back(AddrMap[j].idx); + addr_to_slave.push_back(aw_addr); + mask_to_slave.push_back('0); + $display(" Push address : %x", aw_addr); + end + end + + end + + num_slaves_matched = to_slave_idx.size(); + + // 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, to_slave_idx, masters_axi[i].aw_id); + $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 < num_slaves_matched; 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(0, "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 @@ -205,7 +297,7 @@ package tb_axi_xbar_pkg; end endtask : monitor_mst_aw - // This task monitors a slave port of the crossbar. Every time there is an AW vector it + // 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); @@ -227,14 +319,21 @@ package tb_axi_xbar_pkg; $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(3); + incr_conducted_tests(4); // push the required w beats into the right fifo + $display(" Expect %0d W beats.", slaves_axi[i].aw_len + 1); 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) ? @@ -334,13 +433,14 @@ package tb_axi_xbar_pkg; // 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_len: mst_axi_len }; + 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); + $display(" Expect R response, len: %0d.", mst_axi_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}; @@ -498,6 +598,12 @@ package tb_axi_xbar_pkg; 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 + if (tests_conducted > tests_expected) begin + $error("Conducted more than the expected tests!"); + end endtask : print_result endclass : axi_xbar_monitor endpackage