// SPDX-License-Identifier: LGPL-3.0-or-later // // Vivado/XPM replacement for the legacy Ettus FIFO Generator building blocks // used by axi_fifo_2clk.v on Xilinx 7-series devices. // // This core deliberately provides the old native FIFO interface while using // xpm_fifo_async internally. The external reset may be asynchronous, as it // was for the FIFO Generator instances. XPM's rst input, however, must be // synchronous to wr_clk, so reset assertion is captured asynchronously and // then presented to XPM through a wr_clk synchronizer. Read/write requests // and external flags are held inactive/conservative until XPM reset-busy has // cleared. `timescale 1ns/1ps `default_nettype none module fifo_2clk_xpm_core #( parameter integer FIFO_DEPTH = 32, parameter integer COUNT_WIDTH = 6, parameter integer CDC_SYNC_STAGES = 3, parameter MEMORY_TYPE = "distributed" )( input wire rst, input wire wr_clk, input wire [71:0] din, input wire wr_en, output wire full, output wire [COUNT_WIDTH-1:0] wr_data_count, input wire rd_clk, output wire [71:0] dout, input wire rd_en, output wire empty, output wire [COUNT_WIDTH-1:0] rd_data_count ); // ------------------------------------------------------------------------ // Reset adaptation // ------------------------------------------------------------------------ // Legacy FIFO Generator accepted an asynchronous reset. XPM_FIFO_ASYNC // requires rst to be synchronous to wr_clk. rst_capture catches even a // short asynchronous assertion; xpm_rst_sync then converts it into a reset // signal that changes only on wr_clk edges. reg rst_capture = 1'b1; always @(posedge wr_clk or posedge rst) begin if (rst) rst_capture <= 1'b1; else rst_capture <= 1'b0; end (* ASYNC_REG = "TRUE", SHREG_EXTRACT = "NO" *) reg [1:0] xpm_rst_sync = 2'b11; always @(posedge wr_clk) begin xpm_rst_sync[0] <= rst_capture; xpm_rst_sync[1] <= xpm_rst_sync[0]; end wire xpm_rst = xpm_rst_sync[1]; wire xpm_full; wire xpm_empty; wire xpm_wr_rst_busy; wire xpm_rd_rst_busy; wire [71:0] xpm_dout; wire [COUNT_WIDTH-1:0] xpm_wr_data_count; wire [COUNT_WIDTH-1:0] xpm_rd_data_count; // ------------------------------------------------------------------------ // Read-domain reset/hold handling // ------------------------------------------------------------------------ // Do NOT combine xpm_rst (wr_clk domain) with rd-domain signals and feed the // result into an asynchronous PRE/CLR. Vivado report_cdc correctly reports // that topology as CDC-10 (combinational logic before a synchronizer). // // Instead, synchronize each reset indication into rd_clk using dedicated XPM // CDC structures, then make all hold/release decisions synchronously in the // read domain. // Preserve the legacy FIFO Generator behavior at the external interface: // assertion of rst is visible immediately in the read domain, while release // is synchronized to rd_clk. wire legacy_rd_rst; xpm_cdc_async_rst #( .DEST_SYNC_FF (2), .INIT_SYNC_FF (1), .RST_ACTIVE_HIGH(1) ) legacy_rd_rst_sync_i ( .src_arst (rst), .dest_clk (rd_clk), .dest_arst(legacy_rd_rst) ); // xpm_rst is generated synchronously in the write domain. Synchronize that // reset request into rd_clk so the read interface can remain held until the // XPM reset has actually propagated through the write side. INIT=1 keeps the // read side conservative during FPGA configuration/startup. wire xpm_rst_rd; xpm_cdc_sync_rst #( .DEST_SYNC_FF (2), .INIT (1), .INIT_SYNC_FF (1), .SIM_ASSERT_CHK(1) ) xpm_rst_to_rd_i ( .src_rst (xpm_rst), .dest_clk(rd_clk), .dest_rst(xpm_rst_rd) ); // A reset event is complete only after the read domain has observed the XPM // reset sequence (xpm_rst_rd and/or rd_rst_busy asserted) and both indicators // have subsequently gone low. This prevents a short external reset from // creating a gap where the legacy interface is released before XPM has // propagated the reset from wr_clk into rd_clk. reg rd_reset_pending = 1'b1; reg rd_reset_seen = 1'b0; reg legacy_rd_rst_d = 1'b1; always @(posedge rd_clk) begin legacy_rd_rst_d <= legacy_rd_rst; // Start a fresh acknowledgement cycle for each external reset event. if (legacy_rd_rst && !legacy_rd_rst_d) begin rd_reset_pending <= 1'b1; rd_reset_seen <= 1'b0; end else if (legacy_rd_rst) begin // The XPM reset may assert and even complete while the legacy reset is // still being synchronously released. Remember that it was observed. rd_reset_pending <= 1'b1; if (xpm_rst_rd || xpm_rd_rst_busy) rd_reset_seen <= 1'b1; end else if (rd_reset_pending) begin if (xpm_rst_rd || xpm_rd_rst_busy) rd_reset_seen <= 1'b1; if (rd_reset_seen && !xpm_rst_rd && !xpm_rd_rst_busy) rd_reset_pending <= 1'b0; end end // All terms below are now local to rd_clk (or an XPM-synchronized reset). wire rd_hold = legacy_rd_rst | rd_reset_pending | xpm_rst_rd | xpm_rd_rst_busy; wire wr_hold = rst | xpm_rst | xpm_wr_rst_busy; // XPM requires wr_en/rd_en low while reset or the corresponding reset-busy // flag is active. Gating with full/empty also suppresses overflow/underflow // requests from legacy call sites that leave an enable asserted continuously. wire xpm_wr_en = wr_en & ~wr_hold & ~xpm_full; wire xpm_rd_en = rd_en & ~rd_hold & ~xpm_empty; // Match the conservative reset behavior of the Ettus FIFO Generator IP: // full is asserted during reset and empty is asserted until read-side reset // recovery is complete. assign full = wr_hold | xpm_full; assign empty = rd_hold | xpm_empty; assign dout = rd_hold ? 72'b0 : xpm_dout; assign wr_data_count = wr_hold ? {COUNT_WIDTH{1'b0}} : xpm_wr_data_count; assign rd_data_count = rd_hold ? {COUNT_WIDTH{1'b0}} : xpm_rd_data_count; // USE_ADV_FEATURES="0404" enables only wr_data_count (bit 2) and // rd_data_count (bit 10), preserving the legacy ports without enabling the // other optional status logic. xpm_fifo_async #( .CDC_SYNC_STAGES (CDC_SYNC_STAGES), .DOUT_RESET_VALUE ("0"), .ECC_MODE ("no_ecc"), .FIFO_MEMORY_TYPE (MEMORY_TYPE), .FIFO_READ_LATENCY (0), .FIFO_WRITE_DEPTH (FIFO_DEPTH), .FULL_RESET_VALUE (1), .PROG_EMPTY_THRESH (10), .PROG_FULL_THRESH (10), .RD_DATA_COUNT_WIDTH(COUNT_WIDTH), .READ_DATA_WIDTH (72), .READ_MODE ("fwft"), .RELATED_CLOCKS (0), .SIM_ASSERT_CHK (1), .USE_ADV_FEATURES ("0404"), .WAKEUP_TIME (0), .WRITE_DATA_WIDTH (72), .WR_DATA_COUNT_WIDTH(COUNT_WIDTH) ) xpm_fifo_async_i ( .almost_empty (), .almost_full (), .data_valid (), .dbiterr (), .dout (xpm_dout), .empty (xpm_empty), .full (xpm_full), .overflow (), .prog_empty (), .prog_full (), .rd_data_count(xpm_rd_data_count), .rd_rst_busy (xpm_rd_rst_busy), .sbiterr (), .underflow (), .wr_ack (), .wr_data_count(xpm_wr_data_count), .wr_rst_busy (xpm_wr_rst_busy), .din (din), .injectdbiterr(1'b0), .injectsbiterr(1'b0), .rd_clk (rd_clk), .rd_en (xpm_rd_en), .rst (xpm_rst), .sleep (1'b0), .wr_clk (wr_clk), .wr_en (xpm_wr_en) ); endmodule `default_nettype wire