From eb84b6c949dfb3df2d0f2499eb85d2fed36f2aef Mon Sep 17 00:00:00 2001 From: Josef Miegl Date: Tue, 25 Aug 2026 22:22:39 +0200 Subject: [PATCH] fifo: synchronize read-domain reset handling Synchronize legacy and XPM reset indications into the FIFO read clock domain instead of feeding cross-domain reset logic into asynchronous PRE/CLR paths. This removes the FIFO-induced CDC-10 findings while preserving legacy reset behavior. --- lib/fifo/fifo_2clk_xpm_core.v | 85 +++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 14 deletions(-) diff --git a/lib/fifo/fifo_2clk_xpm_core.v b/lib/fifo/fifo_2clk_xpm_core.v index 82194bf..0f64f05 100644 --- a/lib/fifo/fifo_2clk_xpm_core.v +++ b/lib/fifo/fifo_2clk_xpm_core.v @@ -65,23 +65,80 @@ module fifo_2clk_xpm_core #( wire [COUNT_WIDTH-1:0] xpm_wr_data_count; wire [COUNT_WIDTH-1:0] xpm_rd_data_count; - // Keep the read-side legacy interface quiescent from the instant the old - // asynchronous reset is asserted until both XPM reset propagation and the - // read-domain reset-busy interval have completed. Assertion is asynchronous; - // release is synchronized to rd_clk. - wire rd_hold_req = rst | xpm_rst | xpm_rd_rst_busy; - (* ASYNC_REG = "TRUE", SHREG_EXTRACT = "NO" *) - reg [1:0] rd_hold_sync = 2'b11; - always @(posedge rd_clk or posedge rd_hold_req) begin - if (rd_hold_req) - rd_hold_sync <= 2'b11; - else begin - rd_hold_sync[0] <= 1'b0; - rd_hold_sync[1] <= rd_hold_sync[0]; + // ------------------------------------------------------------------------ + // 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 - wire rd_hold = rd_hold_sync[1]; + // 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