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.
213 lines
7.6 KiB
Verilog
213 lines
7.6 KiB
Verilog
// 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
|