diff options
Diffstat (limited to 'rtl/wb2axip/afifo.v')
| -rw-r--r-- | rtl/wb2axip/afifo.v | 801 |
1 files changed, 801 insertions, 0 deletions
diff --git a/rtl/wb2axip/afifo.v b/rtl/wb2axip/afifo.v new file mode 100644 index 0000000..99af9cc --- /dev/null +++ b/rtl/wb2axip/afifo.v @@ -0,0 +1,801 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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<<LGFIFO)-1:0]; + reg [LGFIFO:0] rd_addr, wr_addr, + rd_wgray, wr_rgray; + wire [LGFIFO:0] next_rd_addr, next_wr_addr; + reg [LGFIFO:0] rgray, wgray; + (* ASYNC_REG = "TRUE" *) reg [(LGFIFO+1)*(NFF-1)-1:0] + rgray_cross, wgray_cross; + wire wclk; + reg [WIDTH-1:0] lcl_rd_data; + reg lcl_read, lcl_rd_empty; + // }}} + + // wclk - Write clock generation + // {{{ + generate if (WRITE_ON_POSEDGE) + begin : GEN_POSEDGE_WRITES + + assign wclk = i_wclk; + + end else begin : GEN_NEGEDGE_WRITES + + assign wclk = !i_wclk; + + end endgenerate + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Write to and read from memory + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // wr_addr, wgray + // {{{ + assign next_wr_addr = wr_addr + 1; + always @(posedge wclk or negedge i_wr_reset_n) + if (!i_wr_reset_n) + begin + wr_addr <= 0; + wgray <= 0; + end else if (i_wr && !o_wr_full) + begin + wr_addr <= next_wr_addr; + wgray <= next_wr_addr ^ (next_wr_addr >> 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<NFF-1; k=k+1) + `ASSERT((f_wcross[k*(LGFIFO+1) +: LGFIFO+1] + ^ (f_wcross[k*(LGFIFO+1)+: LGFIFO+1]>>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<NFF-1; k=k+1) + `ASSERT((f_rcross[k*(LGFIFO+1) +: LGFIFO+1] + ^ (f_rcross[k*(LGFIFO+1) +: LGFIFO+1]>>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<NFF-1; k=k+1) + f_rdcross_fill[k] = f_wcross[k*(LGFIFO+1) +: LGFIFO+1] + - rd_addr; + + always @(*) + f_rdcross_fill[NFF-1] = f_rd_waddr - rd_addr; + + always @(*) + for(k=0; k<NFF; k=k+1) + `ASSERT(f_rdcross_fill[k] <= { 1'b1, {(LGFIFO){1'b0}} }); + + always @(*) + for(k=1; k<NFF; k=k+1) + `ASSERT(f_rdcross_fill[k] <= f_rdcross_fill[k-1]); + always @(*) + `ASSERT(f_rdcross_fill[0] <= f_fill); + // }}} + + // f_wrcross_fill + // {{{ + always @(*) + for(k=0; k<NFF-1; k=k+1) + f_wrcross_fill[k] = wr_addr - + f_rcross[k*(LGFIFO+1) +: LGFIFO+1]; + + always @(*) + f_wrcross_fill[NFF-1] = wr_addr - f_wr_raddr; + + always @(*) + for(k=0; k<NFF; k=k+1) + `ASSERT(f_wrcross_fill[k] <= { 1'b1, {(LGFIFO){1'b0}} }); + + always @(*) + for(k=1; k<NFF; k=k+1) + `ASSERT(f_wrcross_fill[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 |
