//////////////////////////////////////////////////////////////////////////////// // // Filename: rtl/wbxclk.v // {{{ // Project: WB2AXIPSP: bus bridges and other odds and ends // // Purpose: To cross clock domains with a (pipelined) wishbone bus. // // Challenges: // 1. Wishbone has no capacity for back pressure. That means that we'll // need to be careful not to issue more STB requests than ACKs // that will fit in the return buffer. // // Imagine, for example, a faster return clock but a slave that needs // many clocks to get going. During that time, many requests // might be issued. If they all suddenly get returned at once, // flooding the return ACK FIFO, then we have a problem. // // 2. Bus aborts. If we ever have to abort a transaction, that's going // to be a pain. The FIFOs will need to be reset and the // downstream CYC line dropped. This needs to be done // synchronously in both domains, but there's no real choice but // to make the crossing asynchronous. // // 3. Synchronous CYC. Lowering CYC is a normal part of the protocol, as // is raising CYC. CYC is used as a bus locking scheme, so we'll // need to know when it is (properly) lowered downstream. This // can be done by passing a synchronous CYC drop request through // the pipeline in addition to the bus aborts above. // // Status: // Formally verified against a set of bus properties, not yet // used in any real or simulated designs // // Creator: Dan Gisselquist, Ph.D. // Gisselquist Technology, LLC // //////////////////////////////////////////////////////////////////////////////// // }}} // Copyright (C) 2020-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 wbxclk #( // {{{ parameter AW=32, DW=32, LGFIFO = 5 // }}} ) ( // {{{ input wire i_wb_clk, i_reset, // Input/master bus input wire i_wb_cyc, i_wb_stb, i_wb_we, input wire [(AW-1):0] i_wb_addr, input wire [(DW-1):0] i_wb_data, input wire [(DW/8-1):0] i_wb_sel, output wire o_wb_stall, output reg o_wb_ack, output reg [(DW-1):0] o_wb_data, output reg o_wb_err, // Delayed bus input wire i_xclk_clk, output reg o_xclk_cyc, output reg o_xclk_stb, output reg o_xclk_we, output reg [(AW-1):0] o_xclk_addr, output reg [(DW-1):0] o_xclk_data, output reg [(DW/8-1):0] o_xclk_sel, input wire i_xclk_stall, input wire i_xclk_ack, input wire [(DW-1):0] i_xclk_data, input wire i_xclk_err // }}} ); // // Declare our signals // {{{ localparam NFF = 2; reg wb_active; reg [NFF-2:0] bus_abort_pipe; reg [LGFIFO:0] acks_outstanding; reg ackfifo_single, ackfifo_empty, ackfifo_full; wire ack_stb, err_stb; wire [DW-1:0] ret_wb_data; wire req_fifo_stall, no_returns; // // Verilator lint_off SYNCASYNCNET reg bus_abort; // Verilator lint_on SYNCASYNCNET // wire req_stb, req_fifo_empty; reg xclk_err_state, ign_ackfifo_stall; reg xck_reset; reg [NFF-2:0] xck_reset_pipe; wire req_we; wire [AW-1:0] req_addr; wire [DW-1:0] req_data; wire [DW/8-1:0] req_sel; `ifdef FORMAL wire [LGFIFO:0] ackfifo_prefill, reqfifo_prefill; `endif // }}} // // // On the original wishbone clock ... // // FIFO/queue up our requests // {{{ initial wb_active = 1'b0; always @(posedge i_wb_clk) if (i_reset || !i_wb_cyc) wb_active <= 1'b0; else if (i_wb_stb && !o_wb_stall) wb_active <= 1'b1; initial { bus_abort, bus_abort_pipe } = -1; always @(posedge i_wb_clk) if (i_reset) { bus_abort, bus_abort_pipe } <= -1; else if (!i_wb_cyc && (!ackfifo_empty)) { bus_abort, bus_abort_pipe } <= -1; else if (o_wb_err) { bus_abort, bus_abort_pipe } <= -1; else if (ackfifo_empty) { bus_abort, bus_abort_pipe } <= { bus_abort_pipe, 1'b0 }; `ifdef FORMAL always @(*) if (bus_abort_pipe) assert(bus_abort); `endif // }}} // // The request FIFO itself // {{{ afifo #( `ifdef FORMAL .OPT_REGISTER_READS(0), .F_OPT_DATA_STB(1'b0), `endif .NFF(NFF), .LGFIFO(LGFIFO), .WIDTH(2+AW+DW+(DW/8)) ) reqfifo(.i_wclk(i_wb_clk), .i_wr_reset_n(!bus_abort), .i_wr((i_wb_stb&&!o_wb_stall) || (wb_active && !i_wb_cyc)), .i_wr_data({ i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, i_wb_sel }), .o_wr_full(req_fifo_stall), // .i_rclk(i_xclk_clk), .i_rd_reset_n(!xck_reset), .i_rd(!o_xclk_stb || !i_xclk_stall), .o_rd_data({ req_stb, req_we, req_addr, req_data, req_sel }), .o_rd_empty(req_fifo_empty) `ifdef FORMAL , .f_fill(reqfifo_prefill) `endif ); // }}} // // Downstream bus--issuing requests // {{{ initial { xck_reset, xck_reset_pipe } = -1; always @(posedge i_xclk_clk or posedge bus_abort) if (bus_abort) { xck_reset, xck_reset_pipe } <= -1; else { xck_reset, xck_reset_pipe } <= { xck_reset_pipe, 1'b0 }; `ifdef FORMAL always @(*) if (xck_reset_pipe) assert(xck_reset); `endif initial xclk_err_state = 1'b0; always @(posedge i_xclk_clk) if (xck_reset || (!req_fifo_empty && !req_stb)) xclk_err_state <= 1'b0; else if (o_xclk_cyc && i_xclk_err) xclk_err_state <= 1'b1; initial o_xclk_cyc = 1'b0; always @(posedge i_xclk_clk) if (xck_reset || (o_xclk_cyc && i_xclk_err)) o_xclk_cyc <= 1'b0; else if (!req_fifo_empty && !req_stb) o_xclk_cyc <= 1'b0; else if (!req_fifo_empty && !xclk_err_state) o_xclk_cyc <= req_stb; initial o_xclk_stb = 1'b0; always @(posedge i_xclk_clk) if (xck_reset || (o_xclk_cyc && i_xclk_err) || xclk_err_state) o_xclk_stb <= 1'b0; else if (!o_xclk_stb || !i_xclk_stall) o_xclk_stb <= req_stb && !req_fifo_empty; always @(posedge i_xclk_clk) if ((!o_xclk_stb || !i_xclk_stall) && req_stb && !req_fifo_empty) o_xclk_we <= req_we; always @(posedge i_xclk_clk) if (!o_xclk_stb || !i_xclk_stall) begin o_xclk_addr <= req_addr; o_xclk_data <= req_data; o_xclk_sel <= req_sel; end // }}} // // Request counting // {{{ initial acks_outstanding = 0; initial ackfifo_single = 0; initial ackfifo_empty = 1; initial ackfifo_full = 0; always @(posedge i_wb_clk) if (i_reset || !i_wb_cyc || o_wb_err) begin acks_outstanding <= 0; ackfifo_single <= 0; ackfifo_empty <= 1; ackfifo_full <= 0; end else case({ (i_wb_stb && !o_wb_stall), o_wb_ack }) 2'b10: begin acks_outstanding <= acks_outstanding + 1; ackfifo_empty <= 0; ackfifo_single <= ackfifo_empty; ackfifo_full <= (&acks_outstanding[LGFIFO-1:0]); end 2'b01: begin acks_outstanding <= acks_outstanding - 1; ackfifo_empty <= (acks_outstanding <= 1); ackfifo_single <= (acks_outstanding == 2); ackfifo_full <= 0; end default: begin end endcase `ifdef FORMAL always @(*) begin assert(ackfifo_single == (acks_outstanding == 1)); assert(ackfifo_empty == (acks_outstanding == 0)); assert(ackfifo_full == (acks_outstanding >= (1<= 2); assume(fxck_step >= 2); assume(fwb_step <= 4'b1000); assume(fxck_step <= 4'b1000); // assume(fwb_step == 4'b1000); // assume(fxck_step == 4'b0111); assume((fwb_step == 4'b0111) || (fxck_step == 4'b0111)); end always @(posedge gbl_clk) begin fwb_count <= fwb_count + fwb_step; fxck_count <= fxck_count + fxck_step; end always @(*) begin assume(i_wb_clk == fwb_count[3]); assume(i_xclk_clk == fxck_count[3]); end // }}} //////////////////////////////////////////////////////////////////////// // // .... // //////////////////////////////////////////////////////////////////////// // // // // Cross clock stability assumptions // {{{ always @(posedge gbl_clk) if (!$rose(i_wb_clk)) begin assume($stable(i_reset)); assume($stable(i_wb_cyc)); assume($stable(i_wb_stb)); assume($stable(i_wb_we)); assume($stable(i_wb_addr)); assume($stable(i_wb_data)); assume($stable(i_wb_sel)); end always @(posedge gbl_clk) if (!$rose(i_xclk_clk)) begin assume($stable(i_xclk_ack)); assume($stable(i_xclk_err)); assume($stable(i_xclk_data)); assume($stable(i_xclk_stall)); end reg past_wb_clk, past_wb_stb, past_wb_we, past_wb_cyc, past_wb_reset, past_wb_err; reg past_xclk, past_xclk_stall, past_xclk_ack, past_xclk_err; reg [DW-1:0] past_xclk_data; reg f_drop_cyc_request; always @(posedge gbl_clk) begin past_wb_clk <= i_wb_clk; past_wb_reset<= i_reset; past_wb_cyc <= i_wb_cyc; past_wb_stb <= i_wb_stb; past_wb_we <= i_wb_we; past_wb_err <= o_wb_err; end always @(*) if (!i_wb_clk || past_wb_clk) begin assume(past_wb_reset== i_reset); assume(past_wb_cyc == i_wb_cyc); assume(past_wb_stb == i_wb_stb); assume(past_wb_we == i_wb_we); assume(past_wb_err == o_wb_err); end else begin if (past_wb_err && past_wb_cyc) assume(!i_wb_cyc); if (fwb_outstanding > 0) assume(past_wb_we == i_wb_we); end always @(posedge gbl_clk) begin past_xclk <= i_xclk_clk; past_xclk_stall <= i_xclk_stall; past_xclk_data <= i_xclk_data; past_xclk_ack <= i_xclk_ack; past_xclk_err <= i_xclk_err; end always @(*) if (!i_xclk_clk || past_xclk) begin assume(past_xclk_stall == i_xclk_stall); assume(past_xclk_data == i_xclk_data); assume(past_xclk_ack == i_xclk_ack); assume(past_xclk_err == i_xclk_err); end // }}} //////////////////////////////////////////////////////////////////////// // // Wishbone bus property checks // {{{ //////////////////////////////////////////////////////////////////////// // // fwb_slave #( // {{{ .AW(AW), .DW(DW), .F_LGDEPTH(LGFIFO+1), .F_OPT_DISCONTINUOUS(1) // }}} ) slv( // {{{ i_wb_clk, i_reset, i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, i_wb_sel, o_wb_ack, o_wb_stall, o_wb_data, o_wb_err, fwb_nreqs, fwb_nacks, fwb_outstanding // }}} ); fwb_master #( // {{{ .AW(AW), .DW(DW), .F_LGDEPTH(LGFIFO+1), .F_OPT_DISCONTINUOUS(1), .F_MAX_STALL(4), .F_MAX_ACK_DELAY(7) // }}} ) xclkwb( // {{{ i_xclk_clk, xck_reset, o_xclk_cyc, o_xclk_stb, o_xclk_we, o_xclk_addr, o_xclk_data, o_xclk_sel, i_xclk_ack, i_xclk_stall, i_xclk_data, i_xclk_err, fxck_nreqs, fxck_nacks, fxck_outstanding // }}} ); // }}} //////////////////////////////////////////////////////////////////////// // // (Random/unsorted) properties // {{{ //////////////////////////////////////////////////////////////////////// // always @(*) if (reqfifo_fill != (o_xclk_stb ? 1:0) && !req_stb) assert(ackfifo_fill == 0 || xclk_err_state); always @(*) reqfifo_fill = reqfifo_prefill + (o_xclk_stb ? 1:0); always @(*) ackfifo_fill = ackfifo_prefill // + (no_returns ? 0:1) + ((o_wb_ack || o_wb_err) ? 1:0); always @(*) if (fwb_outstanding > 0) assert(wb_active); always @(*) if (f_drop_cyc_request && !bus_abort && !xclk_err_state) begin // req_stb is low, so cycle line has dropped normally // If the cycle line has since risen, there may be requests // within the request FIFO in addition to the drop-CYC message. if (i_wb_cyc && wb_active) assert(reqfifo_fill == fwb_outstanding + 1); // We wouldn't place a drop CYC message into the FIFO // unless XCLK-CYC was already high assert(o_xclk_cyc && !o_xclk_stb); assert(ackfifo_fill == 0); assert(fxck_outstanding == 0); if (reqfifo_fill > 1) assert(wb_active); else assert(!wb_active); end else if (!bus_abort && xclk_err_state) begin // // Bus error downstream causes an abort assert(fxck_outstanding == 0); assert(xck_reset || wb_active || !i_wb_cyc); assert(!o_xclk_stb); if (ackfifo_fill == 1) assert(no_returns || err_stb); else if (!xck_reset && ackfifo_fill == 1) assert(o_wb_err); end else if (!bus_abort && i_wb_cyc && !xck_reset && !xclk_err_state) begin // // Normal operation in operation // assert(reqfifo_fill <= fwb_outstanding + 1); assert(ackfifo_fill <= fwb_outstanding); assert(fxck_outstanding <= fwb_outstanding); if (o_xclk_cyc) assert(wb_active || f_drop_cyc_request); if (reqfifo_fill == (o_xclk_stb ? 1:0) || req_stb) // Either first request is for strobe, or other // request counters are valid assert(reqfifo_fill + ackfifo_fill + fxck_outstanding == fwb_outstanding); else begin // First request is to drop CYC assert(reqfifo_fill== fwb_outstanding + 1); assert(ackfifo_fill == 0); assert(fxck_outstanding == 0); assert(!o_xclk_stb); assert(o_xclk_cyc); end if (acks_outstanding == 0) assert((reqfifo_fill == 0)||!req_stb); end always @(*) if (o_wb_ack && wb_active) begin assert(o_xclk_cyc || xclk_err_state); assert(!f_drop_cyc_request); assert(!xck_reset || bus_abort); end always @(*) if (!bus_abort && acks_outstanding == 0) assert(reqfifo_fill <= (f_drop_cyc_request ? 1:0) + ((o_xclk_stb && xck_reset) ? 1:0)); always @(*) if (ackfifo_fill != 0) assert(o_xclk_cyc || xck_reset || xclk_err_state); always @(*) if (fxck_outstanding > fwb_outstanding) assert((!i_wb_cyc && wb_active) || i_reset || bus_abort || xck_reset); always @(*) if (!i_reset && xck_reset && !o_xclk_cyc) assert(!i_wb_cyc || fwb_outstanding == reqfifo_fill); always @(*) if (bus_abort && i_wb_cyc) assert(!wb_active); always @(*) if (acks_outstanding < (1< 0) && ((fxck_outstanding > 0) || o_xclk_stb)) assert(i_wb_we == o_xclk_we); always @(*) if (!i_reset && i_wb_cyc) assert(acks_outstanding == fwb_outstanding); always @(*) if (xclk_err_state) assert(!o_xclk_cyc); always @(*) if (!i_reset && !bus_abort && !i_wb_cyc) begin if (ackfifo_empty) begin if (reqfifo_fill > (o_xclk_stb ? 1:0)) assert(!req_stb || xck_reset); assert(reqfifo_fill <= 1); if (xck_reset && !xck_reset_pipe) assert(!o_xclk_cyc); end else begin // ??? end end always @(*) f_drop_cyc_request = !req_stb && (reqfifo_fill > (o_xclk_stb ? 1:0)); always @(*) if (!i_reset && !xck_reset && !bus_abort && i_wb_cyc) begin if (!o_xclk_cyc && !xclk_err_state) assert(acks_outstanding == reqfifo_fill - (f_drop_cyc_request ? 1:0)); else if (!o_xclk_cyc && xclk_err_state) assert(acks_outstanding >= reqfifo_fill + ackfifo_fill); else if (o_xclk_cyc && !xclk_err_state) begin assert(acks_outstanding >= reqfifo_fill - (f_drop_cyc_request ? 1:0)); assert(acks_outstanding >= ackfifo_fill); assert(acks_outstanding >= fxck_outstanding); assert(acks_outstanding == reqfifo_fill - (((reqfifo_fill > (o_xclk_stb ? 1:0))&&(!req_stb)) ? 1:0) + ackfifo_fill + fxck_outstanding); end if (f_drop_cyc_request) assert(acks_outstanding +1 == reqfifo_fill); else if (reqfifo_fill == 0) assert(!wb_active || o_xclk_cyc || xclk_err_state); end else if (!i_reset && !xck_reset && !bus_abort && f_drop_cyc_request) begin assert(acks_outstanding +1 == reqfifo_fill); assert(ackfifo_fill == 0); assert(fxck_outstanding == 0); assert(!o_xclk_stb); assert(o_xclk_cyc); end always @(*) if (!bus_abort && wb_active && reqfifo_fill == 0 && !xclk_err_state) assert(o_xclk_cyc); always @(*) if (f_drop_cyc_request && !bus_abort) assert(!xck_reset); always @(*) assert(!xclk_err_state || acks_outstanding != 0 || xck_reset); always @(*) if (o_xclk_cyc && !i_wb_cyc) begin // assert(bus_abort || !xclk_err_state); if (!wb_active && !bus_abort && !xck_reset) assert(f_drop_cyc_request); end else if (i_wb_cyc) begin if (wb_active && !xck_reset) assert(o_xclk_cyc || xclk_err_state ||(reqfifo_fill >= acks_outstanding)); end // always @(*) // if (acks_outstanding >= (1< 0 || o_xclk_stb) assert(!ign_ackfifo_stall); // }}} //////////////////////////////////////////////////////////////////////// // // Sub properties for the REQ FIFO // {{{ //////////////////////////////////////////////////////////////////////// // // always @(*) if ((acks_outstanding > 0)&&(reqfifo_fill != (o_xclk_stb ? 1:0))) begin // Something valuable is in the request FIFO if (i_wb_cyc && !f_drop_cyc_request) `BMC_ASSERT(req_we == i_wb_we); else if (req_stb && o_xclk_stb) `BMC_ASSERT(o_xclk_we == req_we); // if (acks_outstanding > 0) if (!o_xclk_cyc || o_xclk_stb || fxck_outstanding > 0 || ackfifo_fill > 0) `BMC_ASSERT(req_stb); end // else the request FIFO is empty, nothing is in it // No assumptions therefore need be made always @(*) if (!bus_abort && reqfifo_fill == acks_outstanding) `BMC_ASSERT(!req_fifo_stall || !f_drop_cyc_request); always @(*) if (!i_reset && o_xclk_cyc && (reqfifo_fill != (o_xclk_stb ? 1:0))) `BMC_ASSERT(!req_stb || req_we == o_xclk_we || fxck_outstanding == 0); // }}} //////////////////////////////////////////////////////////////////////// // // Sub properties for the ACK FIFO // {{{ //////////////////////////////////////////////////////////////////////// // // always @(*) if (!no_returns) begin `BMC_ASSERT(ack_stb ^ err_stb); if ((ackfifo_fill ==(o_wb_ack ? 2:1)) && xclk_err_state) `BMC_ASSERT(err_stb); else if (ackfifo_fill > (o_wb_ack ? 2:1)) `BMC_ASSERT(!err_stb); end // }}} //////////////////////////////////////////////////////////////////////// // // Cover properties // {{{ //////////////////////////////////////////////////////////////////////// // // reg cvr_abort; reg [3:0] cvr_replies, cvr_post_abort; initial cvr_abort = 0; always @(posedge i_wb_clk) if (i_reset) cvr_abort <= 0; else if (o_wb_err && acks_outstanding > 2) cvr_abort <= 1; initial cvr_replies = 0; always @(posedge i_wb_clk) if (i_reset) cvr_replies <= 0; else if (o_wb_ack || o_wb_err) cvr_replies <= cvr_replies + 1; initial cvr_post_abort = 0; always @(posedge i_wb_clk) if (i_reset) cvr_post_abort <= 0; else if (cvr_abort && o_wb_ack) cvr_post_abort <= cvr_post_abort + 1; always @(*) begin cover(cvr_replies > 1); // 33 cover(cvr_replies > 3); // 38 cover(cvr_replies > 9); cover(cvr_abort); // 31 cover(cvr_post_abort > 1 && cvr_replies > 1); // 63 cover(cvr_post_abort > 1 && cvr_replies > 2); // 63 cover(cvr_post_abort > 1 && cvr_replies > 3); // 65 cover(cvr_post_abort > 2 && cvr_replies > 3); // 65 cover(cvr_post_abort > 3 && cvr_replies > 3); // 68 cover(cvr_post_abort > 4 && cvr_replies > 3); // 70 cover(cvr_post_abort > 3 && cvr_replies > 6); // 72 end always @(posedge gbl_clk) if (!i_reset) cover(cvr_replies > 9 && !i_wb_clk && acks_outstanding == 0 && fwb_nreqs == fwb_nacks && fwb_nreqs == cvr_replies && !bus_abort && fwb_count != fxck_count); always @(posedge gbl_clk) if (!i_reset) cover(cvr_replies > 9 && !i_wb_clk && acks_outstanding == 0 && fwb_nreqs == fwb_nacks && fwb_nreqs == cvr_replies && !bus_abort && fwb_count != fxck_count && fwb_step != fxck_step); // }}} //////////////////////////////////////////////////////////////////////// // // Simplifying (careless) assumptions // {{{ //////////////////////////////////////////////////////////////////////// // // // None (at present) // }}} `endif // }}} endmodule