Files
b210-k7-fpga/top/x400/x4xx_qsfp_wrapper.sv
T
61782b02d7 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>


Original-commit: 6d3765605262016a80f71e36357f749ea35cbe5a
2021-06-10 11:56:58 -05:00

567 lines
19 KiB
Systemverilog

//
// 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