module ad9484_interface_400m ( // ADC Physical Interface (LVDS) input wire [7:0] adc_d_p, // ADC Data P input wire [7:0] adc_d_n, // ADC Data N input wire adc_dco_p, // Data Clock Output P (400MHz) input wire adc_dco_n, // Data Clock Output N (400MHz) // Audit F-0.1: AD9484 OR (overrange) LVDS pair, DDR like data. // Routed on the 50T main board to bank 14 pins M6/N6. Asserts for any // sample whose absolute value exceeds full-scale. input wire adc_or_p, input wire adc_or_n, // System Interface input wire sys_clk, // 100MHz system clock (for control only) input wire reset_n, // Output at 400MHz domain output wire [7:0] adc_data_400m, // ADC data at 400MHz output wire adc_data_valid_400m, // Valid at 400MHz output wire adc_dco_bufg, // Buffered 400MHz DCO clock for downstream use // Audit F-0.1: OR flag, clk_400m domain. High on any sample in the // current 400 MHz cycle where the ADC reports overrange. output wire adc_overrange_400m ); // LVDS to single-ended conversion wire [7:0] adc_data; wire adc_dco; // IBUFDS for each data bit // NOTE: IOSTANDARD and DIFF_TERM are set via XDC constraints, not RTL // parameters, to support multiple FPGA targets with different bank voltages: // - XC7A200T (FBG484): Bank 14 VCCO = 2.5V → LVDS_25 // - XC7A50T (FTG256): Bank 14 VCCO = 3.3V → LVDS_33 genvar i; generate for (i = 0; i < 8; i = i + 1) begin : data_buffers IBUFDS #( .DIFF_TERM("FALSE"), // Overridden by XDC DIFF_TERM property .IOSTANDARD("DEFAULT") // Overridden by XDC IOSTANDARD property ) ibufds_data ( .O(adc_data[i]), .I(adc_d_p[i]), .IB(adc_d_n[i]) ); end endgenerate // IBUFDS for DCO IBUFDS #( .DIFF_TERM("FALSE"), // Overridden by XDC DIFF_TERM property .IOSTANDARD("DEFAULT") // Overridden by XDC IOSTANDARD property ) ibufds_dco ( .O(adc_dco), .I(adc_dco_p), .IB(adc_dco_n) ); // ============================================================================ // Clock buffering strategy for source-synchronous ADC interface: // // BUFIO: Near-zero insertion delay, can only drive IOB primitives (IDDR). // Used for IDDR clocking to match the data path delay through IBUFDS. // This eliminates the hold violation caused by BUFG insertion delay. // // BUFG: Global clock buffer for fabric logic (downstream processing). // Has ~4 ns insertion delay but that's fine for fabric-to-fabric paths. // ============================================================================ wire adc_dco_bufio; // Near-zero delay — drives IDDR only wire adc_dco_buffered; // BUFG output — drives fabric logic BUFIO bufio_dco ( .I(adc_dco), .O(adc_dco_bufio) ); // MMCME2 jitter-cleaning wrapper replaces the direct BUFG. // The PLL feedback loop attenuates input jitter from ~50 ps to ~20-30 ps, // reducing clock uncertainty and improving WNS on the 400 MHz CIC path. wire mmcm_locked; adc_clk_mmcm mmcm_inst ( .clk_in (adc_dco), // 400 MHz from IBUFDS output .reset_n (reset_n), .clk_400m_out (adc_dco_buffered), // Jitter-cleaned 400 MHz on BUFG .mmcm_locked (mmcm_locked) ); assign adc_dco_bufg = adc_dco_buffered; // IDDR for capturing DDR data wire [7:0] adc_data_rise; // Data on rising edge (BUFIO domain) wire [7:0] adc_data_fall; // Data on falling edge (BUFIO domain) genvar j; generate for (j = 0; j < 8; j = j + 1) begin : iddr_gen IDDR #( .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), .INIT_Q1(1'b0), .INIT_Q2(1'b0), .SRTYPE("SYNC") ) iddr_inst ( .Q1(adc_data_rise[j]), // Rising edge data .Q2(adc_data_fall[j]), // Falling edge data .C(adc_dco_bufio), // BUFIO clock (near-zero insertion delay) .CE(1'b1), .D(adc_data[j]), .R(1'b0), .S(1'b0) ); end endgenerate // ============================================================================ // Re-register IDDR outputs into BUFG domain // IDDR with SAME_EDGE_PIPELINED produces outputs stable for a full clock cycle. // BUFIO and BUFG are derived from the same source (adc_dco), so they are // frequency-matched. This single register stage transfers from IOB (BUFIO) // to fabric (BUFG) with guaranteed timing. // ============================================================================ reg [7:0] adc_data_rise_bufg; reg [7:0] adc_data_fall_bufg; always @(posedge adc_dco_buffered) begin adc_data_rise_bufg <= adc_data_rise; adc_data_fall_bufg <= adc_data_fall; end // Combine rising and falling edge data to get 400MSPS stream reg [7:0] adc_data_400m_reg; reg adc_data_valid_400m_reg; reg dco_phase; // ── Reset synchronizer ──────────────────────────────────────── // reset_n comes from the 100 MHz sys_clk domain. Assertion (going low) // is asynchronous and safe — the FFs enter reset instantly. De-assertion // (going high) must be synchronised to adc_dco_buffered to avoid // metastability. This is the classic "async assert, sync de-assert" pattern. // // mmcm_locked gates de-assertion: the 400 MHz domain stays in reset until // the MMCM PLL has locked and the jitter-cleaned clock is stable. (* ASYNC_REG = "TRUE" *) reg [1:0] reset_sync_400m; wire reset_n_400m; wire reset_n_gated = reset_n & mmcm_locked; always @(posedge adc_dco_buffered or negedge reset_n_gated) begin if (!reset_n_gated) reset_sync_400m <= 2'b00; // async assert (or MMCM not locked) else reset_sync_400m <= {reset_sync_400m[0], 1'b1}; // sync de-assert end assign reset_n_400m = reset_sync_400m[1]; always @(posedge adc_dco_buffered or negedge reset_n_400m) begin if (!reset_n_400m) begin adc_data_400m_reg <= 8'b0; adc_data_valid_400m_reg <= 1'b0; dco_phase <= 1'b0; end else begin dco_phase <= ~dco_phase; if (dco_phase) begin // Output falling edge data (completes the 400MSPS stream) adc_data_400m_reg <= adc_data_fall_bufg; end else begin // Output rising edge data adc_data_400m_reg <= adc_data_rise_bufg; end adc_data_valid_400m_reg <= 1'b1; // Always valid when ADC is running end end assign adc_data_400m = adc_data_400m_reg; assign adc_data_valid_400m = adc_data_valid_400m_reg; // ============================================================================ // Audit F-0.1: AD9484 OR (overrange) capture // OR is a DDR LVDS pair (same as data). Buffer it, capture both edges with an // IDDR in the BUFIO domain, then OR the two phases into a single clk_400m // flag. Register once for stability. No latching — downstream is expected to // stickify in its own domain. // ============================================================================ wire adc_or_raw; IBUFDS #( .DIFF_TERM("FALSE"), .IOSTANDARD("DEFAULT") ) ibufds_or ( .O(adc_or_raw), .I(adc_or_p), .IB(adc_or_n) ); wire adc_or_rise; wire adc_or_fall; IDDR #( .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), .INIT_Q1(1'b0), .INIT_Q2(1'b0), .SRTYPE("SYNC") ) iddr_or ( .Q1(adc_or_rise), .Q2(adc_or_fall), .C(adc_dco_bufio), .CE(1'b1), .D(adc_or_raw), .R(1'b0), .S(1'b0) ); reg adc_or_rise_bufg; reg adc_or_fall_bufg; always @(posedge adc_dco_buffered) begin adc_or_rise_bufg <= adc_or_rise; adc_or_fall_bufg <= adc_or_fall; end reg adc_overrange_r; always @(posedge adc_dco_buffered or negedge reset_n_400m) begin if (!reset_n_400m) adc_overrange_r <= 1'b0; else adc_overrange_r <= adc_or_rise_bufg | adc_or_fall_bufg; end assign adc_overrange_400m = adc_overrange_r; endmodule