summaryrefslogtreecommitdiff
path: root/rtl/wb2axip/sfifo.v
diff options
context:
space:
mode:
Diffstat (limited to 'rtl/wb2axip/sfifo.v')
-rw-r--r--rtl/wb2axip/sfifo.v482
1 files changed, 482 insertions, 0 deletions
diff --git a/rtl/wb2axip/sfifo.v b/rtl/wb2axip/sfifo.v
new file mode 100644
index 0000000..c60dffe
--- /dev/null
+++ b/rtl/wb2axip/sfifo.v
@@ -0,0 +1,482 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// Filename: sfifo.v
+// {{{
+// Project: WB2AXIPSP: bus bridges and other odds and ends
+//
+// Purpose: A synchronous data FIFO.
+//
+// Creator: Dan Gisselquist, Ph.D.
+// Gisselquist Technology, LLC
+//
+////////////////////////////////////////////////////////////////////////////////
+//
+// Written and distributed by Gisselquist Technology, LLC
+// }}}
+// This design is hereby granted to the public domain.
+// {{{
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.
+//
+////////////////////////////////////////////////////////////////////////////////
+//
+`default_nettype none
+// }}}
+module sfifo #(
+ // {{{
+ parameter BW=8, // Byte/data width
+ parameter LGFLEN=4,
+ parameter [0:0] OPT_ASYNC_READ = 1'b1,
+ parameter [0:0] OPT_WRITE_ON_FULL = 1'b0,
+ parameter [0:0] OPT_READ_ON_EMPTY = 1'b0
+ // }}}
+ ) (
+ // {{{
+ input wire i_clk,
+ input wire i_reset,
+ //
+ // Write interface
+ input wire i_wr,
+ input wire [(BW-1):0] i_data,
+ output wire o_full,
+ output reg [LGFLEN:0] o_fill,
+ //
+ // Read interface
+ input wire i_rd,
+ output reg [(BW-1):0] o_data,
+ output wire o_empty // True if FIFO is empty
+`ifdef FORMAL
+`ifdef F_PEEK
+ , output wire [LGFLEN:0] f_first_addr,
+ output wire [LGFLEN:0] f_second_addr,
+ output reg [BW-1:0] f_first_data, f_second_data,
+
+ output reg f_first_in_fifo,
+ f_second_in_fifo,
+ output reg [LGFLEN:0] f_distance_to_first,
+ f_distance_to_second
+`endif
+`endif
+ // }}}
+ );
+
+ // Register/net declarations
+ // {{{
+ localparam FLEN=(1<<LGFLEN);
+ reg r_full, r_empty;
+ reg [(BW-1):0] mem[0:(FLEN-1)];
+ reg [LGFLEN:0] wr_addr, rd_addr;
+
+ wire w_wr = (i_wr && !o_full);
+ wire w_rd = (i_rd && !o_empty);
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Write half
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ // o_fill
+ // {{{
+ initial o_fill = 0;
+ always @(posedge i_clk)
+ if (i_reset)
+ o_fill <= 0;
+ else case({ w_wr, w_rd })
+ 2'b01: o_fill <= o_fill - 1;
+ 2'b10: o_fill <= o_fill + 1;
+ default: o_fill <= wr_addr - rd_addr;
+ endcase
+ // }}}
+
+ // r_full, o_full
+ // {{{
+ initial r_full = 0;
+ always @(posedge i_clk)
+ if (i_reset)
+ r_full <= 0;
+ else case({ w_wr, w_rd})
+ 2'b01: r_full <= 1'b0;
+ 2'b10: r_full <= (o_fill == { 1'b0, {(LGFLEN){1'b1}} });
+ default: r_full <= (o_fill == { 1'b1, {(LGFLEN){1'b0}} });
+ endcase
+
+ assign o_full = (i_rd && OPT_WRITE_ON_FULL) ? 1'b0 : r_full;
+ // }}}
+
+ // wr_addr, the write address pointer
+ // {{{
+ initial wr_addr = 0;
+ always @(posedge i_clk)
+ if (i_reset)
+ wr_addr <= 0;
+ else if (w_wr)
+ wr_addr <= wr_addr + 1'b1;
+ // }}}
+
+ // Write to memory
+ // {{{
+ always @(posedge i_clk)
+ if (w_wr)
+ mem[wr_addr[(LGFLEN-1):0]] <= i_data;
+ // }}}
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Read half
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ // rd_addr, the read address pointer
+ // {{{
+ initial rd_addr = 0;
+ always @(posedge i_clk)
+ if (i_reset)
+ rd_addr <= 0;
+ else if (w_rd)
+ rd_addr <= rd_addr + 1;
+ // }}}
+
+ // r_empty, o_empty
+ // {{{
+ initial r_empty = 1'b1;
+ always @(posedge i_clk)
+ if (i_reset)
+ r_empty <= 1'b1;
+ else case ({ w_wr, w_rd })
+ 2'b01: r_empty <= (o_fill <= 1);
+ 2'b10: r_empty <= 1'b0;
+ default: begin end
+ endcase
+
+ assign o_empty = (OPT_READ_ON_EMPTY && i_wr) ? 1'b0 : r_empty;
+ // }}}
+
+ // Read from the FIFO
+ // {{{
+ generate if (OPT_ASYNC_READ && OPT_READ_ON_EMPTY)
+ begin : ASYNCHRONOUS_READ_ON_EMPTY
+ // o_data
+ // {{{
+ always @(*)
+ begin
+ o_data = mem[rd_addr[LGFLEN-1:0]];
+ if (r_empty)
+ o_data = i_data;
+ end
+ // }}}
+ end else if (OPT_ASYNC_READ)
+ begin : ASYNCHRONOUS_READ
+ // o_data
+ // {{{
+ always @(*)
+ o_data = mem[rd_addr[LGFLEN-1:0]];
+ // }}}
+ end else begin : REGISTERED_READ
+ // {{{
+ reg bypass_valid;
+ reg [BW-1:0] bypass_data, rd_data;
+ reg [LGFLEN-1:0] rd_next;
+
+ always @(*)
+ rd_next = rd_addr[LGFLEN-1:0] + 1;
+
+ // Memory read, bypassing it if we must
+ // {{{
+ initial bypass_valid = 0;
+ always @(posedge i_clk)
+ if (i_reset)
+ bypass_valid <= 0;
+ else if (r_empty || i_rd)
+ begin
+ if (!i_wr)
+ bypass_valid <= 1'b0;
+ else if (r_empty || (i_rd && (o_fill == 1)))
+ bypass_valid <= 1'b1;
+ else
+ bypass_valid <= 1'b0;
+ end
+
+ always @(posedge i_clk)
+ if (r_empty || i_rd)
+ bypass_data <= i_data;
+
+ initial mem[0] = 0;
+ initial rd_data = 0;
+ always @(posedge i_clk)
+ if (w_rd)
+ rd_data <= mem[rd_next];
+
+ always @(*)
+ if (OPT_READ_ON_EMPTY && r_empty)
+ o_data = i_data;
+ else if (bypass_valid)
+ o_data = bypass_data;
+ else
+ o_data = rd_data;
+ // }}}
+ // }}}
+ end endgenerate
+ // }}}
+ // }}}
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+//
+// FORMAL METHODS
+// {{{
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+//
+`ifdef FORMAL
+
+//
+// Assumptions about our input(s)
+//
+//
+`ifdef SFIFO
+`define ASSUME assume
+`else
+`define ASSUME assert
+`endif
+
+ reg f_past_valid;
+ wire [LGFLEN:0] f_fill, f_next;
+ wire f_empty;
+
+ initial f_past_valid = 1'b0;
+ always @(posedge i_clk)
+ f_past_valid <= 1'b1;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Assertions about our flags and counters
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ assign f_fill = wr_addr - rd_addr;
+ assign f_empty = (wr_addr == rd_addr);
+ assign f_next = rd_addr + 1'b1;
+
+ always @(*)
+ begin
+ assert(f_fill <= { 1'b1, {(LGFLEN){1'b0}} });
+ assert(o_fill == f_fill);
+
+ assert(r_full == (f_fill == {1'b1, {(LGFLEN){1'b0}} }));
+ assert(r_empty == (f_fill == 0));
+
+ if (!OPT_WRITE_ON_FULL)
+ begin
+ assert(o_full == r_full);
+ end else begin
+ assert(o_full == (r_full && !i_rd));
+ end
+
+ if (!OPT_READ_ON_EMPTY)
+ begin
+ assert(o_empty == r_empty);
+ end else begin
+ assert(o_empty == (r_empty && !i_wr));
+ end
+ end
+
+ always @(posedge i_clk)
+ if (!OPT_ASYNC_READ && f_past_valid)
+ begin
+ if (f_fill == 0)
+ begin
+ assert(r_empty);
+ assert(o_empty || (OPT_READ_ON_EMPTY && i_wr));
+ end else if ($past(f_fill)>1)
+ begin
+ assert(!r_empty);
+ end else if ($past(!i_rd && f_fill > 0))
+ assert(!r_empty);
+ end
+
+ always @(*)
+ if (!r_empty)
+ begin
+ // This also applies for the registered read case
+ assert(mem[rd_addr[LGFLEN-1:0]] == o_data);
+ end else if (OPT_READ_ON_EMPTY)
+ assert(o_data == i_data);
+
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Formal contract: (Twin write test)
+ // {{{
+ // If you write two values in succession, you should be able to read
+ // those same two values in succession some time later.
+ //
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ // Verilator lint_off UNDRIVEN
+ (* anyconst *) reg [LGFLEN:0] fw_first_addr;
+ // Verilator lint_on UNDRIVEN
+`ifndef F_PEEK
+ wire [LGFLEN:0] f_first_addr;
+ wire [LGFLEN:0] f_second_addr;
+ reg [BW-1:0] f_first_data, f_second_data;
+
+ reg f_first_in_fifo, f_second_in_fifo;
+ reg [LGFLEN:0] f_distance_to_first, f_distance_to_second;
+`endif
+ reg f_first_addr_in_fifo, f_second_addr_in_fifo;
+
+ assign f_first_addr = fw_first_addr;
+ assign f_second_addr = f_first_addr + 1;
+
+ always @(*)
+ begin
+ f_distance_to_first = (f_first_addr - rd_addr);
+ f_first_addr_in_fifo = 0;
+ if ((f_fill != 0) && (f_distance_to_first < f_fill))
+ f_first_addr_in_fifo = 1;
+ end
+
+ always @(*)
+ begin
+ f_distance_to_second = (f_second_addr - rd_addr);
+ f_second_addr_in_fifo = 0;
+ if ((f_fill != 0) && (f_distance_to_second < f_fill))
+ f_second_addr_in_fifo = 1;
+ end
+
+ always @(posedge i_clk)
+ if (w_wr && wr_addr == f_first_addr)
+ f_first_data <= i_data;
+
+ always @(posedge i_clk)
+ if (w_wr && wr_addr == f_second_addr)
+ f_second_data <= i_data;
+
+ always @(*)
+ if (f_first_addr_in_fifo)
+ assert(mem[f_first_addr[LGFLEN-1:0]] == f_first_data);
+ always @(*)
+ f_first_in_fifo = (f_first_addr_in_fifo && (mem[f_first_addr[LGFLEN-1:0]] == f_first_data));
+
+ always @(*)
+ if (f_second_addr_in_fifo)
+ assert(mem[f_second_addr[LGFLEN-1:0]] == f_second_data);
+
+ always @(*)
+ f_second_in_fifo = (f_second_addr_in_fifo && (mem[f_second_addr[LGFLEN-1:0]] == f_second_data));
+
+ always @(*)
+ if (f_first_in_fifo && (o_fill == 1 || f_distance_to_first == 0))
+ assert(o_data == f_first_data);
+
+ always @(*)
+ if (f_second_in_fifo && (o_fill == 1 || f_distance_to_second == 0))
+ assert(o_data == f_second_data);
+
+ always @(posedge i_clk)
+ if (f_past_valid && !$past(i_reset))
+ begin
+ case({$past(f_first_in_fifo), $past(f_second_in_fifo)})
+ 2'b00: begin
+ if ($past(w_wr && (!w_rd || !r_empty))
+ &&($past(wr_addr == f_first_addr)))
+ begin
+ assert(f_first_in_fifo);
+ end else begin
+ assert(!f_first_in_fifo);
+ end
+ //
+ // The second could be in the FIFO, since
+ // one might write other data than f_first_data
+ //
+ // assert(!f_second_in_fifo);
+ end
+ 2'b01: begin
+ assert(!f_first_in_fifo);
+ if ($past(w_rd && (rd_addr==f_second_addr)))
+ begin
+ assert((o_empty&&!OPT_ASYNC_READ)||!f_second_in_fifo);
+ end else begin
+ assert(f_second_in_fifo);
+ end
+ end
+ 2'b10: begin
+ if ($past(w_wr)
+ &&($past(wr_addr == f_second_addr)))
+ begin
+ assert(f_second_in_fifo);
+ end else begin
+ assert(!f_second_in_fifo);
+ end
+ if ($past(!w_rd ||(rd_addr != f_first_addr)))
+ assert(f_first_in_fifo);
+ end
+ 2'b11: begin
+ assert(f_second_in_fifo);
+ if ($past(!w_rd ||(rd_addr != f_first_addr)))
+ begin
+ assert(f_first_in_fifo);
+ if (rd_addr == f_first_addr)
+ assert(o_data == f_first_data);
+ end else begin
+ assert(!f_first_in_fifo);
+ assert(o_data == f_second_data);
+ end
+ end
+ endcase
+ end
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Cover properties
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+`ifdef SFIFO
+ reg f_was_full;
+ initial f_was_full = 0;
+ always @(posedge i_clk)
+ if (o_full)
+ f_was_full <= 1;
+
+ always @(posedge i_clk)
+ cover($fell(f_empty));
+
+ always @(posedge i_clk)
+ cover($fell(o_empty));
+
+ always @(posedge i_clk)
+ cover(f_was_full && f_empty);
+
+ always @(posedge i_clk)
+ cover($past(o_full,2)&&(!$past(o_full))&&(o_full));
+
+ always @(posedge i_clk)
+ if (f_past_valid)
+ cover($past(o_empty,2)&&(!$past(o_empty))&& o_empty);
+`endif
+ // }}}
+
+ // Make Verilator happy
+ // Verilator lint_off UNUSED
+ wire unused_formal;
+ assign unused_formal = &{ 1'b0, f_next[LGFLEN], f_empty };
+ // Verilator lint_on UNUSED
+`endif // FORMAL
+// }}}
+endmodule
+`ifndef YOSYS
+`default_nettype wire
+`endif