Files

123 lines
4.7 KiB
Systemverilog

`default_nettype none
// Handwritten dual-clock FIFO. Only Gray pointers cross clock domains. Memory
// writes occur in w_clk and the read data is registered in r_clk, allowing BRAM
// inference without a combinational CDC path.
module async_fifo_gray #(
parameter int unsigned DATA_WIDTH = 69,
parameter int unsigned DEPTH = 16384
) (
input wire logic w_clk,
input wire logic w_rst,
input wire logic w_en,
input wire logic [DATA_WIDTH-1:0] w_data,
output logic w_full,
output logic [$clog2(DEPTH):0] w_level,
output logic [63:0] overflow_attempt_count,
input wire logic r_clk,
input wire logic r_rst,
input wire logic r_en,
output logic [DATA_WIDTH-1:0] r_data,
output logic r_valid,
output logic r_empty,
output logic [$clog2(DEPTH):0] r_level,
output logic [63:0] underflow_attempt_count
);
localparam int unsigned ADDR_WIDTH = $clog2(DEPTH);
localparam int unsigned PTR_WIDTH = ADDR_WIDTH + 1;
(* ram_style = "block" *) logic [DATA_WIDTH-1:0] mem [0:DEPTH-1];
logic [PTR_WIDTH-1:0] wbin, wgray, rbin, rgray;
logic [PTR_WIDTH-1:0] wbin_next, wgray_next;
logic [PTR_WIDTH-1:0] rbin_next, rgray_next;
(* ASYNC_REG = "TRUE" *) logic [PTR_WIDTH-1:0] rgray_wsync1, rgray_wsync2;
(* ASYNC_REG = "TRUE" *) logic [PTR_WIDTH-1:0] wgray_rsync1, wgray_rsync2;
logic [PTR_WIDTH-1:0] rbin_wsync, wbin_rsync;
logic [DATA_WIDTH-1:0] r_mem_data;
logic w_push, r_pop;
integer i;
initial begin
if (DEPTH < 4 || (DEPTH & (DEPTH-1)) != 0)
$error("async_fifo_gray DEPTH must be a power of two >= 4");
if (DATA_WIDTH == 0) $error("async_fifo_gray DATA_WIDTH must be nonzero");
end
assign w_push = w_en && !w_full;
assign r_pop = r_en && !r_empty;
assign r_data = r_valid ? r_mem_data : {DATA_WIDTH{1'b0}};
assign wbin_next = wbin + {{(PTR_WIDTH-1){1'b0}}, w_push};
assign rbin_next = rbin + {{(PTR_WIDTH-1){1'b0}}, r_pop};
assign wgray_next = (wbin_next >> 1) ^ wbin_next;
assign rgray_next = (rbin_next >> 1) ^ rbin_next;
always_comb begin
rbin_wsync[PTR_WIDTH-1] = rgray_wsync2[PTR_WIDTH-1];
wbin_rsync[PTR_WIDTH-1] = wgray_rsync2[PTR_WIDTH-1];
for (i = PTR_WIDTH-2; i >= 0; i = i - 1) begin
rbin_wsync[i] = rbin_wsync[i+1] ^ rgray_wsync2[i];
wbin_rsync[i] = wbin_rsync[i+1] ^ wgray_rsync2[i];
end
end
always_comb begin
w_level = wbin - rbin_wsync;
r_level = wbin_rsync - rbin;
end
always_ff @(posedge w_clk or posedge w_rst) begin
if (w_rst) begin
wbin <= '0;
wgray <= '0;
w_full <= 1'b0;
rgray_wsync1 <= '0;
rgray_wsync2 <= '0;
overflow_attempt_count <= 64'd0;
end else begin
rgray_wsync1 <= rgray;
rgray_wsync2 <= rgray_wsync1;
if (w_en && w_full)
overflow_attempt_count <= overflow_attempt_count + 64'd1;
wbin <= wbin_next;
wgray <= wgray_next;
// Invert the two most-significant synchronized read-pointer bits.
w_full <= (wgray_next == {~rgray_wsync2[PTR_WIDTH-1:PTR_WIDTH-2],
rgray_wsync2[PTR_WIDTH-3:0]});
end
end
always_ff @(posedge r_clk or posedge r_rst) begin
if (r_rst) begin
rbin <= '0;
rgray <= '0;
r_valid <= 1'b0;
r_empty <= 1'b1;
wgray_rsync1 <= '0;
wgray_rsync2 <= '0;
underflow_attempt_count <= 64'd0;
end else begin
wgray_rsync1 <= wgray;
wgray_rsync2 <= wgray_rsync1;
r_valid <= r_pop;
if (r_en && r_empty)
underflow_attempt_count <= underflow_attempt_count + 64'd1;
rbin <= rbin_next;
rgray <= rgray_next;
r_empty <= (rgray_next == wgray_rsync2);
end
end
// Keep memory access out of the asynchronously-reset pointer processes.
// This is the supported true-dual-clock block-RAM inference template.
always_ff @(posedge w_clk) begin
if (w_push) mem[wbin[ADDR_WIDTH-1:0]] <= w_data;
end
always_ff @(posedge r_clk) begin
if (r_pop) r_mem_data <= mem[rbin[ADDR_WIDTH-1:0]];
end
endmodule
`default_nettype wire