This fixes some issues with the 2021.1 version of the 10 GbE IP. In the new version, link status is sporadically detected by the Xilinx IP when nothing is connected to the port. Also, when the SFP is inserted, the link status goes up and down multiple times. This change fixes this by adding a counter and only considering it "linked" when the link has been stable for some amount of time. Original-commit: 846c7be2e42f1db6d7c7cc0b1128e014f83e3bae
324 lines
11 KiB
Verilog
324 lines
11 KiB
Verilog
//
|
|
// Copyright 2021 Ettus Research, A National Instruments Brand
|
|
//
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
//
|
|
// Module: ten_gige_phy
|
|
//
|
|
// Description:
|
|
//
|
|
// Wrapper for the Xilinx xxv_ethernet IP (10G/25G Ethernet Subsystem).
|
|
//
|
|
|
|
|
|
module ten_gige_phy (
|
|
input wire areset,
|
|
input wire dclk,
|
|
output wire xgmii_clk,
|
|
|
|
// Transceiver IO
|
|
output wire txp,
|
|
output wire txn,
|
|
input wire rxp,
|
|
input wire rxn,
|
|
|
|
// XGMII Interface
|
|
input wire [63:0] xgmii_txd,
|
|
input wire [ 7:0] xgmii_txc,
|
|
output wire [63:0] xgmii_rxd,
|
|
output wire [ 7:0] xgmii_rxc,
|
|
|
|
// GTYE4_COMMON
|
|
input wire qpll0_refclk,
|
|
input wire qpll0_clk,
|
|
input wire qpll0_lock,
|
|
output wire qpll0_reset,
|
|
input wire qpll1_refclk,
|
|
input wire qpll1_clk,
|
|
input wire qpll1_lock,
|
|
output wire qpll1_reset,
|
|
|
|
output wire rxrecclkout,
|
|
output wire [7:0] core_status,
|
|
output reg reset_done
|
|
);
|
|
|
|
// Create a constant that tells us if we're in simulation or synthesis.
|
|
localparam SIMULATION = 0
|
|
//synthesis translate_off
|
|
+ 1
|
|
//synthesis translate_on
|
|
;
|
|
|
|
// xgmii_clk frequency in Hz
|
|
localparam XGMII_FREQ = 125_000_000;
|
|
// Cycles to wait before period reset while link is down.
|
|
localparam RESET_WAIT = XGMII_FREQ/2;
|
|
// Cycles to wait before reporting the link is up. Use a much smaller number
|
|
// in simulation to save time.
|
|
localparam LINK_WAIT = SIMULATION ? (125) : RESET_WAIT;
|
|
// Duration of reset in cycles (must be less than LINK_WAIT)
|
|
localparam LINK_RST_DURATION = 100;
|
|
// Width of the wait counter. Must be wide enough for the WAIT values above.
|
|
localparam WAIT_W = $clog2(RESET_WAIT+1);
|
|
|
|
wire rx_serdes_reset;
|
|
wire tx_reset;
|
|
wire rx_reset;
|
|
|
|
wire a_gt_reset_tx_done, gt_reset_tx_done;
|
|
wire a_gt_reset_rx_done, gt_reset_rx_done;
|
|
|
|
wire stat_rx_status_tmp;
|
|
reg stat_rx_status = 0;
|
|
|
|
reg [WAIT_W-1:0] wait_count = 0;
|
|
reg gt_rx_reset_in = 0;
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Xilinx 10G/25G IP High Speed Ethernet Subsystem Instance
|
|
//---------------------------------------------------------------------------
|
|
|
|
// All connections below follow the Xilinx IP example design, except that
|
|
// rx_core_clk is driven by tx_mii_clk instead of rx_clk_out. This puts the
|
|
// RX and TX interfaces on the same clock domain.
|
|
|
|
// gtwiz_reset_qpll1reset_out is not connected to qpll1reset in the example
|
|
// design. Instead, qpll1reset is connected to 0.
|
|
assign qpll1_reset = 1'b0;
|
|
|
|
xge_pcs_pma xge_pcs_pma_i (
|
|
.gt_rxp_in_0 (rxp),
|
|
.gt_rxn_in_0 (rxn),
|
|
.gt_txp_out_0 (txp),
|
|
.gt_txn_out_0 (txn),
|
|
.tx_mii_clk_0 (xgmii_clk),
|
|
.rx_core_clk_0 (xgmii_clk),
|
|
.rx_clk_out_0 (),
|
|
.gt_loopback_in_0 (3'b0),
|
|
.rx_reset_0 (rx_reset),
|
|
.rxrecclkout_0 (rxrecclkout),
|
|
.rx_mii_d_0 (xgmii_rxd),
|
|
.rx_mii_c_0 (xgmii_rxc),
|
|
.ctl_rx_test_pattern_0 (1'b0),
|
|
.ctl_rx_test_pattern_enable_0 (1'b0),
|
|
.ctl_rx_data_pattern_select_0 (1'b0),
|
|
.ctl_rx_prbs31_test_pattern_enable_0 (1'b0),
|
|
.stat_rx_block_lock_0 (),
|
|
.stat_rx_framing_err_valid_0 (),
|
|
.stat_rx_framing_err_0 (),
|
|
.stat_rx_hi_ber_0 (),
|
|
.stat_rx_valid_ctrl_code_0 (),
|
|
.stat_rx_bad_code_0 (),
|
|
.stat_rx_bad_code_valid_0 (),
|
|
.stat_rx_error_valid_0 (),
|
|
.stat_rx_error_0 (),
|
|
.stat_rx_fifo_error_0 (),
|
|
.stat_rx_local_fault_0 (),
|
|
.stat_rx_status_0 (stat_rx_status_tmp), // rx_core_clk_0 domain
|
|
.tx_reset_0 (tx_reset),
|
|
.tx_mii_d_0 (xgmii_txd),
|
|
.tx_mii_c_0 (xgmii_txc),
|
|
.ctl_tx_test_pattern_0 (1'b0),
|
|
.ctl_tx_test_pattern_enable_0 (1'b0),
|
|
.ctl_tx_test_pattern_select_0 (1'b0),
|
|
.ctl_tx_data_pattern_select_0 (1'b0),
|
|
.ctl_tx_test_pattern_seed_a_0 (58'b0),
|
|
.ctl_tx_test_pattern_seed_b_0 (58'b0),
|
|
.ctl_tx_prbs31_test_pattern_enable_0 (1'b0),
|
|
.stat_tx_local_fault_0 (),
|
|
.gt_dmonitorout_0 (),
|
|
.gt_eyescandataerror_0 (),
|
|
.gt_eyescanreset_0 (1'b0),
|
|
.gt_eyescantrigger_0 (1'b0),
|
|
.gt_pcsrsvdin_0 (16'b0),
|
|
.gt_rxbufreset_0 (1'b0),
|
|
.gt_rxbufstatus_0 (),
|
|
.gt_rxcdrhold_0 (1'b0),
|
|
.gt_rxcommadeten_0 (1'b0),
|
|
.gt_rxdfeagchold_0 (1'b0),
|
|
.gt_rxdfelpmreset_0 (1'b0),
|
|
.gt_rxlatclk_0 (1'b0),
|
|
.gt_rxlpmen_0 (1'b0),
|
|
.gt_rxpcsreset_0 (1'b0),
|
|
.gt_rxpmareset_0 (1'b0),
|
|
.gt_rxpolarity_0 (1'b0),
|
|
.gt_rxprbscntreset_0 (1'b0),
|
|
.gt_rxprbserr_0 (),
|
|
.gt_rxprbssel_0 (4'b0),
|
|
.gt_rxrate_0 (3'b0),
|
|
.gt_rxslide_in_0 (1'b0),
|
|
.gt_rxstartofseq_0 (),
|
|
.gt_txbufstatus_0 (),
|
|
.gt_txinhibit_0 (1'b0),
|
|
.gt_txlatclk_0 (1'b0),
|
|
.gt_txmaincursor_0 (7'h50),
|
|
.gt_txpcsreset_0 (1'b0),
|
|
.gt_txpmareset_0 (1'b0),
|
|
.gt_txpolarity_0 (1'b0),
|
|
.gt_txpostcursor_0 (5'b0),
|
|
.gt_txprbsforceerr_0 (1'b0),
|
|
.gt_txelecidle_0 (1'b0),
|
|
.gt_txprbssel_0 (4'b0),
|
|
.gt_txprecursor_0 (5'b0),
|
|
.gt_txdiffctrl_0 (5'h18),
|
|
.gt_drpdo_0 (),
|
|
.gt_drprdy_0 (),
|
|
.gt_drpen_0 (1'b0),
|
|
.gt_drpwe_0 (1'b0),
|
|
.gt_drpaddr_0 (10'b0),
|
|
.gt_drpdi_0 (16'b0),
|
|
.gt_drpclk_0 (dclk),
|
|
.gt_drprst_0 (1'b0),
|
|
.gtpowergood_out_0 (),
|
|
.txoutclksel_in_0 (3'b101),
|
|
.rxoutclksel_in_0 (3'b101),
|
|
.rx_serdes_reset_0 (rx_serdes_reset),
|
|
.gt_reset_all_in_0 (areset),
|
|
.gt_tx_reset_in_0 (1'b0),
|
|
.gt_rx_reset_in_0 (gt_rx_reset_in),
|
|
.gt_reset_tx_done_out_0 (a_gt_reset_tx_done),
|
|
.gt_reset_rx_done_out_0 (a_gt_reset_rx_done),
|
|
.sys_reset (areset),
|
|
.dclk (dclk),
|
|
.qpll0clk_in (qpll0_clk),
|
|
.qpll0refclk_in (qpll0_refclk),
|
|
.qpll1clk_in (qpll1_clk),
|
|
.qpll1refclk_in (qpll1_refclk),
|
|
.gtwiz_reset_qpll0lock_in (qpll0_lock),
|
|
.gtwiz_reset_qpll1lock_in (qpll1_lock),
|
|
.gtwiz_reset_qpll0reset_out (qpll0_reset),
|
|
.gtwiz_reset_qpll1reset_out ()
|
|
);
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Status
|
|
//---------------------------------------------------------------------------
|
|
|
|
assign core_status[7:1] = 0; // Unused
|
|
assign core_status[0] = stat_rx_status; // Link status
|
|
|
|
// Safely combine the RX and TX reset done signals into a single glitch-free
|
|
// signal.
|
|
|
|
synchronizer sync_reset_tx_done (
|
|
.clk (xgmii_clk),
|
|
.rst (1'b0),
|
|
.in (a_gt_reset_tx_done),
|
|
.out (gt_reset_tx_done)
|
|
);
|
|
|
|
synchronizer sync_reset_rx_done (
|
|
.clk (xgmii_clk),
|
|
.rst (1'b0),
|
|
.in (a_gt_reset_rx_done),
|
|
.out (gt_reset_rx_done)
|
|
);
|
|
|
|
always @(posedge xgmii_clk) begin : ResetDoneProc
|
|
reset_done <= gt_reset_tx_done & gt_reset_rx_done;
|
|
end
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Reset Logic
|
|
//---------------------------------------------------------------------------
|
|
|
|
// The reset synchronization below is taken from the example design, except
|
|
// that rx_clk_out was replaced by tx_mii_clk (xgmii_clk).
|
|
|
|
synchronizer sync_rx_serdes_reset (
|
|
.clk (xgmii_clk),
|
|
.rst (1'b0),
|
|
.in (~a_gt_reset_rx_done),
|
|
.out (rx_serdes_reset)
|
|
);
|
|
|
|
synchronizer sync_tx_reset (
|
|
.clk (xgmii_clk),
|
|
.rst (1'b0),
|
|
.in (~a_gt_reset_tx_done),
|
|
.out (tx_reset)
|
|
);
|
|
|
|
synchronizer sync_rx_reset (
|
|
.clk (xgmii_clk),
|
|
.rst (1'b0),
|
|
.in (~a_gt_reset_rx_done),
|
|
.out (rx_reset)
|
|
);
|
|
|
|
localparam [1:0] ST_IDLE = 0;
|
|
localparam [1:0] ST_RESET = 1;
|
|
localparam [1:0] ST_WAIT_FOR_LINK = 2;
|
|
localparam [1:0] ST_LINKED = 3;
|
|
|
|
reg [1:0] state = ST_IDLE;
|
|
|
|
// This state machine resets the RX GT part of the Xilinx IP periodically
|
|
// while the link is down. Once the Xilinx IP reports the link as up, it
|
|
// waits a bit to make sure the link is stable before reporting that it is
|
|
// up. This allows us to tolerate a some amount of flakiness while the port
|
|
// is being plugged in.
|
|
always @(posedge xgmii_clk, posedge areset) begin
|
|
if (areset) begin
|
|
gt_rx_reset_in <= 0;
|
|
wait_count <= 0;
|
|
stat_rx_status <= 0;
|
|
state <= ST_IDLE;
|
|
end else begin
|
|
case (state)
|
|
ST_IDLE : begin
|
|
// Wait for a delay then reset the RX GT core if the link is down.
|
|
wait_count <= wait_count + 1;
|
|
if (stat_rx_status_tmp) begin
|
|
// Link indication asserted, so let's see if it's stable.
|
|
wait_count <= 0;
|
|
state <= ST_WAIT_FOR_LINK;
|
|
end else if (wait_count >= RESET_WAIT) begin
|
|
// Link didn't assert during our wait, so reset the core, in case
|
|
// it's in a bad state.
|
|
wait_count <= 0;
|
|
state <= ST_RESET;
|
|
end
|
|
end
|
|
ST_RESET : begin
|
|
// Reset the RX GT logic
|
|
gt_rx_reset_in <= 1;
|
|
wait_count <= wait_count + 1;
|
|
if (wait_count >= LINK_RST_DURATION) begin
|
|
// Reset is done, so deassert and then try again
|
|
gt_rx_reset_in <= 0;
|
|
wait_count <= 0;
|
|
state <= ST_IDLE;
|
|
end
|
|
end
|
|
ST_WAIT_FOR_LINK : begin
|
|
// Wait for a while to make sure the link is stable
|
|
wait_count <= wait_count + 1;
|
|
if (!stat_rx_status_tmp) begin
|
|
// Link was not stable!
|
|
wait_count <= 0;
|
|
state <= ST_RESET;
|
|
end else if (wait_count >= LINK_WAIT) begin
|
|
// Link stayed stable during the wait
|
|
wait_count <= 0;
|
|
state <= ST_LINKED;
|
|
end
|
|
end
|
|
ST_LINKED : begin
|
|
// We've had a solid link for a while know, so let's call it good.
|
|
wait_count <= 0;
|
|
stat_rx_status <= 1;
|
|
if (!stat_rx_status_tmp) begin
|
|
stat_rx_status <= 0;
|
|
state <= ST_RESET;
|
|
end
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
|
|
endmodule
|