diff options
author | Wade Fife <wade.fife@ettus.com> | 2021-06-08 19:40:46 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-10 11:56:58 -0500 |
commit | 6d3765605262016a80f71e36357f749ea35cbe5a (patch) | |
tree | 7d62d6622befd4132ac1ee085effa1426f7f53e5 /fpga/usrp3/top/x400/x4xx_qsfp_wrapper.sv | |
parent | f706b89e6974e28ce76aadeeb06169becc86acba (diff) | |
download | uhd-6d3765605262016a80f71e36357f749ea35cbe5a.tar.gz uhd-6d3765605262016a80f71e36357f749ea35cbe5a.tar.bz2 uhd-6d3765605262016a80f71e36357f749ea35cbe5a.zip |
fpga: x400: Add support for X410 motherboard FPGA
Co-authored-by: Andrew Moch <Andrew.Moch@ni.com>
Co-authored-by: Daniel Jepson <daniel.jepson@ni.com>
Co-authored-by: Javier Valenzuela <javier.valenzuela@ni.com>
Co-authored-by: Joerg Hofrichter <joerg.hofrichter@ni.com>
Co-authored-by: Kumaran Subramoniam <kumaran.subramoniam@ni.com>
Co-authored-by: Max Köhler <max.koehler@ni.com>
Co-authored-by: Michael Auchter <michael.auchter@ni.com>
Co-authored-by: Paul Butler <paul.butler@ni.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Co-authored-by: Hector Rubio <hrubio@ni.com>
Diffstat (limited to 'fpga/usrp3/top/x400/x4xx_qsfp_wrapper.sv')
-rw-r--r-- | fpga/usrp3/top/x400/x4xx_qsfp_wrapper.sv | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/fpga/usrp3/top/x400/x4xx_qsfp_wrapper.sv b/fpga/usrp3/top/x400/x4xx_qsfp_wrapper.sv new file mode 100644 index 000000000..d83397d81 --- /dev/null +++ b/fpga/usrp3/top/x400/x4xx_qsfp_wrapper.sv @@ -0,0 +1,566 @@ +// +// Copyright 2021 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: x4xx_qsfp_wrapper +// +// Description: +// +// Consolidates the logic necessary for a QSFP port, depending on the +// requested protocol. +// +// Parameters: +// +// PROTOCOL : Indicates the protocol to use for each of the 4 QSFP +// lanes. See x4xx_mgt_types.vh for possible values. +// CPU_W : Width of CPU interface +// CHDR_W : CHDR bus width +// BYTE_MTU : Transport MTU in bytes +// PORTNUM : Port number to distinguish multiple QSFP ports +// RFNOC_PROTOVER : RFNoC protocol version for IPv4 interface +// + +`include "./x4xx_mgt_types.vh" + + +module x4xx_qsfp_wrapper #( + // Must be a value defined in x4xx_mgt_types.vh + parameter integer PROTOCOL [3:0] = {`MGT_Disabled, + `MGT_Disabled, + `MGT_Disabled, + `MGT_Disabled}, + parameter CPU_W = 64, + parameter CHDR_W = 64, + parameter BYTE_MTU = $clog2(8*1024), + parameter [ 7:0] PORTNUM = 8'd0, + parameter [15:0] RFNOC_PROTOVER = {8'd1, 8'd0} +)( + // Resets + input logic areset, + input logic bus_rst, + input logic clk40_rst, + + // Clocks + input logic refclk_p, + input logic refclk_n, + input logic clk100, + input logic bus_clk, + + // AXI-Lite register access + AxiLiteIf.slave s_axi, + + // Ethernet DMA AXI to PS memory + AxiIf.master axi_hp, + + // MGT high-speed IO + output logic [3:0] tx_p, + output logic [3:0] tx_n, + input logic [3:0] rx_p, + input logic [3:0] rx_n, + + // CHDR router interface + AxiStreamIf.master e2v [4], + AxiStreamIf.slave v2e [4], + + // ETH DMA IRQs + output logic [3:0] eth_rx_irq, + output logic [3:0] eth_tx_irq, + + // Misc. + output logic rx_rec_clk_out, + input logic [15:0] device_id, + + output logic [3:0][31:0] port_info, + + output logic [3:0] link_up, + output logic [3:0] activity +); + + import PkgAxiLite::*; + + localparam REG_BASE_SFP_IO = 14'h0; + localparam REG_BASE_ETH_SWITCH = 14'h1000; + localparam CPU_USER_W = $clog2(CPU_W/8)+1; + localparam CHDR_USER_W = $clog2(CHDR_W/8); + localparam REG_DWIDTH = 32; + localparam REG_AWIDTH_MISC = 14; + localparam logic [3:0] DISABLED = { PROTOCOL[3] == `MGT_Disabled, + PROTOCOL[2] == `MGT_Disabled, + PROTOCOL[1] == `MGT_Disabled, + PROTOCOL[0] == `MGT_Disabled }; + localparam logic [3:0] IS10GBE = { PROTOCOL[3] == `MGT_10GbE, + PROTOCOL[2] == `MGT_10GbE, + PROTOCOL[1] == `MGT_10GbE, + PROTOCOL[0] == `MGT_10GbE }; + localparam logic [3:0] IS100GBE = { 3'b0,PROTOCOL[0] == `MGT_100GbE }; + localparam logic [3:0] ISAURORA = { 3'b0,PROTOCOL[0] == `MGT_Aurora }; + + + `include "../../lib/axi4_sv/axi.vh" + `include "../../lib/axi4lite_sv/axi_lite.vh" + + + //--------------------------------------------------------------------------- + // Interfaces + //--------------------------------------------------------------------------- + + // AXI-Lite interface + AxiLiteIf #(REG_DWIDTH,40) + m_axi_dma[3:0] (s_axi.clk, s_axi.rst); + + // 0x0000-0x3FFF - Bottom goes to XGE top goes to UIO + AxiLiteIf #(REG_DWIDTH,40) + m_axi_misc[3:0] (s_axi.clk, s_axi.rst); + AxiLiteIf_v #(REG_DWIDTH,REG_AWIDTH_MISC) + m_axi_misc_v[3:0] (s_axi.clk, s_axi.rst); + + // 0x4000-0x5FFF - Goes to 100G Mac + AxiLiteIf #(REG_DWIDTH,40) + m_axi_mac[3:0] (s_axi.clk, s_axi.rst); + + // AXI (Full) for DMA back to CPU memory + AxiIf #(128,49) + axi_hp_dma[3:0] (s_axi.clk, s_axi.rst); + + + //--------------------------------------------------------------------------- + // AXI Interconnect + //--------------------------------------------------------------------------- + // + // Break the incoming register request into 12 different spaces: + // + // 0x0_0000 - dma0 + // 0x0_8000 - misc0 - +0x0000 NIXGE + // +0x2000 UIO + // 0x0_C000 - mac0 + // + // 0x1_0000 - dma1 + // 0x1_8000 - misc1 - +0x0000 NIXGE + // +0x2000 UIO + // 0x1_C000 - mac1 + // + // 0x2_0000 - dma2 + // 0x2_8000 - misc2 - +0x0000 NIXGE + // +0x2000 UIO + // 0x2_C000 - mac2 + // + // 0x3_0000 - dma3 + // 0x3_8000 - misc3 - +0x0000 NIXGE + // +0x2000 UIO + // 0x3_C000 - mac3 + // + //--------------------------------------------------------------------------- + + axi_interconnect_eth axi_interconnect_eth_i ( + .s_axi_eth (s_axi), + .m_axi_dma (m_axi_dma), + .m_axi_misc (m_axi_misc), + .m_axi_mac (m_axi_mac) + ); + + + //--------------------------------------------------------------------------- + // Map DMA Engine Masters to CPU Memory Port + //--------------------------------------------------------------------------- + + // Everything Disabled + if (DISABLED == 4'b1111) begin : axi_hp_noconnect + always_comb begin + axi_hp.drive_read_idle(); + axi_hp.drive_aw_idle(); + axi_hp.drive_w_idle(); + axi_hp.bready = 1'b0; + axi_hp.rready = 1'b0; + end + end : axi_hp_noconnect else + // Only port0 Enabled + if (DISABLED == 4'b1110) begin : axi_hp_directconnect + always_comb begin + `AXI4_ASSIGN(axi_hp,axi_hp_dma[0]) + axi_hp_dma[1].wready = 1'b0; + axi_hp_dma[2].wready = 1'b0; + axi_hp_dma[3].wready = 1'b0; + axi_hp_dma[1].awready = 1'b0; + axi_hp_dma[2].awready = 1'b0; + axi_hp_dma[3].awready = 1'b0; + axi_hp_dma[1].arready = 1'b0; + axi_hp_dma[2].arready = 1'b0; + axi_hp_dma[3].arready = 1'b0; + axi_hp_dma[1].bvalid = 1'b0; + axi_hp_dma[2].bvalid = 1'b0; + axi_hp_dma[3].bvalid = 1'b0; + axi_hp_dma[1].rvalid = 1'b0; + axi_hp_dma[2].rvalid = 1'b0; + axi_hp_dma[3].rvalid = 1'b0; + end + // All other cases + end : axi_hp_directconnect else begin : axi_hp_interconnect + axi_interconnect_dma axi_interconnect_dma_i ( + .m_axi_hp (axi_hp), + .s_axi_hp_dma (axi_hp_dma) + ); + end : axi_hp_interconnect + + + //--------------------------------------------------------------------------- + // 10 Gigabit Ethernet + //--------------------------------------------------------------------------- + + logic refclk; // 156 Mhz Ref 10 GbE + logic [0:0] qpll0_reset; + logic [3:0] qpll0_reset_i; + logic [0:0] qpll0_lock; + logic [0:0] qpll0_clk; + logic [0:0] qpll0_refclk; + logic [0:0] qpll1_reset; + logic [3:0] qpll1_reset_i; + logic [0:0] qpll1_lock; + logic [0:0] qpll1_clk; + logic [0:0] qpll1_refclk; + + assign qpll0_reset[0] = qpll0_reset_i[0] || qpll0_reset_i[1] || + qpll0_reset_i[2] || qpll0_reset_i[3]; + assign qpll1_reset[0] = qpll1_reset_i[0] || qpll1_reset_i[1] || + qpll1_reset_i[2] || qpll1_reset_i[3]; + + // The following logic is shared amongst potentially 4X10GBE interfaces + if (IS10GBE != 0) begin : xge_common + + // Clocking signals for MGTs + IBUFDS_GTE4 ibufds_gte4_refclk ( + .I (refclk_p), + .IB (refclk_n), + .CEB (1'b0), + .O (refclk), + .ODIV2 () + ); + + xge_pcs_pma_common_wrapper xge_pcs_pma_common_wrapper_i ( + .refclk (refclk), + .qpll0reset (qpll0_reset), + .qpll0lock (qpll0_lock), + .qpll0outclk (qpll0_clk), + .qpll0outrefclk (qpll0_refclk), + .qpll1reset (qpll1_reset), + .qpll1lock (qpll1_lock), + .qpll1outclk (qpll1_clk), + .qpll1outrefclk (qpll1_refclk) + ); + + end : xge_common + + + //--------------------------------------------------------------------------- + // Generate QSFP Lanes + //--------------------------------------------------------------------------- + + logic [3:0] rx_rec_clk_out_i; + assign rx_rec_clk_out = rx_rec_clk_out_i[0]; + + generate + genvar lane; + begin : mgt_lanes + // Repeat logic for up to 4 QSFP lanes + for(lane = 0; lane < 4; lane++) begin : lane_loop + + //--------------------------------------- + // AXI-Lite to RegPort Bridge + //--------------------------------------- + + // Map to 0x4000 space + always_comb begin + `AXI4LITE_ASSIGN(m_axi_misc_v[lane],m_axi_misc[lane]) + m_axi_misc_v[lane].araddr = 0; + m_axi_misc_v[lane].araddr[13:0] = m_axi_misc[lane].araddr[13:0]; + m_axi_misc_v[lane].awaddr = 0; + m_axi_misc_v[lane].awaddr[13:0] = m_axi_misc[lane].awaddr[13:0]; + end + + // AXI4-Lite to RegPort (PS to PL Register Access) + // NOTE: We always have a register interface even if the block is + // unused, so that the driver can query the status. + typedef logic [REG_AWIDTH_MISC-1:0] reg_addr_t; + typedef logic [REG_DWIDTH-1:0] reg_data_t; + + logic reg_wr_req; + reg_addr_t reg_wr_addr; + reg_data_t reg_wr_data; + logic reg_rd_req; + reg_addr_t reg_rd_addr; + logic reg_rd_resp, reg_rd_resp_io, reg_rd_resp_eth_if; + reg_data_t reg_rd_data, reg_rd_data_io, reg_rd_data_eth_if; + + axil_regport_master #( + .DWIDTH (REG_DWIDTH), // Width of the AXI4-Lite data bus (must be 32 or 64) + .AWIDTH (REG_AWIDTH_MISC), // Width of the address bus + .WRBASE (0), // Write address base + .RDBASE (0), // Read address base + .TIMEOUT (10) // log2(timeout). Read will timeout after (2^TIMEOUT - 1) cycles + ) axil_regport_master_i ( + // Clock and reset + .s_axi_aclk (m_axi_misc_v[lane].clk), + .s_axi_aresetn (!m_axi_misc_v[lane].rst), + `AXI4LITE_PORT_ASSIGN_NR(s_axi,m_axi_misc_v[lane]) + // Register port: Write port (domain: reg_clk) + .reg_clk (bus_clk), + .reg_wr_req (reg_wr_req), + .reg_wr_addr (reg_wr_addr), + .reg_wr_data (reg_wr_data), + .reg_wr_keep (/*unused*/), + // Register port: Read port (domain: reg_clk) + .reg_rd_req (reg_rd_req), + .reg_rd_addr (reg_rd_addr), + .reg_rd_resp (reg_rd_resp), + .reg_rd_data (reg_rd_data) + ); + + // Regport Mux for response + regport_resp_mux #( + .WIDTH (REG_DWIDTH), + .NUM_SLAVES (2) + ) regport_resp_mux_i ( + .clk(bus_clk), .reset(bus_rst), + .sla_rd_resp({reg_rd_resp_eth_if, reg_rd_resp_io}), + .sla_rd_data({reg_rd_data_eth_if, reg_rd_data_io}), + .mst_rd_resp(reg_rd_resp), .mst_rd_data(reg_rd_data) + ); + + + //--------------------------------------- + // MGT IO Core + //--------------------------------------- + + localparam MGT_W = (IS100GBE) ? 512 : 64; + localparam MGT_USER_W = $clog2(MGT_W/8)+1; + + // The Clocking for the MGT interfaces comes from the MGT Wrapper + // depending on the bus it may change. + logic mgt_rst, mgt_clk; + AxiStreamIf #(.DATA_WIDTH(MGT_W),.USER_WIDTH(MGT_USER_W)) + mgt_tx(mgt_clk, mgt_rst); + AxiStreamIf #(.DATA_WIDTH(MGT_W),.USER_WIDTH(MGT_USER_W),.TKEEP(0)) + mgt_rx(mgt_clk, mgt_rst); + + logic mgt_pause_req; + logic [3:0] tx_p_lane; + logic [3:0] tx_n_lane; + + if (IS10GBE[lane]) begin + // Single lane case: + assign tx_p[lane] = tx_p_lane[lane]; + assign tx_n[lane] = tx_n_lane[lane]; + end else if (IS100GBE[lane] || ISAURORA[lane]) begin + // Multi lane case: + assign tx_p = tx_p_lane; + assign tx_n = tx_n_lane; + end + + x4xx_mgt_io_core #( + .PROTOCOL (PROTOCOL[lane]), + .REG_BASE (REG_BASE_SFP_IO), + .REG_DWIDTH (REG_DWIDTH), // Width of the AXI4-Lite data bus (must be 32 or 64) + .REG_AWIDTH (REG_AWIDTH_MISC), // Width of the address bus + .PORTNUM (PORTNUM), + .LANENUM (lane) + ) x4xx_mgt_io_core_i ( + // Must reset all channels on quad when QSFP GTX core is reset + .areset (areset), + + .mgt_rst (mgt_rst), + .mgt_clk (mgt_clk), + + .clk100 (clk100), + + .bus_rst (bus_rst), + .bus_clk (bus_clk), + + .refclk_p (refclk_p), + .refclk_n (refclk_n), + .tx_p (tx_p_lane), + .tx_n (tx_n_lane), + .rx_p (rx_p), + .rx_n (rx_n), + + // Common signals (for single lane instances) + .qpll0_reset (qpll0_reset_i[lane]), + .qpll0_lock (qpll0_lock), + .qpll0_clk (qpll0_clk), + .qpll0_refclk (qpll0_refclk), + .qpll1_reset (qpll1_reset_i[lane]), + .qpll1_lock (qpll1_lock), + .qpll1_clk (qpll1_clk), + .qpll1_refclk (qpll1_refclk), + + // RegPort + .reg_wr_req (reg_wr_req), + .reg_wr_addr (reg_wr_addr), + .reg_wr_data (reg_wr_data), + .reg_rd_req (reg_rd_req), + .reg_rd_addr (reg_rd_addr), + .reg_rd_resp (reg_rd_resp_io), + .reg_rd_data (reg_rd_data_io), + // AxiLite + .m_axi_mac (m_axi_mac[lane]), + // Pause + .mgt_pause_req (mgt_pause_req), + // Data + .mgt_tx (mgt_tx), + .mgt_rx (mgt_rx), + + // Misc. + .rx_rec_clk_out (rx_rec_clk_out_i[lane]), + .port_info (port_info[lane]), + .link_up (link_up[lane]), + .activity (activity[lane]) + ); + + + if (IS100GBE[lane] || IS10GBE[lane]) begin : eth_port + + //--------------------------------------- + // Ethernet IPv4 Interface for CHDR + //--------------------------------------- + + // Option to use a bigger FIFO for 100GBe. + // This is address width so +1 doubles the size +2 quadruples it. + localparam CHDR_FIFO_SIZE = (IS100GBE[lane]) ? BYTE_MTU+2 : BYTE_MTU; + + AxiStreamIf #(.DATA_WIDTH(CPU_W), .USER_WIDTH(CPU_USER_W), .TUSER(0)) + c2e (s_axi.clk, s_axi.rst); + AxiStreamIf #(.DATA_WIDTH(CPU_W), .USER_WIDTH(CPU_USER_W), .TUSER(0)) + e2c (s_axi.clk, s_axi.rst); + + localparam PAUSE_EN = (IS100GBE[lane]) ? 1 : 0; + + // Ethernet interface + // (1) routes the packet to CHDR/CPU + // (2) implements a wrap back (eth_tx/eth_rx) + eth_ipv4_interface #( + .PROTOVER (RFNOC_PROTOVER), + .CPU_FIFO_SIZE (BYTE_MTU), + .CHDR_FIFO_SIZE (CHDR_FIFO_SIZE), + .NODE_INST (0), + .BASE (REG_BASE_ETH_SWITCH), + .PREAMBLE_BYTES (0), + .ADD_SOF (0), + .SYNC (0), // c2e/e2c don't use the same clock as eth_tx/eth_rx + .PAUSE_EN (PAUSE_EN), + .ENET_W (MGT_W), + .CPU_W (CPU_W), + .CHDR_W (CHDR_W) + ) eth_ipv4_interface_i ( + .bus_clk (bus_clk), + .bus_rst (bus_rst), + .device_id (device_id), + .reg_wr_req (reg_wr_req), + .reg_wr_addr (reg_wr_addr), + .reg_wr_data (reg_wr_data), + .reg_rd_req (reg_rd_req), + .reg_rd_addr (reg_rd_addr), + .reg_rd_resp (reg_rd_resp_eth_if), + .reg_rd_data (reg_rd_data_eth_if), + .eth_pause_req (mgt_pause_req), + .eth_tx (mgt_tx), + .eth_rx (mgt_rx), + .e2v (e2v[lane]), + .v2e (v2e[lane]), + .e2c (e2c), + .c2e (c2e), + .my_udp_chdr_port (/* unused */), + .my_ip (/* unused */), + .my_mac (/* unused */) + ); + + axi_eth_dma axi_eth_dma_i ( + .c2e (c2e), + .e2c (e2c), + .s_axi_eth_dma (m_axi_dma[lane]), + .axi_hp (axi_hp_dma[lane]), + .eth_tx_irq (eth_tx_irq[lane]), + .eth_rx_irq (eth_rx_irq[lane]) + ); + + end : eth_port else begin : not_eth + + //--------------------------------------- + // Terminate DMA for Unused Ethernet + //--------------------------------------- + + // Set unused ETH_DMA ports to default value + always_comb begin + m_axi_dma[lane].drive_read_resp(.resp(SLVERR),.data(0)); + m_axi_dma[lane].drive_write_resp(.resp(SLVERR)); + m_axi_dma[lane].arready = 1'b1; + m_axi_dma[lane].awready = 1'b1; + m_axi_dma[lane].wready = 1'b1; + + axi_hp_dma[lane].drive_read_idle(); + axi_hp_dma[lane].drive_aw_idle(); + axi_hp_dma[lane].drive_w_idle(); + axi_hp_dma[lane].bready = 1'b0; + axi_hp_dma[lane].rready = 1'b0; + + mgt_pause_req = 0'b0; + + eth_rx_irq[lane] = 1'b0; + eth_tx_irq[lane] = 1'b0; + + reg_rd_resp_eth_if = 1'b0; + reg_rd_data_eth_if = 'h0; + end + + if (ISAURORA[lane]) begin : aurora_port + + //--------------------------------------- + // Aurora + //--------------------------------------- + + Aurora_not_yet_supported(); + + // if MGT_W and CHDR_W mismatch figure out what to do + always_comb begin + e2v[lane].tdata = mgt_rx.tdata; + e2v[lane].tuser = 'b0; + e2v[lane].tkeep = 'b1; + e2v[lane].tlast = mgt_rx.tlast; + e2v[lane].tvalid = mgt_rx.tvalid; + mgt_rx.tready = e2v[lane].tready; + + mgt_tx.tdata = v2e[lane].tdata; + mgt_tx.tuser = 'b0; + mgt_tx.tkeep = 'b1; + mgt_tx.tlast = v2e[lane].tlast; + mgt_tx.tvalid = v2e[lane].tvalid; + v2e[lane].tready = mgt_tx.tready; + end + + end else begin : inactive_port + + //--------------------------------------- + // Disabled Port + //--------------------------------------- + + always_comb begin + e2v[lane].tdata = 'b0; + e2v[lane].tuser = 'b0; + e2v[lane].tkeep = 'b1; + e2v[lane].tlast = 1'b0; + e2v[lane].tvalid = 1'b0; + mgt_rx.tready = 1'b1; + + mgt_tx.tdata = 'b0; + mgt_tx.tuser = 'b0; + mgt_tx.tkeep = 'b1; + mgt_tx.tlast = 1'b0; + mgt_tx.tvalid = 1'b0; + v2e[lane].tready = 1'b1; + end + + end : inactive_port + end : not_eth + end : lane_loop + end : mgt_lanes + endgenerate + +endmodule |