diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a7ff3ac81..066fdc0ee 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - SYNOPSYS_DC: synopsys-2019.12 dc_shell -64bit + SYNOPSYS_DC: synopsys-2022.03 dc_shell -64bit before_script: - export PATH=~/.cargo/bin:$PATH diff --git a/CHANGELOG.md b/CHANGELOG.md index 90cce6ae5..c6f5aaed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added ### Changed +- `axi_demux`: Replace write FIFO (`w_fifo`) with a write credit counter. ### Fixed diff --git a/src/axi_burst_splitter.sv b/src/axi_burst_splitter.sv index a46ce2c5e..4fe6f7e78 100644 --- a/src/axi_burst_splitter.sv +++ b/src/axi_burst_splitter.sv @@ -78,7 +78,6 @@ module axi_burst_splitter #( .NoMstPorts ( 2 ), .MaxTrans ( MaxTxns ), .AxiLookBits ( IdWidth ), - .FallThrough ( 1'b1 ), .SpillAw ( 1'b0 ), .SpillW ( 1'b0 ), .SpillB ( 1'b0 ), diff --git a/src/axi_demux.sv b/src/axi_demux.sv index cae7401eb..4ab4aadcf 100644 --- a/src/axi_demux.sv +++ b/src/axi_demux.sv @@ -51,7 +51,6 @@ module axi_demux #( parameter int unsigned MaxTrans = 32'd8, parameter int unsigned AxiLookBits = 32'd3, parameter bit UniqueIds = 1'b0, - parameter bit FallThrough = 1'b0, parameter bit SpillAw = 1'b1, parameter bit SpillW = 1'b0, parameter bit SpillB = 1'b0, @@ -74,7 +73,8 @@ module axi_demux #( input axi_resp_t [NoMstPorts-1:0] mst_resps_i ); - localparam int unsigned IdCounterWidth = MaxTrans > 1 ? $clog2(MaxTrans) : 1; + localparam int unsigned IdCounterWidth = cf_math_pkg::idx_width(MaxTrans); + typedef logic [IdCounterWidth-1:0] id_cnt_t; //-------------------------------------- // Typedefs for the FIFOs / Queues @@ -175,14 +175,14 @@ module axi_demux #( // AW ID counter select_t lookup_aw_select; logic aw_select_occupied, aw_id_cnt_full; - logic aw_push; // Upon an ATOP load, inject IDs from the AW into the AR channel logic atop_inject; - // W FIFO: stores the decision to which master W beats should go - logic w_fifo_pop; - logic w_fifo_full, w_fifo_empty; - select_t w_select; + // 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; // Register which locks the AW valid signal logic lock_aw_valid_d, lock_aw_valid_q, load_aw_lock; @@ -274,9 +274,9 @@ module axi_demux #( lock_aw_valid_d = lock_aw_valid_q; load_aw_lock = 1'b0; // AW ID counter and W FIFO - aw_push = 1'b0; + w_cnt_up = 1'b0; // ATOP injection into ar counter - atop_inject = 1'b0; + 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; @@ -289,19 +289,23 @@ module axi_demux #( atop_inject = slv_aw_chan_select.aw_chan.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; end end else begin - // An AW can be handled if `i_aw_id_counter` and `i_w_fifo` are not full. An ATOP that + // 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_fifo_full && + if (!aw_id_cnt_full && (w_open != {IdCounterWidth{1'b1}}) && (!(ar_id_cnt_full && slv_aw_chan_select.aw_chan.atop[axi_pkg::ATOP_R_RESP]) || !AtopSupport)) begin - // there is a valid AW vector make the id lookup and go further, if it passes - if (slv_aw_valid && (!aw_select_occupied || - (slv_aw_chan_select.aw_select == lookup_aw_select))) 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_aw_valid && + ((w_open == '0) || (w_select == slv_aw_chan_select.aw_select)) && + (!aw_select_occupied || (slv_aw_chan_select.aw_select == lookup_aw_select))) begin // connect the handshake aw_valid = 1'b1; // push arbitration to the W FIFO regardless, do not wait for the AW transaction - aw_push = 1'b1; + w_cnt_up = 1'b1; // on AW transaction if (aw_ready) begin slv_aw_ready = 1'b1; @@ -345,32 +349,36 @@ module axi_demux #( .inject_i ( 1'b0 ), .push_axi_id_i ( slv_aw_chan_select.aw_chan.id[0+:AxiLookBits] ), .push_mst_select_i ( slv_aw_chan_select.aw_select ), - .push_i ( aw_push ), + .push_i ( w_cnt_up ), .pop_axi_id_i ( slv_b_chan.id[0+:AxiLookBits] ), .pop_i ( slv_b_valid & slv_b_ready ) ); // pop from ID counter on outward transaction end - // FIFO to save W selection - fifo_v3 #( - .FALL_THROUGH ( FallThrough ), - .DEPTH ( MaxTrans ), - .dtype ( select_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 ( slv_aw_chan_select.aw_select ), - .push_i ( aw_push ), // controlled from proc_aw_chan - .data_o ( w_select ), // where the w beat should go - .pop_i ( w_fifo_pop ) // controlled from proc_w_chan + // 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_chan_select.aw_select`. + 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_chan_select.aw_select, w_cnt_up, select_t'(0), clk_i, rst_ni) + assign w_select = (|w_open) ? w_select_q : slv_aw_chan_select.aw_select; + assign w_select_valid = w_cnt_up | (|w_open); + //-------------------------------------- // W Channel //-------------------------------------- @@ -571,8 +579,8 @@ module axi_demux #( .idx_o ( ) ); - assign ar_ready = ar_valid & mst_resps_i[slv_ar_chan_select.ar_select].ar_ready; - assign aw_ready = aw_valid & mst_resps_i[slv_aw_chan_select.aw_select].aw_ready; + assign ar_ready = ar_valid & mst_resps_i[slv_ar_chan_select.ar_select].ar_ready; + assign aw_ready = aw_valid & mst_resps_i[slv_aw_chan_select.aw_select].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! @@ -580,7 +588,7 @@ module axi_demux #( // default assignments mst_reqs_o = '0; slv_w_ready = 1'b0; - w_fifo_pop = 1'b0; + w_cnt_down = 1'b0; for (int unsigned i = 0; i < NoMstPorts; i++) begin // AW channel @@ -593,10 +601,10 @@ module axi_demux #( // W channel mst_reqs_o[i].w = slv_w_chan; mst_reqs_o[i].w_valid = 1'b0; - if (!w_fifo_empty && (w_select == i)) begin + if (w_select_valid && (w_select == select_t'(i))) begin mst_reqs_o[i].w_valid = slv_w_valid; slv_w_ready = mst_resps_i[i].w_ready; - w_fifo_pop = slv_w_valid & mst_resps_i[i].w_ready & slv_w_chan.last; + w_cnt_down = slv_w_valid & mst_resps_i[i].w_ready & slv_w_chan.last; end // B channel @@ -658,6 +666,9 @@ module axi_demux #( internal_aw_select: assert property( @(posedge clk_i) (aw_valid |-> slv_aw_chan_select.aw_select < NoMstPorts)) else $fatal(1, "slv_aw_chan_select.aw_select 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 @@ -858,7 +869,6 @@ module axi_demux_intf #( .MaxTrans ( MAX_TRANS ), .AxiLookBits ( AXI_LOOK_BITS ), .UniqueIds ( UNIQUE_IDS ), - .FallThrough ( FALL_THROUGH ), .SpillAw ( SPILL_AW ), .SpillW ( SPILL_W ), .SpillB ( SPILL_B ), diff --git a/src/axi_id_serialize.sv b/src/axi_id_serialize.sv index 8688cb355..df21c8b1d 100644 --- a/src/axi_id_serialize.sv +++ b/src/axi_id_serialize.sv @@ -160,7 +160,6 @@ module axi_id_serialize #( .NoMstPorts ( AxiMstPortMaxUniqIds ), .MaxTrans ( AxiSlvPortMaxTxns ), .AxiLookBits ( AxiSlvPortIdWidth ), - .FallThrough ( 1'b1 ), .SpillAw ( 1'b1 ), .SpillW ( 1'b0 ), .SpillB ( 1'b0 ), diff --git a/src/axi_isolate.sv b/src/axi_isolate.sv index da6bbc8bc..57c2529a0 100644 --- a/src/axi_isolate.sv +++ b/src/axi_isolate.sv @@ -107,7 +107,6 @@ module axi_isolate #( // We don't need many bits here as the common case will be to go for the pass-through. .AxiLookBits ( 1 ), .UniqueIds ( 1'b0 ), - .FallThrough ( 1'b1 ), .SpillAw ( 1'b0 ), .SpillW ( 1'b0 ), .SpillB ( 1'b0 ), diff --git a/src/axi_to_mem_banked.sv b/src/axi_to_mem_banked.sv index e50383d02..cc7c6198d 100644 --- a/src/axi_to_mem_banked.sv +++ b/src/axi_to_mem_banked.sv @@ -151,7 +151,6 @@ module axi_to_mem_banked #( .MaxTrans ( MemLatency+2 ), // allow multiple Ax vectors to not starve W channel .AxiLookBits ( 32'd1 ), // select is fixed, do not need it .UniqueIds ( 1'b0 ), - .FallThrough ( 1'b1 ), .SpillAw ( 1'b1 ), .SpillW ( 1'b1 ), .SpillB ( 1'b1 ), diff --git a/src/axi_xbar.sv b/src/axi_xbar.sv index 764084619..0971ef6f5 100644 --- a/src/axi_xbar.sv +++ b/src/axi_xbar.sv @@ -164,7 +164,6 @@ import cf_math_pkg::idx_width; .MaxTrans ( Cfg.MaxMstTrans ), .AxiLookBits ( Cfg.AxiIdUsedSlvPorts ), .UniqueIds ( Cfg.UniqueIds ), - .FallThrough ( Cfg.FallThrough ), .SpillAw ( Cfg.LatencyMode[9] ), .SpillW ( Cfg.LatencyMode[8] ), .SpillB ( Cfg.LatencyMode[7] ),