//////////////////////////////////////////////////////////////////////////////// // // Filename: wbxbar.v // {{{ // Project: WB2AXIPSP: bus bridges and other odds and ends // // Purpose: A Configurable wishbone cross-bar interconnect, conforming // to the WB-B4 pipeline specification, as described on the // ZipCPU blog. // // Performance: // Throughput: One transaction per clock // Latency: One clock to get access to an unused channel, another to // place the results on the slave bus, and another to return, or a minimum // of three clocks. // // Usage: To use, you'll need to set NM and NS to the number of masters // (input ports) and the number of slaves respectively. You'll then // want to set the addresses for the slaves in the SLAVE_ADDR array, // together with the SLAVE_MASK array indicating which SLAVE_ADDRs // are valid. Address and data widths should be adjusted at the same // time. // // Voila, you are now set up! // // Now let's fine tune this: // // LGMAXBURST can be set to control the maximum number of outstanding // transactions. An LGMAXBURST of 6 will allow 63 outstanding // transactions. // // OPT_TIMEOUT, if set to a non-zero value, is a number of clock periods // to wait for a slave to respond. Should the timeout expire and the // slave not respond, a bus error will be returned and the slave will // be issued a bus abort signal (CYC will be dropped). // // OPT_STARVATION_TIMEOUT, if set, applies the OPT_TIMEOUT counter to // how long a particular master waits for arbitration. If the master is // "starved", a bus error will be returned. // // OPT_DBLBUFFER is used to increase clock speed by registering all // outputs. // // OPT_LOWPOWER is an experimental feature that, if set, will cause any // unused FFs to be set to zero rather than flopping in the electronic // wind, in an effort to minimize transitions over bus wires. This will // cost some extra logic, for ... an uncertain power savings. // // // Creator: Dan Gisselquist, Ph.D. // Gisselquist Technology, LLC // //////////////////////////////////////////////////////////////////////////////// // }}} // Copyright (C) 2019-2024, Gisselquist Technology, LLC // {{{ // This file is part of the WB2AXIP project. // // The WB2AXIP project contains free software and gateware, licensed under the // Apache License, Version 2.0 (the "License"). You may not use this project, // or this file, except in compliance with the License. You may obtain a copy // of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the 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. // //////////////////////////////////////////////////////////////////////////////// // //`default_nettype none // }}} module wbxbar #( // {{{ parameter NM = 4, NS=8, parameter AW = 32, DW=32, parameter [NS*AW-1:0] SLAVE_ADDR = { { 3'b111, {(AW-3){1'b0}} }, { 3'b110, {(AW-3){1'b0}} }, { 3'b101, {(AW-3){1'b0}} }, { 3'b100, {(AW-3){1'b0}} }, { 3'b011, {(AW-3){1'b0}} }, { 3'b010, {(AW-3){1'b0}} }, { 4'b0010, {(AW-4){1'b0}} }, { 4'b0000, {(AW-4){1'b0}} } }, parameter [NS*AW-1:0] SLAVE_MASK = (NS <= 1) ? 0 : { {(NS-2){ 3'b111, {(AW-3){1'b0}} }}, {(2){ 4'b1111, {(AW-4){1'b0}} }} }, // // LGMAXBURST is the log_2 of the length of the longest burst // that might be seen. It's used to set the size of the // internal counters that are used to make certain that the // cross bar doesn't switch while still waiting on a response. parameter LGMAXBURST=6, // // OPT_TIMEOUT is used to help recover from a misbehaving slave. // If set, this value will determine the number of clock cycles // to wait for a misbehaving slave before returning a bus error. // Alternatively, if set to zero, this functionality will be // removed. parameter OPT_TIMEOUT = 0, // 1023; // // If OPT_TIMEOUT is set, then OPT_STARVATION_TIMEOUT may also // be set. The starvation timeout adds to the bus error timeout // generation the possibility that a master will wait // OPT_TIMEOUT counts without receiving the bus. This may be // the case, for example, if one bus master is consuming a // peripheral to such an extent that there's no time/room for // another bus master to use it. In that case, when the timeout // runs out, the waiting bus master will be given a bus error. parameter [0:0] OPT_STARVATION_TIMEOUT = 1'b0 && (OPT_TIMEOUT > 0), // // OPT_DBLBUFFER is used to register all of the outputs, and // thus avoid adding additional combinational latency through // the core that might require a slower clock speed. parameter [0:0] OPT_DBLBUFFER = 1'b0, // // OPT_LOWPOWER adds logic to try to force unused values to // zero, rather than to allow a variety of logic optimizations // that could be used to reduce the logic count of the device. // Hence, OPT_LOWPOWER will use more logic, but it won't drive // bus wires unless there's a value to drive onto them. parameter [0:0] OPT_LOWPOWER = 1'b1 // }}} ) ( // {{{ input wire i_clk, i_reset, // // Here are the bus inputs from each of the WB bus masters input wire [NM-1:0] i_mcyc, i_mstb, i_mwe, input wire [NM*AW-1:0] i_maddr, input wire [NM*DW-1:0] i_mdata, input wire [NM*DW/8-1:0] i_msel, // // .... and their return data output wire [NM-1:0] o_mstall, output wire [NM-1:0] o_mack, output reg [NM*DW-1:0] o_mdata, output wire [NM-1:0] o_merr, // // // Here are the output ports, used to control each of the // various slave ports that we are connected to output reg [NS-1:0] o_scyc, o_sstb, o_swe, output reg [NS*AW-1:0] o_saddr, output reg [NS*DW-1:0] o_sdata, output reg [NS*DW/8-1:0] o_ssel, // // ... and their return data back to us. input wire [NS-1:0] i_sstall, i_sack, input wire [NS*DW-1:0] i_sdata, input wire [NS-1:0] i_serr // }}} ); // // //////////////////////////////////////////////////////////////////////// // // Register declarations // {{{ // // TIMEOUT_WIDTH is the number of bits in counter used to check // on a timeout. localparam TIMEOUT_WIDTH = $clog2(OPT_TIMEOUT); // // LGNM is the log (base two) of the number of bus masters // connecting to this crossbar localparam LGNM = (NM>1) ? $clog2(NM) : 1; // // LGNS is the log (base two) of the number of slaves plus one // come out of the system. The extra "plus one" is used for a // pseudo slave representing the case where the given address // doesn't connect to any of the slaves. This address will // generate a bus error. localparam LGNS = $clog2(NS+1); // At one time I used o_macc and o_sacc to put into the outgoing // trace file, just enough logic to tell me if a transaction was // taking place on the given clock. // // assign o_macc = (i_mstb & ~o_mstall); // assign o_sacc = (o_sstb & ~i_sstall); // // These definitions work with Veri1ator, just not with Yosys // reg [NM-1:0][NS:0] request; // reg [NM-1:0][NS-1:0] requested; // reg [NM-1:0][NS:0] grant; // // These definitions work with both wire [NS:0] request [0:NM-1]; reg [NS-1:0] requested [0:NM-1]; reg [NS:0] grant [0:NM-1]; reg [NM-1:0] mgrant; reg [NS-1:0] sgrant; // Verilator lint_off UNUSED wire [LGMAXBURST-1:0] w_mpending [0:NM-1]; // Verilator lint_on UNUSED reg [NM-1:0] mfull, mnearfull, mempty; wire [NM-1:0] timed_out; localparam NMFULL = (NM > 1) ? (1< 0) begin : CHECK_TIMEOUT // {{{ for(N=0; N 0) begin deadlock_timer <= deadlock_timer - 1; r_timed_out <= (deadlock_timer <= 1); end assign timed_out[N] = r_timed_out; end // }}} end else begin : NO_TIMEOUT // {{{ assign timed_out = 0; // }}} end endgenerate // }}} //////////////////////////////////////////////////////////////////////// // // Parameter consistency check // {{{ //////////////////////////////////////////////////////////////////////// // // initial begin : PARAMETER_CONSISTENCY_CHECK // {{{ if (NM == 0) begin $display("ERROR: At least one master must be defined"); $stop; end if (NS == 0) begin $display("ERROR: At least one slave must be defined"); $stop; end if (OPT_STARVATION_TIMEOUT != 0 && OPT_TIMEOUT == 0) begin $display("ERROR: The starvation timeout is implemented as part of the regular timeout"); $display(" Without a timeout, the starvation timeout will not work"); $stop; end // }}} end // }}} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // Formal properties used to verify the core // {{{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// `ifdef FORMAL // Register declarations // {{{ localparam F_MAX_DELAY = 4; localparam F_LGDEPTH = LGMAXBURST; // reg f_past_valid; // // Our bus checker keeps track of the number of requests, // acknowledgments, and the number of outstanding transactions on // every channel, both the masters driving us wire [F_LGDEPTH-1:0] f_mreqs [0:NM-1]; wire [F_LGDEPTH-1:0] f_macks [0:NM-1]; wire [F_LGDEPTH-1:0] f_moutstanding [0:NM-1]; // // as well as the slaves that we drive ourselves wire [F_LGDEPTH-1:0] f_sreqs [0:NS-1]; wire [F_LGDEPTH-1:0] f_sacks [0:NS-1]; wire [F_LGDEPTH-1:0] f_soutstanding [0:NS-1]; // }}} initial assert(!OPT_STARVATION_TIMEOUT || OPT_TIMEOUT > 0); initial f_past_valid = 0; always @(posedge i_clk) f_past_valid = 1'b1; always @(*) if (!f_past_valid) assume(i_reset); generate for(N=0; N= ((OPT_BUFFER_DECODER && dcd_stb[N]) ? 1:0) + (o_mack[N] && OPT_DBLBUFFER) ? 1:0); always @(*) n_outstanding = f_moutstanding[N] - ((OPT_BUFFER_DECODER && dcd_stb[N]) ? 1:0) - ((o_mack[N] && OPT_DBLBUFFER) ? 1:0); always @(posedge i_clk) if (i_mcyc[N] && !mgrant[N] && !o_merr[N]) assert(f_moutstanding[N] == ((OPT_BUFFER_DECODER & dcd_stb[N]) ? 1:0)); else if (i_mcyc[N] && mgrant[N] && !i_reset) for(iM=0; iM 0) assert(i_mwe[N] == o_swe[iM]); if (o_sstb[iM]) assert(i_mwe[N] == o_swe[iM]); if (o_mack[N]) assert(i_mwe[N] == o_swe[iM]); if (o_scyc[iM] && i_sack[iM]) assert(i_mwe[N] == o_swe[iM]); if (o_merr[N] && !timed_out[N]) assert(i_mwe[N] == o_swe[iM]); if (o_scyc[iM] && i_serr[iM]) assert(i_mwe[N] == o_swe[iM]); end end always @(*) if (!i_reset && OPT_BUFFER_DECODER && i_mcyc[N]) begin if (dcd_stb[N]) assert(i_mwe[N] == m_we[N]); end // }}} end endgenerate generate for(M=0; M 1) begin : DOUBLE_ADDRESS_CHECK // {{{ // // Check that no slave address has been assigned twice. // This check only needs to be done once at the beginning // of the run, during the BMC section. reg address_found; always @(*) if (!f_past_valid) begin address_found = 0; for(iM=0; iM 1) begin always @(posedge i_clk) cover(!f_cvr_aborted && (&f_m_ackd[1:0])); end endgenerate initial f_s_ackd = 0; generate for (M=0; M 1) begin always @(posedge i_clk) cover(!f_cvr_aborted && (&f_s_ackd[NS-1:0])); end endgenerate // }}} `endif // }}} endmodule `ifndef YOSYS //`default_nettype wire `endif