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/ip/eth_100g_bd | |
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/ip/eth_100g_bd')
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/Makefile.inc | 53 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/PkgEth100gLbus.sv | 36 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g.sv | 1220 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_axis2lbus.sv | 120 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_bd.tcl | 361 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_lbus2axis.sv | 555 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/Makefile | 62 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/axi_lbus_tb.sv | 285 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/lbus_all_tb.sv | 22 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/lbus_axi_tb.sv | 343 | ||||
-rw-r--r-- | fpga/usrp3/top/x400/ip/eth_100g_bd/model_100gbe.sv | 234 |
11 files changed, 3291 insertions, 0 deletions
diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/Makefile.inc b/fpga/usrp3/top/x400/ip/eth_100g_bd/Makefile.inc new file mode 100644 index 000000000..88c9c1184 --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/Makefile.inc @@ -0,0 +1,53 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +include $(TOOLS_DIR)/make/viv_ip_builder.mak + +IP_100G_HDL_SRCS = $(addprefix $(IP_DIR)/eth_100g_bd/, \ +PkgEth100gLbus.sv \ +eth_100g.sv \ +eth_100g_axis2lbus.sv \ +eth_100g_lbus2axis.sv \ +) + +IP_100G_HDL_SIM_SRCS = $(addprefix $(IP_DIR)/eth_100g_bd/, \ +model_100gbe.sv \ +) \ +$(wildcard $(addprefix $(IP_BUILD_DIR)/eth_100g_bd/eth_100g_bd/, \ +sim/eth_100g_bd.v\ +ip/*/ip_0/sim/*.v\ +ip/*/sim/*.h\ +ip/*/sim/*.v\ +ip/eth_100g_bd_cmac_usplus_0_0/cmac_usplus_v2_6_1/*.v\ +ip/eth_100g_bd_cmac_usplus_0_0/eth_100g_bd_cmac_usplus_0_0/example_design/*.v\ +ip/eth_100g_bd_cmac_usplus_0_0/eth_100g_bd_cmac_usplus_0_0/header_files/*.h\ +ip/eth_100g_bd_cmac_usplus_0_0/eth_100g_bd_cmac_usplus_0_0.v\ +ipshared/*/hdl/*.v\ +ipshared/*/hdl/*.sv\ +)) + +IP_100G_ORIG_SRCS = $(addprefix $(IP_DIR)/eth_100g_bd/, \ +eth_100g_bd.tcl \ +) + +IP_100G_BDTCL_SRCS = $(addprefix $(IP_BUILD_DIR)/eth_100g_bd/, \ +eth_100g_bd.tcl \ +) + +IP_100G_BD_SRCS = $(addprefix $(IP_BUILD_DIR)/eth_100g_bd/, \ +eth_100g_bd/eth_100g_bd.bd \ +) + +BD_100G_BD_OUTS = $(addprefix $(IP_BUILD_DIR)/eth_100g_bd/, \ +eth_100g_bd.bd.out \ +eth_100g_bd/eth_100g_bd_ooc.xdc \ +eth_100g_bd/synth/eth_100g_bd.v \ +) + +EMPTY_IP_SRCS = + +$(IP_100G_BD_SRCS) $(BD_100G_BD_OUTS) $(IP_100G_BDTCL_SRCS): $(IP_100G_ORIG_SRCS) + $(call BUILD_VIVADO_BDTCL,eth_100g_bd,$(ARCH),$(PART_ID),$(IP_DIR),$(IP_BUILD_DIR),$(EMPTY_IP_SRCS)) diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/PkgEth100gLbus.sv b/fpga/usrp3/top/x400/ip/eth_100g_bd/PkgEth100gLbus.sv new file mode 100644 index 000000000..588313e55 --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/PkgEth100gLbus.sv @@ -0,0 +1,36 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: PkgEth100gLbus +// +// Description: +// +// Package to define an Lbus record +// + +//----------------------------------------------------------------------------- +// Lbus interface +// +// This is the segmented local bus interface on the Xilinx CMAC IP +// see Xilinx CMAC documentation for detail +// https://www.xilinx.com/support/documentation/ip_documentation/cmac_usplus/v2_4/pg203-cmac-usplus.pdf +//----------------------------------------------------------------------------- + +package PkgEth100gLbus; + + localparam DATA_WIDTH = 512; + localparam NUM_SEG = 4; + localparam SEG_DATA_WIDTH = DATA_WIDTH/NUM_SEG; + + typedef struct packed { + logic [SEG_DATA_WIDTH-1:0] data; + logic [$clog2(SEG_DATA_WIDTH/8)-1:0] mty; + logic sop; + logic eop; + logic err; + logic ena; + } lbus_t; + +endpackage : PkgEth100gLbus diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g.sv b/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g.sv new file mode 100644 index 000000000..456db06d9 --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g.sv @@ -0,0 +1,1220 @@ +// +// Copyright 2021 Ettus Research, A National Instruments brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: eth_100g +// +// Description: Wrapper for the Xilinx 100G mac + + +module eth_100g #( + logic PAUSE_EN = 1, + logic [15:0] PAUSE_QUANTA = 16'hFFFF, + logic [15:0] PAUSE_REFRESH = 16'hFFFF + )( + + // Resets + input logic areset, + // Clock for misc stuff + input logic clk100, + // Low jitter refclk + input logic refclk_p, + input logic refclk_n, + // RX Clk for output + output logic rx_rec_clk_out, + // 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, + + // Data port + output logic mgt_clk, + output logic mgt_rst, + input logic mgt_pause_req, + // Interface clocks for mgt_tx and mgt_rx are NOT used (logic uses mgt_clk) + AxiStreamIf.slave mgt_tx, + AxiStreamIf.master mgt_rx, + // Axi port + AxiLiteIf.slave mgt_axil, + // Misc + output logic [31:0] phy_status, + input logic [31:0] mac_ctrl, + output logic [31:0] mac_status, + output logic phy_reset, + output logic link_up +); + + logic tx_ovfout; + logic tx_unfout; + logic stat_rx_aligned; + logic stat_auto_config_done; + logic stat_auto_config_done_bclk; + logic usr_tx_reset; + logic usr_rx_reset; + + // Heirarchical refference (xilinx says it will synthesize) + // eth_100g_bd_i/cmac_usplus_0/gt_rxrecclkout} + assign rx_rec_clk_out = eth_100g_bd_i.cmac_usplus_0.gt_rxrecclkout[0]; + //status registers + always_comb begin + phy_status = 0; + phy_status[0] = usr_tx_reset; + phy_status[1] = usr_rx_reset; + + end + + logic [8:0] pause_mask; // from mac ctl register bits 24:16 + + always_ff @(posedge mgt_clk) begin : mac_status_reg + if (mgt_rst) begin + mac_status <= 0; + end else begin + mac_status[0] <= mac_status[0] || tx_ovfout; + mac_status[1] <= mac_status[1] || tx_unfout; + mac_status[2] <= stat_rx_aligned; + mac_status[3] <= mac_status[3] || (!mgt_rx.tready && mgt_rx.tvalid); + mac_status[4] <= stat_auto_config_done; + mac_status[24:16] <= pause_mask; + end + end + + //extra simulation checks + localparam USE_MAC_CHECKS = 1; + if (USE_MAC_CHECKS) begin + always_ff @(posedge mgt_rx.clk) begin : check_no_holdoff + if (!mgt_rx.rst) begin + if (!mgt_rx.tready && mgt_rx.tvalid) begin + $fatal(1,"MAC RX can't hold off the MAC"); + end + assert(tx_ovfout==0) else + $fatal(1,"MAC TX had an overflow!"); + assert(tx_unfout==0) else + $fatal(1,"MAC TX had an underflow!"); + end + end + end + + initial begin + assert (mgt_tx.DATA_WIDTH == 512) else + $fatal("mgt_rx.DATA_WIDTH must be 512"); + // $clog2(512/8)+1 + assert (mgt_rx.USER_WIDTH == 7) else + $fatal("mgt_rx.USER_WIDTH must be 7"); + assert (mgt_tx.TDATA == 1) else + $fatal("mgt_tx.TDATA must be enabled"); + assert (mgt_tx.TUSER == 1) else + $fatal("mgt_tx.TUSER must be enabled"); + assert (mgt_tx.TKEEP == 1) else + $fatal("mgt_tx.TKEEP must be enabled"); + assert (mgt_tx.TLAST == 1) else + $fatal("mgt_tx.TLAST must be enabled"); + assert (mgt_rx.DATA_WIDTH == 512) else + $fatal("mgt_rx.DATA_WIDTH must be 512"); + // $clog2(512/8)+1 + assert (mgt_rx.USER_WIDTH == 7) else + $fatal("mgt_rx.DATA_WIDTH must be 7"); + assert (mgt_rx.TDATA == 1) else + $fatal("mgt_rx.TDATA must be enabled"); + assert (mgt_rx.TUSER == 1) else + $fatal("mgt_rx.TUSER must be enabled"); + assert (mgt_rx.TKEEP == 0) else + $fatal("mgt_rx.TKEEP must not be enabled"); + assert (mgt_rx.TLAST == 1) else + $fatal("mgt_rx.TLAST must be enabled"); + end + + AxiStreamIf #(.DATA_WIDTH(512),.TUSER(0),.TKEEP(0)) + eth100g_tx(mgt_clk,mgt_rst); + AxiStreamIf #(.DATA_WIDTH(512),.USER_WIDTH(7),.TKEEP(0)) + eth100g_rx(mgt_clk,mgt_rst); + + logic mgt_tx_idle; + logic mgt_tx_pause; + + always_comb begin + eth100g_tx.tdata = mgt_tx.tdata; + eth100g_tx.tuser = 0; + eth100g_tx.tkeep = mgt_tx.tkeep; + eth100g_tx.tvalid = mgt_tx.tvalid && !mgt_tx_pause; + eth100g_tx.tlast = mgt_tx.tlast; + mgt_tx.tready = eth100g_tx.tready && !mgt_tx_pause; + end + + always_comb begin + mgt_rx.tdata = eth100g_rx.tdata; + mgt_rx.tuser = eth100g_rx.tuser; + mgt_rx.tuser[mgt_rx.USER_WIDTH-1] = // assign error bit [MSB] + // CRC failure + eth100g_rx.tuser[mgt_rx.USER_WIDTH-1] || + // Missed a DATA word. + mgt_rx.tvalid && !mgt_rx.tready; + mgt_rx.tkeep = eth100g_rx.tkeep; + mgt_rx.tvalid = eth100g_rx.tvalid; + mgt_rx.tlast = eth100g_rx.tlast; + // The MAC ignores hold off. Data must be consumed every clock it is valid. + // eth100g_rx.tready = mgt_rx.tready; + end + + // This is a heavily replicated signal, add some pipeline + // to it to make it easier to spread out + logic mgt_rst_0; + + + // Flow control signals + // 0-7 map to PCP codes 0-7. 8 is a global pause request + logic [8:0] stat_rx_pause_req ; + logic [8:0] ctl_tx_pause_req ; // drive for at least 16 clocks + logic ctl_tx_resend_pause; // resend the pause request (tieing this high forces a spam of resend requests) + + // QuantaPeriod is 512 bit times or 5.12 ns + // resend pause requests so (quanta*QuantaPeriod)/(refresh*QuantaPeriod) is the percentage of BW that gets through. + // pause_mask is part of the mac_ctrl register + always_comb begin + ctl_tx_resend_pause = 0; + ctl_tx_pause_req = '0; + if (mgt_pause_req) begin + ctl_tx_pause_req = pause_mask; + end + end + logic mgt_tx_pause_req; + assign mgt_tx_pause_req = (pause_mask & stat_rx_pause_req) != 0; + always_ff @(posedge mgt_clk,posedge areset) begin : reset_timing_dff + if (areset) begin + mgt_rst_0 <= 1'b1; + mgt_rst <= 1'b1; + mgt_tx_pause <= 1'b0; + mgt_tx_idle <= 1'b1; + end else begin + mgt_rst_0 <= !link_up; + mgt_rst <= mgt_rst_0; + //idle until a valid sets + if (mgt_tx_idle) begin + if (!eth100g_tx.tvalid) begin + mgt_tx_pause <= mgt_tx_pause_req; + end else begin + // one clock packet + if (eth100g_tx.tvalid && eth100g_tx.tlast && eth100g_tx.tready) begin + mgt_tx_idle <= 1; + mgt_tx_pause <= mgt_tx_pause_req; + end else begin + mgt_tx_idle <= 0; + end + end + //set idle if end of packet is accepted + end else if (eth100g_tx.tvalid && eth100g_tx.tlast && eth100g_tx.tready) begin + mgt_tx_idle <= 1; + mgt_tx_pause <= mgt_tx_pause_req; + end + end + end + + always_comb phy_reset = usr_tx_reset || usr_rx_reset; + always_comb link_up = stat_rx_aligned && !phy_reset; + + // resets stat counts and moves the total to the readable version. + localparam PM_COUNT = 40000; + logic pm_tick = 0; + logic [15:0] pm_tick_count; + always_ff @(posedge mgt_axil.clk) begin : pm_tick_counter + if (mgt_axil.rst) begin + pm_tick_count = 0; + pm_tick = 0; + end else begin + if (pm_tick_count == PM_COUNT-1) begin + pm_tick_count = 0; + pm_tick = 1; + end else begin + pm_tick_count = pm_tick_count+1; + pm_tick = 0; + end + end + end + + `include "../../../../lib/axi4lite_sv/axi_lite.vh" + AxiLiteIf_v #(.DATA_WIDTH(mgt_axil.DATA_WIDTH),.ADDR_WIDTH(32)) + mgt_axil_v(.clk(mgt_axil.clk),.rst(mgt_axil.rst)); + + localparam AUTO_CONNECT=1; + // When enabled the port will automatically attempt to connect to an Ethernet partner + // without requiring any action from SW. If it is not defined, SW will have to perform + // a similar set of writes. Xilinx publishes a driver for the MAC, that we could associate. + // The sequence of writes was taken from the CMAC example, without any deep knowledge + // of what the standard Ethernet connection protocol is. + + // Inject writes to perform connection inbetween other SW writes to read the mac. + if (AUTO_CONNECT) begin : yes_auto_connect + // defined in https://www.xilinx.com/support/documentation/ip_documentation/cmac_usplus/v2_4/pg203-cmac-usplus.pdf + // pg 187 + localparam CONFIGURATION_TX_REG1 = 32'h000C; + localparam ctl_tx_ctl_enable = 0; + localparam ctl_tx_ctl_tx_send_lfi = 3; + localparam ctl_tx_ctl_tx_send_rfi = 4; + localparam ctl_tx_ctl_tx_send_idle = 5; + localparam ctl_tx_ctl_test_pattern = 16; + + localparam CONFIGURATION_RX_REG1 = 32'h0014; + localparam ctl_rx_ctl_enable = 0; + localparam ctl_rx_ctl_rx_force_resync = 7; + localparam ctl_rx_ctl_test_pattern = 8; + + localparam RSFEC_CONFIG_INDICATION_CORRECTION = 32'h1000; + localparam rs_fec_in_ctl_rx_rsfec_enable_correction = 0; + localparam rs_fec_in_ctl_rx_rsfec_enable_indication = 1; + localparam rs_fec_in_ctl_rsfec_ieee_error_indication_mode = 2; + + localparam RSFEC_CONFIG_ENABLE = 32'h107C; + localparam rs_fec_in_ctl_rx_rsfec_enable = 0; + localparam rs_fec_in_ctl_tx_rsfec_enable = 1; + + // Extra configuration for Pause Frames (CMAC guide Pg 210) + //0x0084 : 32'h00003DFF [CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG1] + //0x0088 : 32'h0001C631 [CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG2] + //0x0048 : 32'hFFFFFFFF [CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1] + //0x004C : 32'hFFFFFFFF [CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG2] + //0x0050 : 32'hFFFFFFFF [CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG3] + //0x0054 : 32'hFFFFFFFF [CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG4] + //0x0058 : 32'h0000FFFF [CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG5] + //0x0034 : 32'hFFFFFFFF [CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1] + //0x0038 : 32'hFFFFFFFF [CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG2] + //0x003C : 32'hFFFFFFFF [CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG3] + //0x0040 : 32'hFFFFFFFF [CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG4] + //0x0044 : 32'h0000FFFF [CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG5] + //0x0030 : 32'h000001FF [CONFIGURATION_TX_FLOW_CONTROL_CONTROL_REG1] + + localparam CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG1 = 32'h0084; + //3DFF - 0011 1101 1111 1111 + localparam ctl_rx_pause_en = 0; // 9 bits + localparam ctl_rx_enable_gcp = 10; + localparam ctl_rx_enable_pcp = 11; + localparam ctl_rx_enable_gpp = 12; + localparam ctl_rx_enable_ppp = 13; + localparam ctl_rx_pause_ack = 23; // 8 bits + localparam CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG2 = 32'h0088; + //1C631 - 0001 1100 0110 0011 0001 + localparam ctl_rx_check_mcast_gcp = 0; //1 + localparam ctl_rx_check_ucast_gcp = 1; + localparam ctl_rx_check_sa_gcp = 2; + localparam ctl_rx_check_etype_gcp = 3; + localparam ctl_rx_check_opcode_gcp = 4; //1 + localparam ctl_rx_check_mcast_pcp = 5; //1 + localparam ctl_rx_check_ucast_pcp = 6; + localparam ctl_rx_check_sa_pcp = 7; + localparam ctl_rx_check_etype_pcp = 8; + localparam ctl_rx_check_opcode_pcp = 9; //1 + localparam ctl_rx_check_mcast_gpp = 10; //1 + localparam ctl_rx_check_ucast_gpp = 11; + localparam ctl_rx_check_sa_gpp = 12; + localparam ctl_rx_check_etype_gpp = 13; + localparam ctl_rx_check_opcode_gpp = 14; //1 + localparam ctl_rx_check_opcode_ppp = 15; //1 + localparam ctl_rx_check_mcast_ppp = 16; //1 + localparam ctl_rx_check_ucast_ppp = 17; + localparam ctl_rx_check_sa_ppp = 18; + localparam ctl_rx_check_etype_ppp = 19; + + localparam CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1 = 32'h0048; + localparam ctl_tx_pause_quanta0 = 0; + localparam ctl_tx_pause_quanta1 = 16; + localparam CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG2 = 32'h004C; + localparam ctl_tx_pause_quanta2 = 0; + localparam ctl_tx_pause_quanta3 = 16; + localparam CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG3 = 32'h0050; + localparam ctl_tx_pause_quanta4 = 0; + localparam ctl_tx_pause_quanta5 = 16; + localparam CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG4 = 32'h0054; + localparam ctl_tx_pause_quanta6 = 0; + localparam ctl_tx_pause_quanta7 = 16; + localparam CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG5 = 32'h0058; + localparam ctl_tx_pause_quanta8 = 0; + localparam CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1 = 32'h0034; + localparam ctl_tx_pause_refresh_timer0 = 0; + localparam ctl_tx_pause_refresh_timer1 = 16; + localparam CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG2 = 32'h0038; + localparam ctl_tx_pause_refresh_timer2 = 0; + localparam ctl_tx_pause_refresh_timer3 = 16; + localparam CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG3 = 32'h003C; + localparam ctl_tx_pause_refresh_timer4 = 0; + localparam ctl_tx_pause_refresh_timer5 = 16; + localparam CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG4 = 32'h0040; + localparam ctl_tx_pause_refresh_timer6 = 0; + localparam ctl_tx_pause_refresh_timer7 = 16; + localparam CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG5 = 32'h0044; + localparam ctl_tx_pause_refresh_timer8 = 0; + localparam CONFIGURATION_TX_FLOW_CONTROL_CONTROL_REG1 = 32'h0030; + // 1FF + localparam ctl_tx_pause_enable = 0; // 9 bits + + + AxiLiteIf #(.DATA_WIDTH(mgt_axil.DATA_WIDTH),.ADDR_WIDTH(32)) + auto_axil(.clk(mgt_axil.clk),.rst(mgt_axil.rst)); + + typedef enum logic [4:0] { + ST_RESET = 5'd0, + ST_WR_CONFIGURATION_TX_REG1_IDLE = 5'd1, + ST_WR_RSFEC_CONFIG_INDICATION_CORRECTION = 5'd2, + ST_WR_RSFEC_CONFIG_ENABLE = 5'd3, + ST_WR_CONFIGURATION_RX_REG1 = 5'd4, + ST_WAIT = 5'd5, + ST_WR_CONFIGURATION_TX_REG1_TX_ENABLE = 5'd6, + ST_READY = 5'd7, + // EXTRA PAUSE WRITES + ST_WR_CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG1 = 5'd8, + ST_WR_CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG2 = 5'd9, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1 = 5'd10, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG2 = 5'd11, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG3 = 5'd12, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG4 = 5'd13, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG5 = 5'd14, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1 = 5'd15, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG2 = 5'd16, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG3 = 5'd17, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG4 = 5'd18, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG5 = 5'd19, + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_CONTROL_REG1 = 5'd20 + } auto_connect_state_t; + + auto_connect_state_t auto_connect_state = ST_RESET; + logic auto_rst0, auto_rst1, auto_rst2, auto_rst3; + logic mgt_axil_in_progress; + logic w_req, aw_req; + logic phy_reset_bclk,stat_rx_aligned_bclk; + logic auto_enable; + + synchronizer #( .STAGES(2), .WIDTH(1), .INITIAL_VAL(0) ) phy_reset_sync_i ( + .clk(mgt_axil.clk), .rst(1'b0), .in(phy_reset), .out(phy_reset_bclk) + ); + + synchronizer #( .STAGES(2), .WIDTH(1), .INITIAL_VAL(0) ) rx_aligned_sync_i ( + .clk(mgt_axil.clk), .rst(1'b0), .in(stat_rx_aligned), .out(stat_rx_aligned_bclk) + ); + + synchronizer #( .STAGES(2), .WIDTH(1), .INITIAL_VAL(0) ) auto_config_done_sync_i ( + .clk(mgt_clk), .rst(1'b0), .in(stat_auto_config_done_bclk), .out(stat_auto_config_done) + ); + + + synchronizer #( .STAGES(2), .WIDTH(1), .INITIAL_VAL(0) ) auto_enable_sync_i ( + .clk(mgt_axil.clk), .rst(1'b0), .in(mac_ctrl[0]), .out(auto_enable) + ); + + synchronizer #( .STAGES(2), .WIDTH(9), .INITIAL_VAL(9'h100) ) pause_mask_sync_i ( + .clk(mgt_clk), .rst(1'b0), .in(mac_ctrl[24:16]), .out(pause_mask) + ); + + + always_ff @(posedge mgt_axil.clk) begin : auto_enable_logic + if (mgt_axil.rst) begin + auto_rst0 <= 1'b1; + auto_rst1 <= 1'b1; + auto_rst2 <= 1'b1; + auto_rst3 <= 1'b1; + auto_connect_state <= ST_RESET; + stat_auto_config_done_bclk <= 1'b0; + mgt_axil_in_progress <= 1'b0; + w_req <= 1'b0; + aw_req <= 1'b0; + + // default is to drive mgt_axi_through + /* write address channel */ + auto_axil.awaddr <= 'bX; + auto_axil.awvalid <= 1'b0; + mgt_axil.awready <= 1'b0; + /* write data channel */ + auto_axil.wdata <= 'bX; + auto_axil.wstrb <= 'b0; + auto_axil.wvalid <= 1'b0; + mgt_axil.wready <= 1'b0; + /* write resp channel */ + mgt_axil.bresp[1:0] <= 'b0; + mgt_axil.bvalid <= 1'b0; + auto_axil.bready <= 1'b0; + /* read address channel */ + auto_axil.araddr <= 'b0; + auto_axil.arvalid <= 1'b0; + mgt_axil.arready <= 1'b0; + /* read resp channel */ + mgt_axil.rdata <= 'bX; + mgt_axil.rresp[1:0] <= 'b0; + mgt_axil.rvalid <= 1'b0; + auto_axil.rready <= 1'b0; + + end else begin + // 4 clocks to mimic Xilinx Example behavior + auto_rst0 <= phy_reset_bclk; + auto_rst1 <= auto_rst0; + auto_rst2 <= auto_rst1; + auto_rst3 <= auto_rst2; + // assumes one access in flight at time (valid for standard Xilinx AXIL driver) + // set if anyone starts driving a W Address / W Data / R Address channel + if (auto_axil.awvalid || auto_axil.wvalid || auto_axil.arvalid) begin + mgt_axil_in_progress <= 1'b1; + // clear on an acknowledged response + end else if ((auto_axil.bvalid && auto_axil.bready) || + (auto_axil.rvalid && auto_axil.rready)) begin + mgt_axil_in_progress <= 1'b0; + end + + // default is to drive mgt_axi_through + /* write address channel */ + auto_axil.awaddr <= mgt_axil.awaddr; + auto_axil.awvalid <= mgt_axil.awvalid; + mgt_axil.awready <= auto_axil.awready; + /* write data channel */ + auto_axil.wdata <= mgt_axil.wdata; + auto_axil.wstrb <= mgt_axil.wstrb; + auto_axil.wvalid <= mgt_axil.wvalid; + mgt_axil.wready <= auto_axil.wready; + /* write resp channel */ + mgt_axil.bresp <= auto_axil.bresp; + mgt_axil.bvalid <= auto_axil.bvalid; + auto_axil.bready <= mgt_axil.bready; + /* read address channel */ + auto_axil.araddr <= mgt_axil.araddr; + auto_axil.arvalid <= mgt_axil.arvalid; + mgt_axil.arready <= auto_axil.arready; + /* read resp channel */ + mgt_axil.rdata <= auto_axil.rdata; + mgt_axil.rresp <= auto_axil.rresp; + mgt_axil.rvalid <= auto_axil.rvalid; + auto_axil.rready <= mgt_axil.rready; + + if (auto_rst3) begin + auto_connect_state = ST_RESET; + stat_auto_config_done_bclk <= 1'b0; + end else begin + case (auto_connect_state) + + ST_RESET: begin + stat_auto_config_done_bclk <= 1'b0; + if (!mgt_axil_in_progress && auto_enable) begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_connect_state <= ST_WR_CONFIGURATION_TX_REG1_IDLE; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_REG1_IDLE: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // start transmitting alignment pattern + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_ctl_enable] <= 0; + auto_axil.wdata[ctl_tx_ctl_tx_send_idle] <= 0; + auto_axil.wdata[ctl_tx_ctl_tx_send_lfi] <= 0; + auto_axil.wdata[ctl_tx_ctl_tx_send_rfi] <= 1; + auto_axil.wdata[ctl_tx_ctl_test_pattern] <= 0; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_REG1; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_RSFEC_CONFIG_INDICATION_CORRECTION; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_RSFEC_CONFIG_INDICATION_CORRECTION: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // configure fec + auto_axil.wdata <= 0; + auto_axil.wdata[rs_fec_in_ctl_rx_rsfec_enable_correction] <= 1; + auto_axil.wdata[rs_fec_in_ctl_rx_rsfec_enable_indication] <= 1; + auto_axil.wdata[rs_fec_in_ctl_rsfec_ieee_error_indication_mode] <= 1; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= RSFEC_CONFIG_INDICATION_CORRECTION; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_RSFEC_CONFIG_ENABLE; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_RSFEC_CONFIG_ENABLE: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // enable fec + auto_axil.wdata <= 0; + auto_axil.wdata[rs_fec_in_ctl_rx_rsfec_enable] <= 1; + auto_axil.wdata[rs_fec_in_ctl_tx_rsfec_enable] <= 1; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= RSFEC_CONFIG_ENABLE; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_RX_REG1; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_RX_REG1: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_rx_ctl_enable] <= 1; + auto_axil.wdata[ctl_rx_ctl_rx_force_resync] <= 0; + auto_axil.wdata[ctl_rx_ctl_test_pattern] <= 0; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_RX_REG1; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WAIT; + end + end + + ST_WAIT: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // don't drive any writes, but hold off bus + auto_axil.wdata <= 0; + auto_axil.wstrb <= 0; + auto_axil.wvalid <= 0; + auto_axil.awaddr <= 0; + auto_axil.awvalid <= 0; + auto_axil.bready <= 0; + if (stat_rx_aligned_bclk) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_REG1_TX_ENABLE; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_REG1_TX_ENABLE: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // stop transmitting alignment pattern + // and start transmitting data + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_ctl_enable] <= 1; + auto_axil.wdata[ctl_tx_ctl_tx_send_idle] <= 0; + auto_axil.wdata[ctl_tx_ctl_tx_send_lfi] <= 0; + auto_axil.wdata[ctl_tx_ctl_tx_send_rfi] <= 0; + auto_axil.wdata[ctl_tx_ctl_test_pattern] <= 0; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_REG1; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + if (PAUSE_EN) begin + auto_connect_state <= ST_WR_CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG1; + w_req <= 1'b1; + aw_req <= 1'b1; + end else begin + auto_connect_state <= ST_READY; + end + end + end + + ST_WR_CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG1: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + //3DFF - 0011 1101 1111 1111 + auto_axil.wdata[ctl_rx_pause_en+:9] <= '1; + auto_axil.wdata[ctl_rx_enable_gcp] <= 1; + auto_axil.wdata[ctl_rx_enable_pcp] <= 1; + auto_axil.wdata[ctl_rx_enable_gpp] <= 1; + auto_axil.wdata[ctl_rx_enable_ppp] <= 1; + //ctl_rx_pause_ack = 23; // 8 bits NOT SET + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG1; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG2; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG2: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + //1C631 - 0001 1100 0110 0011 0001 + auto_axil.wdata[ctl_rx_check_mcast_gcp ] <= 1; //1 + auto_axil.wdata[ctl_rx_check_ucast_gcp ] <= 0; + auto_axil.wdata[ctl_rx_check_sa_gcp ] <= 0; + auto_axil.wdata[ctl_rx_check_etype_gcp ] <= 0; + auto_axil.wdata[ctl_rx_check_opcode_gcp] <= 1; //1 + auto_axil.wdata[ctl_rx_check_mcast_pcp ] <= 1; //1 + auto_axil.wdata[ctl_rx_check_ucast_pcp ] <= 0; + auto_axil.wdata[ctl_rx_check_sa_pcp ] <= 0; + auto_axil.wdata[ctl_rx_check_etype_pcp ] <= 0; + auto_axil.wdata[ctl_rx_check_opcode_pcp] <= 1; //1 + auto_axil.wdata[ctl_rx_check_mcast_gpp ] <= 1; //1 + auto_axil.wdata[ctl_rx_check_ucast_gpp ] <= 0; + auto_axil.wdata[ctl_rx_check_sa_gpp ] <= 0; + auto_axil.wdata[ctl_rx_check_etype_gpp ] <= 0; + auto_axil.wdata[ctl_rx_check_opcode_gpp] <= 1; //1 + auto_axil.wdata[ctl_rx_check_opcode_ppp] <= 1; //1 + auto_axil.wdata[ctl_rx_check_mcast_ppp ] <= 1; //1 + auto_axil.wdata[ctl_rx_check_ucast_ppp ] <= 0; + auto_axil.wdata[ctl_rx_check_sa_ppp ] <= 0; + auto_axil.wdata[ctl_rx_check_etype_ppp ] <= 0; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_RX_FLOW_CONTROL_CONTROL_REG2; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_quanta0+:16] <= PAUSE_QUANTA; + auto_axil.wdata[ctl_tx_pause_quanta1+:16] <= PAUSE_QUANTA; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG2; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG2: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_quanta2+:16] <= PAUSE_QUANTA; + auto_axil.wdata[ctl_tx_pause_quanta3+:16] <= PAUSE_QUANTA; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG2; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG3; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG3: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_quanta4+:16] <= PAUSE_QUANTA; + auto_axil.wdata[ctl_tx_pause_quanta5+:16] <= PAUSE_QUANTA; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG3; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG4; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG4: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_quanta6+:16] <= PAUSE_QUANTA; + auto_axil.wdata[ctl_tx_pause_quanta7+:16] <= PAUSE_QUANTA; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG4; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG5; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG5: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_quanta8+:16] <= PAUSE_QUANTA; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG5; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_refresh_timer0+:16] <= PAUSE_REFRESH; + auto_axil.wdata[ctl_tx_pause_refresh_timer1+:16] <= PAUSE_REFRESH; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG2; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG2: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_refresh_timer2+:16] <= PAUSE_REFRESH; + auto_axil.wdata[ctl_tx_pause_refresh_timer3+:16] <= PAUSE_REFRESH; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG2; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG3; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG3: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_refresh_timer4+:16] <= PAUSE_REFRESH; + auto_axil.wdata[ctl_tx_pause_refresh_timer5+:16] <= PAUSE_REFRESH; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG3; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG4; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG4: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_refresh_timer6+:16] <= PAUSE_REFRESH; + auto_axil.wdata[ctl_tx_pause_refresh_timer7+:16] <= PAUSE_REFRESH; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG4; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG5; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG5: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + auto_axil.wdata[ctl_tx_pause_refresh_timer8+:16] <= PAUSE_REFRESH; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG5; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_WR_CONFIGURATION_TX_FLOW_CONTROL_CONTROL_REG1; + w_req <= 1'b1; + aw_req <= 1'b1; + end + end + + ST_WR_CONFIGURATION_TX_FLOW_CONTROL_CONTROL_REG1: begin + mgt_axil.awready <= 0; + mgt_axil.wready <= 0; + mgt_axil.arready <= 0; + auto_axil.arvalid <= 0; + // turn on RX interface + auto_axil.wdata <= 0; + // 1FF + auto_axil.wdata[ctl_tx_pause_enable+:9] <= '1; + auto_axil.wstrb <= '1; + auto_axil.wvalid <= w_req; + auto_axil.awaddr <= CONFIGURATION_TX_FLOW_CONTROL_CONTROL_REG1; + auto_axil.awvalid <= aw_req; + auto_axil.bready <= 1'b1; + if (auto_axil.wready) begin + auto_axil.wvalid <= 1'b0; + w_req <= 1'b0; + end + if (auto_axil.awready) begin + auto_axil.awvalid <= 1'b0; + aw_req <= 1'b0; + end + if (auto_axil.bvalid) begin + auto_connect_state <= ST_READY; + end + end + + ST_READY: begin + stat_auto_config_done_bclk <= 1'b1; + if (!stat_rx_aligned_bclk) begin + auto_connect_state <= ST_RESET; + end + end + + endcase + end + end + end + + always_comb begin + `AXI4LITE_ASSIGN(mgt_axil_v,auto_axil) + + // window address to 0x0000-x1FFF + mgt_axil_v.araddr = 0; + mgt_axil_v.araddr[12:0] = auto_axil.araddr[12:0]; + // window address to 0x0000-x1FFF + mgt_axil_v.awaddr = 0; + mgt_axil_v.awaddr[12:0] = auto_axil.awaddr[12:0]; + end + + end else begin : no_auto_connect + + always_comb begin + `AXI4LITE_ASSIGN(mgt_axil_v,mgt_axil) + + // window address to 0x0000-x1FFF + mgt_axil_v.araddr = 0; + mgt_axil_v.araddr[12:0] = mgt_axil.araddr[12:0]; + // window address to 0x0000-x1FFF + mgt_axil_v.awaddr = 0; + mgt_axil_v.awaddr[12:0] = mgt_axil.awaddr[12:0]; + + stat_auto_config_done_bclk = 1'b1; + end + end + + import PkgEth100gLbus::*; + + + lbus_t lbus_rx [3:0]; + lbus_t lbus_tx [3:0]; + logic lbus_tx_rdyout; + + eth_100g_lbus2axi #(.NUM_SEG(4)) lbus2axi ( + .axis(eth100g_rx), + .lbus_in(lbus_rx) + ); + + eth_100g_axi2lbus #(.NUM_SEG(4)) axi2lbus ( + .axis(eth100g_tx), + .lbus_rdy(lbus_tx_rdyout), + .lbus_out(lbus_tx) + ); + + eth_100g_bd eth_100g_bd_i ( + .refclk_clk_n(refclk_n), + .refclk_clk_p(refclk_p), + .gt_rx_gt_port_0_n(rx_n[0]), + .gt_rx_gt_port_0_p(rx_p[0]), + .gt_rx_gt_port_1_n(rx_n[1]), + .gt_rx_gt_port_1_p(rx_p[1]), + .gt_rx_gt_port_2_n(rx_n[2]), + .gt_rx_gt_port_2_p(rx_p[2]), + .gt_rx_gt_port_3_n(rx_n[3]), + .gt_rx_gt_port_3_p(rx_p[3]), + .init_clk(clk100), + .gt_tx_gt_port_0_n(tx_n[0]), + .gt_tx_gt_port_0_p(tx_p[0]), + .gt_tx_gt_port_1_n(tx_n[1]), + .gt_tx_gt_port_1_p(tx_p[1]), + .gt_tx_gt_port_2_n(tx_n[2]), + .gt_tx_gt_port_2_p(tx_p[2]), + .gt_tx_gt_port_3_n(tx_n[3]), + .gt_tx_gt_port_3_p(tx_p[3]), + .sys_reset(areset), + .usr_rx_reset(usr_rx_reset), + .usr_tx_reset(usr_tx_reset), + .gt_txusrclk2(mgt_clk), + .rx_clk(mgt_clk), //feedback in + .tx_ovfout(tx_ovfout), + .tx_unfout(tx_unfout), + .stat_rx_aligned(stat_rx_aligned), + .drp_clk(clk100), + .core_drp_daddr(10'b0), + .core_drp_den(1'b0), + .core_drp_di(16'b0), + .core_drp_do(), + .core_drp_drdy(), + .core_drp_dwe(1'b0), + `AXI4LITE_PORT_ASSIGN(s_axi,mgt_axil_v) + .pm_tick(pm_tick), + .ctl_tx_pause_req(ctl_tx_pause_req), + .ctl_tx_resend_pause(ctl_tx_resend_pause), + .stat_rx_pause_req(stat_rx_pause_req), + .eth100g_rx_lbus_seg0_data(lbus_rx[0].data), + .eth100g_rx_lbus_seg0_ena(lbus_rx[0].ena), + .eth100g_rx_lbus_seg0_eop(lbus_rx[0].eop), + .eth100g_rx_lbus_seg0_err(lbus_rx[0].err), + .eth100g_rx_lbus_seg0_mty(lbus_rx[0].mty), + .eth100g_rx_lbus_seg0_sop(lbus_rx[0].sop), + .eth100g_rx_lbus_seg1_data(lbus_rx[1].data), + .eth100g_rx_lbus_seg1_ena(lbus_rx[1].ena), + .eth100g_rx_lbus_seg1_eop(lbus_rx[1].eop), + .eth100g_rx_lbus_seg1_err(lbus_rx[1].err), + .eth100g_rx_lbus_seg1_mty(lbus_rx[1].mty), + .eth100g_rx_lbus_seg1_sop(lbus_rx[1].sop), + .eth100g_rx_lbus_seg2_data(lbus_rx[2].data), + .eth100g_rx_lbus_seg2_ena(lbus_rx[2].ena), + .eth100g_rx_lbus_seg2_eop(lbus_rx[2].eop), + .eth100g_rx_lbus_seg2_err(lbus_rx[2].err), + .eth100g_rx_lbus_seg2_mty(lbus_rx[2].mty), + .eth100g_rx_lbus_seg2_sop(lbus_rx[2].sop), + .eth100g_rx_lbus_seg3_data(lbus_rx[3].data), + .eth100g_rx_lbus_seg3_ena(lbus_rx[3].ena), + .eth100g_rx_lbus_seg3_eop(lbus_rx[3].eop), + .eth100g_rx_lbus_seg3_err(lbus_rx[3].err), + .eth100g_rx_lbus_seg3_mty(lbus_rx[3].mty), + .eth100g_rx_lbus_seg3_sop(lbus_rx[3].sop), + .eth100g_tx_lbus_seg0_data(lbus_tx[0].data), + .eth100g_tx_lbus_seg0_ena(lbus_tx[0].ena), + .eth100g_tx_lbus_seg0_eop(lbus_tx[0].eop), + .eth100g_tx_lbus_seg0_err(lbus_tx[0].err), + .eth100g_tx_lbus_seg0_mty(lbus_tx[0].mty), + .eth100g_tx_lbus_seg0_sop(lbus_tx[0].sop), + .eth100g_tx_lbus_seg1_data(lbus_tx[1].data), + .eth100g_tx_lbus_seg1_ena(lbus_tx[1].ena), + .eth100g_tx_lbus_seg1_eop(lbus_tx[1].eop), + .eth100g_tx_lbus_seg1_err(lbus_tx[1].err), + .eth100g_tx_lbus_seg1_mty(lbus_tx[1].mty), + .eth100g_tx_lbus_seg1_sop(lbus_tx[1].sop), + .eth100g_tx_lbus_seg2_data(lbus_tx[2].data), + .eth100g_tx_lbus_seg2_ena(lbus_tx[2].ena), + .eth100g_tx_lbus_seg2_eop(lbus_tx[2].eop), + .eth100g_tx_lbus_seg2_err(lbus_tx[2].err), + .eth100g_tx_lbus_seg2_mty(lbus_tx[2].mty), + .eth100g_tx_lbus_seg2_sop(lbus_tx[2].sop), + .eth100g_tx_lbus_seg3_data(lbus_tx[3].data), + .eth100g_tx_lbus_seg3_ena(lbus_tx[3].ena), + .eth100g_tx_lbus_seg3_eop(lbus_tx[3].eop), + .eth100g_tx_lbus_seg3_err(lbus_tx[3].err), + .eth100g_tx_lbus_seg3_mty(lbus_tx[3].mty), + .eth100g_tx_lbus_seg3_sop(lbus_tx[3].sop), + .eth100g_tx_tx_rdyout(lbus_tx_rdyout)); + +endmodule diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_axis2lbus.sv b/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_axis2lbus.sv new file mode 100644 index 000000000..a3c319043 --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_axis2lbus.sv @@ -0,0 +1,120 @@ +// +// Copyright 2021 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: eth_100g_axi2lbus +// +// Description: +// Translate from AXI4S (Xilinx segmented ifc) to lbus. +// +// Built using example provided from Xilinx +// +// Parameters: +// - FIFO_DEPTH - FIFO will be 2** deep +// - NUM_SEG - Number of lbus segments coming in + +import PkgEth100gLbus::*; + +module eth_100g_axi2lbus #( + parameter FIFO_DEPTH = 5, + parameter NUM_SEG = 4 +) +( + + // AXIS IF + AxiStreamIf.slave axis, + + // Lbus Segments + + input logic lbus_rdy, + output lbus_t lbus_out [NUM_SEG-1:0] + +); + + localparam SEG_DATA_WIDTH = DATA_WIDTH/NUM_SEG; + localparam SEG_BYTES = SEG_DATA_WIDTH/8; + localparam SEG_MTY_WIDTH = $clog2(SEG_BYTES); + + // post rotation lbus signals + lbus_t lbus_d [NUM_SEG-1:0]; + + // Find last so we can find SOP + logic found_last; + + // Propagate ready when asserting , propagate delayed ready while deasserting + logic axis_tready_i; + assign axis.tready = axis_tready_i | lbus_rdy; + + always @(posedge axis.clk) begin + axis_tready_i <= lbus_rdy; + end + + //declare a segment width axis bus so I can use it's methods + AxiStreamIf #(.DATA_WIDTH(SEG_DATA_WIDTH),.USER_WIDTH($clog2(SEG_BYTES))) + seg_axi (axis.clk, axis.rst); + assign seg_axi.tlast = 1'b1; + + logic [NUM_SEG:0] valid; + assign valid[NUM_SEG] = 1'b0; + + genvar b; + genvar s; + generate begin : lbus_gen + for (s=0; s < NUM_SEG; s=s+1) begin : segment_loop + // Reverse data byte ordering on each segment + for (b = 0; b < DATA_WIDTH/32; b=b+1) begin : byte_loop + assign lbus_d[s].data[b*8 +: 8] = axis.tdata[((s+1)*(DATA_WIDTH/NUM_SEG)-8-(b*8)) +: 8]; + end : byte_loop + // valid if tkeep is set for any bytes in the segment + assign valid[s] = (| axis.tkeep[s*SEG_BYTES +: SEG_BYTES]) & axis.tvalid; + // enable when valid and transfering + assign lbus_d[s].ena = valid[s] & axis.tready; + // eop on last valid byte if last is set + // we init an extra valid bit to 0 so if all valid bits for all segments are set we trigger an eop on the final segment + assign lbus_d[s].eop = (valid[s] ^ valid[s+1]) & axis.tlast; + // set error on all segmetns if tuser is set + assign lbus_d[s].err = valid[s] & axis.tuser; + // translate keep to trailing bytes and invert sign + always_comb begin + if (lbus_d[s].eop) begin + lbus_d[s].mty = SEG_BYTES - seg_axi.keep2trailing(axis.tkeep[s*SEG_BYTES+: SEG_BYTES]); + end else begin + lbus_d[s].mty = 'b0; + end + end + //SOP can only occur on segment 0, so init all the bits to zero, then assign segment 0 + if (s==0) begin + assign lbus_d[s].sop = found_last & axis.tvalid & axis.tready; + end else begin + assign lbus_d[s].sop = 1'b0; + end + // assign the output DFF's + always_ff @(posedge axis.clk) begin + lbus_out[s].data <= lbus_d[s].data; + lbus_out[s].ena <= lbus_d[s].ena; + lbus_out[s].sop <= lbus_d[s].sop; + lbus_out[s].eop <= lbus_d[s].eop; + lbus_out[s].err <= lbus_d[s].err; + lbus_out[s].mty <= lbus_d[s].mty; + end + + end : segment_loop + end : lbus_gen + endgenerate + + + + // SOP Statemachine + always_ff @(posedge axis.clk) begin : sop_sm + if(axis.rst) begin + found_last <= 1'b1; + end else begin + if(axis.tvalid & axis.tlast & axis.tready) found_last <= 1'b1; + else if(axis.tvalid & axis.tready) found_last <= 1'b0; + end + end : sop_sm + +endmodule + + diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_bd.tcl b/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_bd.tcl new file mode 100644 index 000000000..624e0902e --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_bd.tcl @@ -0,0 +1,361 @@ + +################################################################ +# This is a generated script based on design: eth_100g_bd +# +# Though there are limitations about the generated script, +# the main purpose of this utility is to make learning +# IP Integrator Tcl commands easier. +################################################################ + +namespace eval _tcl { +proc get_script_folder {} { + set script_path [file normalize [info script]] + set script_folder [file dirname $script_path] + return $script_folder +} +} +variable script_folder +set script_folder [_tcl::get_script_folder] + +################################################################ +# Check if script is running in correct Vivado version. +################################################################ +set scripts_vivado_version 2019.1 +set current_vivado_version [version -short] + +if { [string first $scripts_vivado_version $current_vivado_version] == -1 } { + puts "" + catch {common::send_msg_id "BD_TCL-109" "ERROR" "This script was generated using Vivado <$scripts_vivado_version> and is being run in <$current_vivado_version> of Vivado. Please run the script in Vivado <$scripts_vivado_version> then open the design in Vivado <$current_vivado_version>. Upgrade the design by running \"Tools => Report => Report IP Status...\", then run write_bd_tcl to create an updated script."} + + return 1 +} + +################################################################ +# START +################################################################ + +# To test this script, run the following commands from Vivado Tcl console: +# source eth_100g_bd_script.tcl + +# If there is no project opened, this script will create a +# project, but make sure you do not have an existing project +# <./myproj/project_1.xpr> in the current working folder. + +set list_projs [get_projects -quiet] +if { $list_projs eq "" } { + create_project project_1 myproj -part xczu28dr-ffvg1517-1-e +} + + +# CHANGE DESIGN NAME HERE +variable design_name +set design_name eth_100g_bd + +# If you do not already have an existing IP Integrator design open, +# you can create a design using the following command: +# create_bd_design $design_name + +# Creating design if needed +set errMsg "" +set nRet 0 + +set cur_design [current_bd_design -quiet] +set list_cells [get_bd_cells -quiet] + +if { ${design_name} eq "" } { + # USE CASES: + # 1) Design_name not set + + set errMsg "Please set the variable <design_name> to a non-empty value." + set nRet 1 + +} elseif { ${cur_design} ne "" && ${list_cells} eq "" } { + # USE CASES: + # 2): Current design opened AND is empty AND names same. + # 3): Current design opened AND is empty AND names diff; design_name NOT in project. + # 4): Current design opened AND is empty AND names diff; design_name exists in project. + + if { $cur_design ne $design_name } { + common::send_msg_id "BD_TCL-001" "INFO" "Changing value of <design_name> from <$design_name> to <$cur_design> since current design is empty." + set design_name [get_property NAME $cur_design] + } + common::send_msg_id "BD_TCL-002" "INFO" "Constructing design in IPI design <$cur_design>..." + +} elseif { ${cur_design} ne "" && $list_cells ne "" && $cur_design eq $design_name } { + # USE CASES: + # 5) Current design opened AND has components AND same names. + + set errMsg "Design <$design_name> already exists in your project, please set the variable <design_name> to another value." + set nRet 1 +} elseif { [get_files -quiet ${design_name}.bd] ne "" } { + # USE CASES: + # 6) Current opened design, has components, but diff names, design_name exists in project. + # 7) No opened design, design_name exists in project. + + set errMsg "Design <$design_name> already exists in your project, please set the variable <design_name> to another value." + set nRet 2 + +} else { + # USE CASES: + # 8) No opened design, design_name not in project. + # 9) Current opened design, has components, but diff names, design_name not in project. + + common::send_msg_id "BD_TCL-003" "INFO" "Currently there is no design <$design_name> in project, so creating one..." + + create_bd_design $design_name + + common::send_msg_id "BD_TCL-004" "INFO" "Making design <$design_name> as current_bd_design." + current_bd_design $design_name + +} + +common::send_msg_id "BD_TCL-005" "INFO" "Currently the variable <design_name> is equal to \"$design_name\"." + +if { $nRet != 0 } { + catch {common::send_msg_id "BD_TCL-114" "ERROR" $errMsg} + return $nRet +} + +set bCheckIPsPassed 1 +################################################################## +# CHECK IPs +################################################################## +set bCheckIPs 1 +if { $bCheckIPs == 1 } { + set list_check_ips "\ +xilinx.com:ip:cmac_usplus:2.6\ +xilinx.com:ip:xlconstant:1.1\ +" + + set list_ips_missing "" + common::send_msg_id "BD_TCL-006" "INFO" "Checking if the following IPs exist in the project's IP catalog: $list_check_ips ." + + foreach ip_vlnv $list_check_ips { + set ip_obj [get_ipdefs -all $ip_vlnv] + if { $ip_obj eq "" } { + lappend list_ips_missing $ip_vlnv + } + } + + if { $list_ips_missing ne "" } { + catch {common::send_msg_id "BD_TCL-115" "ERROR" "The following IPs are not found in the IP Catalog:\n $list_ips_missing\n\nResolution: Please add the repository containing the IP(s) to the project." } + set bCheckIPsPassed 0 + } + +} + +if { $bCheckIPsPassed != 1 } { + common::send_msg_id "BD_TCL-1003" "WARNING" "Will not continue with creation of design due to the error(s) above." + return 3 +} + +################################################################## +# DESIGN PROCs +################################################################## + + + +# Procedure to create entire design; Provide argument to make +# procedure reusable. If parentCell is "", will use root. +proc create_root_design { parentCell } { + + variable script_folder + variable design_name + + if { $parentCell eq "" } { + set parentCell [get_bd_cells /] + } + + # Get object for parentCell + set parentObj [get_bd_cells $parentCell] + if { $parentObj == "" } { + catch {common::send_msg_id "BD_TCL-100" "ERROR" "Unable to find parent cell <$parentCell>!"} + return + } + + # Make sure parentObj is hier blk + set parentType [get_property TYPE $parentObj] + if { $parentType ne "hier" } { + catch {common::send_msg_id "BD_TCL-101" "ERROR" "Parent <$parentObj> has TYPE = <$parentType>. Expected to be <hier>."} + return + } + + # Save current instance; Restore later + set oldCurInst [current_bd_instance .] + + # Set parent object as current + current_bd_instance $parentObj + + + # Create interface ports + set core_drp [ create_bd_intf_port -mode Slave -vlnv xilinx.com:interface:drp_rtl:1.0 core_drp ] + + set eth100g_rx [ create_bd_intf_port -mode Master -vlnv xilinx.com:display_cmac_usplus:lbus_ports:2.0 eth100g_rx ] + + set eth100g_tx [ create_bd_intf_port -mode Slave -vlnv xilinx.com:display_cmac_usplus:lbus_ports:2.0 eth100g_tx ] + + set gt_rx [ create_bd_intf_port -mode Slave -vlnv xilinx.com:display_cmac_usplus:gt_ports:2.0 gt_rx ] + + set gt_tx [ create_bd_intf_port -mode Master -vlnv xilinx.com:display_cmac_usplus:gt_ports:2.0 gt_tx ] + + set refclk [ create_bd_intf_port -mode Slave -vlnv xilinx.com:interface:diff_clock_rtl:1.0 refclk ] + set_property -dict [ list \ + CONFIG.FREQ_HZ {156250000} \ + ] $refclk + + set s_axi [ create_bd_intf_port -mode Slave -vlnv xilinx.com:interface:aximm_rtl:1.0 s_axi ] + set_property -dict [ list \ + CONFIG.ADDR_WIDTH {32} \ + CONFIG.ARUSER_WIDTH {0} \ + CONFIG.AWUSER_WIDTH {0} \ + CONFIG.BUSER_WIDTH {0} \ + CONFIG.DATA_WIDTH {32} \ + CONFIG.HAS_BRESP {1} \ + CONFIG.HAS_BURST {0} \ + CONFIG.HAS_CACHE {0} \ + CONFIG.HAS_LOCK {0} \ + CONFIG.HAS_PROT {0} \ + CONFIG.HAS_QOS {0} \ + CONFIG.HAS_REGION {0} \ + CONFIG.HAS_RRESP {1} \ + CONFIG.HAS_WSTRB {1} \ + CONFIG.ID_WIDTH {0} \ + CONFIG.MAX_BURST_LENGTH {1} \ + CONFIG.NUM_READ_OUTSTANDING {1} \ + CONFIG.NUM_READ_THREADS {1} \ + CONFIG.NUM_WRITE_OUTSTANDING {1} \ + CONFIG.NUM_WRITE_THREADS {1} \ + CONFIG.PROTOCOL {AXI4LITE} \ + CONFIG.READ_WRITE_MODE {READ_WRITE} \ + CONFIG.RUSER_BITS_PER_BYTE {0} \ + CONFIG.RUSER_WIDTH {0} \ + CONFIG.SUPPORTS_NARROW_BURST {0} \ + CONFIG.WUSER_BITS_PER_BYTE {0} \ + CONFIG.WUSER_WIDTH {0} \ + ] $s_axi + + + # Create ports + set ctl_tx_pause_req [ create_bd_port -dir I -from 8 -to 0 ctl_tx_pause_req ] + set ctl_tx_resend_pause [ create_bd_port -dir I ctl_tx_resend_pause ] + set drp_clk [ create_bd_port -dir I -type clk drp_clk ] + set gt_txusrclk2 [ create_bd_port -dir O -type clk gt_txusrclk2 ] + set_property -dict [ list \ + CONFIG.ASSOCIATED_BUSIF {eth100g_rx:eth100g_tx} \ + CONFIG.FREQ_HZ {322265625} \ + ] $gt_txusrclk2 + set init_clk [ create_bd_port -dir I -type clk init_clk ] + set pm_tick [ create_bd_port -dir I pm_tick ] + set rx_clk [ create_bd_port -dir I -type clk rx_clk ] + set_property -dict [ list \ + CONFIG.FREQ_HZ {322265625} \ + ] $rx_clk + set s_axi_aclk [ create_bd_port -dir I -type clk s_axi_aclk ] + set s_axi_sreset [ create_bd_port -dir I -type rst s_axi_sreset ] + set_property -dict [ list \ + CONFIG.POLARITY {ACTIVE_HIGH} \ + ] $s_axi_sreset + set stat_rx_aligned [ create_bd_port -dir O stat_rx_aligned ] + set stat_rx_pause_req [ create_bd_port -dir O -from 8 -to 0 stat_rx_pause_req ] + set sys_reset [ create_bd_port -dir I -type rst sys_reset ] + set_property -dict [ list \ + CONFIG.POLARITY {ACTIVE_HIGH} \ + ] $sys_reset + set tx_ovfout [ create_bd_port -dir O tx_ovfout ] + set tx_unfout [ create_bd_port -dir O tx_unfout ] + set usr_rx_reset [ create_bd_port -dir O -type rst usr_rx_reset ] + set usr_tx_reset [ create_bd_port -dir O -type rst usr_tx_reset ] + + # Create instance: cmac_usplus_0, and set properties + set cmac_usplus_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:cmac_usplus:2.6 cmac_usplus_0 ] + set_property -dict [ list \ + CONFIG.CMAC_CAUI4_MODE {1} \ + CONFIG.CMAC_CORE_SELECT {CMACE4_X0Y0} \ + CONFIG.ENABLE_AXI_INTERFACE {1} \ + CONFIG.GT_DRP_CLK {100} \ + CONFIG.GT_GROUP_SELECT {X0Y4~X0Y7} \ + CONFIG.GT_REF_CLK_FREQ {156.25} \ + CONFIG.INCLUDE_AUTO_NEG_LT_LOGIC {0} \ + CONFIG.INCLUDE_RS_FEC {1} \ + CONFIG.INCLUDE_SHARED_LOGIC {2} \ + CONFIG.INCLUDE_STATISTICS_COUNTERS {1} \ + CONFIG.LANE10_GT_LOC {NA} \ + CONFIG.LANE1_GT_LOC {X0Y4} \ + CONFIG.LANE2_GT_LOC {X0Y5} \ + CONFIG.LANE3_GT_LOC {X0Y6} \ + CONFIG.LANE4_GT_LOC {X0Y7} \ + CONFIG.LANE5_GT_LOC {NA} \ + CONFIG.LANE6_GT_LOC {NA} \ + CONFIG.LANE7_GT_LOC {NA} \ + CONFIG.LANE8_GT_LOC {NA} \ + CONFIG.LANE9_GT_LOC {NA} \ + CONFIG.NUM_LANES {4} \ + CONFIG.RX_CHECK_ACK {0} \ + CONFIG.RX_EQ_MODE {AUTO} \ + CONFIG.RX_FLOW_CONTROL {1} \ + CONFIG.TX_FLOW_CONTROL {1} \ + CONFIG.USER_INTERFACE {LBUS} \ + ] $cmac_usplus_0 + + # Create instance: tie_loopback, and set properties + set tie_loopback [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconstant:1.1 tie_loopback ] + set_property -dict [ list \ + CONFIG.CONST_VAL {0} \ + CONFIG.CONST_WIDTH {12} \ + ] $tie_loopback + + # Create instance: tie_zero, and set properties + set tie_zero [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconstant:1.1 tie_zero ] + set_property -dict [ list \ + CONFIG.CONST_VAL {0} \ + ] $tie_zero + + # Create interface connections + connect_bd_intf_net -intf_net RefClk_1 [get_bd_intf_ports refclk] [get_bd_intf_pins cmac_usplus_0/gt_ref_clk] + connect_bd_intf_net -intf_net Rx_1 [get_bd_intf_ports gt_rx] [get_bd_intf_pins cmac_usplus_0/gt_rx] + connect_bd_intf_net -intf_net cmac_usplus_0_gt_tx [get_bd_intf_ports gt_tx] [get_bd_intf_pins cmac_usplus_0/gt_tx] + connect_bd_intf_net -intf_net cmac_usplus_0_lbus_rx [get_bd_intf_ports eth100g_rx] [get_bd_intf_pins cmac_usplus_0/lbus_rx] + connect_bd_intf_net -intf_net eth_100g_tx_1 [get_bd_intf_ports eth100g_tx] [get_bd_intf_pins cmac_usplus_0/lbus_tx] + connect_bd_intf_net -intf_net sDrp_1 [get_bd_intf_ports core_drp] [get_bd_intf_pins cmac_usplus_0/core_drp] + connect_bd_intf_net -intf_net s_axi_1 [get_bd_intf_ports s_axi] [get_bd_intf_pins cmac_usplus_0/s_axi] + + # Create port connections + connect_bd_net -net SysClk_1 [get_bd_ports init_clk] [get_bd_pins cmac_usplus_0/init_clk] + connect_bd_net -net aResetIn_1 [get_bd_ports sys_reset] [get_bd_pins cmac_usplus_0/sys_reset] + connect_bd_net -net cmac_usplus_0_gt_txusrclk2 [get_bd_ports gt_txusrclk2] [get_bd_pins cmac_usplus_0/gt_txusrclk2] + connect_bd_net -net cmac_usplus_0_stat_rx_aligned [get_bd_ports stat_rx_aligned] [get_bd_pins cmac_usplus_0/stat_rx_aligned] + connect_bd_net -net cmac_usplus_0_stat_rx_pause_req [get_bd_ports stat_rx_pause_req] [get_bd_pins cmac_usplus_0/stat_rx_pause_req] + connect_bd_net -net cmac_usplus_0_tx_ovfout [get_bd_ports tx_ovfout] [get_bd_pins cmac_usplus_0/tx_ovfout] + connect_bd_net -net cmac_usplus_0_tx_unfout [get_bd_ports tx_unfout] [get_bd_pins cmac_usplus_0/tx_unfout] + connect_bd_net -net cmac_usplus_0_usr_rx_reset [get_bd_ports usr_rx_reset] [get_bd_pins cmac_usplus_0/usr_rx_reset] + connect_bd_net -net cmac_usplus_0_usr_tx_reset [get_bd_ports usr_tx_reset] [get_bd_pins cmac_usplus_0/usr_tx_reset] + connect_bd_net -net ctl_tx_pause_req_1 [get_bd_ports ctl_tx_pause_req] [get_bd_pins cmac_usplus_0/ctl_tx_pause_req] + connect_bd_net -net ctl_tx_resend_pause_1 [get_bd_ports ctl_tx_resend_pause] [get_bd_pins cmac_usplus_0/ctl_tx_resend_pause] + connect_bd_net -net drp_clk_1 [get_bd_ports drp_clk] [get_bd_pins cmac_usplus_0/drp_clk] + connect_bd_net -net pm_tick_1 [get_bd_ports pm_tick] [get_bd_pins cmac_usplus_0/pm_tick] + connect_bd_net -net rx_clk_1 [get_bd_ports rx_clk] [get_bd_pins cmac_usplus_0/rx_clk] + connect_bd_net -net s_axi_aclk_1 [get_bd_ports s_axi_aclk] [get_bd_pins cmac_usplus_0/s_axi_aclk] + connect_bd_net -net s_axi_sreset_1 [get_bd_ports s_axi_sreset] [get_bd_pins cmac_usplus_0/s_axi_sreset] + connect_bd_net -net tie_loopback_dout [get_bd_pins cmac_usplus_0/gt_loopback_in] [get_bd_pins tie_loopback/dout] + connect_bd_net -net tie_zero_dout [get_bd_pins cmac_usplus_0/gtwiz_reset_rx_datapath] [get_bd_pins cmac_usplus_0/gtwiz_reset_tx_datapath] [get_bd_pins tie_zero/dout] + + # Create address segments + create_bd_addr_seg -range 0x00002000 -offset 0x00000000 [get_bd_addr_spaces s_axi] [get_bd_addr_segs cmac_usplus_0/s_axi/Reg] SEG_cmac_usplus_0_Reg + + + # Restore current instance + current_bd_instance $oldCurInst + + validate_bd_design + save_bd_design +} +# End of create_root_design() + + +################################################################## +# MAIN FLOW +################################################################## + +create_root_design "" + + diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_lbus2axis.sv b/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_lbus2axis.sv new file mode 100644 index 000000000..0fd1ab42f --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/eth_100g_lbus2axis.sv @@ -0,0 +1,555 @@ +// +// Copyright 2021 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: eth_100g_lbus2axi +// +// Description: +// Translate from lbus (xilinx segmented ifc) to +// AXI4S. +// +// Built using example provided from Xilinx +// +// Parameters: +// - FIFO_DEPTH - FIFO will be 2** deep +// - NUM_SEG - Number of lbus segments coming in +// +// Notes on timing difficulty +// The path back to pop is challenged +// -LBUS is popped out of the FIFO (SRL read can be slow) +// -LBUS is rotated N to 1 Mux (N= number of segments) For 100g N=4 +// -Find where EOP is (search for the first 1) +// -Unrotate the number of words and use that to calculate pop +// +// Fifo Output +// Data starts from the SRL and is indexed by the read pointer +// Data_Valid comes from a comparison on fullness +// Invalid control is forced to zero (necessary for algorithm) +// It's not necessary to force all the data to zero just the control plane. +// +// Fifo output data is rotated (4 to 1) mux then reinterpreted as lbus data +// +// The rotated control signals are analyzed to determine +// no_eop, no_sop, some_empty, no_ena +// +// eop is specifically inspected in a 4in,4out function to find a pseudo +// one hot. this is unrotated along with enable, and combined with +// datavalid to determine the next pop, which controls incrementing of the +// rd_pointer. +// + +import PkgEth100gLbus::*; + +module eth_100g_lbus2axi #( + parameter FIFO_DEPTH = 5, + parameter NUM_SEG = 4 +) +( + + // AXIS IF + AxiStreamIf.master axis, + + // Lbus Segments + input lbus_t lbus_in [NUM_SEG-1:0] + +); + + localparam SEG_BYTES = SEG_DATA_WIDTH/8; + localparam SEG_MTY_WIDTH = $clog2(SEG_BYTES); + localparam SEG_SHMEAR_WIDTH = SEG_DATA_WIDTH + SEG_MTY_WIDTH + 4; + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////// Data Input to FIFO /////////// + ////////////////////////////////////////////////////////////////////////////////// + + lbus_t lbus_fout_p[NUM_SEG-1:0]; //{ena,err,eop,sop,mty,data} + lbus_t lbus_fout[NUM_SEG-1:0]; //{ena,err,eop,sop,mty,data} + //FIFO Logic + logic push; + logic [NUM_SEG-1:0] pop; + + logic [NUM_SEG-1:0] full; + logic [NUM_SEG-1:0] empty; + + // always push the fifo on all lanes + assign push = lbus_in[0].ena; + + // For each lane of incoming data place it into a separate FIFO + generate + genvar b1,gseg1; + begin : gen_seg_fifo + for(gseg1 = 0; gseg1 < NUM_SEG; gseg1=gseg1+1) begin + + ////////////////////////////////////////////////////////////////////////////////// + // INLINE FIFO + ////////////////////////////////////////////////////////////////////////////////// + + // simulation error if we push a full fifo + always_comb begin + if (push) begin + assert (!full[gseg1]) else $error("Pushing full fifo!"); + end + end + + // limit fanout to improve timing + (* max_fanout = 75 *) logic [4:0] a; + + for (b1=0;b1<SEG_DATA_WIDTH;b1=b1+1) begin : gen_srl_data + SRLC32E srl_data( + .Q(lbus_fout_p[gseg1].data[b1]), .Q31(), + .A(a), + .CE(push),.CLK(axis.clk),.D(lbus_in[gseg1].data[b1]) + ); + end + for (b1=0;b1<SEG_MTY_WIDTH;b1=b1+1) begin : gen_srl_mty + SRLC32E srl_mty( + .Q(lbus_fout_p[gseg1].mty[b1]), .Q31(), + .A(a), + .CE(push),.CLK(axis.clk),.D(lbus_in[gseg1].mty[b1]) + ); + end + SRLC32E srl_err( + .Q(lbus_fout_p[gseg1].err), .Q31(), + .A(a), + .CE(push),.CLK(axis.clk),.D(lbus_in[gseg1].err) + ); + + // empty on prebuffer and SRL + logic my_empty; + always @(posedge axis.clk) + begin + if(axis.rst) begin + a <= 0; + my_empty <= 1; + full[gseg1] <= 0; + end else if(pop[gseg1] & ~push) begin + full[gseg1] <= 0; + if(a==0) begin + my_empty <= 1; + end else begin + a <= a - 1; + end + end else if(push & ~pop[gseg1]) begin + my_empty <= 0; + if(~my_empty) begin + a <= a + 1; + end + if(a == 30) begin + full[gseg1] <= 1; + end + end + end + + // FIFO for time sensitive control signals. This creates a separate 31 deep fifo from + // DFF's on just 3 signals. The data signals continue to use an SRL to save space. + // The design bellow is a FIFO followed by a single DFF regsiter that is automatically + // prefilled when the FIFO has data. + logic [4:0] w_ptr,r_ptr,r_ptr_d,fullness; + logic [31:0] ena_mem, sop_mem, eop_mem; + + // Final fifo stage after memory to remove address muxing from timing path + // this adds one clock of latency to empty flag as it will take 2 clocks to propagate + // into fifo. + + //push critical timing signals to final flop. This adds 1 clock of latency on + // the final empty flag, but removes muxing of the memory elements + logic push_dff; + + //using r_ptr_d to avoid extra latency in fullness change + always_comb begin + if (pop[gseg1]) begin + r_ptr_d = r_ptr+1; + end else begin + r_ptr_d = r_ptr; + end + fullness = w_ptr-r_ptr_d; + push_dff = (fullness != 0) & (pop[gseg1] | empty[gseg1]); + end + + // speedier fifo implementation on these three control signals + // THE goal of this complexity is to have the outputs be a direct FF output + // instead of a muxed memory output. + always @(posedge axis.clk) + begin + if(axis.rst) begin + ena_mem <= '0; + sop_mem <= '0; + eop_mem <= '0; + lbus_fout_p[gseg1].ena <= 1'b0; + lbus_fout_p[gseg1].sop <= 1'b0; + lbus_fout_p[gseg1].eop <= 1'b0; + w_ptr <= 0; + r_ptr <= 0; + empty[gseg1] <= 1'b1; + end else begin + if(push) begin + ena_mem[w_ptr] <= lbus_in[gseg1].ena; + sop_mem[w_ptr] <= lbus_in[gseg1].sop; + eop_mem[w_ptr] <= lbus_in[gseg1].eop; + w_ptr <= w_ptr+1; + end + + r_ptr <= r_ptr_d; + + if (push_dff) begin + empty[gseg1] <= 1'b0; + lbus_fout_p[gseg1].ena <= ena_mem[r_ptr_d]; + lbus_fout_p[gseg1].sop <= sop_mem[r_ptr_d]; + lbus_fout_p[gseg1].eop <= eop_mem[r_ptr_d]; + end else if (pop[gseg1]) begin + empty[gseg1] <= 1'b1; + end + end + end + + // clear the enables if this fifo segment is not valid + always_comb begin + //default assignment + lbus_fout[gseg1] = lbus_fout_p[gseg1]; + if (empty[gseg1]) begin + // clear ena,err,eop,sop,mty (But not data - saves fanout!) + lbus_fout[gseg1].ena = 0; + lbus_fout[gseg1].err = 0; + lbus_fout[gseg1].eop = 0; + lbus_fout[gseg1].sop = 0; + lbus_fout[gseg1].mty = '0; + end else begin + // clear bits if the segment isn't enabled + lbus_fout[gseg1].eop = lbus_fout_p[gseg1].eop && lbus_fout_p[gseg1].ena; + lbus_fout[gseg1].sop = lbus_fout_p[gseg1].sop && lbus_fout_p[gseg1].ena; + lbus_fout[gseg1].err = lbus_fout_p[gseg1].err && lbus_fout_p[gseg1].ena; + end + end + + end + end : gen_seg_fifo + endgenerate + + // post rotation lbus signals + lbus_t lbus_rot [NUM_SEG-1:0]; + + // rotated signals as vectors for decision making + logic [NUM_SEG-1:0] ena; + logic [NUM_SEG-1:0] sop; + logic [NUM_SEG-1:0] eop; + logic [NUM_SEG-1:0] rot_ena; + logic [NUM_SEG-1:0] rot_sop; + logic [NUM_SEG-1:0] rot_eop; + logic [NUM_SEG-1:0] rot_empty; + + always_comb begin + foreach (rot_ena[s]) begin + ena[s] = lbus_fout[s].ena; + sop[s] = lbus_fout[s].sop; + eop[s] = lbus_fout[s].eop; + rot_ena[s] = lbus_rot[s].ena; + rot_sop[s] = lbus_rot[s].sop; + rot_eop[s] = lbus_rot[s].eop; + end + end + + + logic [$clog2(NUM_SEG)-1:0] rot; + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////// Generate Decision Information /////////// + ////////////////////////////////////////////////////////////////////////////////// + logic no_sop; + logic no_eop; + logic no_ena; + logic some_empty; + logic send_idle; + + always_comb begin + no_sop = sop == 0; + no_eop = eop == 0; + no_ena = ena == 0; + // check for an empy byte + some_empty = 1'b0; + foreach (ena[seg]) begin : segment_loop + if (ena[seg] == 0) begin + some_empty = 1'b1; + end + end : segment_loop; + end + + always_comb begin + if (no_ena) begin + send_idle = 1'b0; + end else begin + // generally either there is an EOP with some empty segments + // or all empty segments on an unrotated bus. I'm not sure + // what this implies on an rotated bus + send_idle = no_eop & some_empty; + end + end + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////// Calculate Pop /////////// + ////////////////////////////////////////////////////////////////////////////////// + // After rotation figure out how far till eop + + //========================================================================== + // one-hot to thermometer code + // The goal is to find how far down till we reach the first eop + // This represents the bytes we will trasnfer this clock + //========================================================================== + // Xilinx example + // case (in_reqs) + // 4'b1000: onehot2thermo = 4'b1111; + // + // 4'b1100: onehot2thermo = 4'b0111; + // 4'b0100: onehot2thermo = 4'b0111; + // + // 4'b1110: onehot2thermo = 4'b0011; + // 4'b0110: onehot2thermo = 4'b0011; + // 4'b0010: onehot2thermo = 4'b0011; + // + // 4'b1111: onehot2thermo = 4'b0001; + // 4'b0111: onehot2thermo = 4'b0001; + // 4'b0011: onehot2thermo = 4'b0001; + // 4'b0001: onehot2thermo = 4'b0001; + // + // default: onehot2thermo = 4'b0000; + // endcase + logic [NUM_SEG-1:0] rot_xfer_now; + logic [NUM_SEG-1:0] mask [NUM_SEG-1:0]; + logic [NUM_SEG-1:0] m1hot [NUM_SEG-1:0]; + logic [NUM_SEG-1:0] meop [NUM_SEG-1:0]; + logic [NUM_SEG-1:0] match; + + always_comb begin + rot_xfer_now = '0; + foreach (rot_eop[s]) begin + // The function + // XXX1=>0001 + // XX10=>0011 + // X100=>0111 + // 1000=>1111 + // MASK + // 2**(0+1)-1 = 0001 + // 2**(1+1)-1 = 0011 + // 2**(2+1)-1 = 0111 + // 2**(3+1)-1 = 1111 + mask[s] = 2**(s+1)-1; // Constant + // MASK + // 2**0 = 0001 + // 2**1 = 0010 + // 2**2 = 0100 + // 2**3 = 1000 + m1hot[s] = 2**s; // Constant + // Mask valid_eop + meop[s] = rot_eop & mask[s]; + // compare against 1hot + match[s] = meop[s] == m1hot[s]; + if (match[s]) begin + rot_xfer_now = mask[s]; + end + end + end + + // unrotate the values and calculate pop + logic [NUM_SEG-1:0] xfer_now; + logic [NUM_SEG-1:0] filler_seg; + + always_comb begin + if (send_idle) + xfer_now = '0; + else if (no_eop | no_sop) + xfer_now = '1; + else + // rotate left + xfer_now = {rot_xfer_now,rot_xfer_now} >> (NUM_SEG - rot); + end + + // Flush out valid segments with no enable + assign filler_seg = ~ena & ~empty; + + assign pop = (xfer_now | filler_seg) & ~empty; + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////// Calculate Rotate for the next clock /////////// + ////////////////////////////////////////////////////////////////////////////////// + logic [$clog2(NUM_SEG)-1:0] next_rot; + always_comb begin + next_rot = 0; + foreach (rot_empty[s]) begin + if (~rot_empty[s] & lbus_rot[s].sop) begin + next_rot = s; + end + end + end + + always @(posedge axis.clk) + begin + if(axis.rst) begin + rot <= '0; + //no valid data on any segment + end else if( no_ena ) begin + rot <= '0; + // If EOP, but no SoP + end else if( no_sop & ~no_eop & some_empty) begin + rot <= '0; + // If SOP, accumulate rotation to push to seg 0 + end else if( ~no_sop ) begin + rot <= rot+next_rot; + end + end + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////// Rotation of segments from fifo output /////////// + ////////////////////////////////////////////////////////////////////////////////// + generate + genvar b2,gseg2; + begin : rotate_lbus + //perform a bitwise rotation. + for(b2 = 0; b2 < SEG_SHMEAR_WIDTH; b2=b2+1) begin + logic [NUM_SEG-1:0] slice, slice_rotated; + + //copy a horizontal slice across the segments + for(gseg2 = 0; gseg2 < NUM_SEG; gseg2=gseg2+1) begin + assign slice[gseg2] = lbus_fout[gseg2][b2]; + end + + // rotate the slice (should make SEG_SHMEAR_WIDTH copies of NUM_SEG to 1 mux) + assign slice_rotated = {slice,slice} >> rot; //rotate_right + + // Copy slice back to the struct + for(gseg2 = 0; gseg2 < NUM_SEG; gseg2=gseg2+1) begin + assign lbus_rot[gseg2][b2] = slice_rotated[gseg2]; + end + + end + end : rotate_lbus + endgenerate + + always_comb begin : rotate_data_valid + rot_empty = {empty,empty} >> rot; //rotate_right + end : rotate_data_valid + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////// LBUS out DFF ///////////////////// + ////////////////////////////////////////////////////////////////////////////////// + // This pipe stage is mainly to allow suming MTY bits and to add space for + // Vivado to try to pipeline the output + // post rotation lbus signals + lbus_t lbus_out [NUM_SEG-1:0]; + logic [NUM_SEG-1:0] axi_seg_valid; + + always_ff @(posedge axis.clk) + begin + if (axis.rst) begin + foreach (lbus_out[seg]) begin : segment_loop + lbus_out[seg] <= '0; + end + axi_seg_valid <= '0; + end else begin + lbus_out <= lbus_rot; + if (send_idle) + axi_seg_valid <= '0; + else if (no_eop) + axi_seg_valid <= '1; + else + axi_seg_valid <= rot_xfer_now; + end + end + + //////////////////////////////////////////////////////////////////////////// + // Generate AXI + //////////////////////////////////////////////////////////////////////////// + logic [axis.DATA_WIDTH - 1:0] axis_tdata_w; + logic [$clog2(axis.DATA_WIDTH/8) - 1:0] axis_tuser_bytes_w; + logic [axis.DATA_WIDTH/8 - 1:0] axis_tkeep_w; + logic [NUM_SEG-1:0] axis_tlast_w; + logic [NUM_SEG-1:0] axis_tvalid_w; + logic [NUM_SEG-1:0] axis_tuser_err_w; + + always_comb begin : axis_translate + axis_tuser_bytes_w = 'd0; // init to zero before summing + foreach (axis_tvalid_w[seg]) begin : segment_loop + axis_tvalid_w[seg] = lbus_out[seg].ena & axi_seg_valid[seg]; + axis_tlast_w[seg] = lbus_out[seg].eop; + axis_tuser_err_w[seg] = lbus_out[seg].err; + + // sum all the segment mty vectors + if (lbus_out[seg].ena && axi_seg_valid[seg]) begin + axis_tuser_bytes_w += SEG_DATA_WIDTH/8 - lbus_out[seg].mty; + end + + // 512 bit word = 64 bytes = 4 X 128 bit(16 byte) segments + // assign bytes : LbusOrder + // S0 : S0B0..S0B15 + // S1 : S1B0..S1B15 + // S2 : S2B0..S2B15 + // S3 : S3B0..S3B15 + // AXI (swap Endianess on each segment) + // AXI = S3B15..S3B0, S2B15..S2B0, S1B15..S1B0, S0B15..S0B0 + for(int b = 0; b < SEG_BYTES; b=b+1) begin : tdata_loop + // ( 1 * 128 )-8- 0*8) 120+:8 = S0B0 + // ( 1 * 128 )-8- 1*8) 112+:8 = S0B1 + // ... + // ( 1 * 128 )-8-14*8) 8+:8 = S0B14 + // ( 1 * 128 )-8-15*8) 0+:8 = S0B15 + //////////////////////////////////// + // ( 2 * 128 )-8- 0*8) 248+:8 = S1B0 + // ( 2 * 128 )-8- 1*8) 240+:8 = S1B1 + // ... + // ( 2 * 128 )-8-14*8) 136+:8 = S1B14 + // ( 2 * 128 )-8-15*8) 128+:8 = S1B15 + //////////////////////////////////// + // ... + //////////////////////////////////// + // ( 4 * 128 )-8- 0*8) 504+:8 = S3B0 + // ( 4 * 128 )-8- 1*8) 496+:8 = S3B1 + // ... + // ( 4 * 128 )-8-14*8) 136+:8 = S3B14 + // ( 4 * 128 )-8-15*8) 384+:8 = S3B15 + axis_tdata_w[((seg+1)*axis.DATA_WIDTH/NUM_SEG-8-b*8) +: 8] = lbus_out[seg].data[b*8 +: 8]; + end : tdata_loop + end : segment_loop + end : axis_translate + + // convert bytes to keep + always_comb begin + axis_tkeep_w = '1; + if (axis_tlast_w != 0 && axis_tuser_bytes_w != 0) begin + foreach(axis_tkeep_w[b]) begin + axis_tkeep_w[b] = axis_tuser_bytes_w > b; + end + end + end + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////// AXIS output flop ///////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + localparam AXIS_MTY_WIDTH = $clog2(axis.BYTES_PER_WORD); + + always_ff @(posedge axis.clk) + begin + if (axis.rst) begin + axis.tdata <= '0; + axis.tvalid <= 1'b0; + axis.tlast <= 1'b0; + axis.tuser <= '0; + axis.tkeep <= '0; + end else begin + axis.tdata <= axis_tdata_w; + axis.tvalid <= |axis_tvalid_w; + axis.tlast <= |axis_tlast_w; + + if (axis.TKEEP == 1) begin + axis.tkeep <= axis_tkeep_w; + end else begin + axis.tkeep <= 'X; + end + + // trailing bytes in last word + axis.tuser[AXIS_MTY_WIDTH-1:0] <= axis_tuser_bytes_w; + // MSB is error + axis.tuser[AXIS_MTY_WIDTH] <= |axis_tuser_err_w; + end + end + +endmodule diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/Makefile b/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/Makefile new file mode 100644 index 000000000..5ec8b6868 --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/Makefile @@ -0,0 +1,62 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +# Define BASE_DIR to point to the "top" dir +BASE_DIR = $(abspath ../../../../../top) +# Include viv_sim_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/axi4s_sv/Makefile.srcs +include $(BASE_DIR)/../lib/fifo/Makefile.srcs + + +# If you generate the Xilinx CORE with an AXI interface you can find Xilinx's LBUS translators here +#$(abspath ../../../build-ip/xczu28drffvg1517-1e/eth_100g_bd/eth_100g_bd/ip/eth_100g_bd_cmac_usplus_0_0/eth_100g_bd_cmac_usplus_0_0/example_design/eth_100g_bd_cmac_usplus_0_0_lbus2axis_segmented_top.v) \ +#$(abspath ../../../build-ip/xczu28drffvg1517-1e/eth_100g_bd/eth_100g_bd/ip/eth_100g_bd_cmac_usplus_0_0/eth_100g_bd_cmac_usplus_0_0/example_design/eth_100g_bd_cmac_usplus_0_0_axis2lbus_segmented_top.v) \ + +DESIGN_SRCS = $(abspath \ +$(abspath ../eth_100g_axis2lbus.sv) \ +$(abspath ../eth_100g_lbus2axis.sv) \ +$(FIFO_SRCS) \ +$(AXI4S_SV_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +MODELSIM_LIBS += secureip unimacro_ver unisims_ver xilinx_vip xpm fifo_generator_v13_2_4 +MODELSIM_ARGS += glbl -t 1fs +# Define toplevel module +TB_TOP_MODULE ?= lbus_all_tb +SIM_TOP = $(TB_TOP_MODULE) + +SIM_SRCS = \ +$(abspath ../PkgEth100gLbus.sv) \ +$(abspath axi_lbus_tb.sv) \ +$(abspath lbus_axi_tb.sv) \ +$(abspath $(TB_TOP_MODULE).sv) \ +$(VIVADO_PATH)/data/verilog/src/glbl.v \ + +# Suppressing the following worthless reminder. +#* Warning: M:/usrp4-hw/oss-repo/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes.sv(228): (vlog-2583) [SVCHK] - +# Extra checking for conflicts with always_comb and always_latch variables is done at vopt time +SVLOG_ARGS = -suppress 2583 -keep_delta +VLOG_ARGS = -keep_delta + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/axi_lbus_tb.sv b/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/axi_lbus_tb.sv new file mode 100644 index 000000000..cfde2d5aa --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/axi_lbus_tb.sv @@ -0,0 +1,285 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_lbus_tb +// +// Description: +// +// Testbench for eth_interface +// + +module axi_lbus_tb #( + parameter TEST_NAME = "" +)( + /* no IO */ +); + // Include macros and time declarations for use with PkgTestExec + `define TEST_EXEC_OBJ test + `include "test_exec.svh" + import PkgAxiStreamBfm::*; + import PkgEthernet::*; + import PkgTestExec::*; + import PkgEth100gLbus::*; + + localparam DATA_WIDTH = 512; + localparam USER_WIDTH = $clog2(DATA_WIDTH/8)+1; + localparam NUM_SEG = 4; + localparam SEG_DATA_WIDTH = DATA_WIDTH/NUM_SEG; + localparam SEG_BYTES = SEG_DATA_WIDTH/8; + + //---------------------------------------------------- + // clocks + //---------------------------------------------------- + logic clk; + logic rst; + + sim_clock_gen #(.PERIOD(5), .AUTOSTART(1)) + clk_gen (.clk(clk), .rst(rst)); + + //---------------------------------------------------- + //interfaces + //---------------------------------------------------- + AxiStreamIf #(.DATA_WIDTH(DATA_WIDTH),.USER_WIDTH(USER_WIDTH)) + axis (clk, rst); + + //---------------------------------------------------- + // DUT + //---------------------------------------------------- + lbus_t lbus_out [NUM_SEG-1:0]; + lbus_t lbus_g [NUM_SEG-1:0]; + logic lbus_rdy = 1; + + eth_100g_axi2lbus #(.FIFO_DEPTH(5),.NUM_SEG(NUM_SEG)) DUT ( + .axis(axis), + .lbus_rdy(lbus_rdy), + .lbus_out(lbus_out) + ); + + //---------------------------------------------------- + // Xilinx golden model + // When Xilinx is generated with an AXI interface xilinx prints + // an axi2lbus converter that has trouble meeting timing + // this is preserved to allow comparison to that + // TODO: remove when timing is passing and refactor is done + //---------------------------------------------------- +/* + eth_100g_bd_cmac_usplus_0_0_axis2lbus_segmented_top GOLD ( + .core_clk(clk), + .core_rst(rst), + + // AXIS IF + .axis_tvalid(axis.tvalid), + .axis_tready(), + .axis_tdata(axis.tdata), + .axis_tlast(axis.tlast), + .axis_tkeep(axis.tkeep), + .axis_tuser(1'b0), + // LBUS IF + .lbus_rdyout(lbus_rdy), + .lbus_ovfout(1'b0), + .lbus_unfout(1'b0), + // Segment 0 + .lbus_ena0(lbus_g[0].ena), + .lbus_data0(lbus_g[0].data), + .lbus_sop0(lbus_g[0].sop), + .lbus_eop0(lbus_g[0].eop), + .lbus_mty0(lbus_g[0].mty), + .lbus_err0(lbus_g[0].err), + // Segment 1 + .lbus_ena1(lbus_g[1].ena), + .lbus_data1(lbus_g[1].data), + .lbus_sop1(lbus_g[1].sop), + .lbus_eop1(lbus_g[1].eop), + .lbus_mty1(lbus_g[1].mty), + .lbus_err1(lbus_g[1].err), + // Segment 2 + .lbus_ena2(lbus_g[2].ena), + .lbus_data2(lbus_g[2].data), + .lbus_sop2(lbus_g[2].sop), + .lbus_eop2(lbus_g[2].eop), + .lbus_mty2(lbus_g[2].mty), + .lbus_err2(lbus_g[2].err), + // Segment 3 + .lbus_ena3(lbus_g[3].ena), + .lbus_data3(lbus_g[3].data), + .lbus_sop3(lbus_g[3].sop), + .lbus_eop3(lbus_g[3].eop), + .lbus_mty3(lbus_g[3].mty), + .lbus_err3(lbus_g[3].err) + ); +*/ + //---------------------------------------------------- + //BFMS + //---------------------------------------------------- + + TestExec test = new(); + AxiStreamBfm #(.DATA_WIDTH(DATA_WIDTH),.USER_WIDTH(USER_WIDTH)) axi = + new(.slave(null),.master(axis)); + + //--------------------------------------------------------------------------- + // Tests + //--------------------------------------------------------------------------- + + // use Test timeout to check reset goes away + task test_reset(); + test.start_test({TEST_NAME,"Wait for Reset"}, 10us); + wait(!rst); + repeat (10) @(posedge clk); + test.end_test(); + endtask : test_reset + + typedef AxiStreamPacket #(DATA_WIDTH,USER_WIDTH) AxisPacket_t; + typedef XportStreamPacket #(DATA_WIDTH) XportPacket_t; + + + task automatic check_lbus(AxisPacket_t packets[$]); + + int idle_insert = 0; + int byte_count = 0; + int first_clk = 1; + + // loop over packets + foreach(packets[i]) begin + automatic raw_pkt_t pay; + pay = packets[i].dump_bytes; + + first_clk=1; + while (pay.size() > 0) begin + @(posedge clk); + if (lbus_rdy) begin + if (lbus_out[0].ena) begin + for (int s=0; s < NUM_SEG; s++) begin + byte_count = 0; + for (int b = SEG_DATA_WIDTH/8-1; b >= 0; b--) begin + if (pay.size() > 0) begin + assert (lbus_out[s].data[b*8 +: 8] == pay.pop_front()) else $error("Data Mismatch"); + byte_count++; + end + end + assert (lbus_out[s].ena == (byte_count > 0) ) else $error("Ena Mismatch"); + if (lbus_out[s].ena) begin + if (first_clk && s==0) begin + assert (lbus_out[s].sop == 1) else $error("Sop not set"); + end else begin + assert (lbus_out[s].sop == 0) else $error("Sop Set"); + end + assert (lbus_out[s].mty == (SEG_BYTES-byte_count) % SEG_BYTES) else $error("Mty Mismatch"); + assert (lbus_out[s].eop != (pay.size() > 0) ) else $error("Eop Mismatch"); + assert (lbus_out[s].err == 0) else $error("Err Mismatch"); // not checking error yet + end else begin + assert (lbus_out[s].sop == 0) else $error("Sop Set while disabled"); + assert (lbus_out[s].mty == 0) else $error("Mty set while disabled"); + assert (lbus_out[s].eop == 0) else $error("Eop set while disabled"); + assert (lbus_out[s].err == 0) else $error("Err set while disabled"); + end + end // segment loop + first_clk=0; + end // first segment is enabled + + end else begin //not lbus_rdy + for (int s=0; s < NUM_SEG; s++) begin + assert (lbus_out[s].ena == 0) else $error("Idle Ena set"); + assert (lbus_out[s].err == 0) else $error("Idle Err set"); + assert (lbus_out[s].sop == 0) else $error("Idle Sop set"); + assert (lbus_out[s].eop == 0) else $error("Idle Eop set"); + assert (lbus_out[s].mty == 0) else $error("Idle Mty non zero"); + end + end + end // while pay.size > 0 + end // foreach packet + + endtask : check_lbus; + + task automatic test_transfers(int num_samples[$]); + automatic AxisPacket_t send[$]; + automatic AxisPacket_t expected[$]; + automatic int sample_sum = 0; + + test.start_test({TEST_NAME,"Test Transfer"}, 200us); + + foreach (num_samples[i]) begin + automatic raw_pkt_t pay; + + expected[i] = new; + send[i] = new; + + get_ramp_raw_pkt(.num_samps(num_samples[i]),.ramp_start((sample_sum)%256), + .ramp_inc(1),.pkt(pay),.SWIDTH(8)); + sample_sum += num_samples[i]; + send[i].push_bytes(pay); + + // rebuild the expected packet for comparison without the preamble + expected[i].push_bytes(pay); + end + + fork + begin // tx_thread + foreach (send[i]) begin + axi.put(send[i]); + end + end + begin //rx_thread + check_lbus(expected); + end + join + + test.end_test(); + endtask : test_transfers + + //---------------------------------------------------- + // Main test loop + //---------------------------------------------------- + initial begin : tb_main + automatic int num_samples[$]; + automatic int random_value; + clk_gen.reset(); + + // stalling is not allowed by whoever is reading the MAC + // i.e. ready is ignored + axi.set_master_stall_prob(0); + axi.run(); + test_reset(); + + $display("Fixed Sequences"); + num_samples = {64,65,66,67,68,69,70,71}; + test_transfers(num_samples); + num_samples = {64,128,256,512,256,64,64,64}; + test_transfers(num_samples); + num_samples = {65,64,64,64,64,64,64,64}; + test_transfers(num_samples); + num_samples = {65,64,80,80,80,80,80,80}; + test_transfers(num_samples); + num_samples = {64,64,96,80,96,80,96,80}; + test_transfers(num_samples); + $display("Tons of 64"); + num_samples.delete(); + repeat(1000) begin + num_samples.push_back(64); + end + test_transfers(num_samples); + $display("Tons of 65"); + num_samples.delete(); + repeat(1000) begin + num_samples.push_back(65); + end + test_transfers(num_samples); + $display("Tons of Random"); + num_samples.delete(); + repeat(1000) begin + random_value = $urandom_range(64,512); + num_samples.push_back(random_value); + end + test_transfers(num_samples); + + + // End the TB, but don't $finish, since we don't want to kill other + // instances of this testbench that may be running. + test.end_tb(0); + + // Kill the clocks to end this instance of the testbench + clk_gen.kill(); + end // initial begin + +endmodule diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/lbus_all_tb.sv b/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/lbus_all_tb.sv new file mode 100644 index 000000000..98caa0a86 --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/lbus_all_tb.sv @@ -0,0 +1,22 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: lbus_all_tb +// +// Description: +// +// Testbench for LBU<->AXI +// + +module lbus_all_tb #( + /* no PARAM */ +)( + /* no IO */ +); + + lbus_axi_tb #(.TEST_NAME("L2A")) L2A (); + axi_lbus_tb #(.TEST_NAME("A2L")) A2L (); + +endmodule diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/lbus_axi_tb.sv b/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/lbus_axi_tb.sv new file mode 100644 index 000000000..fa5812abe --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/lbus_tb/lbus_axi_tb.sv @@ -0,0 +1,343 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: lbus_axi_tb +// +// Description: +// +// Testbench for eth_interface +// + +module lbus_axi_tb #( + parameter TEST_NAME = "" +)( + /* no IO */ +); + // Include macros and time declarations for use with PkgTestExec + `define TEST_EXEC_OBJ test + `include "test_exec.svh" + import PkgAxiStreamBfm::*; + import PkgEthernet::*; + import PkgTestExec::*; + import PkgEth100gLbus::*; + + localparam DATA_WIDTH = 512; + localparam USER_WIDTH = $clog2(DATA_WIDTH/8)+1; + localparam NUM_SEG = 4; + localparam SEG_DATA_WIDTH = DATA_WIDTH/NUM_SEG; + localparam SEG_BYTES = SEG_DATA_WIDTH/8; + + //---------------------------------------------------- + // clocks + //---------------------------------------------------- + logic clk; + logic rst; + + sim_clock_gen #(.PERIOD(5), .AUTOSTART(1)) + clk_gen (.clk(clk), .rst(rst)); + + //---------------------------------------------------- + //interfaces + //---------------------------------------------------- + AxiStreamIf #(.DATA_WIDTH(DATA_WIDTH),.USER_WIDTH(USER_WIDTH)) + axis (clk, rst); + + AxiStreamIf #(.DATA_WIDTH(DATA_WIDTH),.USER_WIDTH(USER_WIDTH)) + axig (clk, rst); + + //---------------------------------------------------- + // DUT + //---------------------------------------------------- + lbus_t lbus_in [NUM_SEG-1:0]; + + eth_100g_lbus2axi #(.FIFO_DEPTH(5),.NUM_SEG(NUM_SEG)) DUT ( + .axis(axis), + .lbus_in(lbus_in) + ); + + //---------------------------------------------------- + // Xilinx golden model + // When Xilinx is generated with an AXI interface xilinx prints + // a lbus2axis converter that has trouble meeting timing + // this is preserved to allow comparison to that + // TODO: remove when timing is passing and refactor is done + //---------------------------------------------------- +/* + eth_100g_bd_cmac_usplus_0_0_lbus2axis_segmented_top GOLD ( + .core_clk(clk), + .core_rst(rst), + + // AXIS IF + .axis_tvalid(axig.tvalid), + .axis_tdata(axig.tdata), + .axis_tlast(axig.tlast), + .axis_tkeep(axig.tkeep), + .axis_tuser(), + // Segment 0 + .lbus_ena0(lbus_in[0].ena), + .lbus_data0(lbus_in[0].data), + .lbus_sop0(lbus_in[0].sop), + .lbus_eop0(lbus_in[0].eop), + .lbus_mty0(lbus_in[0].mty), + .lbus_err0(lbus_in[0].err), + // Segment 1 + .lbus_ena1(lbus_in[1].ena), + .lbus_data1(lbus_in[1].data), + .lbus_sop1(lbus_in[1].sop), + .lbus_eop1(lbus_in[1].eop), + .lbus_mty1(lbus_in[1].mty), + .lbus_err1(lbus_in[1].err), + // Segment 2 + .lbus_ena2(lbus_in[2].ena), + .lbus_data2(lbus_in[2].data), + .lbus_sop2(lbus_in[2].sop), + .lbus_eop2(lbus_in[2].eop), + .lbus_mty2(lbus_in[2].mty), + .lbus_err2(lbus_in[2].err), + // Segment 3 + .lbus_ena3(lbus_in[3].ena), + .lbus_data3(lbus_in[3].data), + .lbus_sop3(lbus_in[3].sop), + .lbus_eop3(lbus_in[3].eop), + .lbus_mty3(lbus_in[3].mty), + .lbus_err3(lbus_in[3].err) + ); +*/ + //---------------------------------------------------- + //BFMS + //---------------------------------------------------- + + TestExec test = new(); + AxiStreamBfm #(.DATA_WIDTH(DATA_WIDTH),.USER_WIDTH(USER_WIDTH)) axi = + new(.slave(axis),.master(null)); + + //--------------------------------------------------------------------------- + // Tests + //--------------------------------------------------------------------------- + + // use Test timeout to check reset goes away + task test_reset(); + test.start_test({TEST_NAME,"Wait for Reset"}, 10us); + wait(!rst); + repeat (10) @(posedge clk); + test.end_test(); + endtask : test_reset + + typedef AxiStreamPacket #(DATA_WIDTH,USER_WIDTH) AxisPacket_t; + typedef XportStreamPacket #(DATA_WIDTH) XportPacket_t; + + task automatic clear_lbus_in(); + for (int i=0 ; i < NUM_SEG ; ++i) begin + lbus_in[i].data = 'b0; + lbus_in[i].mty = 'b0; + lbus_in[i].sop = 1'b0; + lbus_in[i].eop = 1'b0; + lbus_in[i].err = 1'b0; + lbus_in[i].ena = 1'b0; + end + endtask : clear_lbus_in; + + task automatic send_lbus(AxisPacket_t packets[$]); + + int seg = 0; + int b = 0; + int idle_insert = 0; + + // loop over packets + foreach(packets[i]) begin + automatic raw_pkt_t pay; + pay = packets[i].dump_bytes; + // set for the first segment + lbus_in[seg].sop = 1; + // empty this packets payload + while (pay.size() > 0) begin + lbus_in[seg].ena = 1; + lbus_in[seg].data |= pay.pop_front()<<(SEG_DATA_WIDTH-b*8-8); + if (pay.size() == 0) begin + lbus_in[seg].eop = 1; + lbus_in[seg].mty = SEG_DATA_WIDTH-1-b; + end + if (seg == NUM_SEG-1 && b == SEG_DATA_WIDTH/8-1) begin + @(posedge clk); + clear_lbus_in(); + idle_insert++; + // insert period idle period + if (idle_insert > 2) begin + @(posedge clk); + idle_insert =0; + end + end + b = (b+1) % (SEG_DATA_WIDTH/8); + if (b==0) begin + seg = (seg+1) % NUM_SEG; + end + end // pay.size > 0 + if (b != 0) begin + seg = (seg+1) % NUM_SEG; + if (seg == 0) begin + @(posedge clk); + clear_lbus_in(); + idle_insert++; + // insert period idle period + if (idle_insert > 2) begin + @(posedge clk); + idle_insert =0; + end + end + b = 0; + end + + // 25% of the time we start a new packet + if ($urandom_range(99) < 25) begin + b = 0; + seg = 0; + // 25% of the time add an idle cycle + if ($urandom_range(99) < 25) begin + @(posedge clk); + clear_lbus_in(); + idle_insert++; + // insert period idle period + if (idle_insert > 2) begin + idle_insert =0; + @(posedge clk); + end + @(posedge clk); + end else begin + @(posedge clk); + clear_lbus_in(); + idle_insert++; + // insert period idle period + if (idle_insert > 2) begin + idle_insert =0; + @(posedge clk); + end + end + end + + end // foreach packet + + @(posedge clk); + clear_lbus_in(); + + endtask : send_lbus; + + task automatic compare_packet(AxisPacket_t actual, expected); + + automatic XportPacket_t actual_copy = new(); + automatic XportPacket_t expected_copy = new(); + actual_copy.import_axis(actual); + expected_copy.import_axis(expected); + + expected_copy.tkeep_to_tuser(); + actual_copy.clear_unused_bytes(); + + if (!expected_copy.equal(actual_copy)) begin + $display("Expected"); + expected_copy.print(); + $display("Actual"); + actual_copy.print(); + if (!expected_copy.equal(actual_copy)) + $error("ERROR :: packet mismatch"); + + end + + endtask : compare_packet; + + task automatic test_transfers(int num_samples[$]); + automatic AxisPacket_t send[$]; + automatic AxisPacket_t expected[$]; + automatic int sample_sum = 0; + + test.start_test({TEST_NAME,"Test Transfer"}, 200us); + + foreach (num_samples[i]) begin + automatic raw_pkt_t pay; + + expected[i] = new; + send[i] = new; + + get_ramp_raw_pkt(.num_samps(num_samples[i]),.ramp_start((sample_sum)%256), + .ramp_inc(1),.pkt(pay),.SWIDTH(8)); + sample_sum += num_samples[i]; + send[i].push_bytes(pay); + + // rebuild the expected packet for comparison without the preamble + expected[i].push_bytes(pay); + end + + fork + begin // tx_thread + send_lbus(send); + end + begin //rx_thread + foreach(expected[i]) begin + automatic AxisPacket_t actual; + axi.get(actual); + compare_packet(actual,expected[i]); + end + end + join + + test.end_test(); + endtask : test_transfers + + //---------------------------------------------------- + // Main test loop + //---------------------------------------------------- + initial begin : tb_main + automatic int num_samples[$]; + automatic int random_value; + clk_gen.reset(); + + // stalling is not allowed by whoever is reading the MAC + // i.e. ready is ignored + axi.slave_tready_init = 1'b1; + axi.set_master_stall_prob(0); + axi.set_slave_stall_prob(0); + axi.run(); + clear_lbus_in(); + test_reset(); + + $display("Fixed Sequences"); + num_samples = {64,65,66,67,68,69,70,71}; + test_transfers(num_samples); + num_samples = {64,128,256,512,256,64,64,64}; + test_transfers(num_samples); + num_samples = {65,64,64,64,64,64,64,64}; + test_transfers(num_samples); + num_samples = {65,64,80,80,80,80,80,80}; + test_transfers(num_samples); + num_samples = {64,64,96,80,96,80,96,80}; + test_transfers(num_samples); + $display("Tons of 64"); + num_samples.delete(); + repeat(1000) begin + num_samples.push_back(64); + end + test_transfers(num_samples); + $display("Tons of 65"); + num_samples.delete(); + repeat(1000) begin + num_samples.push_back(65); + end + test_transfers(num_samples); + $display("Tons of Random"); + num_samples.delete(); + repeat(1000) begin + random_value = $urandom_range(64,512); + num_samples.push_back(random_value); + end + test_transfers(num_samples); + + + // End the TB, but don't $finish, since we don't want to kill other + // instances of this testbench that may be running. + test.end_tb(0); + + // Kill the clocks to end this instance of the testbench + clk_gen.kill(); + end // initial begin + +endmodule diff --git a/fpga/usrp3/top/x400/ip/eth_100g_bd/model_100gbe.sv b/fpga/usrp3/top/x400/ip/eth_100g_bd/model_100gbe.sv new file mode 100644 index 000000000..e7995dd44 --- /dev/null +++ b/fpga/usrp3/top/x400/ip/eth_100g_bd/model_100gbe.sv @@ -0,0 +1,234 @@ +// +// Copyright 2021 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: model_100gbe +// +// Description: +// +// A wrapper of the 100gbe core to axistream interface. this model can be +// used drive packets into the X400 translated to serial Ethernet. This is +// generally pretty slower than just driving things in at the output of the +// mac. +// + +package Pkg100gbMac; + import PkgAxiLite::*; + import PkgAxiLiteBfm::*; + import PkgTestExec::*; + + localparam DATA_WIDTH =32; + localparam ADDR_WIDTH =15; + + typedef AxiLiteBfm #(DATA_WIDTH, ADDR_WIDTH) MacAxiLiteBfm_t; + + // defined in https://www.xilinx.com/support/documentation/ip_documentation/cmac_usplus/v2_4/pg203-cmac-usplus.pdf + // pg 187 + localparam CONFIGURATION_TX_REG1 = 32'h000C; + localparam ctl_tx_ctl_enable = 0; + localparam ctl_tx_ctl_tx_send_lfi = 3; + localparam ctl_tx_ctl_tx_send_rfi = 4; + localparam ctl_tx_ctl_tx_send_idle = 5; + localparam ctl_tx_ctl_test_pattern = 16; + + localparam CONFIGURATION_RX_REG1 = 32'h0014; + localparam ctl_rx_ctl_enable = 0; + localparam ctl_rx_ctl_rx_force_resync = 7; + localparam ctl_rx_ctl_test_pattern = 8; + + localparam RSFEC_CONFIG_INDICATION_CORRECTION = 32'h1000; + localparam rs_fec_in_ctl_rx_rsfec_enable_correction = 0; + localparam rs_fec_in_ctl_rx_rsfec_enable_indication = 1; + localparam rs_fec_in_ctl_rsfec_ieee_error_indication_mode = 2; + + localparam RSFEC_CONFIG_ENABLE = 32'h107C; + localparam rs_fec_in_ctl_rx_rsfec_enable = 0; + localparam rs_fec_in_ctl_tx_rsfec_enable = 1; + + localparam STAT_RX_STATUS_REG = 32'h0204; + localparam stat_rx_status = 0; + localparam stat_rx_aligned = 1; + localparam stat_rx_misaligned = 2; + localparam stat_rx_aligned_err = 3; + + task automatic init_mac (int offset, MacAxiLiteBfm_t axi); + automatic logic [31:0] data; + automatic resp_t resp; + + // start transmitting alignment pattern + data = 0; + data[ctl_tx_ctl_enable] = 0; + data[ctl_tx_ctl_tx_send_idle] = 0; + data[ctl_tx_ctl_tx_send_lfi] = 0; + data[ctl_tx_ctl_tx_send_rfi] = 1; + data[ctl_tx_ctl_test_pattern] = 0; + axi.wr(CONFIGURATION_TX_REG1+offset,data); + + // configure fec + data = 0; + data[rs_fec_in_ctl_rx_rsfec_enable_correction] = 1; + data[rs_fec_in_ctl_rx_rsfec_enable_indication] = 1; + data[rs_fec_in_ctl_rsfec_ieee_error_indication_mode] = 1; + axi.wr(RSFEC_CONFIG_INDICATION_CORRECTION+offset,data); + + data = 0; + data[rs_fec_in_ctl_rx_rsfec_enable] = 1; + data[rs_fec_in_ctl_tx_rsfec_enable] = 1; + axi.wr(RSFEC_CONFIG_ENABLE+offset,data); + + // turn on RX interface + data = 0; + data[ctl_rx_ctl_enable] = 1; + data[ctl_rx_ctl_rx_force_resync] = 0; + data[ctl_rx_ctl_test_pattern] = 0; + axi.wr(CONFIGURATION_RX_REG1+offset,data); + + do begin + axi.rd_block(STAT_RX_STATUS_REG+offset,data,resp); + assert (resp==OKAY); + end while (data[stat_rx_aligned] !== 1); + + // stop transmitting alignment pattern + // and start transmitting data + data = 0; + data[ctl_tx_ctl_enable] = 1; + data[ctl_tx_ctl_tx_send_idle] = 0; + data[ctl_tx_ctl_tx_send_lfi] = 0; + data[ctl_tx_ctl_tx_send_rfi] = 0; + data[ctl_tx_ctl_test_pattern] = 0; + axi.wr(CONFIGURATION_TX_REG1+offset,data); + + endtask : init_mac + +endpackage : Pkg100gbMac + +module model_100gbe ( + input logic areset, + // 156.25 Mhz refclk + input logic ref_clk, + + // QSFP 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, + + // CLK and RESET out + output logic mgt_clk, + output logic mgt_rst, + output logic link_up, + + // Data port + AxiStreamIf.slave mgt_tx, + AxiStreamIf.master mgt_rx + +); + + // Include macros and time declarations for use with PkgTestExec + `define TEST_EXEC_OBJ test + `include "test_exec.svh" + import PkgAxiLiteBfm::*; + import PkgTestExec::*; + + logic refclk_p; + logic refclk_n; + + logic clk40,clk40_rst; + logic clk100,clk100_rst; + logic phy_reset; + + assign refclk_p = ref_clk; + assign refclk_n = ~ref_clk; + + //interface + AxiLiteIf #(Pkg100gbMac::DATA_WIDTH,Pkg100gbMac::ADDR_WIDTH) + mgt_axil (clk40, clk40_rst); + //bfm + Pkg100gbMac::MacAxiLiteBfm_t axi = new(.master(mgt_axil)); + TestExec mac_test = new(); + + sim_clock_gen #(.PERIOD(25.0), .AUTOSTART(1)) + clk40_gen (.clk(clk40), .rst(clk40_rst)); + sim_clock_gen #(.PERIOD(100.0), .AUTOSTART(1)) + clk100_gen (.clk(clk100), .rst(clk100_rst)); + + initial begin : init_model + clk40_gen.reset(); + axi.run(); + wait(!clk40_rst); + repeat (10) @(posedge clk40); + + wait(!phy_reset); // both usr_clk's are ok + + mac_test.start_test("model_100gbe::Wait for MAC link_up", 150us); + //Added autoconnect - uncomment to test connecting over AXI + //Pkg100gbMac::init_mac(0,axi); + mac_test.end_test(); + end + + + AxiStreamIf #(.DATA_WIDTH(512),.USER_WIDTH(7),.TKEEP(0)) + eth100g_rx(mgt_clk,mgt_rst); + + always_comb begin + mgt_rx.tdata = eth100g_rx.tdata; + mgt_rx.tuser = eth100g_rx.tuser; + mgt_rx.tkeep = eth100g_rx.trailing2keep(eth100g_rx.tuser); + mgt_rx.tvalid = eth100g_rx.tvalid; + mgt_rx.tlast = eth100g_rx.tlast; + eth100g_rx.tready = mgt_rx.tready; + // The MAC ignores hold off. Data must be consumed every clock it is valid. + if (!mgt_rst) begin + if (!mgt_rx.tready && mgt_rx.tvalid) begin + $error("Model 100Gbe : can't hold off the MAC"); + end + end + end + + // model does not pause. Users could access this heirarchically to test it + logic mgt_pause_req; + assign mgt_pause_req = 1'b0; + + // hold off link up untill the stat_auto_config writes are complete + logic link_up_model; + logic [31:0] mac_status; + always_comb begin + link_up = link_up_model && mac_status[4]; + end + + eth_100g #(.PAUSE_QUANTA(10),.PAUSE_REFRESH(100)) eth_100gx ( + .areset(areset), + //-- Free running 100 MHz clock used for InitClk and AxiLite to mac + //-- 3.125 - 161.132812 MHz. + .clk100(clk100), + // MGT Reference Clock 100/125/156.25/161.1328125 MHz + .refclk_p(refclk_p), + .refclk_n(refclk_n), + // MGT TX/RX differential signals + .tx_p(tx_p), + .tx_n(tx_n), + .rx_p(rx_p), + .rx_n(rx_n), + // 322.26666 Mhz clock generated by 100G Phy from RefClock + .mgt_clk(mgt_clk), + .mgt_rst(mgt_rst), + // pause + .mgt_pause_req(mgt_pause_req), + //------------------------ AXI Stream TX Interface ------------------------ + .mgt_tx(mgt_tx), + //---------------------- AXI Stream RX Interface ------------------------ + // There is no RxTReady signal support by the Ethernet100G IP. Received data has to + // be read immediately or it is lost. + // tUser indicates an error on rcvd packet + .mgt_rx(eth100g_rx), + .mgt_axil(mgt_axil), + // LEDs of QSFP28 port + .phy_status(), + .mac_ctrl(32'h01000001), // autoconfig / pause mask set to global + .mac_status(mac_status), + .phy_reset(phy_reset), + .link_up(link_up_model) + ); + +endmodule |