`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