//////////////////////////////////////////////////////////////////////////////// // // Filename: afifo.v // {{{ // Project: WB2AXIPSP: bus bridges and other odds and ends // // Purpose: A basic asynchronous FIFO. // // 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 afifo #( // {{{ // LGFIFO is the log based-two of the number of entries // in the FIFO, log_2(fifo size) parameter LGFIFO = 3, // // WIDTH is the number of data bits in each entry parameter WIDTH = 16, // // NFF is the number of flip flops used to cross clock domains. // 2 is a minimum. Some applications appreciate the better parameter NFF = 2, // // This core can either write on the positive edge of the clock // or the negative edge. Set WRITE_ON_POSEDGE (the default) // to write on the positive edge of the clock. parameter [0:0] WRITE_ON_POSEDGE = 1'b1, // // Many logic elements can read from memory asynchronously. // This burdens any following logic. By setting // OPT_REGISTER_READS, we force all reads to be synchronous and // not burdened by any logic. You can spare a clock of latency // by clearing this register. parameter [0:0] OPT_REGISTER_READS = 1'b1 `ifdef FORMAL // F_OPT_DATA_STB // {{{ // In the formal proof, F_OPT_DATA_STB includes a series of // assumptions associated with a data strobe I/O pin--things // like a discontinuous clock--just to make sure the core still // works in those circumstances , parameter [0:0] F_OPT_DATA_STB = 1'b1 // }}} `endif // }}} ) ( // {{{ // // The (incoming) write data interface input wire i_wclk, i_wr_reset_n, i_wr, input wire [WIDTH-1:0] i_wr_data, output reg o_wr_full, // // The (incoming) write data interface input wire i_rclk, i_rd_reset_n, i_rd, output reg [WIDTH-1:0] o_rd_data, output reg o_rd_empty `ifdef FORMAL , output reg [LGFIFO:0] f_fill `endif // }}} ); // Register/net declarations // {{{ // MSB = most significant bit of the FIFO address vector. It's // just short-hand for LGFIFO, and won't work any other way. localparam MSB = LGFIFO; // reg [WIDTH-1:0] mem [(1<> 1); end // }}} // Write to memory // {{{ always @(posedge wclk) if (i_wr && !o_wr_full) mem[wr_addr[LGFIFO-1:0]] <= i_wr_data; // }}} // rd_addr, rgray // {{{ assign next_rd_addr = rd_addr + 1; always @(posedge i_rclk or negedge i_rd_reset_n) if (!i_rd_reset_n) begin rd_addr <= 0; rgray <= 0; end else if (lcl_read && !lcl_rd_empty) begin rd_addr <= next_rd_addr; rgray <= next_rd_addr ^ (next_rd_addr >> 1); end // }}} // Read from memory // {{{ always @(*) lcl_rd_data = mem[rd_addr[LGFIFO-1:0]]; // }}} // }}} //////////////////////////////////////////////////////////////////////// // // Cross clock domains // {{{ //////////////////////////////////////////////////////////////////////// // // // read pointer -> wr_rgray // {{{ always @(posedge wclk or negedge i_wr_reset_n) if (!i_wr_reset_n) { wr_rgray, rgray_cross } <= 0; else { wr_rgray, rgray_cross } <= { rgray_cross, rgray }; // }}} // write pointer -> rd_wgray // {{{ always @(posedge i_rclk or negedge i_rd_reset_n) if (!i_rd_reset_n) { rd_wgray, wgray_cross } <= 0; else { rd_wgray, wgray_cross } <= { wgray_cross, wgray }; // }}} // }}} //////////////////////////////////////////////////////////////////////// // // Flag generation // {{{ //////////////////////////////////////////////////////////////////////// // // always @(*) o_wr_full = (wr_rgray == { ~wgray[MSB:MSB-1], wgray[MSB-2:0] }); always @(*) lcl_rd_empty = (rd_wgray == rgray); // o_rd_empty, o_rd_data // {{{ generate if (OPT_REGISTER_READS) begin : GEN_REGISTERED_READ // {{{ always @(*) lcl_read = (o_rd_empty || i_rd); always @(posedge i_rclk or negedge i_rd_reset_n) if (!i_rd_reset_n) o_rd_empty <= 1'b1; else if (lcl_read) o_rd_empty <= lcl_rd_empty; always @(posedge i_rclk) if (lcl_read) o_rd_data <= lcl_rd_data; // }}} end else begin : GEN_COMBINATORIAL_FLAGS // {{{ always @(*) lcl_read = i_rd; always @(*) o_rd_empty = lcl_rd_empty; always @(*) o_rd_data = lcl_rd_data; // }}} end endgenerate // }}} // }}} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // // Formal properties // {{{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// `ifdef FORMAL // Start out with some register/net/macro declarations, f_past_valid,etc // {{{ `ifdef AFIFO `define ASSERT assert `define ASSUME assume `else `define ASSERT assert `define ASSUME assert `endif (* gclk *) reg gbl_clk; reg f_past_valid_gbl, f_past_valid_rd, f_rd_in_reset, f_wr_in_reset; reg [WIDTH-1:0] past_rd_data, past_wr_data; reg past_wr_reset_n, past_rd_reset_n, past_rd_empty, past_wclk, past_rclk, past_rd; reg [(LGFIFO+1)*(NFF-1)-1:0] f_wcross, f_rcross; reg [LGFIFO:0] f_rd_waddr, f_wr_raddr; reg [LGFIFO:0] f_rdcross_fill [NFF-1:0]; reg [LGFIFO:0] f_wrcross_fill [NFF-1:0]; initial f_past_valid_gbl = 1'b0; always @(posedge gbl_clk) f_past_valid_gbl <= 1'b1; initial f_past_valid_rd = 1'b0; always @(posedge i_rclk) f_past_valid_rd <= 1'b1; // }}} //////////////////////////////////////////////////////////////////////// // // Reset checks // {{{ //////////////////////////////////////////////////////////////////////// // // initial f_wr_in_reset = 1'b1; always @(posedge wclk or negedge i_wr_reset_n) if (!i_wr_reset_n) f_wr_in_reset <= 1'b1; else f_wr_in_reset <= 1'b0; initial f_rd_in_reset = 1'b1; always @(posedge i_rclk or negedge i_rd_reset_n) if (!i_rd_reset_n) f_rd_in_reset <= 1'b1; else f_rd_in_reset <= 1'b0; // // Resets are ... // 1. Asserted always initially, and ... always @(*) if (!f_past_valid_gbl) begin `ASSUME(!i_wr_reset_n); `ASSUME(!i_rd_reset_n); end // 2. They only ever become active together always @(*) if (past_wr_reset_n && !i_wr_reset_n) `ASSUME(!i_rd_reset_n); always @(*) if (past_rd_reset_n && !i_rd_reset_n) `ASSUME(!i_wr_reset_n); // }}} //////////////////////////////////////////////////////////////////////// // // Synchronous signal assumptions // {{{ //////////////////////////////////////////////////////////////////////// // // always @(posedge gbl_clk) begin past_wr_reset_n <= i_wr_reset_n; past_rd_reset_n <= i_rd_reset_n; past_wclk <= wclk; past_rclk <= i_rclk; past_rd <= i_rd; past_rd_data <= lcl_rd_data; past_wr_data <= i_wr_data; past_rd_empty<= lcl_rd_empty; end // // Read side may be assumed to be synchronous always @(*) if (f_past_valid_gbl && i_rd_reset_n && (past_rclk || !i_rclk)) // i.e. if (!$rose(i_rclk)) `ASSUME(i_rd == past_rd); always @(*) if (f_past_valid_rd && !f_rd_in_reset && !lcl_rd_empty &&(past_rclk || !i_rclk)) begin `ASSERT(lcl_rd_data == past_rd_data); `ASSERT(lcl_rd_empty == past_rd_empty); end generate if (F_OPT_DATA_STB) begin always @(posedge gbl_clk) `ASSUME(!o_wr_full); always @(posedge gbl_clk) if (!i_wr_reset_n) `ASSUME(!i_wclk); always @(posedge gbl_clk) `ASSUME(i_wr == i_wr_reset_n); always @(posedge gbl_clk) if ($changed(i_wr_reset_n)) `ASSUME($stable(wclk)); end endgenerate // }}} //////////////////////////////////////////////////////////////////////// // // Fill checks // {{{ //////////////////////////////////////////////////////////////////////// // // always @(*) f_fill = wr_addr - rd_addr; always @(*) if (!f_wr_in_reset) `ASSERT(f_fill <= { 1'b1, {(MSB){1'b0}} }); always @(*) if (wr_addr == rd_addr) `ASSERT(lcl_rd_empty); always @(*) if ((!f_wr_in_reset && !f_rd_in_reset) && wr_addr == { ~rd_addr[MSB], rd_addr[MSB-1:0] }) `ASSERT(o_wr_full); // }}} //////////////////////////////////////////////////////////////////////// // // Induction checks // {{{ //////////////////////////////////////////////////////////////////////// // // // f_wr_in_reset -- write logic is in its reset state // {{{ always @(*) if (f_wr_in_reset) begin `ASSERT(wr_addr == 0); `ASSERT(wgray_cross == 0); `ASSERT(rd_addr == 0); `ASSERT(rgray_cross == 0); `ASSERT(rd_wgray == 0); `ASSERT(lcl_rd_empty); `ASSERT(!o_wr_full); end // }}} // f_rd_in_reset -- read logic is in its reset state // {{{ always @(*) if (f_rd_in_reset) begin `ASSERT(rd_addr == 0); `ASSERT(rgray_cross == 0); `ASSERT(rd_wgray == 0); `ASSERT(lcl_rd_empty); end // }}} // f_wr_raddr -- a read address to match the gray values // {{{ always @(posedge wclk or negedge i_wr_reset_n) if (!i_wr_reset_n) { f_wr_raddr, f_rcross } <= 0; else { f_wr_raddr, f_rcross } <= { f_rcross, rd_addr }; // }}} // f_rd_waddr -- a write address to match the gray values // {{{ always @(posedge i_rclk or negedge i_rd_reset_n) if (!i_rd_reset_n) { f_rd_waddr, f_wcross } <= 0; else { f_rd_waddr, f_wcross } <= { f_wcross, wr_addr }; // }}} integer k; // wgray check // {{{ always @(*) `ASSERT((wr_addr ^ (wr_addr >> 1)) == wgray); // }}} // wgray_cross check // {{{ always @(*) for(k=0; k>1)) == wgray_cross[k*(LGFIFO+1) +: LGFIFO+1]); // }}} // rgray check // {{{ always @(*) `ASSERT((rd_addr ^ (rd_addr >> 1)) == rgray); // }}} // rgray_cross check // {{{ always @(*) for(k=0; k>1)) == rgray_cross[k*(LGFIFO+1) +: LGFIFO+1]); // }}} // wr_rgray // {{{ always @(*) `ASSERT((f_wr_raddr ^ (f_wr_raddr >> 1)) == wr_rgray); // }}} // rd_wgray // {{{ always @(*) `ASSERT((f_rd_waddr ^ (f_rd_waddr >> 1)) == rd_wgray); // }}} // f_rdcross_fill // {{{ always @(*) for(k=0; k= f_wrcross_fill[k-1]); always @(*) `ASSERT(f_wrcross_fill[0] >= f_fill); // }}} // }}} //////////////////////////////////////////////////////////////////////// // // Clock generation // {{{ //////////////////////////////////////////////////////////////////////// // // // Here's the challenge: if we use $past with any of our clocks, such // as to determine stability or any such, the proof takes forever, and // we need to guarantee a minimum number of transitions within the // depth of the proof. If, on the other hand, we build our own $past // primitives--we can then finish much faster and be successful on // any depth of proof. // pre_xclk is what the clock will become on the next global clock edge. // By using it here, we can check things @(*) instead of // @(posedge gbl_clk). Further, we can check $rose(pre_xclk) (or $fell) // and essentially check things @(*) but while using @(global_clk). // In other words, we can transition on @(posedge gbl_clk), but stay // in sync with the data--rather than being behind by a clock. // now_xclk is what the clock is currently. // (* anyseq *) reg pre_wclk, pre_rclk; reg now_wclk, now_rclk; always @(posedge gbl_clk) begin now_wclk <= pre_wclk; now_rclk <= pre_rclk; end always @(*) begin assume(i_wclk == now_wclk); assume(i_rclk == now_rclk); end always @(posedge gbl_clk) assume(i_rclk == $past(pre_rclk)); // Assume both clocks start idle // {{{ always @(*) if (!f_past_valid_gbl) begin assume(!pre_wclk && !wclk); assume(!pre_rclk && !i_rclk); end // }}} // }}} //////////////////////////////////////////////////////////////////////// // // Formal contract check --- the twin write test // {{{ //////////////////////////////////////////////////////////////////////// // // // Tracking register declarations // {{{ reg [WIDTH-1:0] f_first, f_next; (* anyconst *) reg [LGFIFO:0] f_addr; reg [LGFIFO:0] f_next_addr; reg f_first_in_fifo, f_next_in_fifo; reg [LGFIFO:0] f_to_first, f_to_next; reg [1:0] f_state; always @(*) f_next_addr = f_addr + 1; // }}} // distance_to*, *_in_fifo // {{{ always @(*) begin f_to_first = f_addr - rd_addr; f_first_in_fifo = 1'b1; if ((f_to_first >= f_fill)||(f_fill == 0)) f_first_in_fifo = 1'b0; if (mem[f_addr] != f_first) f_first_in_fifo = 1'b0; // // Check the second item // f_to_next = f_next_addr - rd_addr; f_next_in_fifo = 1'b1; if ((f_to_next >= f_fill)||(f_fill == 0)) f_next_in_fifo = 1'b0; if (mem[f_next_addr] != f_next) f_next_in_fifo = 1'b0; end // }}} // f_state -- generate our state variable // {{{ initial f_state = 0; always @(posedge gbl_clk) if (!i_wr_reset_n) f_state <= 0; else case(f_state) 2'b00: if (($rose(pre_wclk))&& i_wr && !o_wr_full &&(wr_addr == f_addr)) begin f_state <= 2'b01; f_first <= i_wr_data; end 2'b01: if ($rose(pre_rclk)&& lcl_read && rd_addr == f_addr) f_state <= 2'b00; else if ($rose(pre_wclk) && i_wr && !o_wr_full ) begin f_state <= 2'b10; f_next <= i_wr_data; end 2'b10: if ($rose(pre_rclk) && lcl_read && !lcl_rd_empty && rd_addr == f_addr) f_state <= 2'b11; 2'b11: if ($rose(pre_rclk) && lcl_read && !lcl_rd_empty && rd_addr == f_next_addr) f_state <= 2'b00; endcase // }}} // f_state invariants // {{{ always @(*) if (i_wr_reset_n) case(f_state) 2'b00: begin end 2'b01: begin `ASSERT(f_first_in_fifo); `ASSERT(wr_addr == f_next_addr); `ASSERT(f_fill >= 1); end 2'b10: begin `ASSERT(f_first_in_fifo); `ASSERT(f_next_in_fifo); if (!lcl_rd_empty && (rd_addr == f_addr)) `ASSERT(lcl_rd_data == f_first); `ASSERT(f_fill >= 2); end 2'b11: begin `ASSERT(rd_addr == f_next_addr); `ASSERT(f_next_in_fifo); `ASSERT(f_fill >= 1); if (!lcl_rd_empty) `ASSERT(lcl_rd_data == f_next); end endcase // }}} generate if (OPT_REGISTER_READS) begin reg past_o_rd_empty; always @(posedge gbl_clk) past_o_rd_empty <= o_rd_empty; always @(posedge gbl_clk) if (f_past_valid_gbl && i_rd_reset_n) begin if ($past(!o_rd_empty && !i_rd && i_rd_reset_n)) `ASSERT($stable(o_rd_data)); end always @(posedge gbl_clk) if (!f_rd_in_reset && i_rd_reset_n && i_rclk && !past_rclk) begin if (past_o_rd_empty) `ASSERT(o_rd_data == past_rd_data); if (past_rd) `ASSERT(o_rd_data == past_rd_data); end end endgenerate // }}} //////////////////////////////////////////////////////////////////////// // // Cover checks // {{{ //////////////////////////////////////////////////////////////////////// // // // Prove that we can read and write the FIFO // {{{ always @(*) if (i_wr_reset_n && i_rd_reset_n) begin cover(o_rd_empty); cover(!o_rd_empty); cover(f_state == 2'b01); cover(f_state == 2'b10); cover(f_state == 2'b11); cover(&f_fill[MSB-1:0]); cover(i_rd); cover(i_rd && !o_rd_empty); end // }}} `ifdef AFIFO generate if (!F_OPT_DATA_STB) begin : COVER_FULL // {{{ reg cvr_full; initial cvr_full = 1'b0; always @(posedge gbl_clk) if (!i_wr_reset_n) cvr_full <= 1'b0; else if (o_wr_full) cvr_full <= 1'b1; always @(*) if (f_past_valid_gbl && i_wr_reset_n) begin cover(o_wr_full); cover(o_rd_empty && cvr_full); cover(o_rd_empty && f_fill == 0 && cvr_full); end // }}} end else begin : COVER_NEARLY_FULL // {{{ reg cvr_nearly_full; initial cvr_nearly_full = 1'b0; always @(posedge gbl_clk) if (!i_wr_reset_n) cvr_nearly_full <= 1'b0; else if (f_fill == { 1'b0, {(LGFIFO){1'b1} }}) cvr_nearly_full <= 1'b1; always @(*) if (f_past_valid_gbl && i_wr_reset_n) begin cover(f_fill == { 1'b0, {(LGFIFO){1'b1} }}); cover(cvr_nearly_full && i_wr_reset_n); cover(o_rd_empty && cvr_nearly_full); cover(o_rd_empty && f_fill == 0 && cvr_nearly_full); end // }}} end endgenerate `endif // }}} `endif // }}} endmodule