blob: 4d4911c99fc1f0d519e5a476b2bb7bf6514eab7c [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 SRAM FIFO (Dual port SRAM)
`include "prim_assert.sv"
module prim_fifo_async_sram_adapter #(
parameter int unsigned Width = 32,
parameter int unsigned Depth = 16,
// SRAM parameters
parameter int unsigned SramAw = 16,
// If SramDw > Width, upper data bits are 0.
parameter int unsigned SramDw = 32,
parameter logic [SramAw-1:0] SramBaseAddr = 'h 0,
// derived
localparam int unsigned DepthW = $clog2(Depth+1)
) (
// Write port
input clk_wr_i,
input rst_wr_ni,
input wvalid_i,
output logic wready_o,
input [Width-1:0] wdata_i,
output logic [DepthW-1:0] wdepth_o,
// Read port
input clk_rd_i,
input rst_rd_ni,
output logic rvalid_o,
input rready_i,
output logic [Width-1:0] rdata_o,
output logic [DepthW-1:0] rdepth_o,
output logic r_full_o,
output logic r_notempty_o,
output logic w_full_o,
// TODO: watermark(threshold) ?
// SRAM interface
// Write SRAM port
output logic w_sram_req_o,
input w_sram_gnt_i,
output logic w_sram_write_o,
output logic [SramAw-1:0] w_sram_addr_o,
output logic [SramDw-1:0] w_sram_wdata_o,
output logic [SramDw-1:0] w_sram_wmask_o,
input w_sram_rvalid_i, // not used
input [SramDw-1:0] w_sram_rdata_i, // not used
input [1:0] w_sram_rerror_i, // not used
// Read SRAM port
output logic r_sram_req_o,
input r_sram_gnt_i,
output logic r_sram_write_o,
output logic [SramAw-1:0] r_sram_addr_o,
output logic [SramDw-1:0] r_sram_wdata_o, // not used
output logic [SramDw-1:0] r_sram_wmask_o, // not used
input r_sram_rvalid_i,
input [SramDw-1:0] r_sram_rdata_i,
input [1:0] r_sram_rerror_i
);
////////////////
// Definition //
////////////////
// w_: write clock domain signals
// r_: read clock domain signals
// PtrVW: Pointer Value (without msb, flip) width
localparam int unsigned PtrVW = $clog2(Depth);
// PtrW: Read/Write pointer with flip bit
localparam int unsigned PtrW = PtrVW+1;
////////////
// Signal //
////////////
logic [PtrW-1:0] w_wptr_q, w_wptr_d, w_wptr_gray_d, w_wptr_gray_q;
logic [PtrW-1:0] r_wptr_gray, r_wptr;
logic [PtrVW-1:0] w_wptr_v, r_wptr_v;
logic w_wptr_p, r_wptr_p; // phase
logic [PtrW-1:0] r_rptr_q, r_rptr_d, r_rptr_gray_d, r_rptr_gray_q;
logic [PtrW-1:0] w_rptr_gray, w_rptr;
logic [PtrVW-1:0] r_rptr_v, w_rptr_v;
logic r_rptr_p, w_rptr_p; // phase
logic w_wptr_inc, r_rptr_inc;
logic w_full, r_full, r_empty;
// SRAM response one clock delayed. So store the value into read clock
// domain
logic stored;
logic [Width-1:0] rdata_q, rdata_d;
// SRAM has another read pointer (for address of SRAM req)
// It is -1 of r_rptr if stored, else same to r_rptr
logic r_sram_rptr_inc;
logic [PtrW-1:0] r_sram_rptr;
// r_sram_rptr == r_wptr
// Used to determine r_sram_req
logic r_sramrptr_empty;
logic rfifo_ack; // Used to check if FIFO read interface consumes a data
logic rsram_ack;
//////////////
// Datapath //
//////////////
// Begin: Write pointer sync to read clock ========================
assign w_wptr_inc = wvalid_i & wready_o;
assign w_wptr_d = w_wptr_q + PtrW'(1);
always_ff @(posedge clk_wr_i or negedge rst_wr_ni) begin
if (!rst_wr_ni) begin
w_wptr_q <= PtrW'(0);
w_wptr_gray_q <= PtrW'(0);
end else if (w_wptr_inc) begin
w_wptr_q <= w_wptr_d;
w_wptr_gray_q <= w_wptr_gray_d;
end
end
assign w_wptr_v = w_wptr_q[0+:PtrVW];
assign w_wptr_p = w_wptr_q[PtrW-1];
assign w_wptr_gray_d = dec2gray(w_wptr_d);
prim_flop_2sync #(
.Width (PtrW)
) u_sync_wptr_gray (
.clk_i (clk_rd_i),
.rst_ni (rst_rd_ni),
.d_i (w_wptr_gray_q),
.q_o (r_wptr_gray)
);
assign r_wptr = gray2dec(r_wptr_gray);
assign r_wptr_p = r_wptr[PtrW-1];
assign r_wptr_v = r_wptr[0+:PtrVW];
assign wdepth_o = (w_wptr_p == w_rptr_p)
? DepthW'(w_wptr_v - w_rptr_v)
: DepthW'({1'b1, w_wptr_v} - {1'b 0, w_rptr_v});
// End: Write pointer sync to read clock ------------------------
// Begin: Read pointer sync to write clock ========================
//assign r_rptr_inc = rvalid_o & rready_i;
//assign r_rptr_inc = r_sram_req_o && r_sram_gnt_i;
// Increase the read pointer (crossing the clock domain) only when the
// reader acked.
assign r_rptr_inc = rfifo_ack;
assign r_rptr_d = r_rptr_q + PtrW'(1);
always_ff @(posedge clk_rd_i or negedge rst_rd_ni) begin
if (!rst_rd_ni) begin
r_rptr_q <= PtrW'(0);
r_rptr_gray_q <= PtrW'(0);
end else if (r_rptr_inc) begin
r_rptr_q <= r_rptr_d;
r_rptr_gray_q <= r_rptr_gray_d;
end
end
assign r_rptr_v = r_rptr_q[0+:PtrVW];
assign r_rptr_p = r_rptr_q[PtrW-1];
assign r_rptr_gray_d = dec2gray(r_rptr_d);
prim_flop_2sync #(
.Width (PtrW)
) u_sync_rptr_gray (
.clk_i (clk_wr_i),
.rst_ni (rst_wr_ni),
.d_i (r_rptr_gray_q),
.q_o (w_rptr_gray)
);
assign w_rptr = gray2dec(w_rptr_gray);
assign w_rptr_p = w_rptr[PtrW-1];
assign w_rptr_v = w_rptr[0+:PtrVW];
assign rdepth_o = (r_wptr_p == r_rptr_p)
? DepthW'(r_wptr_v - r_rptr_v)
: DepthW'({1'b1, r_wptr_v} - {1'b 0, r_rptr_v});
// End: Read pointer sync to write clock ------------------------
// Begin: SRAM Read pointer
assign r_sram_rptr_inc = rsram_ack;
always_ff @(posedge clk_rd_i or negedge rst_rd_ni) begin
if (!rst_rd_ni) begin
r_sram_rptr <= PtrW'(0);
end else if (r_sram_rptr_inc) begin
r_sram_rptr <= r_sram_rptr + PtrW'(1);
end
end
assign r_sramrptr_empty = (r_wptr == r_sram_rptr);
// End: SRAM Read pointer
// Full/ Empty
// Lint complains PtrW'(1) << (PtrW-1). So changed as below
localparam logic [PtrW-1:0] XorMask = {1'b 1, {PtrW-1{1'b0}}};
assign w_full = (w_wptr_q == (w_rptr ^ XorMask));
assign r_full = (r_wptr == (r_rptr_q ^ XorMask));
assign r_empty = (r_wptr == r_rptr_q);
logic unused_r_empty;
assign unused_r_empty = r_empty;
assign r_full_o = r_full;
assign w_full_o = w_full;
// The notempty status !(wptr == rptr) assert one clock earlier than the
// actual `rvalid` signals.
//
// The reason is due to the SRAM read latency. The module uses SRAM FIFO
// interface. When the logic in producer domain pushes entries, the pointer
// is increased. This triggers the FIFO logic in the consumer clock domain
// fetches data from SRAM.
//
// The pointer crosses the clock boundary. It takes usually two cycles (in
// the consumer side). Then, as the read and write pointer in the read clock
// domain has a gap by 1, the FIFO not empty status is raised.
//
// At this time, the logic just sent the read request to the SRAM. The data
// is not yet read. The `rvalid` asserts when it receives data from the
// SRAM.
//
// So, if the consumer reads data at the same cycle when notempty status is
// raised, it reads incorrect data.
assign r_notempty_o = rvalid_o;
assign rsram_ack = r_sram_req_o && r_sram_gnt_i;
assign rfifo_ack = rvalid_o && rready_i;
// SRAM Write Request
assign w_sram_req_o = wvalid_i && !w_full;
assign wready_o = !w_full && w_sram_gnt_i;
assign w_sram_write_o = 1'b 1; // Always write
assign w_sram_addr_o = SramBaseAddr + SramAw'(w_wptr_v);
assign w_sram_wdata_o = SramDw'(wdata_i);
assign w_sram_wmask_o = SramDw'({Width{1'b1}});
logic unused_w_sram;
assign unused_w_sram = ^{w_sram_rvalid_i, w_sram_rdata_i, w_sram_rerror_i};
// SRAM Read Request
// Request Scenario (!r_empty):
// - storage empty: Send request if
// !r_sram_rvalid_i || (rfifo_ack && r_sram_rvalid_i);
// - storage !empty: depends on the rfifo_ack:
// - r_rptr_inc: Can request more
// - !r_rptr_inc: Can't request
always_comb begin : r_sram_req
r_sram_req_o = 1'b 0;
// Karnough Map (!empty): sram_req
// {sram_rv, rfifo_ack} | 00 | 01 | 11 | 10
// ----------------------------------------------------------
// stored | 0 | 1 | impossible | 1 | 0
// | 1 | 0 | 1 | X | impossible
//
// req_o = r_ptr_inc || (!stored && !r_sram_rvalid_i)
if (stored) begin
// storage has data. depends on rfifo_ack
// rfifo_ack can be replaced to rready_i as `rvalid_o` is 1
r_sram_req_o = !r_sramrptr_empty && rfifo_ack;
end else begin
// storage has no data.
// Can send request only when the reader accept the request or no
// previous request sent out.
r_sram_req_o = !r_sramrptr_empty && !(r_sram_rvalid_i ^ rfifo_ack);
end
end : r_sram_req
assign rvalid_o = stored || r_sram_rvalid_i;
assign r_sram_write_o = 1'b 0; // always read
assign r_sram_wdata_o = '0;
assign r_sram_wmask_o = '0;
// Send SRAM request with sram read pointer.
assign r_sram_addr_o = SramBaseAddr + SramAw'(r_sram_rptr[0+:PtrVW]);
assign rdata_d = (r_sram_rvalid_i) ? r_sram_rdata_i[0+:Width] : Width'(0);
assign rdata_o = (stored) ? rdata_q : rdata_d;
logic unused_rsram;
assign unused_rsram = ^{r_sram_rerror_i};
if (Width < SramDw) begin : g_unused_rdata
logic unused_rdata;
assign unused_rdata = ^r_sram_rdata_i[SramDw-1:Width];
end : g_unused_rdata
// read clock domain rdata storage
logic store_en;
// Karnough Map (r_sram_rvalid_i):
// rfifo_ack | 0 | 1 |
// ---------------------
// stored 0 | 1 | 0 |
// 1 | 0 | 1 |
//
// stored = s.r.v && XNOR(stored, rptr_inc)
assign store_en = r_sram_rvalid_i && !(stored ^ rfifo_ack);
always_ff @(posedge clk_rd_i or negedge rst_rd_ni) begin
if (!rst_rd_ni) begin
stored <= 1'b 0;
rdata_q <= Width'(0);
end else if (store_en) begin
stored <= 1'b 1;
rdata_q <= rdata_d;
end else if (!r_sram_rvalid_i && rfifo_ack) begin
// No request sent, host reads the data
stored <= 1'b 0;
rdata_q <= Width'(0);
end
end
//////////////
// Function //
//////////////
// dec2gray / gray2dec copied from prim_fifo_async.sv
function automatic [PtrW-1:0] dec2gray(input logic [PtrW-1:0] decval);
logic [PtrW-1:0] decval_sub;
logic [PtrW-1:0] decval_in;
logic unused_decval_msb;
decval_sub = (PtrW)'(Depth) - {1'b0, decval[PtrW-2:0]} - 1'b1;
decval_in = decval[PtrW-1] ? decval_sub : decval;
// We do not care about the MSB, hence we mask it out
unused_decval_msb = decval_in[PtrW-1];
decval_in[PtrW-1] = 1'b0;
// Perform the XOR conversion
dec2gray = decval_in;
dec2gray ^= (decval_in >> 1);
// Override the MSB
dec2gray[PtrW-1] = decval[PtrW-1];
endfunction
// Algorithm walks up from 0..N-1 then flips the upper bit and walks down from N-1 to 0.
function automatic [PtrW-1:0] gray2dec(input logic [PtrW-1:0] grayval);
logic [PtrW-1:0] dec_tmp, dec_tmp_sub;
logic unused_decsub_msb;
dec_tmp = '0;
for (int i = PtrW-2; i >= 0; i--) begin
dec_tmp[i] = dec_tmp[i+1] ^ grayval[i];
end
dec_tmp_sub = (PtrW)'(Depth) - dec_tmp - 1'b1;
if (grayval[PtrW-1]) begin
gray2dec = dec_tmp_sub;
// Override MSB
gray2dec[PtrW-1] = 1'b1;
unused_decsub_msb = dec_tmp_sub[PtrW-1];
end else begin
gray2dec = dec_tmp;
end
endfunction
///////////////
// Assertion //
///////////////
`ASSERT_INIT(ParamCheckDepth_A, (Depth == 2**$clog2(Depth)))
// Use FF if less than 4.
`ASSERT_INIT(MinDepth_A, Depth >= 4)
// SramDw greather than or equal to Width
`ASSERT_INIT(WidthMatch_A, SramDw >= Width)
// Not stored, Not read valid, but rptr_inc case is impossible
`ASSERT(RptrIncDataValid_A,
r_rptr_inc |-> stored || r_sram_rvalid_i,
clk_rd_i, !rst_rd_ni)
`ASSERT(SramRvalid_A,
r_sram_rvalid_i |-> !stored || r_rptr_inc,
clk_rd_i, !rst_rd_ni)
// FIFO interface
`ASSERT(NoWAckInFull_A, w_wptr_inc |-> !w_full,
clk_wr_i, !rst_wr_ni)
`ASSERT(WptrIncrease_A,
w_wptr_inc |=> w_wptr_v == PtrVW'($past(w_wptr_v,2) + 1),
clk_wr_i, !rst_wr_ni)
`ASSERT(WptrGrayOneBitAtATime_A,
w_wptr_inc |=> $countones(w_wptr_gray_q ^ $past(w_wptr_gray_q,2)) == 1,
clk_wr_i, !rst_wr_ni)
`ASSERT(NoRAckInEmpty_A, r_rptr_inc |-> !r_empty,
clk_rd_i, !rst_rd_ni)
`ASSERT(RptrIncrease_A,
r_rptr_inc |=> PtrVW'($past(r_rptr_v) + 1) == r_rptr_v,
clk_rd_i, !rst_rd_ni)
`ASSERT(RptrGrayOneBitAtATime_A,
r_rptr_inc |=> $countones(r_rptr_gray_q ^ $past(r_rptr_gray_q)) == 1,
clk_rd_i, !rst_rd_ni)
// SRAM interface
`ASSERT(WSramRvalid_A, !w_sram_rvalid_i, clk_wr_i, !rst_wr_ni)
`ASSUME_FPV(WSramRdataError_M, w_sram_rdata_i == '0 && w_sram_rerror_i == '0,
clk_wr_i, !rst_wr_ni)
`ASSUME(RSramRvalidOneCycle_M,
r_sram_req_o && r_sram_gnt_i |=> r_sram_rvalid_i,
clk_rd_i, !rst_rd_ni)
`ASSUME_FPV(RErrorTied_M, r_sram_rerror_i == '0,
clk_rd_i, !rst_rd_ni)
// FPV coverage
`COVER_FPV(WFull_C, w_full, clk_wr_i, !rst_wr_ni)
endmodule : prim_fifo_async_sram_adapter