blob: 0edebc768eb9d2d18ff11e54e9471125c10053f0 [file] [log] [blame]
// 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 = 4,
parameter bit OutputZeroIfEmpty = 1'b0, // if == 1 always output 0 when FIFO is empty
parameter bit OutputZeroIfInvalid = 1'b0, // if == 1 always output 0 when rvalid_o is low
localparam int unsigned DepthW = $clog2(Depth+1) // derived parameter representing [0..Depth]
) (
// write port
input logic clk_wr_i,
input logic rst_wr_ni,
input logic wvalid_i,
output logic wready_o,
input logic [Width-1:0] wdata_i,
output logic [DepthW-1:0] wdepth_o,
// read port
input logic clk_rd_i,
input logic rst_rd_ni,
output logic rvalid_o,
input logic rready_i,
output logic [Width-1:0] rdata_o,
output logic [DepthW-1:0] rdepth_o
);
// Depth must be a power of 2 for the gray code pointers to work
`ASSERT_INIT(ParamCheckDepth_A, (Depth == 2**$clog2(Depth)))
localparam int unsigned PTRV_W = (Depth == 1) ? 1 : $clog2(Depth);
localparam int unsigned PTR_WIDTH = (Depth == 1) ? 1 : PTRV_W+1;
logic [PTR_WIDTH-1:0] fifo_wptr_q, fifo_wptr_d;
logic [PTR_WIDTH-1:0] fifo_rptr_q, fifo_rptr_d;
logic [PTR_WIDTH-1:0] fifo_wptr_sync_combi, fifo_rptr_sync_combi;
logic [PTR_WIDTH-1:0] fifo_wptr_gray_sync, fifo_rptr_gray_sync, fifo_rptr_sync_q;
logic [PTR_WIDTH-1:0] fifo_wptr_gray_q, fifo_wptr_gray_d;
logic [PTR_WIDTH-1:0] fifo_rptr_gray_q, fifo_rptr_gray_d;
logic fifo_incr_wptr, fifo_incr_rptr;
logic full_wclk, full_rclk, empty_rclk;
logic [Width-1:0] storage [Depth];
///////////////////
// Write Pointer //
///////////////////
assign fifo_incr_wptr = wvalid_i & wready_o;
// decimal version
assign fifo_wptr_d = fifo_wptr_q + PTR_WIDTH'(1'b1);
always_ff @(posedge clk_wr_i or negedge rst_wr_ni) begin
if (!rst_wr_ni) begin
fifo_wptr_q <= '0;
end else if (fifo_incr_wptr) begin
fifo_wptr_q <= fifo_wptr_d;
end
end
// gray-coded version
always_ff @(posedge clk_wr_i or negedge rst_wr_ni) begin
if (!rst_wr_ni) begin
fifo_wptr_gray_q <= '0;
end else if (fifo_incr_wptr) begin
fifo_wptr_gray_q <= fifo_wptr_gray_d;
end
end
// sync gray-coded pointer to read clk
prim_flop_2sync #(.Width(PTR_WIDTH)) sync_wptr (
.clk_i (clk_rd_i),
.rst_ni (rst_rd_ni),
.d_i (fifo_wptr_gray_q),
.q_o (fifo_wptr_gray_sync));
//////////////////
// Read Pointer //
//////////////////
assign fifo_incr_rptr = rvalid_o & rready_i;
// decimal version
assign fifo_rptr_d = fifo_rptr_q + PTR_WIDTH'(1'b1);
always_ff @(posedge clk_rd_i or negedge rst_rd_ni) begin
if (!rst_rd_ni) begin
fifo_rptr_q <= '0;
end else if (fifo_incr_rptr) begin
fifo_rptr_q <= fifo_rptr_d;
end
end
// gray-coded version
always_ff @(posedge clk_rd_i or negedge rst_rd_ni) begin
if (!rst_rd_ni) begin
fifo_rptr_gray_q <= '0;
end else if (fifo_incr_rptr) begin
fifo_rptr_gray_q <= fifo_rptr_gray_d;
end
end
// sync gray-coded pointer to write clk
prim_flop_2sync #(.Width(PTR_WIDTH)) sync_rptr (
.clk_i (clk_wr_i),
.rst_ni (rst_wr_ni),
.d_i (fifo_rptr_gray_q),
.q_o (fifo_rptr_gray_sync));
// Registered version of synced read pointer
always_ff @(posedge clk_wr_i or negedge rst_wr_ni) begin
if (!rst_wr_ni) begin
fifo_rptr_sync_q <= '0;
end else begin
fifo_rptr_sync_q <= fifo_rptr_sync_combi;
end
end
//////////////////
// Empty / Full //
//////////////////
logic [PTR_WIDTH-1:0] xor_mask;
assign xor_mask = PTR_WIDTH'(1'b1) << (PTR_WIDTH-1);
assign full_wclk = (fifo_wptr_q == (fifo_rptr_sync_q ^ xor_mask));
assign full_rclk = (fifo_wptr_sync_combi == (fifo_rptr_q ^ xor_mask));
assign empty_rclk = (fifo_wptr_sync_combi == fifo_rptr_q);
if (Depth > 1) begin : g_depth_calc
// 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_q[PTR_WIDTH-1];
assign rptr_sync_msb = fifo_rptr_sync_q[PTR_WIDTH-1];
assign wptr_value = fifo_wptr_q[0+:PTRV_W];
assign rptr_sync_value = fifo_rptr_sync_q[0+:PTRV_W];
assign wdepth_o = (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)) ;
// Current depth in the read clock side
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_q[PTR_WIDTH-1];
assign wptr_sync_value = fifo_wptr_sync_combi[0+:PTRV_W];
assign rptr_value = fifo_rptr_q[0+:PTRV_W];
assign rdepth_o = (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)) ;
end else begin : g_no_depth_calc
assign rdepth_o = full_rclk;
assign wdepth_o = full_wclk;
end
assign wready_o = ~full_wclk;
assign rvalid_o = ~empty_rclk;
/////////////
// Storage //
/////////////
logic [Width-1:0] rdata_int;
if (Depth > 1) begin : g_storage_mux
always_ff @(posedge clk_wr_i) begin
if (fifo_incr_wptr) begin
storage[fifo_wptr_q[PTRV_W-1:0]] <= wdata_i;
end
end
assign rdata_int = storage[fifo_rptr_q[PTRV_W-1:0]];
end else begin : g_storage_simple
always_ff @(posedge clk_wr_i) begin
if (fifo_incr_wptr) begin
storage[0] <= wdata_i;
end
end
assign rdata_int = storage[0];
end
// rdata_o is qualified with rvalid_o to avoid CDC error
if (OutputZeroIfEmpty == 1'b1) begin : gen_output_zero
if (OutputZeroIfInvalid == 1'b1) begin : gen_invalid_zero
assign rdata_o = empty_rclk ? '0 : (rvalid_o ? rdata_int : '0);
end
else begin : gen_invalid_non_zero
assign rdata_o = empty_rclk ? '0 : rdata_int;
end
end else begin : gen_no_output_zero
if (OutputZeroIfInvalid == 1'b1) begin : gen_invalid_zero
assign rdata_o = rvalid_o ? rdata_int : '0;
end
else begin : gen_invalid_non_zero
assign rdata_o = rdata_int;
end
end
//////////////////////////////////////
// Decimal <-> Gray-code Conversion //
//////////////////////////////////////
// This code is all in a generate context to avoid lint errors when Depth <= 2
if (Depth > 2) begin : g_full_gray_conversion
function automatic [PTR_WIDTH-1:0] dec2gray(input logic [PTR_WIDTH-1:0] decval);
logic [PTR_WIDTH-1:0] decval_sub;
logic [PTR_WIDTH-1:0] decval_in;
logic unused_decval_msb;
decval_sub = (PTR_WIDTH)'(Depth) - {1'b0, decval[PTR_WIDTH-2:0]} - 1'b1;
decval_in = decval[PTR_WIDTH-1] ? decval_sub : decval;
// We do not care about the MSB, hence we mask it out
unused_decval_msb = decval_in[PTR_WIDTH-1];
decval_in[PTR_WIDTH-1] = 1'b0;
// Perform the XOR conversion
dec2gray = decval_in;
dec2gray ^= (decval_in >> 1);
// Override the MSB
dec2gray[PTR_WIDTH-1] = decval[PTR_WIDTH-1];
endfunction
// 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] gray2dec(input logic [PTR_WIDTH-1:0] grayval);
logic [PTR_WIDTH-1:0] dec_tmp, dec_tmp_sub;
logic unused_decsub_msb;
dec_tmp = '0;
for (int i = PTR_WIDTH-2; i >= 0; i--) begin
dec_tmp[i] = dec_tmp[i+1] ^ grayval[i];
end
dec_tmp_sub = (PTR_WIDTH)'(Depth) - dec_tmp - 1'b1;
if (grayval[PTR_WIDTH-1]) begin
gray2dec = dec_tmp_sub;
// Override MSB
gray2dec[PTR_WIDTH-1] = 1'b1;
unused_decsub_msb = dec_tmp_sub[PTR_WIDTH-1];
end else begin
gray2dec = dec_tmp;
end
endfunction
// decimal version of read pointer in write domain
assign fifo_rptr_sync_combi = gray2dec(fifo_rptr_gray_sync);
// decimal version of write pointer in read domain
assign fifo_wptr_sync_combi = gray2dec(fifo_wptr_gray_sync);
assign fifo_rptr_gray_d = dec2gray(fifo_rptr_d);
assign fifo_wptr_gray_d = dec2gray(fifo_wptr_d);
end else if (Depth == 2) begin : g_simple_gray_conversion
assign fifo_rptr_sync_combi = {fifo_rptr_gray_sync[PTR_WIDTH-1], ^fifo_rptr_gray_sync};
assign fifo_wptr_sync_combi = {fifo_wptr_gray_sync[PTR_WIDTH-1], ^fifo_wptr_gray_sync};
assign fifo_rptr_gray_d = {fifo_rptr_d[PTR_WIDTH-1], ^fifo_rptr_d};
assign fifo_wptr_gray_d = {fifo_wptr_d[PTR_WIDTH-1], ^fifo_wptr_d};
end else begin : g_no_gray_conversion
assign fifo_rptr_sync_combi = fifo_rptr_gray_sync;
assign fifo_wptr_sync_combi = fifo_wptr_gray_sync;
assign fifo_rptr_gray_d = fifo_rptr_d;
assign fifo_wptr_gray_d = fifo_wptr_d;
end
// TODO: assertions on full, empty
`ASSERT(GrayWptr_A, ##1 $countones(fifo_wptr_gray_q ^ $past(fifo_wptr_gray_q)) <= 1,
clk_wr_i, !rst_wr_ni)
`ASSERT(GrayRptr_A, ##1 $countones(fifo_rptr_gray_q ^ $past(fifo_rptr_gray_q)) <= 1,
clk_rd_i, !rst_rd_ni)
endmodule