| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Generic asynchronous fifo for use in a variety of devices. |
| |
| `include "prim_assert.sv" |
| |
| module prim_fifo_async #( |
| parameter int unsigned Width = 16, |
| parameter int unsigned Depth = 3, |
| localparam int unsigned DepthW = $clog2(Depth+1) // derived parameter representing [0..Depth] |
| ) ( |
| // write port |
| input clk_wr_i, |
| input rst_wr_ni, |
| input wvalid, |
| output wready, |
| input [Width-1:0] wdata, |
| output [DepthW-1:0] wdepth, |
| |
| // read port |
| input clk_rd_i, |
| input rst_rd_ni, |
| output rvalid, |
| input rready, |
| output [Width-1:0] rdata, |
| output [DepthW-1:0] rdepth |
| ); |
| |
| `ASSERT_INIT(paramCheckDepth, Depth >= 3) |
| |
| localparam int unsigned PTRV_W = $clog2(Depth); |
| localparam logic [PTRV_W-1:0] DepthMinus1 = PTRV_W'(Depth - 1); |
| localparam int unsigned PTR_WIDTH = PTRV_W+1; |
| |
| logic [PTR_WIDTH-1:0] fifo_wptr, fifo_rptr; |
| logic [PTR_WIDTH-1:0] fifo_wptr_sync_combi, fifo_rptr_sync; |
| logic [PTR_WIDTH-1:0] fifo_wptr_gray_sync, fifo_rptr_gray_sync; |
| logic [PTR_WIDTH-1:0] fifo_wptr_gray, fifo_rptr_gray; |
| logic fifo_incr_wptr, fifo_incr_rptr, empty; |
| |
| logic full_wclk, full_rclk; |
| |
| assign wready = !full_wclk; |
| assign rvalid = !empty; |
| |
| // create the write and read pointers |
| |
| assign fifo_incr_wptr = wvalid & wready; |
| assign fifo_incr_rptr = rvalid & rready; |
| |
| /////////////////// |
| // write pointer // |
| /////////////////// |
| |
| always_ff @(posedge clk_wr_i or negedge rst_wr_ni) |
| if (!rst_wr_ni) begin |
| fifo_wptr <= {(PTR_WIDTH){1'b0}}; |
| end else if (fifo_incr_wptr) begin |
| if (fifo_wptr[PTR_WIDTH-2:0] == DepthMinus1) begin |
| fifo_wptr <= {~fifo_wptr[PTR_WIDTH-1],{(PTR_WIDTH-1){1'b0}}}; |
| end else begin |
| fifo_wptr <= fifo_wptr + {{(PTR_WIDTH-1){1'b0}},1'b1}; |
| end |
| end |
| |
| // gray-coded version |
| always_ff @(posedge clk_wr_i or negedge rst_wr_ni) |
| if (!rst_wr_ni) begin |
| fifo_wptr_gray <= {(PTR_WIDTH){1'b0}}; |
| end else if (fifo_incr_wptr) begin |
| if (fifo_wptr[PTR_WIDTH-2:0] == DepthMinus1) begin |
| fifo_wptr_gray <= dec2gray({~fifo_wptr[PTR_WIDTH-1],{(PTR_WIDTH-1){1'b0}}}); |
| end else begin |
| fifo_wptr_gray <= dec2gray(fifo_wptr + {{(PTR_WIDTH-1){1'b0}},1'b1}); |
| end |
| end |
| |
| prim_flop_2sync #(.Width(PTR_WIDTH)) sync_wptr ( |
| .clk_i (clk_rd_i), |
| .rst_ni (rst_rd_ni), |
| .d (fifo_wptr_gray), |
| .q (fifo_wptr_gray_sync)); |
| |
| assign fifo_wptr_sync_combi = gray2dec(fifo_wptr_gray_sync); |
| |
| ////////////////// |
| // read pointer // |
| ////////////////// |
| |
| always_ff @(posedge clk_rd_i or negedge rst_rd_ni) |
| if (!rst_rd_ni) begin |
| fifo_rptr <= {(PTR_WIDTH){1'b0}}; |
| end else if (fifo_incr_rptr) begin |
| if (fifo_rptr[PTR_WIDTH-2:0] == DepthMinus1) begin |
| fifo_rptr <= {~fifo_rptr[PTR_WIDTH-1],{(PTR_WIDTH-1){1'b0}}}; |
| end else begin |
| fifo_rptr <= fifo_rptr + {{(PTR_WIDTH-1){1'b0}},1'b1}; |
| end |
| end |
| |
| // gray-coded version |
| always_ff @(posedge clk_rd_i or negedge rst_rd_ni) |
| if (!rst_rd_ni) begin |
| fifo_rptr_gray <= {(PTR_WIDTH){1'b0}}; |
| end else if (fifo_incr_rptr) begin |
| if (fifo_rptr[PTR_WIDTH-2:0] == DepthMinus1) begin |
| fifo_rptr_gray <= dec2gray({~fifo_rptr[PTR_WIDTH-1],{(PTR_WIDTH-1){1'b0}}}); |
| end else begin |
| fifo_rptr_gray <= dec2gray(fifo_rptr + {{(PTR_WIDTH-1){1'b0}},1'b1}); |
| end |
| end |
| |
| prim_flop_2sync #(.Width(PTR_WIDTH)) sync_rptr ( |
| .clk_i (clk_wr_i), |
| .rst_ni (rst_wr_ni), |
| .d (fifo_rptr_gray), |
| .q (fifo_rptr_gray_sync)); |
| |
| always_ff @(posedge clk_wr_i or negedge rst_wr_ni) |
| if (!rst_wr_ni) begin |
| fifo_rptr_sync <= {PTR_WIDTH{1'b0}}; |
| end else begin |
| fifo_rptr_sync <= gray2dec(fifo_rptr_gray_sync); |
| end |
| |
| ////////////////// |
| // empty / full // |
| ////////////////// |
| |
| assign full_wclk = (fifo_wptr == (fifo_rptr_sync ^ {1'b1,{(PTR_WIDTH-1){1'b0}}})); |
| assign full_rclk = (fifo_wptr_sync_combi == (fifo_rptr ^ {1'b1,{(PTR_WIDTH-1){1'b0}}})); |
| |
| // Current depth in the write clock side |
| logic wptr_msb; |
| logic rptr_sync_msb; |
| logic [PTRV_W-1:0] wptr_value; |
| logic [PTRV_W-1:0] rptr_sync_value; |
| assign wptr_msb = fifo_wptr[PTR_WIDTH-1]; |
| assign rptr_sync_msb = fifo_rptr_sync[PTR_WIDTH-1]; |
| assign wptr_value = fifo_wptr[0+:PTRV_W]; |
| assign rptr_sync_value = fifo_rptr_sync[0+:PTRV_W]; |
| assign wdepth = (full_wclk) ? DepthW'(Depth) : |
| (wptr_msb == rptr_sync_msb) ? DepthW'(wptr_value) - DepthW'(rptr_sync_value) : |
| (DepthW'(Depth) - DepthW'(rptr_sync_value) + DepthW'(wptr_value)) ; |
| |
| // Same again in the read clock side |
| assign empty = (fifo_wptr_sync_combi == fifo_rptr); |
| logic rptr_msb; |
| logic wptr_sync_msb; |
| logic [PTRV_W-1:0] rptr_value; |
| logic [PTRV_W-1:0] wptr_sync_value; |
| assign wptr_sync_msb = fifo_wptr_sync_combi[PTR_WIDTH-1]; |
| assign rptr_msb = fifo_rptr[PTR_WIDTH-1]; |
| assign wptr_sync_value = fifo_wptr_sync_combi[0+:PTRV_W]; |
| assign rptr_value = fifo_rptr[0+:PTRV_W]; |
| assign rdepth = (full_rclk) ? DepthW'(Depth) : |
| (wptr_sync_msb == rptr_msb) ? DepthW'(wptr_sync_value) - DepthW'(rptr_value) : |
| (DepthW'(Depth) - DepthW'(rptr_value) + DepthW'(wptr_sync_value)) ; |
| |
| ///////////// |
| // storage // |
| ///////////// |
| |
| logic [Width-1:0] storage [Depth]; |
| |
| always_ff @(posedge clk_wr_i) |
| if (fifo_incr_wptr) begin |
| storage[fifo_wptr[PTR_WIDTH-2:0]] <= wdata; |
| end |
| |
| assign rdata = storage[fifo_rptr[PTR_WIDTH-2:0]]; |
| |
| // gray code conversion functions. algorithm walks up from 0..N-1 |
| // then flips the upper bit and walks down from N-1 to 0. |
| |
| function automatic [PTR_WIDTH-1:0] dec2gray(input logic [PTR_WIDTH-1:0] decval); |
| logic [PTR_WIDTH-1:0] decval_sub; |
| logic [PTR_WIDTH-2:0] decval_in; |
| logic unused_decval_msb; |
| |
| decval_sub = Depth - {1'b0,decval[PTR_WIDTH-2:0]} - 1'b1; |
| |
| {unused_decval_msb, decval_in} = decval[PTR_WIDTH-1] ? decval_sub : decval; |
| // Was done in two assigns for low bits and top bit |
| // but that generates a (bogus) verilator warning, so do in one assign |
| dec2gray = {decval[PTR_WIDTH-1], |
| {1'b0,decval_in[PTR_WIDTH-2:1]} ^ decval_in[PTR_WIDTH-2:0]}; |
| endfunction |
| |
| function automatic [PTR_WIDTH-1:0] gray2dec(input logic [PTR_WIDTH-1:0] grayval); |
| logic [PTR_WIDTH-2:0] dec_tmp, dec_tmp_sub; |
| logic unused_decsub_msb; |
| |
| dec_tmp[PTR_WIDTH-2] = grayval[PTR_WIDTH-2]; |
| for (int i = PTR_WIDTH-3; i >= 0; i--) |
| dec_tmp[i] = dec_tmp[i+1]^grayval[i]; |
| {unused_decsub_msb, dec_tmp_sub} = Depth - {1'b0,dec_tmp} - 1'b1; |
| if (grayval[PTR_WIDTH-1]) |
| gray2dec = {1'b1,dec_tmp_sub}; |
| else |
| gray2dec = {1'b0,dec_tmp}; |
| endfunction |
| |
| // TODO: assertions on full, empty, gray transitions |
| |
| endmodule |