aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3
diff options
context:
space:
mode:
authorAndrew Moch <Andrew.Moch@ni.com>2020-06-25 21:49:00 +0100
committerWade Fife <wade.fife@ettus.com>2020-06-30 10:29:35 -0500
commiteed4988cc266a63370a4332351d02fadedde3a3b (patch)
treeba8b4d4518153d88d40c835ae5b31192d79ae763 /fpga/usrp3
parent6d9b174086ddebf49183eb09b779bd2c2658573a (diff)
downloaduhd-eed4988cc266a63370a4332351d02fadedde3a3b.tar.gz
uhd-eed4988cc266a63370a4332351d02fadedde3a3b.tar.bz2
uhd-eed4988cc266a63370a4332351d02fadedde3a3b.zip
fpga: lib: Add width agnostic version of Ethernet Interface
The rnfoc/xport section is refactored in System Verilog to allow the following improvements (1) CPU_W - Sets the size of the c2e and e2c pipes. This can be run at a different clock rate than the main ethernet pipe (2) CHDR_W - Sets the size of the v2e and e2v pipes. This can be run at a different clock rate than the main ethernet pipe (3) ENET_W - Sets the size of the eth_tx and eth_rx pipes. eth_interface_tb runs traffic from e2c,e2v,v2e,c2e simultaneously against the original xport_sv implementation, and against the new implementation with widths of 64/128/512. A chdr_management node info request queries the port info of the node0 in the eth_interface. eth_ifc_synth_test.sv can be compiled with the make xsim target to test out the size of various configurations.
Diffstat (limited to 'fpga/usrp3')
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/Makefile.srcs16
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/chdr_xport_adapter.sv529
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_constants.vh76
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/.gitignore3
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/Makefile81
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_all_tb.sv24
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_synth_test.sv161
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_tb.sv1084
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_add_udp.sv200
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_adapter.sv473
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_dispatch.sv435
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_interface.sv228
-rw-r--r--fpga/usrp3/lib/rfnoc/xport_sv/eth_regs.vh29
-rw-r--r--fpga/usrp3/tools/utils/testbenches.excludes1
14 files changed, 3340 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/Makefile.srcs b/fpga/usrp3/lib/rfnoc/xport_sv/Makefile.srcs
new file mode 100644
index 000000000..397a86326
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/Makefile.srcs
@@ -0,0 +1,16 @@
+#
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+#
+
+##################################################
+# RFNoC Utility Sources
+##################################################
+RFNOC_XPORT_SV_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/xport_sv/, \
+chdr_xport_adapter.sv \
+eth_ipv4_add_udp.sv \
+eth_ipv4_chdr_adapter.sv \
+eth_ipv4_chdr_dispatch.sv \
+eth_ipv4_interface.sv \
+))
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/chdr_xport_adapter.sv b/fpga/usrp3/lib/rfnoc/xport_sv/chdr_xport_adapter.sv
new file mode 100644
index 000000000..b898c1fe2
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/chdr_xport_adapter.sv
@@ -0,0 +1,529 @@
+//
+// Copyright 2020 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: chdr_xport_adapter (Ethernet IPV4)
+//
+// Description: An Xport transport adapter module that does the following:
+// - Exposes a configuration port for mgmt packets to configure the node.
+// (chdr_mgmt_pkt_handler)
+// - Implements a return-address map for packets with metadata other than
+// the CHDR. Additional metadata can be passed as a tuser to this module
+// which will store it in a map indexed by the SrcEPID in a management
+// packet. For all returning packets, the metadata will be looked up in
+// the map and attached as the outgoing tuser. (kv_map)
+// - Implements a loopback path for node-info discovery (axi_switch/axi_mux)
+// - Strip UDP headers and extract mac/ip/udp src addresses
+// - Add UDP header for outgoing ethernet traffic
+//
+// Parameters:
+// - PROTOVER: RFNoC protocol version {8'd<major>, 8'd<minor>}
+// - TBL_SIZE: Log2 of the depth of the routing table
+// - NODE_SUBTYPE: The node subtype to return for a node-info discovery
+// - NODE_INST: The node type to return for a node-info discovery
+// - ALLOW_DISC: Controls if the external transport network should be
+// discoverable by management packets from RFNoC side.
+//
+// Signals:
+// - device_id : The ID of the device that has instantiated this module
+// - my_* : MAC address, IP address, and UDP port that responds/accepts CHDR traffic
+// - eth_rx : The input CHDR stream from the transport
+// - eth_tx : The output CHDR stream to transport
+// - v2e : The input CHDR stream from the rfnoc infrastructure
+// - e2v : The output CHDR stream to the rfnoc infrastructure
+//
+
+`include "../xport/rfnoc_xport_types.vh"
+
+module chdr_xport_adapter #(
+ int PREAMBLE_BYTES = 6,
+ int MAX_PACKET_BYTES = 2**16,
+ logic [15:0] PROTOVER = {8'd1, 8'd0},
+ int TBL_SIZE = 6,
+ logic [7:0] NODE_SUBTYPE = NODE_SUBTYPE_XPORT_IPV4_CHDR64,
+ int NODE_INST = 0,
+ bit ALLOW_DISC = 1
+)(
+ // Device info
+ input logic [15:0] device_id,
+ // Device addresses
+ input logic [47:0] my_mac,
+ input logic [31:0] my_ip,
+ input logic [15:0] my_udp_chdr_port,
+
+ // Ethernet
+ AxiStreamIf.slave eth_rx, // tUser={*not used*}
+ AxiStreamIf.master eth_tx, // tUser={1'b0,trailing bytes}
+
+ // CHDR
+ AxiStreamIf.slave v2e, // tUser={*not used*}
+ AxiStreamIf.master e2v // tUser={*not used*}
+);
+
+ //used to store {udp, ipv4, mac}
+ localparam USER_META_W = 96;
+ localparam ENET_USER_W = $clog2(eth_rx.DATA_WIDTH/8)+1;
+ // ---------------------------------------------------
+ // RFNoC Includes
+ // ---------------------------------------------------
+ `include "../core/rfnoc_chdr_utils.vh"
+ `include "../core/rfnoc_chdr_internal_utils.vh"
+ `include "eth_constants.vh"
+ `include "../../axi4s_sv/axi4s.vh"
+
+ // tUser={None}
+ AxiStreamPacketIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.TKEEP(0),.TUSER(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ ru1(eth_rx.clk,eth_rx.rst);// Packet handler input
+ // tUser={None}
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.TKEEP(0),.TUSER(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ ru2(eth_rx.clk,eth_rx.rst);// Packet handler input
+ // tUser={udp_src_port,ipv4_src_addr,eth_src_addr}
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.USER_WIDTH(USER_META_W),.TKEEP(0))
+ ru3(eth_rx.clk,eth_rx.rst);// Packet handler input
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.USER_WIDTH(USER_META_W),.TKEEP(0))
+ ru4(eth_rx.clk,eth_rx.rst);// Packet handler input
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.USER_WIDTH(USER_META_W),.TKEEP(0))
+ ph(eth_rx.clk,eth_rx.rst);// Packet handler input
+ // tUser={udp_src_port,ipv4_src_addr,eth_src_addr}
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.USER_WIDTH(USER_META_W),.TKEEP(0))
+ e2d(eth_rx.clk,eth_rx.rst);// Eth => Demux
+ logic [1:0] e2d_tid;
+ // tUser={udp_src_port,ipv4_src_addr,eth_src_addr}
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.USER_WIDTH(USER_META_W),.TKEEP(0))
+ e2e(eth_rx.clk,eth_rx.rst);// Eth => Eth (loopback)
+ // tUser={udp_dst_port, ipv4_dst_addr, eth_dst_addr}
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.USER_WIDTH(USER_META_W),.TKEEP(0))
+ m2e(eth_rx.clk,eth_rx.rst);// Mux => Eth
+ logic m2e_tdest; // 1: Return to src, 0: CHDR input
+
+ // ---------------------------------------------------
+ // Strip UDP and grab {udp_src_port_old, ipv4_src_addr_old, eth_src_addr_old}
+ // ---------------------------------------------------
+
+ always_comb begin : assign_ru1
+ `AXI4S_ASSIGN(ru1,eth_rx)
+ end
+
+ // Cached fields
+ logic [47:0] eth_src_addr_new, eth_src_addr_old;
+ logic [31:0] ipv4_src_addr_new, ipv4_src_addr_old;
+ logic [15:0] udp_src_port_new, udp_src_port_old;
+
+ // save the fields
+ always_ff @(posedge eth_rx.clk) begin : field_ff
+ if (eth_rx.rst) begin
+ eth_src_addr_old <= '0;
+ ipv4_src_addr_old <= '0;
+ udp_src_port_old <= '0;
+ end else begin
+ eth_src_addr_old <= eth_src_addr_new;
+ ipv4_src_addr_old <= ipv4_src_addr_new;
+ udp_src_port_old <= udp_src_port_new;
+ end
+ end
+
+ // get the fields - don't use assign. assign will not activate with changes to eth_rx.
+ always_comb begin : get_fields
+ eth_src_addr_new = ru1.get_packet_field48(eth_src_addr_old,SRC_MAC_BYTE,.NETWORK_ORDER(1));
+ ipv4_src_addr_new = ru1.get_packet_field32(ipv4_src_addr_old,SRC_IP_BYTE,.NETWORK_ORDER(1));
+ udp_src_port_new = ru1.get_packet_field16(udp_src_port_old,SRC_PORT_BYTE,.NETWORK_ORDER(1));
+ end
+
+ // Strip the udp header
+ axi4s_remove_bytes #(.REM_START(0),.REM_END(UDP_END)
+ ) strip_udp (
+ .i(ru1), .o(ru2)
+ );
+
+ // start driving the port information
+ always_comb begin : assign_ru3
+ `AXI4S_ASSIGN(ru3,ru2)
+ ru3.tuser = {udp_src_port_old, ipv4_src_addr_old, eth_src_addr_old};
+ end
+
+
+ chdr_trim_payload #(
+ .CHDR_W(eth_rx.DATA_WIDTH), .USER_W(USER_META_W)
+ ) chdr_trim_i (
+ .clk(eth_rx.clk), .rst(eth_rx.rst),
+ .s_axis_tdata(ru3.tdata), .s_axis_tuser(ru3.tuser),
+ .s_axis_tlast(ru3.tlast), .s_axis_tvalid(ru3.tvalid), .s_axis_tready(ru3.tready),
+ .m_axis_tdata(ru4.tdata), .m_axis_tuser(ru4.tuser),
+ .m_axis_tlast(ru4.tlast), .m_axis_tvalid(ru4.tvalid), .m_axis_tready(ru4.tready)
+ );
+
+ // Pay close attention to when ph.tuser swtiches versus when it is needed!
+ always_comb begin : assign_ph
+ `AXI4S_ASSIGN(ph,ru4)
+ end
+
+ // ---------------------------------------------------
+ // Transport => DEMUX
+ // ---------------------------------------------------
+ logic op_stb;
+ logic [15:0] op_src_epid;
+ logic [USER_META_W-1:0] op_data;
+ logic lookup_stb, lookup_done_stb, lookup_result_match;
+ logic [15:0] lookup_epid;
+ logic [USER_META_W-1:0] lookup_result_value;
+ logic [47:0] node_info;
+
+ always_comb node_info = chdr_mgmt_build_node_info(
+ { 10'h0, NODE_SUBTYPE},
+ NODE_INST, NODE_TYPE_TRANSPORT, device_id);
+
+ chdr_mgmt_pkt_handler #(
+ .PROTOVER(PROTOVER), .CHDR_W(eth_rx.DATA_WIDTH), .USER_W(USER_META_W), .MGMT_ONLY(0)
+ ) mgmt_ep_i (
+ .clk(eth_rx.clk), .rst(eth_rx.rst),
+ .node_info(node_info),
+ //ph in
+ .s_axis_chdr_tdata(ph.tdata), .s_axis_chdr_tlast(ph.tlast),
+ .s_axis_chdr_tvalid(ph.tvalid), .s_axis_chdr_tready(ph.tready),
+ .s_axis_chdr_tuser(ph.tuser),
+ //e2d out
+ .m_axis_chdr_tdata(e2d.tdata), .m_axis_chdr_tlast(e2d.tlast),
+ .m_axis_chdr_tdest(/* unused */), .m_axis_chdr_tid(e2d_tid),
+ .m_axis_chdr_tvalid(e2d.tvalid), .m_axis_chdr_tready(e2d.tready),
+ //unused ctrlport
+ .ctrlport_req_wr (/* unused */),
+ .ctrlport_req_rd (/* unused */),
+ .ctrlport_req_addr (/* unused */),
+ .ctrlport_req_data (/* unused */),
+ .ctrlport_resp_ack (1'b0 /* unused */),
+ .ctrlport_resp_data (32'b0 /* unused */),
+ // kv_map lookups
+ .op_stb(op_stb),
+ .op_dst_epid(/* unused */),
+ .op_src_epid(op_src_epid),
+ .op_data(op_data)
+ );
+
+ // Key/Value map.
+ // Stores the destination address information for UDP
+ // -- storage is controlled from the chdr_managment_node
+ // -- lookup is done on each packet passing out
+ kv_map #(
+ .KEY_WIDTH(16), .VAL_WIDTH(USER_META_W), .SIZE(TBL_SIZE)
+ ) kv_map_i (
+ .clk(eth_rx.clk), .reset(eth_rx.rst),
+ .insert_stb(op_stb), .insert_key(op_src_epid), .insert_val(op_data),
+ .insert_busy(/* Time between op_stb > Insertion time */),
+ .find_key_stb(lookup_stb), .find_key(lookup_epid),
+ .find_res_stb(lookup_done_stb),
+ .find_res_match(lookup_result_match), .find_res_val(lookup_result_value),
+ .count(/* unused */)
+ );
+
+ logic ph_hdr = 1'b1;
+ always_ff @(posedge eth_rx.clk) begin
+ if (eth_rx.rst)
+ ph_hdr <= 1'b1;
+ else if (ph.tvalid && ph.tready)
+ ph_hdr <= ph.tlast;
+ end
+
+ // chdr_mgmt_pkt_handler does not buffer packets and has at least one cycle of delay.
+ // The tuser caching logic could be more robust.
+ always_ff @(posedge eth_rx.clk) begin
+ if (ph.tvalid && ph.tready && ph_hdr)
+ e2d.tuser <= ph.tuser;
+ end
+
+ // ---------------------------------------------------
+ // Optional management filter
+ // ---------------------------------------------------
+ // tUser={*not used*}
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.TUSER(0),.TKEEP(0))
+ f2m(eth_rx.clk,eth_rx.rst);
+
+ if (ALLOW_DISC) begin : gen_no_mgmt_filter
+ // Allow all packets to pass through
+ always_comb begin
+ f2m.tdata = v2e.tdata;
+ f2m.tlast = v2e.tlast;
+ f2m.tvalid = v2e.tvalid;
+ v2e.tready = f2m.tready;
+ end
+
+ end else begin : gen_mgmt_filter
+ // Disallow forwarding of management discovery packets from RFNoC to the
+ // transport interface for transports that don't support them.
+ //vhook_nowarn unused_*
+ logic [eth_rx.DATA_WIDTH-1:0] unused_tdata;
+ logic unused_tlast, unused_tvalid;
+ logic [eth_rx.DATA_WIDTH-1:0] s_header;
+ logic dispose_pkt;
+
+ // We identify discovery packets by the fact that they are management
+ // packets and that they use the null EPID as the destination.
+ always_comb dispose_pkt = (chdr_get_pkt_type(s_header[63:0]) == CHDR_PKT_TYPE_MGMT) &&
+ (chdr_get_dst_epid(s_header[63:0]) == NULL_EPID);
+
+
+ axi_demux #(
+ .WIDTH (eth_rx.DATA_WIDTH),
+ .SIZE (2),
+ .PRE_FIFO_SIZE (0),
+ .POST_FIFO_SIZE (1)
+ ) axi_demux_mgmt_filter_i (
+ .clk (eth_rx.clk),
+ .reset (eth_rx.rst),
+ .clear (1'b0),
+ .header (s_header),
+ .dest (dispose_pkt),
+ .i_tdata (v2e.tdata),
+ .i_tlast (v2e.tlast),
+ .i_tvalid (v2e.tvalid),
+ .i_tready (v2e.tready),
+ .o_tdata ({unused_tdata, f2m.tdata}),
+ .o_tlast ({unused_tlast, f2m.tlast}),
+ .o_tvalid ({unused_tvalid, f2m.tvalid}),
+ .o_tready ({1'b1, f2m.tready})
+ );
+ end
+
+ // ---------------------------------------------------
+ // MUX and DEMUX for return path
+ // ---------------------------------------------------
+
+ logic [USER_META_W-1:0] unused_tuser;
+
+ axis_switch #(
+ .DATA_W(eth_rx.DATA_WIDTH+USER_META_W), .DEST_W(1), .IN_PORTS(1), .OUT_PORTS(2), .PIPELINE(0)
+ ) rtn_demux_i (
+ .clk(eth_rx.clk), .reset(eth_rx.rst),
+ .s_axis_tdata({e2d.tuser, e2d.tdata}), .s_axis_alloc(1'b0),
+ .s_axis_tdest(e2d_tid == CHDR_MGMT_RETURN_TO_SRC ? 2'b01 : 2'b00),
+ .s_axis_tlast(e2d.tlast), .s_axis_tvalid(e2d.tvalid), .s_axis_tready(e2d.tready),
+ .m_axis_tdata({e2e.tuser, e2e.tdata, unused_tuser, e2v.tdata}),
+ .m_axis_tdest(/* unused */),
+ .m_axis_tlast({e2e.tlast, e2v.tlast}),
+ .m_axis_tvalid({e2e.tvalid, e2v.tvalid}),
+ .m_axis_tready({e2e.tready, e2v.tready})
+ );
+
+ axi_mux #(
+ .WIDTH(eth_rx.DATA_WIDTH+USER_META_W+1), .SIZE(2), .PRE_FIFO_SIZE(0), .POST_FIFO_SIZE(0)
+ ) rtn_mux_i (
+ .clk(eth_rx.clk), .reset(eth_rx.rst), .clear(1'b0),
+ .i_tdata({1'b1, e2e.tuser, e2e.tdata, 1'b0, {USER_META_W{1'b0}}, f2m.tdata}),
+ .i_tlast({e2e.tlast, f2m.tlast}),
+ .i_tvalid({e2e.tvalid, f2m.tvalid}), .i_tready({e2e.tready, f2m.tready}),
+ .o_tdata({m2e_tdest, m2e.tuser, m2e.tdata}), .o_tlast(m2e.tlast),
+ .o_tvalid(m2e.tvalid), .o_tready(m2e.tready)
+ );
+
+ // ---------------------------------------------------
+ // MUX => Transport
+ // ---------------------------------------------------
+
+ // In this section we must determine what value to put in tuser. If tdest is
+ // 1 then tuser is passed through unchanged. If tdest is 0 then the tuser
+ // value is looked up in the KV map using the EPID in the packet header.
+ //
+ // To do this we split the data (tdata, tlast) and the routing information
+ // (tdest, tuser, and the EPID) into two FIFOs. This allows us to perform a
+ // routing lookup and decide what to do while we continue to buffer data.
+ //
+ // With small packets, multiple routing lookups might be enqueued in the
+ // lookup_fifo, but we can only do one lookup at a time. Output logic
+ // controls release of packets from the data FIFO to ensure we only output
+ // one packet per lookup after the lookup is complete.
+
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.TUSER(0),.TKEEP(0))
+ data_fifo_o(eth_rx.clk,eth_rx.rst);
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.TUSER(0),.TKEEP(0))
+ data_fifo_i(eth_rx.clk,eth_rx.rst);
+
+ AxiStreamIf #(.DATA_WIDTH(1+USER_META_W+16),.TUSER(0),.TKEEP(0))
+ lookup_fifo_o(eth_rx.clk,eth_rx.rst);
+ AxiStreamIf #(.DATA_WIDTH(1+USER_META_W+16),.TUSER(0),.TKEEP(0))
+ lookup_fifo_i(eth_rx.clk,eth_rx.rst);
+
+ logic lookup_fifo_tdest;
+ logic [USER_META_W-1:0] lookup_fifo_tuser;
+ logic [ 15:0] lookup_fifo_tepid;
+ logic non_lookup_done_stb;
+ logic data_fifo_o_hdr = 1'b1;
+ logic pass_packet;
+ logic [USER_META_W-1:0] result_tuser;
+ logic result_tuser_valid;
+ logic [USER_META_W-1:0] reg_o_tuser;
+
+ // Track when the next m2e word contains is the start of a new packet
+ logic m2e_hdr = 1'b1;
+ always_ff @(posedge eth_rx.clk) begin : m2e_hdr_ff
+ if (eth_rx.rst)
+ m2e_hdr <= 1'b1;
+ else if (m2e.tvalid && m2e.tready)
+ m2e_hdr <= m2e.tlast;
+ end
+
+ // We can only accept data from the mux when when both the data_fifo and
+ // lookup_fifo are ready.
+ always_comb data_fifo_i.tdata = m2e.tdata;
+ always_comb data_fifo_i.tlast = m2e.tlast;
+ always_comb data_fifo_i.tvalid = m2e.tvalid && m2e.tready;
+ always_comb m2e.tready = data_fifo_i.tready && lookup_fifo_i.tready;
+
+ // The data_fifo only takes the packet data (tdata, tlast). We use an
+ // axi_fifo_short module for the data_fifo because it can tolerate tvalid
+ // going low before a transfer completes.
+ axi_fifo_short #(
+ .WIDTH (1+eth_rx.DATA_WIDTH)
+ ) data_fifo (
+ .clk (eth_rx.clk),
+ .reset (eth_rx.rst),
+ .clear (1'b0),
+ .i_tdata ({data_fifo_i.tlast, data_fifo_i.tdata}),
+ .i_tvalid (data_fifo_i.tvalid),
+ .i_tready (data_fifo_i.tready),
+ .o_tdata ({data_fifo_o.tlast, data_fifo_o.tdata}),
+ .o_tvalid (data_fifo_o.tvalid),
+ .o_tready (data_fifo_o.tready),
+ .space (),
+ .occupied ()
+ );
+
+ // The lookup FIFO only takes the header routing info (tdest, tuser, epid).
+ // We use axi_fifo_short since it can tolerate tvalid going low before a
+ // transfer completes.
+
+ always_comb lookup_fifo_i.tdata = {m2e_tdest, m2e.tuser, chdr_get_dst_epid(m2e.tdata[63:0])};
+ always_comb {lookup_fifo_tdest, lookup_fifo_tuser, lookup_fifo_tepid} = lookup_fifo_o.tdata;
+ always_comb lookup_fifo_i.tvalid = m2e.tvalid && m2e.tready && m2e_hdr;
+
+ axi_fifo_short #(
+ .WIDTH (1+USER_META_W+16)
+ ) lookup_fifo (
+ .clk (eth_rx.clk),
+ .reset (eth_rx.rst),
+ .clear (1'b0),
+ .i_tdata (lookup_fifo_i.tdata),
+ .i_tvalid (lookup_fifo_i.tvalid),
+ .i_tready (lookup_fifo_i.tready),
+ .o_tdata (lookup_fifo_o.tdata),
+ .o_tvalid (lookup_fifo_o.tvalid),
+ .o_tready (lookup_fifo_o.tready),
+ .space (),
+ .occupied ()
+ );
+
+ // Keep track of when we are busy doing a lookup in the KV map.
+ logic lookup_busy = 1'b0;
+ always_ff @(posedge eth_rx.clk) begin : lookup_busy_ff
+ if (eth_rx.rst)
+ lookup_busy <= 1'b0;
+ else begin
+ if (lookup_stb)
+ lookup_busy <= 1'b1;
+ else if (lookup_done_stb)
+ lookup_busy <= 1'b0;
+ end
+ end
+
+ // Determine if we can use the output of the lookup_fifo to do a KV map
+ // lookup. We only perform a KV map lookup if tdest is 0 and we can only do
+ // so if the KV map is free and the holding register for the tuser value is
+ // available.
+ always_comb lookup_epid = lookup_fifo_tepid;
+ always_comb lookup_stb = lookup_fifo_o.tvalid && !lookup_busy &&
+ !lookup_fifo_tdest && !result_tuser_valid;
+
+ // Determine if we can use the output of the lookup FIFO directly (no lookup
+ // is needed). We can only use it if we're not already doing a KV lookup and
+ // if the holding register for the tuser value is available.
+ always_comb non_lookup_done_stb = lookup_fifo_o.tvalid && !lookup_busy &&
+ lookup_fifo_tdest && !result_tuser_valid;
+
+ // Pop the routing info off of the lookup_fifo if we've started its lookup
+ always_comb lookup_fifo_o.tready = lookup_stb || non_lookup_done_stb;
+
+ // Track when the next data_fifo_o word is the start of a new packet
+ always_ff @(posedge eth_rx.clk) begin : data_fifo_o_hdr_ff
+ if (eth_rx.rst)
+ data_fifo_o_hdr <= 1'b1;
+ else if (data_fifo_o.tvalid && data_fifo_o.tready && pass_packet)
+ data_fifo_o_hdr <= data_fifo_o.tlast;
+ end
+
+ // Store the lookup result in a holding register. This can come from the KV
+ // map or the incoming tuser.
+ always_ff @(posedge eth_rx.clk) begin : result_tuser_ff
+ if (eth_rx.rst) begin
+ result_tuser <= {USER_META_W{1'bX}}; // Don't care
+ result_tuser_valid <= 1'b0;
+ end else begin
+ // The tuser holding register becomes available as soon as we start
+ // transmitting the corresponding packet.
+ if (data_fifo_o.tvalid && data_fifo_o.tready && data_fifo_o_hdr && pass_packet) begin
+ result_tuser_valid <= 1'b0;
+ end
+
+ // Load the result of the lookup
+ if (lookup_done_stb) begin
+ result_tuser <= lookup_result_match ? lookup_result_value : {USER_META_W{1'b0}};
+ result_tuser_valid <= 1'b1;
+ end else if (non_lookup_done_stb) begin
+ result_tuser <= lookup_fifo_tuser;
+ result_tuser_valid <= 1'b1;
+ end
+ end
+ end
+
+ // Control when the packet from the data_fifo can be passed through. Put the
+ // tuser value into a register for the duration of the packet.
+ always_ff @(posedge eth_rx.clk) begin : pass_packet_ff
+ if (eth_rx.rst) begin
+ pass_packet <= 1'b0;
+ reg_o_tuser <= {USER_META_W{1'bX}}; // Don't care
+ end else begin
+ // We're done passing through a packet when tlast goes out
+ if (data_fifo_o.tvalid && data_fifo_o.tready && data_fifo_o.tlast && pass_packet) begin
+ pass_packet <= 1'b0;
+ end
+
+ // We can pass the next packet through when we're at the start of a
+ // packet and we have the tuser value waiting in the holding register.
+ if (data_fifo_o_hdr && result_tuser_valid && !pass_packet) begin
+ reg_o_tuser <= result_tuser;
+ pass_packet <= 1'b1;
+ end
+ end
+ end
+
+ // Device addresses
+ logic [15:0] au_udp_dst;
+ logic [31:0] au_ip_dst;
+ logic [47:0] au_mac_dst;
+
+ AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.TKEEP(0),.TUSER(0))
+ au(eth_rx.clk,eth_rx.rst);// Add UDP input
+ always_comb begin
+ {au_udp_dst,au_ip_dst,au_mac_dst} = reg_o_tuser;
+ au.tdata = data_fifo_o.tdata;
+ au.tlast = data_fifo_o.tlast;
+ au.tvalid = data_fifo_o.tvalid & pass_packet;
+ data_fifo_o.tready = au.tready & pass_packet;
+ end
+
+ // add the UDP header back on before sending to EthTx
+ eth_ipv4_add_udp #(
+ .PREAMBLE_BYTES(PREAMBLE_BYTES),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES)
+ ) add_udp_i (
+ .i(au), .o(eth_tx),
+ .mac_src(my_mac),
+ .ip_src(my_ip),
+ .udp_src(my_udp_chdr_port),
+ .mac_dst(au_mac_dst),
+ .ip_dst(au_ip_dst),
+ .udp_dst(au_udp_dst)
+ );
+
+
+endmodule : chdr_xport_adapter
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_constants.vh b/fpga/usrp3/lib/rfnoc/xport_sv/eth_constants.vh
new file mode 100644
index 000000000..371b46cf5
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_constants.vh
@@ -0,0 +1,76 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: eth_constants (Header File)
+//
+// Description:
+// Constants for ethernet
+
+//---------------------------------------
+// Ethernet constants
+//---------------------------------------
+localparam [47:0] ETH_ADDR_BCAST = {48{1'b1}};
+localparam [15:0] ETH_TYPE_IPV4 = 16'h0800;
+localparam [7:0] IPV4_PROTO_UDP = 8'h11;
+localparam [7:0] IPV4_LEN5 = 8'h45;
+
+// tUser conventions
+localparam BYTES_MSB = ENET_USER_W-2;
+localparam ERROR_BIT = ENET_USER_W-1;
+localparam SOF_BIT = ENET_USER_W-1;
+
+//---------------------------------------
+// Ethernet byte positions
+//---------------------------------------
+// Bytes 7-0-------------------------
+// | DstMAC_HI (16) | Preamble (48) |
+// ----------------------------------
+localparam PREAMBLE_BYTE = 0;
+localparam PREAMBLE_END = 5;
+localparam ETH_HDR_BYTE = 0;
+localparam DST_MAC_BYTE = ETH_HDR_BYTE+0;
+// Bytes 15-8-------------------------
+// | SrcMAC_HI (32) | DstMAC_LO (32) |
+// -----------------------------------
+localparam SRC_MAC_BYTE = ETH_HDR_BYTE+6;
+// Bytes 23-16---------------------------------------
+// | IPv4_Line0 (32)| EthType (16) | SrcMAC_LO (16) |
+// --------------------------------------------------
+localparam ETH_TYPE_BYTE = ETH_HDR_BYTE+12;
+localparam ETH_PAYLOAD_BYTE = ETH_HDR_BYTE+14;
+// Bytes 31-24--------------------------
+// | IPv4_Line2 (32) | IPv4_Line1 (32) |
+// -------------------------------------
+localparam IPV4_HDR_BYTE = ETH_PAYLOAD_BYTE;
+localparam IP_VERSION_BYTE = IPV4_HDR_BYTE+0;
+localparam IP_DSCP_BYTE = IPV4_HDR_BYTE+1;
+localparam IP_LENGTH_BYTE = IPV4_HDR_BYTE+2;
+localparam IP_ID_BYTE = IPV4_HDR_BYTE+4;
+localparam IP_FRAG_BYTE = IPV4_HDR_BYTE+6;
+localparam IP_TTL_BYTE = IPV4_HDR_BYTE+8;
+localparam PROTOCOL_BYTE = IPV4_HDR_BYTE+9;
+localparam IP_CHECKSUM_BYTE = IPV4_HDR_BYTE+10;
+// Bytes 39-32------------------------
+// | IPDstAddr (32) | IPSrcAddr (32) |
+// -----------------------------------
+localparam SRC_IP_BYTE = IPV4_HDR_BYTE+12;
+localparam DST_IP_BYTE = IPV4_HDR_BYTE+16;
+localparam IPV4_PAYLOAD_BYTE = IPV4_HDR_BYTE+20;
+// Bytes 48-40------------------------------------------------
+// | Chksum (16) | Length (16) | DstPort (16) | SrcPort (16) |
+// -----------------------------------------------------------
+localparam UDP_HDR_BYTE = IPV4_PAYLOAD_BYTE;
+localparam SRC_PORT_BYTE = UDP_HDR_BYTE+0;
+localparam DST_PORT_BYTE = UDP_HDR_BYTE+2;
+localparam UDP_LENGTH_BYTE = UDP_HDR_BYTE+4;
+localparam UDP_CHECKSUM_BYTE = UDP_HDR_BYTE+6;
+localparam UDP_END = UDP_CHECKSUM_BYTE+1; // last byte in the UDP header
+
+localparam MIN_PACKET_SIZE_BYTE = 63;
+//---------------------------------------
+// CHDR_BYTE_POSITION
+//---------------------------------------
+localparam CHDR_HDR_BYTE = 0;
+localparam CHDR_LENGTH_BYTE = CHDR_HDR_BYTE+2;
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/.gitignore b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/.gitignore
new file mode 100644
index 000000000..0eb397000
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/.gitignore
@@ -0,0 +1,3 @@
+wave.do
+*.dcp
+*.rpt
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/Makefile b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/Makefile
new file mode 100644
index 000000000..b2cc6908e
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/Makefile
@@ -0,0 +1,81 @@
+#
+# Copyright 2020 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
+#-------------------------------------------------
+SYNTH_DUT = eth_ifc_synth_test
+
+# Include makefiles and sources for the DUT and its dependencies
+include $(BASE_DIR)/../lib/control/Makefile.srcs
+include $(BASE_DIR)/../lib/axi/Makefile.srcs
+include $(BASE_DIR)/../lib/axi4s_sv/Makefile.srcs
+include $(BASE_DIR)/../lib/packet_proc/Makefile.srcs
+include $(BASE_DIR)/../lib/xge_interface/Makefile.srcs
+include $(BASE_DIR)/../lib/fifo/Makefile.srcs
+include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs
+include $(BASE_DIR)/../lib/rfnoc/xport/Makefile.srcs
+include $(BASE_DIR)/../lib/rfnoc/xport_sv/Makefile.srcs
+include $(BASE_DIR)/../lib/rfnoc/crossbar/Makefile.srcs
+include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs
+
+BUILD_IP_DIR = $(abspath $(BASE_DIR)/x300/build-ip/xc7k410tffg900-2)
+
+IP_SRC = \
+$(abspath $(BUILD_IP_DIR)/fifo_short_2clk/fifo_short_2clk_sim_netlist.v)
+# Xilinx IP wants lots of libraries
+MODELSIM_LIBS += secureip unimacro_ver unisims_ver xilinx_vip xpm
+MODELSIM_ARGS += glbl -t 1fs
+
+DESIGN_SRCS = $(abspath \
+eth_ifc_synth_test.sv \
+$(FIFO_SRCS) \
+$(CONTROL_LIB_SRCS) \
+$(AXI_SRCS) \
+$(AXI4S_SV_SRCS) \
+$(XGE_INTERFACE_SRCS) \
+$(PACKET_PROC_SRCS) \
+$(RFNOC_UTIL_SRCS) \
+$(RFNOC_XPORT_SRCS) \
+$(RFNOC_XPORT_SV_SRCS) \
+$(RFNOC_XBAR_SRCS) \
+$(RFNOC_CORE_SRCS) \
+$(IP_SRC) \
+)
+
+#-------------------------------------------------
+# Testbench Specific
+#-------------------------------------------------
+# Define only one toplevel module
+TB_TOP_MODULE ?= eth_ifc_all_tb
+#TB_TOP_MODULE ?= eth_ifc_synth_test
+SIM_TOP = $(TB_TOP_MODULE)
+
+SIM_SRCS = \
+$(abspath eth_ifc_tb.sv) \
+$(abspath $(TB_TOP_MODULE).sv)
+
+# supressing 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/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_all_tb.sv b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_all_tb.sv
new file mode 100644
index 000000000..f849bc8af
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_all_tb.sv
@@ -0,0 +1,24 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: eth_ifc_all_tb
+//
+// Description: Testbench for eth_ifc
+//
+
+module eth_ifc_all_tb #(
+ /* no PARAM */
+)(
+ /* no IO */
+);
+
+ eth_ifc_tb #(.TEST_NAME("ORIGINAL"),.ENET_W(64),.CPU_W(64),.CHDR_W(64),.SV_ETH_IFC(0)) ORIGINAL ();
+
+ eth_ifc_tb #(.TEST_NAME("64B"),.ENET_W(64),.CPU_W(64),.CHDR_W(64)) ETH64 ();
+ eth_ifc_tb #(.TEST_NAME("512_64B"),.ENET_W(512),.CPU_W(64),.CHDR_W(64)) ETH512_CHDR64 ();
+ eth_ifc_tb #(.TEST_NAME("512_128B"),.ENET_W(512),.CPU_W(64),.CHDR_W(128)) ETH512_CHDR128 ();
+ eth_ifc_tb #(.TEST_NAME("512B"),.ENET_W(512),.CPU_W(64),.CHDR_W(512)) ETH512_CHDR512 ();
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_synth_test.sv b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_synth_test.sv
new file mode 100644
index 000000000..f37a23383
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_synth_test.sv
@@ -0,0 +1,161 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+//
+// Module: eth_ifc_synth_test
+//
+// Description: Wrapper to test size of 64 bit version
+//
+// Parameters: -- get descriptions from eth_ipv4_interface
+// PROTOVER -
+// MTU - Max Packet Size
+// NODE_INST -
+// RT_TBL_SIZE -
+// REG_AWIDTH -
+// BASE -
+// EWIDTH - Ethernet Width
+// CWIDTH - CPU Width
+// VWIDTH - CHDR Width
+
+module eth_ifc_synth_test #(
+ parameter [15:0] PROTOVER = {8'd1, 8'd0},
+ parameter MTU = 10,
+ parameter NODE_INST = 0,
+ parameter RT_TBL_SIZE = 6,
+ parameter REG_AWIDTH = 14,
+ parameter BASE = 0,
+ parameter EWIDTH = 512,
+ parameter CWIDTH = 64,
+ parameter VWIDTH = 128
+) (
+ input logic bus_clk,
+ input logic bus_rst,
+ input logic [15:0] device_id,
+
+ // Register port: Write port (domain: clk)
+ input logic reg_wr_req,
+ input logic [REG_AWIDTH-1:0] reg_wr_addr,
+ input logic [31:0] reg_wr_data,
+
+ // Register port: Read port (domain: clk)
+ input logic reg_rd_req,
+ input logic [REG_AWIDTH-1:0] reg_rd_addr,
+ output logic reg_rd_resp,
+ output logic [31:0] reg_rd_data,
+
+ // Status ports (domain: clk)
+ output logic [47:0] my_mac,
+ output logic [31:0] my_ip,
+ output logic [15:0] my_udp_chdr_port,
+
+ // Ethernet ports
+ output logic [EWIDTH-1:0] eth_tx_tdata,
+ output logic [$clog2(EWIDTH/8):0] eth_tx_tuser,
+ output logic eth_tx_tlast,
+ output logic eth_tx_tvalid,
+ input logic eth_tx_tready,
+ input logic [EWIDTH-1:0] eth_rx_tdata,
+ input logic [$clog2(EWIDTH/8):0] eth_rx_tuser,
+ input logic eth_rx_tlast,
+ input logic eth_rx_tvalid,
+ output logic eth_rx_tready,
+
+ // CHDR router interface
+ output logic [VWIDTH-1:0] e2v_tdata,
+ output logic e2v_tlast,
+ output logic e2v_tvalid,
+ input logic e2v_tready,
+ input logic [VWIDTH-1:0] v2e_tdata,
+ input logic v2e_tlast,
+ input logic v2e_tvalid,
+ output logic v2e_tready,
+
+ // CPU
+ output logic [CWIDTH-1:0] e2c_tdata,
+ output logic [$clog2(CWIDTH/8):0] e2c_tuser,
+ output logic e2c_tlast,
+ output logic e2c_tvalid,
+ input logic e2c_tready,
+ input logic [CWIDTH-1:0] c2e_tdata,
+ input logic [$clog2(CWIDTH/8):0] c2e_tuser,
+ input logic c2e_tlast,
+ input logic c2e_tvalid,
+ output logic c2e_tready
+
+ );
+
+ localparam MAX_PACKET_BYTES = 2**16;
+ localparam ENET_W=EWIDTH;
+ localparam ENET_USER_W=$clog2(EWIDTH/8)+1;
+ localparam CHDR_W=VWIDTH;
+ localparam CHDR_USER_W=$clog2(VWIDTH/8)+1;
+ localparam CPU_W=CWIDTH;
+ localparam CPU_USER_W=$clog2(CWIDTH/8)+1;
+
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ eth_tx (bus_clk, bus_reset);
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ eth_rx (bus_clk, bus_reset);
+
+ AxiStreamIf #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(CHDR_USER_W),.TKEEP(0),.TUSER(0))
+ v2e (bus_clk, bus_reset);
+ AxiStreamIf #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(CHDR_USER_W),.TKEEP(0),.TUSER(0))
+ e2v (bus_clk, bus_reset);
+
+ AxiStreamIf #(.DATA_WIDTH(CPU_W),.USER_WIDTH(CPU_USER_W),.TKEEP(0))
+ c2e (bus_clk, bus_reset);
+ AxiStreamIf #(.DATA_WIDTH(CPU_W),.USER_WIDTH(CPU_USER_W),.TKEEP(0))
+ e2c (bus_clk, bus_reset);
+
+ assign eth_tx_tdata = eth_tx.tdata;
+ assign eth_tx_tuser = eth_tx.tuser;
+ assign eth_tx_tlast = eth_tx.tlast;
+ assign eth_tx_tvalid = eth_tx.tvalid;
+ assign eth_tx.tready = eth_tx_tready;
+
+ assign eth_rx.tdata = eth_rx_tdata;
+ assign eth_rx.tuser = eth_rx_tuser;
+ assign eth_rx.tlast = eth_rx_tlast;
+ assign eth_rx.tvalid = eth_rx_tvalid;
+ assign eth_rx_tready = eth_rx.tready;
+
+ assign e2v_tdata = e2v.tdata;
+ assign e2v_tlast = e2v.tlast;
+ assign e2v_tvalid = e2v.tvalid;
+ assign e2v.tready = e2v_tready;
+
+ assign v2e.tdata = v2e_tdata;
+ assign v2e.tlast = v2e_tlast;
+ assign v2e.tvalid = v2e_tvalid;
+ assign v2e_tready = v2e.tready;
+
+ assign e2c_tdata = e2c.tdata;
+ assign e2c_tuser = e2c.tuser;
+ assign e2c_tlast = e2c.tlast;
+ assign e2c_tvalid = e2c.tvalid;
+ assign e2c.tready = e2c_tready;
+
+ assign c2e.tdata = c2e_tdata;
+ assign c2e.tuser = c2e_tuser;
+ assign c2e.tlast = c2e_tlast;
+ assign c2e.tvalid = c2e_tvalid;
+ assign c2e_tready = c2e.tready;
+
+ eth_ipv4_interface #(
+ .PROTOVER(PROTOVER), .MTU(MTU), .NODE_INST(NODE_INST),
+ .REG_AWIDTH(REG_AWIDTH), .RT_TBL_SIZE(RT_TBL_SIZE),
+ .BASE(BASE),
+ .DROP_UNKNOWN_MAC(0),
+ .DROP_MIN_PACKET(0),
+ .PREAMBLE_BYTES(0),
+ .ADD_SOF(0),
+ .SYNC(0),
+ .ENET_W(EWIDTH),.CPU_W(CWIDTH),.CHDR_W(VWIDTH)
+ ) eth_interface (
+ .* );
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_tb.sv b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_tb.sv
new file mode 100644
index 000000000..6241da9a5
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_tb.sv
@@ -0,0 +1,1084 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: eth_ifc_tb
+//
+// Description: Testbench for eth_interface
+//
+
+module eth_ifc_tb #(
+ parameter TEST_NAME = "eth_ifc_tb",
+ parameter SV_ETH_IFC =1,
+ parameter ENET_W =64,
+ parameter CPU_W =64,
+ parameter CHDR_W =64
+)(
+ /* no IO */
+);
+ // Include macros and time declarations for use with PkgTestExec
+ `define TEST_EXEC_OBJ test
+ `include "test_exec.svh"
+ import PkgAxiStreamBfm::*;
+ import PkgTestExec::*;
+ import PkgChdrUtils::*;
+ import PkgChdrBfm::*;
+ import PkgEthernet::*;
+
+ //---------------------------------------------------------------------------
+ // Local Parameters
+ //---------------------------------------------------------------------------
+ localparam ENET_USER_W = $clog2(ENET_W/8)+1;
+ localparam CPU_USER_W = $clog2(CPU_W/8)+1;
+ localparam CHDR_USER_W = $clog2(CHDR_W/8)+1;
+ localparam REG_AWIDTH = 14;
+ localparam [15:0] PROTOVER = {8'd1, 8'd0};
+ localparam MTU = 10;
+ localparam NODE_INST = 0;
+ localparam RT_TBL_SIZE = 6;
+ localparam REG_BASE_ETH_SWITCH = 14'h1000;
+ localparam BASE = REG_BASE_ETH_SWITCH;
+ localparam DROP_MIN_PACKET = 0;
+ localparam SYNC = (ENET_W==512) ? 0:1;
+ localparam ETH_PERIOD = (ENET_W==512) ? 3.1:5.0;
+ // can set PREAMBLE_BYTES to 0 or 6 if SV_ETH_IFC, but otherwise
+ // it's hardcoded to 6. (0 is normal for 100G)(6 is normal for old Xge)
+ localparam PREAMBLE_BYTES = SV_ETH_IFC ? 0 : 6;
+ // Include for register offsets
+ `include "../eth_regs.vh"
+ // allows the DUT to push full words and tb does not check tuser/tkeep of packets it's transmitting
+ localparam IGNORE_EXTRA_DATA = 1;
+
+ //---------------------------------------------------------------------------
+ // Clocks
+ //---------------------------------------------------------------------------
+
+ bit clk;
+ bit reset;
+
+ sim_clock_gen #(.PERIOD(5.0), .AUTOSTART(1))
+ clk_gen (.clk(clk), .rst(reset));
+ sim_clock_gen #(.PERIOD(ETH_PERIOD), .AUTOSTART(1))
+ eth_clk_gen (.clk(eth_clk), .rst(eth_reset));
+
+ //---------------------------------------------------------------------------
+ // Bus Functional Models
+ //---------------------------------------------------------------------------
+ TestExec test = new();
+
+ localparam MAX_PACKET_BYTES = 2**16;
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ eth_tx (eth_clk, eth_reset);
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ eth_rx (eth_clk, eth_reset);
+
+ AxiStreamIf #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(CHDR_USER_W),.TKEEP(0),.TUSER(0))
+ v2e (clk, reset);
+ AxiStreamIf #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(CHDR_USER_W),.TKEEP(0),.TUSER(0))
+ e2v (clk, reset);
+
+ AxiStreamIf #(.DATA_WIDTH(CPU_W),.USER_WIDTH(CPU_USER_W),.TKEEP(0))
+ c2e (clk, reset);
+ AxiStreamIf #(.DATA_WIDTH(CPU_W),.USER_WIDTH(CPU_USER_W),.TKEEP(0))
+ e2c (clk, reset);
+
+ // Bus functional model for a axi_stream controller
+ AxiStreamBfm #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES)) eth =
+ new(.master(eth_rx), .slave(eth_tx));
+ AxiStreamBfm #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(CHDR_USER_W),.TKEEP(0),.TUSER(0)) v =
+ new(.master(v2e), .slave(e2v));
+ AxiStreamBfm #(.DATA_WIDTH(CPU_W),.USER_WIDTH(CPU_USER_W),.TKEEP(0)) cpu =
+ new(.master(c2e), .slave(e2c));
+
+ //----------------------------------------------------
+ // Instantiate DUT
+ //----------------------------------------------------
+
+ reg reg_wr_req = 1'b0;
+ reg [REG_AWIDTH-1:0] reg_wr_addr = 0;
+ reg [31:0] reg_wr_data = 32'd0;
+
+ reg reg_rd_req = 1'b0;
+ reg [REG_AWIDTH-1:0] reg_rd_addr = 0;
+ reg reg_rd_resp;
+ reg [31:0] reg_rd_data;
+
+ reg [3:0] eth_tx_tuser = 4'd0;
+ reg [3:0] eth_rx_tuser = 4'd0;
+ reg [3:0] e2c_tuser = 4'd0;
+ reg [3:0] c2e_tuser = 4'd0;
+
+ reg [15:0] device_id =16'd0;
+
+ reg [47:0] my_mac;
+ reg [31:0] my_ip;
+ reg [15:0] my_udp_chdr_port;
+
+
+ if (SV_ETH_IFC) begin : gen_new_dut
+
+ eth_ipv4_interface #(
+ .PREAMBLE_BYTES(PREAMBLE_BYTES),
+ .PROTOVER(PROTOVER), .MTU(MTU), .NODE_INST(NODE_INST),
+ .REG_AWIDTH(REG_AWIDTH), .RT_TBL_SIZE(RT_TBL_SIZE),
+ .BASE(BASE),.SYNC(SYNC),
+ .ENET_W(ENET_W),.CPU_W(CPU_W),.CHDR_W(CHDR_W)
+ ) eth_interface (
+ .bus_clk(clk),.bus_rst(reset),.*
+ );
+ end else begin : gen_old_dut
+ logic [63:0] eth_tx_tdata;
+ logic [3:0] eth_tx_tuser;
+ logic eth_tx_tlast;
+ logic eth_tx_tvalid;
+ logic eth_tx_tready;
+
+ logic [63:0] eth_rx_tdata;
+ logic [3:0] eth_rx_tuser;
+ logic eth_rx_tlast;
+ logic eth_rx_tvalid;
+ logic eth_rx_tready;
+
+ logic [63:0] e2v_tdata;
+ logic e2v_tlast;
+ logic e2v_tvalid;
+ logic e2v_tready;
+
+ logic [63:0] v2e_tdata;
+ logic v2e_tlast;
+ logic v2e_tvalid;
+ logic v2e_tready;
+
+ logic [63:0] e2c_tdata;
+ logic [3:0] e2c_tuser;
+ logic e2c_tlast;
+ logic e2c_tvalid;
+ logic e2c_tready;
+
+ logic [63:0] c2e_tdata;
+ logic [3:0] c2e_tuser;
+ logic c2e_tlast;
+ logic c2e_tvalid;
+ logic c2e_tready;
+
+ always_comb begin
+ eth_tx.tdata = eth_tx_tdata;
+ eth_tx.tuser = eth_tx_tuser;
+ eth_tx.tlast = eth_tx_tlast;
+ eth_tx.tvalid = eth_tx_tvalid;
+ eth_tx_tready = eth_tx.tready;
+
+ eth_rx_tdata = eth_rx.tdata;
+ eth_rx_tuser = eth_rx.tuser;
+ eth_rx_tlast = eth_rx.tlast;
+ eth_rx_tvalid = eth_rx.tvalid;
+ eth_rx.tready = eth_rx_tready;
+
+ e2v.tdata = e2v_tdata;
+ e2v.tlast = e2v_tlast;
+ e2v.tvalid = e2v_tvalid;
+ e2v_tready = e2v.tready;
+
+ v2e_tdata = v2e.tdata;
+ v2e_tlast = v2e.tlast;
+ v2e_tvalid = v2e.tvalid;
+ v2e.tready = v2e_tready;
+
+ e2c.tdata = e2c_tdata;
+ e2c.tuser = e2c_tuser;
+ e2c.tlast = e2c_tlast;
+ e2c.tvalid = e2c_tvalid;
+ e2c_tready = e2c.tready;
+
+ c2e_tdata = c2e.tdata;
+ c2e_tuser = c2e.tuser;
+ c2e_tlast = c2e.tlast;
+ c2e_tvalid = c2e.tvalid;
+ c2e.tready = c2e_tready;
+ end
+
+ eth_interface #(
+ .PROTOVER(PROTOVER), .MTU(MTU), .NODE_INST(NODE_INST),
+ .REG_AWIDTH(REG_AWIDTH), .RT_TBL_SIZE(RT_TBL_SIZE),
+ .BASE(BASE)
+ ) eth_interface (
+ .*,
+ .my_udp_port (my_udp_chdr_port)
+ );
+ end
+ always_comb begin
+ eth_tx.tkeep = '1;
+ end
+
+ task automatic reg_wr (
+ // Register port: Write port (domain: clk)
+ input int addr,
+ input int data);
+ begin
+ @(posedge clk);
+ reg_wr_req = 1'b1;
+ reg_wr_addr = addr;
+ reg_wr_data = data;
+ @(posedge clk);
+ reg_wr_req = 1'b0;
+ end
+ endtask
+
+ task automatic reg_rd_check (
+ // Register port: Write port (domain: clk)
+ input int addr,
+ input int data);
+ begin
+ @(posedge clk);
+ reg_rd_req = 1'b1; // drive at posedge
+ reg_rd_addr = addr;
+ @(negedge clk); // check at negedge
+ if (SV_ETH_IFC) begin
+ assert(reg_rd_resp==1'b0) else $error("resp set early");
+ end else begin
+ // The original doesn't initialize reg_rd_resp so it comes back as X on first read
+ assert(reg_rd_resp==1'b0 || $isunknown(reg_rd_resp)) else $error("resp set early");
+ end
+ @(posedge clk);
+ reg_rd_req = 1'b0;
+ @(negedge clk);
+ assert(data==reg_rd_data) else $error("read data didn't match");
+ assert(reg_rd_resp==1'b1) else $error("resp didn't pulse");
+ @(posedge clk);
+ @(negedge clk);
+ assert(reg_rd_resp==1'b0) else $error("resp set late");
+ end
+ endtask
+
+
+ //---------------------------------------------------------------------------
+ // Reset
+ //---------------------------------------------------------------------------
+
+ task test_reset();
+ wait(!reset);
+ repeat (10) @(posedge clk);
+ test.end_test();
+ endtask : test_reset
+
+ //---------------------------------------------------------------------------
+ // Test Registers
+ //---------------------------------------------------------------------------
+
+ task test_registers();
+ test.start_test({TEST_NAME,"Test/Setup Registers"}, 10us);
+ // DEF_DEST_MAC/IP/UDP are defined in the
+ // sim_ethernet_lib.svh, as the destination
+ // addresses. Using the defaults means
+ // if I don't change the dest address on
+ // a packet it will go to the CHDR
+ reg_wr(REG_MAC_LSB,DEF_DEST_MAC_ADDR[31:0]);
+ reg_wr(REG_MAC_MSB,DEF_DEST_MAC_ADDR[47:32]);
+ reg_wr(REG_IP,DEF_DEST_IP_ADDR);
+ reg_wr(REG_UDP,DEF_DEST_UDP_PORT);
+
+ repeat (3) @(posedge clk);
+
+ `ASSERT_ERROR(my_mac==DEF_DEST_MAC_ADDR, "my mac mismatched!");
+ `ASSERT_ERROR(my_ip==DEF_DEST_IP_ADDR, "my ip mismatched!");
+ `ASSERT_ERROR(my_udp_chdr_port==DEF_DEST_UDP_PORT,"my udp mismatched!");
+
+ reg_wr(REG_BRIDGE_ENABLE,1);
+ reg_wr(REG_BRIDGE_MAC_LSB,DEF_BRIDGE_MAC_ADDR[31:0]);
+ reg_wr(REG_BRIDGE_MAC_MSB,DEF_BRIDGE_MAC_ADDR[47:32]);
+ reg_wr(REG_BRIDGE_IP,DEF_BRIDGE_IP_ADDR);
+ reg_wr(REG_BRIDGE_UDP,DEF_BRIDGE_UDP_PORT);
+
+ repeat (3) @(posedge clk);
+ `ASSERT_ERROR(my_mac==DEF_BRIDGE_MAC_ADDR, "my mac mismatched!");
+ `ASSERT_ERROR(my_ip==DEF_BRIDGE_IP_ADDR, "my ip mismatched!");
+ `ASSERT_ERROR(my_udp_chdr_port==DEF_BRIDGE_UDP_PORT,"my udp mismatched!");
+
+ reg_wr(REG_BRIDGE_ENABLE,0);
+
+ // Readback the values
+ reg_rd_check(REG_MAC_LSB,DEF_DEST_MAC_ADDR[31:0]);
+ reg_rd_check(REG_MAC_MSB,DEF_DEST_MAC_ADDR[47:32]);
+ reg_rd_check(REG_IP,DEF_DEST_IP_ADDR);
+ reg_rd_check(REG_UDP,DEF_DEST_UDP_PORT);
+ reg_rd_check(REG_BRIDGE_ENABLE,0);
+ reg_rd_check(REG_BRIDGE_MAC_LSB,DEF_BRIDGE_MAC_ADDR[31:0]);
+ reg_rd_check(REG_BRIDGE_MAC_MSB,DEF_BRIDGE_MAC_ADDR[47:32]);
+ reg_rd_check(REG_BRIDGE_IP,DEF_BRIDGE_IP_ADDR);
+ reg_rd_check(REG_BRIDGE_UDP,DEF_BRIDGE_UDP_PORT);
+
+ test.end_test();
+ endtask : test_registers
+
+ //---------------------------------------------------------------------------
+ // Ethernet to CPU test
+ //---------------------------------------------------------------------------
+ typedef ChdrData #(CHDR_W)::chdr_word_t chdr_word_t;
+ typedef chdr_word_t word_queue_t[$];
+
+ typedef XportStreamPacket #(ENET_W) EthXportPacket_t;
+ typedef AxiStreamPacket #(ENET_W,ENET_USER_W) EthAxisPacket_t;
+
+ typedef XportStreamPacket #(CPU_W) CpuXportPacket_t;
+ typedef AxiStreamPacket #(CPU_W,CPU_USER_W) CpuAxisPacket_t;
+
+ typedef XportStreamPacket #(CHDR_W) ChdrXportPacket_t;
+ typedef AxiStreamPacket #(CHDR_W,CHDR_USER_W) ChdrAxisPacket_t;
+ typedef ChdrPacket #(CHDR_W,CHDR_USER_W) ChdrPacket_t;
+
+ task automatic test_ethcpu(int num_samples[$], int ERROR_PROB=2);
+ TestExec test_e2c = new();
+ automatic EthXportPacket_t send[$];
+ automatic CpuXportPacket_t expected[$];
+ automatic int sample_sum = 0;
+
+ test_e2c.start_test({TEST_NAME,"Ethernet to CPU"}, 60us);
+ // This path is
+ // eth_rx -> s_mac(eth_adapter) -> s_mac(eth_dispatch) ->
+ //// in_reg(AXI_FIFO)(SIZE=1)
+ // (eth_dispatch) in -> STATMACHINE (Dispatch) + cpu ->
+ //// out_reg_cpu(AXI_FIFO)(SIZE=1)
+ // (eth_dispatch) o_cpu ->
+ //// cpu_out_gate(AXI_GATE)(SIZE=11)
+ // (eth_dispatch) m_cpu -> (eth_adapter) e2c_chdr -> e2c_fifo
+ //// cpu_fifo(AXI_FIFO)(SIZE=CPU_FIFO_SIZE)
+ // (eth_adapater) m_cpu -> e2c
+
+ foreach (num_samples[i]) begin
+ automatic eth_hdr_t eth_hdr;
+ automatic ipv4_hdr_t ipv4_hdr;
+ automatic udp_hdr_t udp_hdr;
+ automatic raw_pkt_t pay,udp_raw;
+ automatic int preamble;
+
+ if (PREAMBLE_BYTES == 6) preamble = NORMAL_PREAMBLE;
+ else if (PREAMBLE_BYTES == 0) preamble = NO_PREAMBLE;
+ else $fatal("Invalid PREAMBLE_BYTES");
+
+ expected[i] = new;
+ send[i] = new;
+
+ udp_hdr.dest_port = 0; //change dest port from default so it goes to cpu
+ 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];
+ udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,pay,
+ .preamble(preamble));
+ send[i].push_bytes(udp_raw);
+ send[i].tkeep_to_tuser(.ERROR_PROB(ERROR_PROB));
+
+ // rebuild the expected packet for comparison without the preamble
+ udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,pay,
+ .preamble(NO_PREAMBLE));
+ expected[i].push_bytes(udp_raw);
+ expected[i].tkeep_to_tuser();
+
+ end
+
+ // iterate in descending order so deleting doesn't shift down
+ // the packets in future loop iterations.
+ for (int i= num_samples.size()-1 ; i >= 0; i--) begin
+ // original only checks for errors in last word.
+ if (!SV_ETH_IFC && send[i].has_error()) send[i].set_error();
+ // If a packet has an error it shouldn't make it through
+ if (send[i].has_error()) expected.delete(i); // MAC ERROR
+ else if (DROP_MIN_PACKET) begin
+ if (send[i].byte_length()-PREAMBLE_BYTES < 64) begin
+ // short packet rejection feature broken in original
+ if (SV_ETH_IFC) expected.delete(i); // TOO SHORT
+ end
+ end
+
+ end
+
+ fork
+ begin // tx_thread
+ foreach(send[i])begin
+ #1 eth.put(send[i]);
+ end
+ end
+ begin //rx_thread
+ foreach(expected[i]) begin
+ automatic CpuAxisPacket_t actual_a;
+ automatic CpuXportPacket_t actual = new();
+ cpu.get(actual_a);
+ actual.import_axis(actual_a);
+ actual.tuser_to_tkeep();
+// $display({"****************::%s::**********************\n",
+// "**send** \n%s\n**expected**\n%s\n**actual**\n%s \n".
+// "%d byte with error %d"},
+// TEST_NAME,
+// send[i].sprint(),expected[i].sprint(),actual.sprint(),
+// num_samples[i],send[i].has_error());
+
+ `ASSERT_ERROR(!actual.compare_w_sof(expected[i]),"failed to send packet to e2c");
+ end
+ end
+ join
+
+ test_e2c.end_test();
+ endtask : test_ethcpu
+
+ task automatic wait_for_udp_packets(int udp_dest_port);
+ automatic EthAxisPacket_t actual_a;
+ automatic raw_pkt_t rcv_raw,rcv_pay;
+ automatic udp_hdr_t rcv_udp;
+ automatic eth_hdr_t rcv_eth;
+ automatic ipv4_hdr_t rcv_ip;
+ automatic int try_count = 0;
+
+ do begin
+ ++try_count;
+ // check if packet is for our port
+ #100;
+ eth.peek(actual_a);
+ rcv_raw = actual_a.dump_bytes();
+ repeat(PREAMBLE_BYTES) rcv_raw.delete(0); // strip preamble
+ decode_udp_pkt(rcv_raw,rcv_eth,rcv_ip,rcv_udp,rcv_pay);
+ `ASSERT_ERROR(try_count != 100,"unclaimed packet on c2e");
+ end while (rcv_udp.dest_port != udp_dest_port);
+
+ endtask : wait_for_udp_packets
+
+
+ task automatic test_cpueth(int num_samples[$]);
+ TestExec test_c2e = new();
+ automatic CpuXportPacket_t send[$];
+ automatic EthXportPacket_t expected[$];
+ automatic int sample_sum = 0;
+
+ test_c2e.start_test({TEST_NAME,"CPU to Ethernet"}, 60us);
+ // This path is
+ // c2e -> (eth_adapter) s_cpu ->
+ //// (ARM_DEFRAMER)(IF ARM)
+ // (eth_adapater) c2e ->
+ //// (ETH_MUX)(SIZE=2)
+ // (eth_adapater) m_mac -> eth_tx
+
+ foreach (num_samples[i]) begin
+ automatic eth_hdr_t eth_hdr;
+ automatic ipv4_hdr_t ipv4_hdr;
+ automatic udp_hdr_t udp_hdr;
+ automatic raw_pkt_t pay,udp_raw;
+ automatic int preamble;
+
+ expected[i] = new;
+ send[i] = new;
+
+ if (PREAMBLE_BYTES == 6) preamble = ZERO_PREAMBLE;
+ else if (PREAMBLE_BYTES == 0) preamble = NO_PREAMBLE;
+ else $fatal("Invalid PREAMBLE_BYTES");
+
+ 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];
+ udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,pay,
+ .preamble(NO_PREAMBLE));
+ send[i].push_bytes(udp_raw);
+ send[i].tkeep_to_tuser();
+
+ // rebuild the expected packet for comparison with a zero preamble
+ udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,pay,
+ .preamble(preamble));
+ if (SV_ETH_IFC) begin
+ while (udp_raw.size < 64) begin
+ udp_raw.push_back(0);
+ end
+ end;
+ expected[i].push_bytes(udp_raw);
+ expected[i].tkeep_to_tuser();
+ end
+
+ fork
+ begin // tx_thread
+ foreach(send[i])begin
+ cpu.put(send[i]);
+ end
+ end
+ begin //rx_thread
+ foreach(expected[i]) begin
+ automatic EthAxisPacket_t actual_a;
+ automatic EthXportPacket_t actual = new();
+ automatic raw_pkt_t rcv_raw,rcv_pay;
+ automatic udp_hdr_t rcv_udp;
+ automatic eth_hdr_t rcv_eth;
+ automatic ipv4_hdr_t rcv_ip;
+ automatic int try_count = 0;
+
+ wait_for_udp_packets(DEF_DEST_UDP_PORT);
+ eth.get(actual_a);
+ actual.import_axis(actual_a);
+ actual.tuser_to_tkeep();
+ `ASSERT_ERROR(!actual.compare_w_pad(expected[i],!SV_ETH_IFC),"failed to send packet to c2e");
+ end
+ end
+ join
+ test_c2e.end_test();
+
+ endtask : test_cpueth
+ //---------------------------------------------------------------------------
+ // Ethernet to CHDR test
+ //---------------------------------------------------------------------------
+
+ function automatic word_queue_t bytes_to_words(raw_pkt_t pay);
+ automatic ChdrXportPacket_t axis_pkt = new();
+
+ axis_pkt.push_bytes(pay);
+ return axis_pkt.data;
+
+ endfunction : bytes_to_words;
+
+ function automatic raw_pkt_t flatten_chdr(ChdrPacket_t chdr_pkt);
+ automatic ChdrAxisPacket_t axis_chdr;
+ automatic ChdrXportPacket_t xport_chdr = new();
+ axis_chdr = chdr_pkt.chdr_to_axis();
+ foreach (axis_chdr.data[i]) begin
+ axis_chdr.keep[i] = '1;
+ axis_chdr.user[i] = '0;
+ end
+ xport_chdr.import_axis(axis_chdr);
+ return xport_chdr.dump_bytes();
+ endfunction : flatten_chdr
+
+ function automatic ChdrPacket_t unflatten_chdr(raw_pkt_t chdr_raw);
+ automatic ChdrXportPacket_t xport_chdr = new();
+ automatic ChdrPacket_t chdr_pkt = new();
+ xport_chdr.push_bytes(chdr_raw);
+ foreach (xport_chdr.data[i]) begin
+ xport_chdr.keep[i] = '1;
+ xport_chdr.user[i] = '0;
+ end
+ chdr_pkt.axis_to_chdr(xport_chdr);
+ return chdr_pkt;
+ endfunction : unflatten_chdr
+
+ task automatic test_ethchdr(int num_samples[$], int ERROR_PROB=2);
+ TestExec test_e2v = new();
+ automatic EthXportPacket_t send[$];
+ automatic ChdrXportPacket_t expected[$];
+ automatic int sample_sum = 0;
+
+ test_e2v.start_test({TEST_NAME,"Ethernet to CHDR"}, 60us);
+ // This path is
+ // eth_rx -> s_mac(eth_adapter) -> s_mac(eth_dispatch) ->
+ //// in_reg(AXI_FIFO)(SIZE=1)
+ // (eth_dispatch) in -> STATMACHINE (Dispatch) + chdr ->
+ //// chdr_user_fifo(AXI_FIFO)(SIZE=8) (capture eth header)
+ //// chdr_out_gate(AXI_GATE)(SIZE=11)
+ // (eth_dispatch) o_chdr ->
+ //// chdr_trim(CHDR_TRIM_PAYLOAD)
+ // (eth_dispatch) m_chdr -> (eth_adapater) e2x_chdr -> (xport_adapter_gen) s_axis_xport
+ //// xport_in_swap (AXIS_DATA_SWAP)
+ // (xport_adapter_gen) i_xport ->
+ //// mgmt_ep(CHDR_MGMT_PKT_HANDLER)
+ // (xport_adapter_gen) x2d ->
+ //// rtn_demux(AXI_SWITCH) x2x(loopback) or m_axis_rfnoc
+ // (xport_adapter_gen) m_axis_rfnoc -> (eth_adapter) e2x_fifo
+ //// chdr_fifo(AXI_FIFO)(SIZE=MTU)
+ // (eth_adapater) m_chdr -> e2v
+
+ foreach (num_samples[i]) begin
+ automatic eth_hdr_t eth_hdr;
+ automatic ipv4_hdr_t ipv4_hdr;
+ automatic udp_hdr_t udp_hdr;
+ automatic raw_pkt_t pay,udp_raw,chdr_raw;
+
+ automatic ChdrPacket_t chdr_pkt = new();
+ automatic chdr_header_t chdr_hdr;
+ automatic chdr_word_t chdr_ts;
+ automatic chdr_word_t chdr_mdata[$];
+ automatic chdr_word_t chdr_data[$];
+ automatic int preamble;
+
+ expected[i] = new;
+ send[i] = new;
+
+ if (PREAMBLE_BYTES == 6) preamble = NORMAL_PREAMBLE;
+ else if (PREAMBLE_BYTES == 0) preamble = NO_PREAMBLE;
+ else $fatal("Invalid PREAMBLE_BYTES");
+
+ // build a payload
+ 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];
+ // Fill data in the chdr packet
+ chdr_hdr = '{
+ vc : 0,
+ dst_epid : 0,
+ seq_num : 0,
+ pkt_type : CHDR_DATA_NO_TS,
+ num_mdata : 0,
+ default : 0
+ };
+ chdr_ts = 0; // no timestamp
+ chdr_mdata.delete(); // not adding meta data
+ chdr_data = bytes_to_words(pay);
+
+ chdr_pkt.write_raw(chdr_hdr, chdr_data, chdr_mdata, chdr_ts);
+ chdr_raw = flatten_chdr(chdr_pkt);
+
+ //build a udp packet
+ udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,chdr_raw,
+ .preamble(preamble));
+ send[i].push_bytes(udp_raw);
+ send[i].tkeep_to_tuser(.ERROR_PROB(ERROR_PROB));
+
+ // expect just the chdr packet (UDP stripped)
+ expected[i].push_bytes(chdr_raw);
+ expected[i].tkeep_to_tuser();
+
+ end
+
+ // iterate in descending order so deleting doesn't shift down
+ // the packets in future loop iterations.
+ for (int i= num_samples.size()-1 ; i >= 0; i--) begin
+ // original only checks for errors in last word.
+ if (!SV_ETH_IFC && send[i].has_error()) send[i].set_error();
+ // If a packet has an error it shouldn't make it through
+ if (send[i].has_error()) expected.delete(i);//MAC ERROR
+ else if (DROP_MIN_PACKET) begin
+ if (send[i].byte_length()-PREAMBLE_BYTES < 64) begin
+ // short packet rejection feature broken in original
+ if (SV_ETH_IFC) expected.delete(i); // TOO SHORT
+ end
+ end
+ end
+
+ fork
+ begin // tx_thread
+ foreach(send[i])begin
+ #1 eth.put(send[i]);
+ end
+ end
+ begin //rx_thread
+ foreach(expected[i]) begin
+ automatic ChdrAxisPacket_t actual_a;
+ automatic ChdrXportPacket_t actual = new();
+ v.get(actual_a);
+ actual.import_axis(actual_a);
+ actual.tuser_to_tkeep();
+ `ASSERT_ERROR(!actual.compare_no_user(expected[i]),"failed to send packet e2v");
+ end
+ end
+ join
+ test_e2v.end_test();
+
+ endtask : test_ethchdr;
+
+ task automatic test_chdreth(int num_samples[$]);
+ TestExec test_v2e = new();
+ automatic ChdrXportPacket_t send[$];
+ automatic EthXportPacket_t expected[$];
+ automatic int sample_sum = 0;
+
+ test_v2e.start_test({TEST_NAME,"CHDR to Ethernet"}, 60us);
+ // This path is
+ // v2e -> s_chdr(eth_adapter) -> s_axis_rfnoc (xport_adapter_gen) ->
+ //// axi_demux_mgmt_filter (AXI_DEMUX) (IF ALLOW_DISC) (discards discovery packets)
+ // (xport_adapter_gen) f2m ->
+ //// rtn_mux(AXI_MUX) between x2x and f2m
+ // (xport_adapter_gen) m2x ->
+ //// data_fifo/lookup_fifo (AXI_FIFO_SHORT)
+ //// LOOKUP LOGIC (lookup_fifo,data_fifo,results)
+ // (xport_adapter_gen) o_xport ->
+ //// xport_out_swap (AXIS_DATA_SWAP)
+ // (xport_adapter_gen) m_axis_xport -> (eth_adapater) x2e_chdr ->
+ //// ENET_HDR_LOGIC (frame_state)
+ // (eth_adapater) frame -> (eth_adapater) x2e_framed
+ //// (ETH_MUX)(SIZE=2)
+ // (eth_adapater) m_mac -> eth_tx
+
+ foreach (num_samples[i]) begin
+ automatic eth_hdr_t eth_hdr;
+ automatic ipv4_hdr_t ipv4_hdr;
+ automatic udp_hdr_t udp_hdr;
+ automatic raw_pkt_t pay,udp_raw,chdr_raw;
+
+ automatic ChdrPacket_t chdr_pkt = new();
+ automatic chdr_header_t chdr_hdr;
+ automatic chdr_word_t chdr_ts;
+ automatic chdr_word_t chdr_mdata[$];
+ automatic chdr_word_t chdr_data[$];
+ automatic int preamble;
+
+ expected[i] = new;
+ send[i] = new;
+
+ if (PREAMBLE_BYTES == 6) preamble = ZERO_PREAMBLE;
+ else if (PREAMBLE_BYTES == 0) preamble = NO_PREAMBLE;
+ else $fatal("Invalid PREAMBLE_BYTES");
+
+ // build a payload
+ 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];
+
+ // Fill data in the chdr packet
+ chdr_hdr = '{
+ vc : 0,
+ dst_epid : 0,
+ seq_num : 0,
+ pkt_type : CHDR_DATA_NO_TS,
+ num_mdata : 0,
+ default : 0
+ };
+ chdr_ts = 0; // no timestamp
+ chdr_mdata.delete(); // not adding meta data
+ chdr_data = bytes_to_words(pay);
+
+ chdr_pkt.write_raw(chdr_hdr, chdr_data, chdr_mdata, chdr_ts);
+ chdr_raw = flatten_chdr(chdr_pkt);
+
+ // send the raw chedar packet
+ send[i].push_bytes(chdr_raw);
+ send[i].tkeep_to_tuser();
+
+ //build a udp packet
+ // modify as the EthInterface does
+ udp_hdr.src_port = DEF_DEST_UDP_PORT;
+ udp_hdr.dest_port = 0; // Extract from router lookup results (Default)
+ udp_hdr.checksum = 0; // Checksum not calculated at this point
+ ipv4_hdr.src_ip = DEF_DEST_IP_ADDR;
+ ipv4_hdr.dest_ip = 0; // Extract from router lookup results (Default)
+ ipv4_hdr.dscp = 0; // hardcoded
+ ipv4_hdr.dont_frag = 1; // hardcoded
+ ipv4_hdr.identification = 0; // hardcoded
+ ipv4_hdr.time_to_live = 8'h10; //hardcoded
+ eth_hdr.src_mac = DEF_DEST_MAC_ADDR;
+ eth_hdr.dest_mac = 0; // Extract from router lookup results (Default)
+
+ udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,chdr_raw,
+ .preamble(preamble));
+
+ // expect udp wrapped chdr
+ expected[i].push_bytes(udp_raw);
+ if (IGNORE_EXTRA_DATA) begin
+ expected[i].clear_user(); // expect all full words!
+ expected[i].tuser_to_tkeep();
+ end else begin
+ expected[i].tkeep_to_tuser();
+ end
+ end
+
+ fork
+ begin // tx_thread
+ foreach(send[i])begin
+ v.put(send[i]);
+ end
+ end
+ begin //rx_thread
+ foreach(expected[i]) begin
+ automatic EthAxisPacket_t actual_a;
+ automatic EthXportPacket_t actual = new();
+ automatic eth_hdr_t eth_hdr;
+ automatic ipv4_hdr_t ipv4_hdr;
+ automatic udp_hdr_t udp_hdr;
+ automatic raw_pkt_t chdr_raw,actual_raw;
+ automatic ChdrPacket_t chdr_pkt;
+ automatic integer chdr_len;
+ localparam UDP_LEN = 8/*udp*/+20/*ipv4*/+14/*eth-no vlan*/;
+
+ wait_for_udp_packets(.udp_dest_port(0));
+ eth.get(actual_a);
+ actual.import_axis(actual_a);
+ actual.tuser_to_tkeep();
+ actual_raw = actual.dump_bytes();
+ repeat(PREAMBLE_BYTES) actual_raw.pop_front();
+ decode_udp_pkt(actual_raw,eth_hdr,ipv4_hdr,udp_hdr,chdr_raw);
+ chdr_pkt = unflatten_chdr(chdr_raw);
+ // fills remainder of packet with zeros
+ if (IGNORE_EXTRA_DATA) begin
+ for (int w=chdr_pkt.header.length+UDP_LEN;w <actual_raw.size();w++) begin
+ actual_raw[w] = '0;
+ end
+ repeat(PREAMBLE_BYTES) actual_raw.push_front(0);
+ actual.empty();
+ actual.push_bytes(actual_raw);
+ end
+ `ASSERT_ERROR(!actual.compare_w_error(expected[i]),"failed to send packet v2e");
+ end
+ end
+ join
+ test_v2e.end_test();
+
+ endtask : test_chdreth
+
+ integer cached_mgmt_seqnum = 0;
+
+ `include "../../core/rfnoc_chdr_utils.vh"
+ `include "../../core/rfnoc_chdr_internal_utils.vh"
+ `include "../../xport/rfnoc_xport_types.vh"
+ task automatic test_chdr_endpoint();
+ TestExec test_e2v = new();
+ automatic EthXportPacket_t send[$];
+ automatic EthXportPacket_t expected[$];
+ automatic int sample_sum = 0;
+
+ automatic eth_hdr_t eth_hdr;
+ automatic ipv4_hdr_t ipv4_hdr;
+ automatic udp_hdr_t udp_hdr;
+ automatic raw_pkt_t pay,udp_raw,chdr_raw;
+
+ automatic ChdrPacket_t chdr_pkt = new();
+ automatic chdr_header_t chdr_hdr;
+ automatic chdr_mgmt_t mgmt_pl;
+ automatic chdr_mgmt_op_t exp_mgmt_op;
+
+ automatic chdr_word_t chdr_ts;
+ automatic chdr_word_t chdr_mdata[$];
+ automatic chdr_word_t chdr_data[$];
+ automatic logic [47:0] node_info;
+ automatic int preamble;
+ localparam NODE_INST=0;
+
+ test_e2v.start_test({TEST_NAME,"ChdrEndpoint"}, 60us);
+
+
+ expected[0] = new;
+ send[0] = new;
+
+ if (PREAMBLE_BYTES == 6) preamble = NORMAL_PREAMBLE;
+ else if (PREAMBLE_BYTES == 0) preamble = NO_PREAMBLE;
+ else $fatal("Invalid PREAMBLE_BYTES");
+
+ // Generic management header
+ mgmt_pl.header = '{
+ default:'0, prot_ver:PROTOVER, chdr_width:translate_chdr_w(CHDR_W), src_epid:1
+ };
+ // Send a node info request to the crossbar
+ mgmt_pl.header.num_hops = 2;
+ mgmt_pl.ops.delete();
+ mgmt_pl.ops[0] = '{ // Hop 1: Send node info
+ op_payload:48'h0, op_code:MGMT_OP_INFO_REQ, ops_pending:8'd1};
+ mgmt_pl.ops[1] = '{ // Hop 1: Return
+ op_payload:48'h0, op_code:MGMT_OP_RETURN, ops_pending:8'd0};
+ mgmt_pl.ops[2] = '{ // Hop 2: Nop for return
+ op_payload:48'h0, op_code:MGMT_OP_NOP, ops_pending:8'd0};
+ chdr_hdr = '{
+ pkt_type:CHDR_MANAGEMENT, seq_num:cached_mgmt_seqnum, dst_epid:16'h0, default:'0};
+ chdr_pkt.write_mgmt(chdr_hdr, mgmt_pl);
+ chdr_raw = flatten_chdr(chdr_pkt);
+
+ //build a udp packet
+ udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,chdr_raw,
+ .preamble(preamble));
+ send[0].push_bytes(udp_raw);
+ send[0].tkeep_to_tuser();
+
+ // build expected response
+ // Generic management header
+ if (PREAMBLE_BYTES == 6) preamble = ZERO_PREAMBLE;
+ else if (PREAMBLE_BYTES == 0) preamble = NO_PREAMBLE;
+ else $fatal("Invalid PREAMBLE_BYTES");
+
+ mgmt_pl.header = '{
+ default:'0, prot_ver:PROTOVER, chdr_width:translate_chdr_w(CHDR_W), src_epid:0
+ };
+ // Send a node info request to the crossbar
+ mgmt_pl.header.num_hops = 1;
+ mgmt_pl.ops.delete();
+ mgmt_pl.ops[0] = '{ // Hop 2: Nop for return
+ op_payload:48'h0, op_code:MGMT_OP_NOP, ops_pending:8'd1};
+
+ node_info = chdr_mgmt_build_node_info({ 10'h0, NODE_SUBTYPE_XPORT_IPV4_CHDR64},
+ NODE_INST, NODE_TYPE_TRANSPORT, device_id);
+ mgmt_pl.ops[1] = '{op_payload:node_info,
+ op_code:MGMT_OP_INFO_RESP, ops_pending:8'd0};
+
+ chdr_hdr = '{
+ pkt_type:CHDR_MANAGEMENT, seq_num:cached_mgmt_seqnum++, dst_epid:16'h1, default:'0};
+ chdr_pkt.write_mgmt(chdr_hdr, mgmt_pl);
+ chdr_raw = flatten_chdr(chdr_pkt);
+
+ // build a udp packet
+ // modify as the EthInterface does
+ udp_hdr.src_port = DEF_DEST_UDP_PORT;
+ udp_hdr.dest_port = DEF_SRC_UDP_PORT; // Extract from router lookup results (Default)
+ udp_hdr.checksum = 0; // Checksum not calculated at this point
+ ipv4_hdr.src_ip = DEF_DEST_IP_ADDR;
+ ipv4_hdr.dest_ip = DEF_SRC_IP_ADDR; // Extract from router lookup results (Default)
+ ipv4_hdr.dscp = 0; // hardcoded
+ ipv4_hdr.dont_frag = 1; // hardcoded
+ ipv4_hdr.identification = 0; // hardcoded
+ ipv4_hdr.time_to_live = 8'h10; //hardcoded
+ eth_hdr.src_mac = DEF_DEST_MAC_ADDR;
+ eth_hdr.dest_mac = DEF_SRC_MAC_ADDR; // Extract from router lookup results (Default)
+
+ udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,chdr_raw,
+ .preamble(preamble));
+
+ expected[0].push_bytes(udp_raw);
+ if (IGNORE_EXTRA_DATA) begin
+ expected[0].clear_user(); // expect all full words!
+ expected[0].tuser_to_tkeep();
+ end else begin
+ expected[0].tkeep_to_tuser();
+ end
+
+ fork
+ begin // tx_thread
+ foreach(send[i])begin
+ #1 eth.put(send[i]);
+ end
+ end
+ begin //rx_thread
+ foreach(expected[i]) begin
+ automatic EthAxisPacket_t actual_a;
+ automatic EthXportPacket_t actual = new();
+ automatic eth_hdr_t eth_hdr;
+ automatic ipv4_hdr_t ipv4_hdr;
+ automatic udp_hdr_t udp_hdr;
+ automatic raw_pkt_t chdr_raw,actual_raw;
+ automatic ChdrPacket_t chdr_pkt;
+ automatic integer chdr_len;
+ localparam UDP_LEN = 8/*udp*/+20/*ipv4*/+14/*eth-no vlan*/;
+
+ eth.get(actual_a);
+ actual.import_axis(actual_a);
+ actual.tuser_to_tkeep();
+ actual_raw = actual.dump_bytes();
+ repeat(PREAMBLE_BYTES) actual_raw.pop_front();
+ decode_udp_pkt(actual_raw,eth_hdr,ipv4_hdr,udp_hdr,chdr_raw);
+ chdr_pkt = unflatten_chdr(chdr_raw);
+ // fills remainder of packet with zeros
+ if (IGNORE_EXTRA_DATA) begin
+ for (int w=chdr_pkt.header.length+UDP_LEN;w <actual_raw.size();w++) begin
+ actual_raw[w] = '0;
+ end
+ repeat(PREAMBLE_BYTES) actual_raw.push_front(0);
+ actual.empty();
+ actual.push_bytes(actual_raw);
+ end
+ `ASSERT_ERROR(!actual.compare_no_user(expected[i]),"failed to get node info");
+ end
+ end
+ join
+ test_e2v.end_test();
+
+ endtask : test_chdr_endpoint
+ //----------------------------------------------------
+ // Main test loop
+ //----------------------------------------------------
+ initial begin : tb_main
+ automatic int num_samples[$];
+ automatic int cpu_num_samples[$];
+ localparam QUICK = 0;
+ test.start_test({TEST_NAME,"Wait for Reset"}, 10us);
+ clk_gen.reset();
+ eth_clk_gen.reset();
+
+ eth.run();
+ cpu.run();
+ v.run();
+
+
+ test_reset();
+ test_registers();
+
+ test_chdr_endpoint();
+
+ num_samples = {1,2,3,4,5,6,7,8,
+ ENET_W/8-1,ENET_W/8,ENET_W/8+1,
+ 2*ENET_W/8-1,2*ENET_W/8,2*ENET_W/8+1,
+ CPU_W/8-1,CPU_W/8,CPU_W/8+1,
+ 2*CPU_W/8-1,2*CPU_W/8,2*CPU_W/8+1,
+ CHDR_W/8-1,CHDR_W/8,CHDR_W/8+1,
+ 2*CHDR_W/8-1,2*CHDR_W/8,2*CHDR_W/8+1
+ };
+ // add some extra samples for CPU packets to try to get above the min
+ //packet size of 64. (just the headers makes up 42 bytes)
+ //this way we still have some short packets to drop, but not as many.
+ foreach (num_samples[i]) cpu_num_samples[i] = num_samples[i]+20;
+ test.start_test({TEST_NAME,"::PacketW Combos NO Errors"}, 10us);
+ fork // run in parallel
+ // ethrx
+ test_ethcpu(cpu_num_samples,.ERROR_PROB(0));
+ test_ethchdr(num_samples,.ERROR_PROB(0));
+ // ethtx
+ test_chdreth(num_samples);
+ test_cpueth(num_samples);
+ join
+ test.end_test();
+
+ if (!QUICK) begin
+
+ test.start_test({TEST_NAME,"::PacketW Combos Errors"}, 10us);
+ fork // run in parallel
+ // ethrx
+ test_ethcpu(cpu_num_samples,.ERROR_PROB(2));
+ test_ethchdr(num_samples,.ERROR_PROB(2));
+ // ethtx
+ test_chdreth(num_samples);
+ test_cpueth(num_samples);
+ join
+ test.end_test();
+
+ num_samples = {16,32,64,128,256,512,1024,1500,1522,9000};
+ test.start_test({TEST_NAME,"::Pwr2 NoErrors"}, 60us);
+ fork // run in parallel
+ // ethrx
+ test_ethcpu(cpu_num_samples,.ERROR_PROB(0));
+ test_ethchdr(num_samples,.ERROR_PROB(0));
+ // ethtx
+ test_chdreth(num_samples);
+ test_cpueth(num_samples);
+ join
+ test.end_test();
+ end
+
+ eth.set_master_stall_prob(0);
+ eth.set_slave_stall_prob(0);
+ cpu.set_master_stall_prob(0);
+ cpu.set_slave_stall_prob(0);
+ v.set_master_stall_prob(0);
+ v.set_slave_stall_prob(0);
+
+ num_samples = {1,2,3,4,5,6,7,8,
+ ENET_W/8-1,ENET_W/8,ENET_W/8+1,
+ 2*ENET_W/8-1,2*ENET_W/8,2*ENET_W/8+1,
+ CPU_W/8-1,CPU_W/8,CPU_W/8+1,
+ 2*CPU_W/8-1,2*CPU_W/8,2*CPU_W/8+1,
+ CHDR_W/8-1,CHDR_W/8,CHDR_W/8+1,
+ 2*CHDR_W/8-1,2*CHDR_W/8,2*CHDR_W/8+1
+ };
+ test.start_test({TEST_NAME,"::Pktw NoStall+Error"}, 10us);
+ fork // run in parallel
+ // ethrx
+ test_ethcpu(cpu_num_samples,.ERROR_PROB(2));
+ test_ethchdr(num_samples,.ERROR_PROB(2));
+ // ethtx
+ test_chdreth(num_samples);
+ test_cpueth(num_samples);
+ join
+ test.end_test();
+
+ // repeat with back to back cpu/chdr packets
+ test.start_test({TEST_NAME,"::Serial Pktw NoStall+Error"}, 10us);
+ fork // run in parallel
+ // ethrx
+ begin
+ test_ethcpu(cpu_num_samples,.ERROR_PROB(2));
+ test_ethchdr(num_samples,.ERROR_PROB(2));
+ end
+ // ethtx
+ begin
+ test_chdreth(num_samples);
+ test_cpueth(num_samples);
+ end
+ join
+ test.end_test();
+
+ // 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();
+ eth_clk_gen.kill();
+ end // initial begin
+
+endmodule
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_add_udp.sv b/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_add_udp.sv
new file mode 100644
index 000000000..07d5f9932
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_add_udp.sv
@@ -0,0 +1,200 @@
+//
+// Copyright 2020 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: eth_ipv4_add_udp.sv
+// Description: Add a UDP header onto an incoming CHDR stream
+//
+// Parameters:
+// - PREAMBLE_BYTES: Number of bytes of Preamble expected
+// - MAX_PACKET_BYTES: Maximum expected packet size
+// Constant fields in the added packet header
+// - ETH_TYPE: EthType in Ethernet Frame header
+// - MISC_IP: IP version / IP header Length / DSCP and ECN in IpV4 header
+// - FLAG_FRAG: Identification in IPv4 header
+// - TTL_PROT: Time to Live / Protocol in IPv4 header
+
+module eth_ipv4_add_udp #(
+ int PREAMBLE_BYTES = 6,
+ int MAX_PACKET_BYTES = 2**16,
+ logic [15:0] ETH_TYPE = 16'h0800, // IPv4
+ logic [15:0] MISC_IP = { 4'd4 /* IPv4 */, 4'd5 /* IP HDR Len */, 8'h00 /* DSCP and ECN */},
+ logic [15:0] IDENT = 16'h0,
+ logic [15:0] FLAG_FRAG = { 3'b010 /* don't fragment */, 13'h0 },
+ logic [15:0] TTL_PROT = { 8'h10 /* TTL */, 8'h11 /* UDP */ }
+)(
+ // Device addresses
+ input logic [47:0] mac_src,
+ input logic [31:0] ip_src,
+ input logic [15:0] udp_src,
+
+ input logic [47:0] mac_dst,
+ input logic [31:0] ip_dst,
+ input logic [15:0] udp_dst,
+
+ // Ethernet Stream - CHDR ONLY
+ AxiStreamIf.slave i, // tUser = {*not used*}
+ AxiStreamIf.master o // tUser = {1'b0,trailing bytes};
+
+);
+
+ localparam ENET_USER_W = $clog2(i.DATA_WIDTH/8)+1;
+ //---------------------------------------
+ // Include for byte positions
+ //---------------------------------------
+ `include "eth_constants.vh"
+ `include "../../axi4s_sv/axi4s.vh"
+
+ // tUser = {1'b0,trailing bytes(always full)}
+ AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(ENET_USER_W),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ s0(i.clk,i.rst);
+ // tUser = {1'b0,trailing bytes(after add)}
+ AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(ENET_USER_W),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ s1(i.clk,i.rst);
+ // tUser = {HeaderInfo,trailing bytes(after add)}
+ AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(ENET_USER_W+96+48),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ s2(i.clk,i.rst);
+ // tUser = {1'b0,trailing bytes}
+ AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(ENET_USER_W+96+48),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ s3(i.clk,i.rst);
+ // tUser = {1'b0,trailing bytes}
+ AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(ENET_USER_W),.TKEEP(0),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ s4(i.clk,i.rst);
+
+ //---------------------------------------
+ // Ethernet Framer
+ //---------------------------------------
+ logic [15:0] chdr_len_new, chdr_len_old, chdr_len_reg;
+
+ logic [15:0] ip_len, ip_len_reg;
+ logic [15:0] udp_len, udp_len_reg;
+ //delayed one clock cycle to pipeline
+ logic [47:0] mac_dst_reg;
+ logic [31:0] ip_dst_reg;
+ logic [15:0] udp_dst_reg;
+ logic [ENET_USER_W-1:0] trailing_bytes;
+
+ always_comb begin : s0_assign
+ `AXI4S_ASSIGN(s0,i)
+ s0.tuser = 0; // all full words going in
+ end
+
+ localparam BYTES_TO_ADD = UDP_END+1+PREAMBLE_BYTES;
+ axi4s_add_bytes #(.ADD_START(0),.ADD_BYTES(BYTES_TO_ADD)
+ ) add_header (
+ .i(s0), .o(s1)
+ );
+
+ // try to calculate what tuser should be on the final word
+ logic [15:0] total_len;
+ logic [15:0] rem_bytes;
+
+ always_ff @(posedge i.clk) begin : chdr_len_ff
+ if (i.rst) begin
+ chdr_len_old <= '0;
+ end else begin
+ if (s0.tvalid && s0.tready) begin
+ chdr_len_old <= chdr_len_new;
+ end
+ end
+ end
+
+ always_comb begin : calc_length_fields
+ // exract fields
+ chdr_len_new = s0.get_packet_field16(chdr_len_old,CHDR_LENGTH_BYTE);
+ ip_len = (16'd28 + chdr_len_new); // 20 for IP, 8 for UDP
+ udp_len = (16'd8 + chdr_len_new);
+ end
+
+ logic [15:0] iphdr_checksum;
+
+ always_comb begin : pack_unpack_header_info
+ `AXI4S_ASSIGN(s2,s1)
+ s2.tuser = { udp_len, ip_len, chdr_len_new, udp_dst, ip_dst, mac_dst, s1.tuser};
+ { udp_len_reg, ip_len_reg, chdr_len_reg, udp_dst_reg, ip_dst_reg,
+ mac_dst_reg,trailing_bytes} = s3.tuser;
+ end
+
+ axi4s_fifo #(
+ .SIZE(1)
+ ) s3_pipline_reg_ (
+ .clear(1'b0),.space(),.occupied(),
+ .i(s2),.o(s3)
+ );
+
+ logic chdr_end_early;
+
+ // track when we cut off the outgoing packet
+ always_ff @(posedge i.clk) begin : chdr_end_early_ff
+ if (i.rst) begin
+ chdr_end_early <= 0;
+ end else begin
+ if (s3.tvalid && s3.tready && s3.tlast)
+ chdr_end_early <= 0;
+ else if (s4.tvalid && s4.tready &&s4.tlast)
+ chdr_end_early <= 1;
+ end
+ end
+
+ localparam [15:0] udp_checksum = 16'h0;
+
+ ip_hdr_checksum #(
+ .LATENCY(1)
+ ) ip_hdr_checksum_i (
+ .clk(i.clk),
+ .in({MISC_IP, ip_len, IDENT, FLAG_FRAG, TTL_PROT, 16'd0, ip_src, ip_dst}),
+ .clken(s2.tready && s2.tvalid),
+ .out(iphdr_checksum)
+ );
+
+ always_comb begin : calc_rem_bytes
+ total_len = (BYTES_TO_ADD + chdr_len_reg);
+ rem_bytes = total_len - s4.word_count*i.DATA_WIDTH/8;
+ end
+
+ // zero value fields are commented out because the fill goes to zero
+ // if those fields become non zero uncomment them
+ always_comb begin : set_header_fields
+ // assign bus
+ s4.tvalid = s3.tvalid && !chdr_end_early;
+ s3.tready = s4.tready || chdr_end_early;
+ if (rem_bytes > i.DATA_WIDTH/8 || chdr_end_early) begin
+ s4.tlast = s3.tvalid && s3.tlast; // s0metimes packets end early
+ s4.tuser = 0;
+ end else if (rem_bytes == i.DATA_WIDTH/8) begin
+ s4.tlast = s3.tvalid;
+ s4.tuser = 0;
+ end else begin
+ s4.tlast = s3.tvalid;
+ s4.tuser = rem_bytes;
+ end
+ s4.tdata = s3.tdata;
+ s4.put_packet_field48(mac_dst_reg, PREAMBLE_BYTES+DST_MAC_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field48(mac_src, PREAMBLE_BYTES+SRC_MAC_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field16(ETH_TYPE, PREAMBLE_BYTES+ETH_TYPE_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field16(MISC_IP, PREAMBLE_BYTES+IP_VERSION_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field16(ip_len_reg, PREAMBLE_BYTES+IP_LENGTH_BYTE, .NETWORK_ORDER(1));
+// s4.put_packet_field16(IDENT, PREAMBLE_BYTES+IP_ID_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field16(FLAG_FRAG, PREAMBLE_BYTES+IP_FRAG_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field16(TTL_PROT, PREAMBLE_BYTES+IP_TTL_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field16(iphdr_checksum, PREAMBLE_BYTES+IP_CHECKSUM_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field32(ip_src, PREAMBLE_BYTES+SRC_IP_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field32(ip_dst_reg, PREAMBLE_BYTES+DST_IP_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field16(udp_src, PREAMBLE_BYTES+SRC_PORT_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field16(udp_dst_reg, PREAMBLE_BYTES+DST_PORT_BYTE, .NETWORK_ORDER(1));
+ s4.put_packet_field16(udp_len_reg, PREAMBLE_BYTES+UDP_LENGTH_BYTE, .NETWORK_ORDER(1));
+// s4.put_packet_field16(udp_checksum, PREAMBLE_BYTES+UDP_CHECKSUM_BYTE,.NETWORK_ORDER(1));
+
+ end
+
+ always_comb begin : assign_output
+ `AXI4S_ASSIGN(o,s4)
+ end
+
+endmodule // eth_ipv4_add_udp
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_adapter.sv b/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_adapter.sv
new file mode 100644
index 000000000..8505836bf
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_adapter.sv
@@ -0,0 +1,473 @@
+//
+// Copyright 2020 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: eth_ipv4_chdr_adapter
+// Description: A generic transport adapter module that can be used in
+// a variety of transports. It does the following:
+// - Exposes a configuration port for mgmt packets to configure the node
+// - Implements a return-address map for packets with metadata other than
+// the CHDR. Additional metadata can be passed as a tuser to this module
+// which will store it in a map indexed by the SrcEPID in a management
+// packet. For all returning packets, the metadata will be looked up in
+// the map and attached as the outgoing tuser.
+// - Implements a loopback path for node-info discovery
+//
+// Parameters:
+// - PROTOVER: RFNoC protocol version {8'd<major>, 8'd<minor>}
+// - MTU: Log2 of the MTU of the packet in 64-bit words
+// - CPU_FIFO_SIZE: Log2 of the FIFO depth (in 64-bit words) for the CPU egress path
+// - RT_TBL_SIZE: Log2 of the depth of the return-address routing table
+// - NODE_INST: The node type to return for a node-info discovery
+// - DROP_UNKNOWN_MAC: Drop packets not addressed to us?
+// - DROP_MIN_PACKET: Drop packets smaller than 64 bytes?
+// - PREAMBLE_BYTES: Number of bytes of Preamble expected
+// - ADD_SOF: Add a SOF indication into the tuser field
+// If false use TKEEP instead of USER
+// - SYNC: Set if MAC is not the same as bus_clk
+// - ENET_W: Width of the link to the Ethernet MAC
+// - CPU_W: Width of the CPU interface
+// - CHDR_W: Width of the CHDR interface
+//
+// Signals:
+// - device_id : The ID of the device that has instantiated this module
+// - eth_rx : The input Ethernet stream from the MAC
+// - eth_tx : The output Ethernet stream to the MAC
+// - v2e : The input CHDR stream from the rfnoc infrastructure
+// - e2v : The output CHDR stream to the rfnoc infrastructure
+// - c2e : The input Ethernet stream from the CPU
+// - e2c : The output Ethernet stream to the CPU
+// - my_mac: The Ethernet (MAC) address of this endpoint
+// - my_ip: The IPv4 address of this endpoint
+// - my_udp_chdr_port: The UDP port allocated for CHDR traffic on this endpoint
+//
+
+module eth_ipv4_chdr_adapter #(
+ logic [15:0] PROTOVER = {8'd1, 8'd0},
+ int MTU = 10,
+ int CPU_FIFO_SIZE = MTU,
+ int RT_TBL_SIZE = 6,
+ int NODE_INST = 0,
+ bit DROP_UNKNOWN_MAC = 0,
+ bit DROP_MIN_PACKET = 0,
+ int PREAMBLE_BYTES = 6,
+ bit ADD_SOF = 1,
+ bit SYNC = 0,
+ int ENET_W = 64,
+ int CPU_W = 64,
+ int CHDR_W = 64
+)(
+ // Device info
+ input logic [15:0] device_id,
+ // Device addresses
+ input logic [47:0] my_mac,
+ input logic [31:0] my_ip,
+ input logic [15:0] my_udp_chdr_port,
+
+ // Ethernet MAC
+ AxiStreamIf.master eth_tx, // tUser = {1'b0,trailing bytes};
+ AxiStreamIf.slave eth_rx, // tUser = {error,trailing bytes};
+ // CHDR router interface
+ AxiStreamIf.master e2v, // tUser = {*not used*};
+ AxiStreamIf.slave v2e, // tUser = {*not used*};
+ // CPU DMA
+ AxiStreamIf.master e2c, // tUser = {sof,trailing bytes};
+ AxiStreamIf.slave c2e // tUser = {1'b0,trailing bytes};
+
+);
+
+ `include "../core/rfnoc_chdr_utils.vh"
+ `include "../core/rfnoc_chdr_internal_utils.vh"
+ `include "../../axi4s_sv/axi4s.vh"
+
+
+ localparam ENET_USER_W = $clog2(ENET_W/8)+1;
+ localparam CPU_USER_W = $clog2(CPU_W/8)+1;
+ localparam CHDR_USER_W = $clog2(CHDR_W/8);
+ localparam MAX_PACKET_BYTES = 2**16;
+ localparam DEBUG = 1;
+
+ `include "eth_constants.vh"
+
+ //---------------------------------------
+ // E2V and E2C DEMUX
+ //---------------------------------------
+ // tUser = {*not used*}
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.TKEEP(0),.TUSER(0)) e2v1(eth_rx.clk,eth_rx.rst);
+ // tUser = {*not used*}
+ AxiStreamIf #(.DATA_WIDTH(CHDR_W),.TKEEP(0),.TUSER(0)) e2v2(e2v.clk,e2v.rst);
+ // tUser = {*not used*}
+ AxiStreamIf #(.DATA_WIDTH(CHDR_W),.TKEEP(0),.TUSER(0)) e2v4(e2v.clk,e2v.rst);
+ // tUser = {*not used*}
+ AxiStreamIf #(.DATA_WIDTH(CHDR_W),.TKEEP(0),.TUSER(0)) e2v5(e2v.clk,e2v.rst);
+
+ // tUser = {1'b0,trailing bytes}
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0)) e2c1(eth_rx.clk,eth_rx.rst);
+ // tUser = {sof,trailing bytes} IF ADD_SOF
+ AxiStreamIf #(.DATA_WIDTH(CPU_W),.USER_WIDTH(CPU_USER_W),
+ .TKEEP(!ADD_SOF), .TUSER(ADD_SOF))
+ e2c2(e2c.clk,e2c.rst);
+
+ logic [47:0] e_my_mac;
+ logic [31:0] e_my_ip;
+ logic [15:0] e_my_udp_chdr_port;
+ // crossing clock boundaries.
+ // my_mac, my_ip,,my_udp_chdr_port must be written
+ // prior to traffic, or an inconsistent version will
+ // exist for a clock period or 2. This would be better
+ // done with a full handshake.
+ synchronizer #(.WIDTH(96),.STAGES(1))
+ e_info_sync (.clk(eth_rx.clk),.rst(eth_rx.rst),
+ .in({my_mac,my_ip,my_udp_chdr_port}),
+ .out({e_my_mac,e_my_ip,e_my_udp_chdr_port}));
+
+ // Ethernet sink. Inspects packet and dispatches
+ // to the correct port.
+ eth_ipv4_chdr_dispatch #(
+ .CPU_FIFO_SIZE(CPU_FIFO_SIZE),
+ .PREAMBLE_BYTES(PREAMBLE_BYTES),
+ .MAX_PACKET_BYTES(MAX_PACKET_BYTES),
+ .DROP_UNKNOWN_MAC(DROP_UNKNOWN_MAC),
+ .DROP_MIN_PACKET(DROP_MIN_PACKET),
+ .ENET_W(ENET_W)
+ ) eth_dispatch_i (
+ .eth_rx (eth_rx),
+ .e2v (e2v1),
+ .e2c (e2c1),
+ .my_mac (e_my_mac),
+ .my_ip (e_my_ip),
+ .my_udp_chdr_port (e_my_udp_chdr_port)
+ );
+
+ //---------------------------------------
+ // E2C Path
+ //---------------------------------------
+ if (ENET_W != CPU_W || !SYNC) begin : gen_e2c_width_conv
+ axi4s_width_conv #(.I_USER_TRAILING_BYTES(1),.O_USER_TRAILING_BYTES(ADD_SOF),.SYNC_CLKS(0))
+ e2c_width_conv (.i(e2c1), .o(e2c2));
+ end else begin : gen_e2c_width_match
+ always_comb begin : e2c_assign
+ `AXI4S_ASSIGN(e2c2,e2c1)
+ end
+ end
+
+ if (ADD_SOF) begin : add_sof
+ logic sof = 1'b1;
+
+ // Add SOF
+ always_ff @(posedge e2c.clk) begin : cpu3_find_sof
+ if (e2c.rst) begin
+ sof <= 1'b1;
+ end else if (e2c2.tvalid && e2c2.tready) begin
+ sof <= e2c2.tlast;
+ end
+ end
+ always_comb begin : e2c2_sof_assign
+ `AXI4S_ASSIGN(e2c,e2c2)
+ e2c.tuser = {sof,e2c2.tuser[CPU_USER_W-2:0]};
+ end
+ end else begin : no_sof
+ if (DEBUG) begin
+ `AXI4S_DEBUG_ASSIGN(e2c,e2c2)
+ end else begin
+ always_comb begin : e2c_nodebug_assign
+ `AXI4S_ASSIGN(e2c,e2c2)
+ end
+ end
+ end
+
+ //---------------------------------------
+ // E2V Path
+ //---------------------------------------
+ if (ENET_W != CHDR_W || !SYNC) begin : gen_e2v_width_conv
+ // assumes full words on input
+ axi4s_width_conv #(.SYNC_CLKS(0))
+ e2v_width_conv (.i(e2v1), .o(e2v2));
+ end else begin : gen_e2v_width_match
+ always_comb begin : e2v_assign
+ `AXI4S_ASSIGN(e2v2,e2v1)
+ end
+ end
+
+
+ //---------------------------------------
+ // CHDR Transport Adapter
+ //---------------------------------------
+
+ // tUser = {*not used*}
+ AxiStreamIf #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0))
+ v2e1D(v2e.clk,v2e.rst);
+ // tUser = {*not used*}
+ AxiStreamIf #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0))
+ v2e1(v2e.clk,v2e.rst);
+ // tUser = {*not used*}
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0))
+ v2e2(eth_rx.clk,eth_rx.rst);
+ // tUser = {*not used*}
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0))
+ v2e3(eth_rx.clk,eth_rx.rst);
+
+ chdr_xport_adapter #(
+ .PREAMBLE_BYTES (PREAMBLE_BYTES),
+ .MAX_PACKET_BYTES (MAX_PACKET_BYTES),
+ .PROTOVER (PROTOVER),
+ .TBL_SIZE (RT_TBL_SIZE),
+ .NODE_INST (NODE_INST),
+ .ALLOW_DISC (1)
+ ) xport_adapter_gen_i (
+ .device_id (device_id),
+ .my_mac (my_mac),
+ .my_ip (my_ip),
+ .my_udp_chdr_port (my_udp_chdr_port),
+
+ .eth_rx (e2v2), // from ethernet
+ .e2v (e2v4), // to CHDR
+ // optional loop from ethernet to ethernet to talk to node
+ .v2e (v2e), // from CHDR
+ .eth_tx (v2e1D) // to ethernet
+ );
+
+ if (DEBUG) begin
+ `AXI4S_DEBUG_ASSIGN(v2e1,v2e1D)
+ end else begin
+ always_comb begin : v2e_nodebug_assign
+ `AXI4S_ASSIGN(v2e1,v2e1D)
+ end
+ end
+
+ // Convert incoming CHDR_W
+ if (ENET_W != CHDR_W || !SYNC) begin : gen_v2e_width_conv
+ axi4s_width_conv #(.SYNC_CLKS(0),.I_USER_TRAILING_BYTES(1),.O_USER_TRAILING_BYTES(1))
+ v2e_width_conv (.i(v2e1), .o(v2e2));
+ end else begin : gen_v2e_width_match
+ always_comb begin : v2e1_assign
+ `AXI4S_ASSIGN(v2e2,v2e1)
+ end
+ end
+
+ // Adding so packet will be contiguous going out
+ // The MAC needs bandwidth feeding it to be greater than the line rate
+ if (ENET_W > CHDR_W || !SYNC) begin : gen_v2e_packet_gate
+ axi4s_packet_gate #(.SIZE(17-$clog2(ENET_W)), .USE_AS_BUFF(0))
+ v2e_gate_i (.clear(1'b0),.error(1'b0),.i(v2e2),.o(v2e3));
+ end else begin : gen_v2e_no_packet_gate
+ always_comb begin : v2e1_assign
+ `AXI4S_ASSIGN(v2e3,v2e2)
+ end
+ end
+
+ //---------------------------------------
+ // E2V Output Buffering
+ //---------------------------------------
+
+ // The transport should hook up to a crossbar downstream, which
+ // may backpressure this module because it is in the middle of
+ // transferring a packet. To ensure that upstream logic is not
+ // blocked, we instantiate one packet worth of buffering here.
+ axi4s_fifo #(
+ .SIZE(MTU)
+ ) chdr_fifo_i (
+ .clear(1'b0),.space(),.occupied(),
+ .i(e2v4),.o(e2v5)
+ );
+
+ if (DEBUG) begin
+ `AXI4S_DEBUG_ASSIGN(e2v,e2v5)
+ end else begin
+ always_comb begin : e2v_direct_assign
+ `AXI4S_ASSIGN(e2v,e2v5)
+ end
+ end
+
+
+
+
+ //---------------------------------------
+ // C2E Path
+ //---------------------------------------
+ // tUser = {1'b0,trailing bytes}
+ AxiStreamIf #(.DATA_WIDTH(c2e.DATA_WIDTH),.USER_WIDTH(c2e.USER_WIDTH),
+ .TKEEP(c2e.TKEEP),.TUSER(c2e.TUSER))
+ c2eD(c2e.clk,c2e.rst);
+ // tUser = {1'b0,trailing bytes}
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0)) c2e1(eth_rx.clk,eth_rx.rst);
+ // tUser = {1'b0,trailing bytes}
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0),.MAX_PACKET_BYTES(MAX_PACKET_BYTES)) c2e2(eth_rx.clk,eth_rx.rst);
+ // tUser = {1'b0,trailing bytes}
+ AxiStreamPacketIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0),.MAX_PACKET_BYTES(MAX_PACKET_BYTES)) c2e3(eth_rx.clk,eth_rx.rst);
+
+
+ if (DEBUG) begin
+ `AXI4S_DEBUG_ASSIGN(c2eD,c2e)
+ end else begin
+ always_comb begin : c2e_nodebug_assign
+ `AXI4S_ASSIGN(c2eD,c2e)
+ end
+ end
+
+ if (ENET_W != CPU_W || !SYNC) begin : gen_c2e_width_conv
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0)) c2e1_0(eth_rx.clk,eth_rx.rst);
+ axi4s_width_conv #(.I_USER_TRAILING_BYTES(c2eD.TUSER),.O_USER_TRAILING_BYTES(1),.SYNC_CLKS(0))
+ c2e_width_conv (.i(c2eD), .o(c2e1_0));
+
+ if (ENET_W > CPU_W || !SYNC) begin : gen_c2e_packet_gate
+ // Adding so packet will be contiguous going out
+ // I think the MAC needs bandwdith feeding it to
+ // be greater than the line rate
+ axi4s_packet_gate #(.SIZE(17-$clog2(ENET_W)), .USE_AS_BUFF(0))
+ c2e_gate_i (.clear(1'b0),.error(1'b0),.i(c2e1_0),.o(c2e1));
+ end else begin : gen_c2e_no_packet_gate
+ always_comb begin : c2e1_assign
+ `AXI4S_ASSIGN(c2e1,c2e1_0)
+ end
+ end
+ end else begin : gen_c2e_width_match
+ always_comb begin : c2e1_assign
+ `AXI4S_ASSIGN(c2e1,c2eD)
+ end
+ end
+
+ if (PREAMBLE_BYTES > 0) begin : gen_add_preamble
+ // Add pad of PREAMBLE_BYTES empty bytes to the ethernet packet going
+ // from the CPU to the SFP. This padding added before MAC addresses
+ // aligns the source and destination IP addresses, UDP headers etc.
+ // Note that the xge_mac_wrapper strips this padding to recreate the ethernet
+ // packet
+ axi4s_add_bytes #(.ADD_START(0),.ADD_BYTES(PREAMBLE_BYTES)
+ ) add_header (
+ .i(c2e1), .o(c2e2)
+ );
+ end else begin : gen_no_preamble
+ always_comb begin : c2e2_assign
+ `AXI4S_ASSIGN(c2e2,c2e1)
+ end
+ end
+
+ localparam FORCE_MIN_PACKET = 1;
+ if (FORCE_MIN_PACKET) begin : gen_force_min
+ // add extra zero bytes to the end of a packet if it is less
+ // than the minimum packet size.
+ typedef enum logic {
+ ST_IDLE,
+ ST_AFTER
+ } pad_state_t;
+ pad_state_t pad_state = ST_IDLE;
+ logic clk_before_minpacket;
+ logic pad_last;
+
+ always_comb clk_before_minpacket = c2e3.reached_packet_byte(MIN_PACKET_SIZE_BYTE);
+
+ always_ff @(posedge eth_rx.clk) begin : pad_state_ff
+ if (eth_rx.rst) begin
+ pad_state <= ST_IDLE;
+ pad_last <= 0;
+ end else begin
+ if (c2e3.tready && c2e3.tvalid && c2e3.tlast) begin
+ pad_state <= ST_IDLE;
+ end else if (c2e3.tready && c2e3.tvalid && clk_before_minpacket) begin
+ pad_state <= ST_AFTER;
+ end
+ if (c2e3.tready && c2e3.tvalid && c2e3.tlast) begin
+ pad_last <= 0;
+ end else if (c2e3.tready && c2e3.tvalid && c2e2.tlast) begin
+ pad_last <= 1;
+ end
+
+
+ end
+ end
+
+ always_comb begin : c2e3_pad
+ if (pad_state == ST_IDLE) begin
+ //force to a full word
+ // preserve SOF if it's there, but force
+ // trailing bytes to zero (full word)
+ c2e3.tuser = 0;
+ c2e3.tuser[ENET_USER_W-1] = c2e2.tuser[ENET_USER_W-1];
+ c2e3.tvalid = c2e2.tvalid;
+
+ // force any tdata bytes that we pad with zero
+ // SW recommended forcing the padding bytes to zero
+ // but I suspect we could save logic by just allowing
+ // trash data.
+ foreach (c2e2.tkeep[i]) begin
+ if (pad_last || (i >= c2e2.tuser[ENET_USER_W-2:0] &&
+ c2e2.tuser[ENET_USER_W-2:0] != 0)) begin
+ c2e3.tdata[i*8 +:8] = 0;
+ end else begin
+ c2e3.tdata[i*8 +:8] = c2e2.tdata[i*8 +:8];
+ end
+ end
+
+ if (ENET_W < 512) begin
+ // hold off input if we reach the end early
+ if (c2e2.tlast) begin
+ c2e2.tready = clk_before_minpacket && c2e3.tready;
+ end else begin
+ c2e2.tready = c2e3.tready;
+ end
+ // add tlast at end of idle
+ c2e3.tlast = clk_before_minpacket && c2e2.tlast;
+ end else begin
+ c2e2.tready = c2e3.tready;
+ c2e3.tlast = c2e2.tlast;
+ end
+
+ end else begin
+ `AXI4S_ASSIGN(c2e3,c2e2)
+ end
+
+ end
+ end else begin : gen_no_force_min
+ always_comb begin : c2e3_assign
+ `AXI4S_ASSIGN(c2e3,c2e2)
+ end
+ end
+
+
+ //---------------------------------------
+ // V2E and C2E MUX
+ //---------------------------------------
+ logic c2e3_tready;
+ logic eth_tx1_tlast;
+ logic eth_tx1_tvalid;
+ logic eth_tx1_tready;
+ logic [ENET_W-1:0] eth_tx1_tdata;
+ logic [ENET_USER_W-1:0] eth_tx1_tuser;
+ always_comb begin
+ c2e3.tready = c2e3_tready;
+ end
+ axi_mux #(
+ .SIZE(2), .PRIO(0), .WIDTH(ENET_W+ENET_USER_W), .PRE_FIFO_SIZE(0), .POST_FIFO_SIZE(1)
+ ) eth_mux_i (
+ .clk(eth_rx.clk), .reset(eth_rx.rst), .clear(1'b0),
+ .i_tdata({c2e3.tuser, c2e3.tdata, v2e3.tuser, v2e3.tdata}), .i_tlast({c2e3.tlast, v2e3.tlast}),
+ .i_tvalid({c2e3.tvalid, v2e3.tvalid}), .i_tready({c2e3_tready, v2e3.tready}),
+ .o_tdata({eth_tx1_tuser, eth_tx1_tdata}), .o_tlast(eth_tx1_tlast),
+ .o_tvalid(eth_tx1_tvalid), .o_tready(eth_tx1_tready)
+ );
+
+ // Clean up the noisy mux output. I suspect it is annoying
+ // the xilinx cores that tlast and tuser(tkeep) flop around
+ // when tvalid isn't true.
+ always_comb begin : eth_tx_assign
+ if (eth_tx1_tvalid) begin
+ eth_tx.tvalid = 1'b1;
+ eth_tx.tdata = eth_tx1_tdata;
+ eth_tx.tlast = eth_tx1_tlast;
+ if (eth_tx1_tlast) begin
+ eth_tx.tuser = eth_tx1_tuser;
+ end else begin
+ eth_tx.tuser = '0;
+ end
+ end else begin
+ eth_tx.tvalid = 1'b0;
+ eth_tx.tdata = 'X; // use X so synth will optimize
+ eth_tx.tlast = 0;
+ eth_tx.tuser = '0;
+ end
+ eth_tx1_tready = eth_tx.tready;
+ end
+
+endmodule // eth_ipv4_chdr_adapter
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_dispatch.sv b/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_dispatch.sv
new file mode 100644
index 000000000..36f887190
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_dispatch.sv
@@ -0,0 +1,435 @@
+//
+// Copyright 2020 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: eth_ipv4_chdr_dispatch
+//
+// Description:
+// This module serves as an Ethernet endpoint for CHDR traffic.
+// Ethernet frames arrive on the eth_rx port where they are
+// inspected and classified as CHDR or !CHDR. A frame contains
+// CHDR payload if it is addressed to us (Eth and IP), is a UDP
+// packet and the destination port is one of the CHDR ports.
+// The UDP payload for CHDR frame is sent out of the e2v
+// in addition to source information for Eth, IP and UDP. All
+// other traffic address to us (Eth) is sent to the e2c port.
+// Traffic not addressed (Eth) to us is dropped(optionally).
+//
+// Parameters:
+// - CPU_FIFO_SIZE: log2 size of CPU RX fifo
+// - PREAMBLE_BYTES: Number of bytes in the Preamble
+// - DROP_UNKNOWN_MAC: Drop packets not addressed to us?
+// - DROP_MIN_PACKET: Drop packets smaller than 64 bytes?
+// - ENET_W: Width of AXI bus going to Ethernet Mac
+//
+// Signals:
+// - eth_rx : The input Ethernet stream from the MAC
+// tUser={error,trailing bytes}
+// - e2v : The output CHDR stream to the rfnoc infrastructure
+// - e2c : The output Ethernet stream to the CPU
+//
+// - my_mac : The Ethernet (MAC) address of this endpoint
+// - my_ip : The IPv4 address of this endpoint
+// - my_udp_chdr_port : The UDP port allocated for CHDR traffic on this endpoint
+//
+
+module eth_ipv4_chdr_dispatch #(
+ int CPU_FIFO_SIZE = $clog2(1558),
+ int PREAMBLE_BYTES = 6,
+ int MAX_PACKET_BYTES = 2**16-1,
+ bit DROP_UNKNOWN_MAC = 0,
+ bit DROP_MIN_PACKET = 0,
+ int ENET_W = 64
+)(
+
+ // AXI-Stream interfaces
+ AxiStreamIf.slave eth_rx, // tUser={error,trailing bytes};
+ AxiStreamIf.master e2v, // tUser={1'b0,trailing bytes};
+ AxiStreamIf.master e2c, // tUser={1'b0,trailing bytes};
+
+ // Device addresses
+ input logic [47:0] my_mac,
+ input logic [31:0] my_ip,
+ input logic [15:0] my_udp_chdr_port
+);
+
+ localparam ENET_USER_W = $clog2(ENET_W/8)+1;
+ //---------------------------------------
+ // Include for byte positions
+ //---------------------------------------
+ `include "eth_constants.vh"
+ // example macro to handle interface assignment
+ `define AXI4S_ASSIGN(O,I) \
+ ``O.tdata = ``I.tdata;\
+ ``O.tuser = ``I.tuser;\
+ ``O.tlast = ``I.tlast;\
+ ``O.tvalid = ``I.tvalid;\
+ ``I.tready = ``O.tready;
+
+ // axi_remov_bytes (PREAMBLE Strip)
+ // tUser = {error,trailing bytes};
+ AxiStreamPacketIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),
+ .TKEEP(0),.MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ in0(eth_rx.clk,eth_rx.rst);
+ // in_reg
+ // tUser = {error,trailing bytes};
+ AxiStreamPacketIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),
+ .TKEEP(0),.MAX_PACKET_BYTES(MAX_PACKET_BYTES))
+ in1(eth_rx.clk,eth_rx.rst);
+ // STATEMACHINE
+ // tUser = {error,trailing bytes};
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0))
+ in2(eth_rx.clk,eth_rx.rst);
+
+ // CPU_BRANCH
+ // tUser = {error,trailing bytes};
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0))
+ cpu0(eth_rx.clk,eth_rx.rst);
+ // out_reg_cpu
+ // tUser = {error,trailing bytes};
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0))
+ cpu1(eth_rx.clk,eth_rx.rst);
+ // cpu_out_gate - throw away error packets
+ // tUser = {1'b0,trailing bytes};
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0))
+ cpu2(eth_rx.clk,eth_rx.rst);
+ // cpu_out_fifo
+ // e2c (OUTPUT)
+
+ // CHDR_Branch
+ // tUser = {error,trailing bytes};
+ AxiStreamIf #(.DATA_WIDTH(ENET_W),.TKEEP(0),.TUSER(0))
+ chdr0(eth_rx.clk,eth_rx.rst);
+ // e2v(OUTPUT)
+
+ //---------------------------------------
+ // Strip Bytes
+ //---------------------------------------
+ if (PREAMBLE_BYTES > 0) begin : gen_strip_preamble
+ // Strip the preamble
+ axi4s_remove_bytes #(.REM_START(0),.REM_END(PREAMBLE_BYTES-1)
+ ) strip_preamble (
+ .i(eth_rx),.o(in0)
+ );
+ end else begin : gen_no_preamble
+ always_comb begin
+ `AXI4S_ASSIGN(in0,eth_rx);
+ end
+ end
+
+ //---------------------------------------
+ // Input pipeline stage
+ //---------------------------------------
+ axi4s_fifo #(
+ .SIZE(1)
+ ) in_reg_i (
+ .clear(1'b0),.space(),.occupied(),
+ .i(in0), .o(in1)
+ );
+
+ //---------------------------------------
+ // Classification state machine
+ //---------------------------------------
+ typedef enum logic [2:0] {
+ ST_IDLE_ETH_L0 = 3'd0,
+ ST_FWD_CHDR = 3'd1,
+ ST_FWD_CPU = 3'd2,
+ ST_DROP_TERM = 3'd3,
+ ST_DROP_WAIT = 3'd4
+ } dispatch_state_t;
+
+ // State info
+ dispatch_state_t dispatch_state,next_dispatch_state = ST_IDLE_ETH_L0;
+ logic cpu_error = 1'b0;
+ logic chdr_error = 1'b0;
+ logic chdr0_error = 1'b0;
+ logic mac_error, mac_error_old = 1'b0;
+ logic min_packet_error, min_packet_error_old = 1'b0;
+ logic reached_min_packet;
+
+ // Cached fields
+ logic [47:0] eth_dst_addr_new, eth_src_addr_new;
+ logic [31:0] ipv4_dst_addr_new, ipv4_src_addr_new;
+ logic [15:0] udp_dst_port_new, udp_src_port_new, eth_type_new;
+ logic [7:0] ip_protocol_new, ip_version_new;
+ logic [47:0] eth_dst_addr_old, eth_src_addr_old;
+ logic [31:0] ipv4_dst_addr_old, ipv4_src_addr_old;
+ logic [15:0] udp_dst_port_old, udp_src_port_old, eth_type_old;
+ logic [7:0] ip_protocol_old, ip_version_old;
+ logic reached_min_packet_new, reached_min_packet_old;
+ logic reached_end_of_udp;
+
+ logic eth_dst_is_broadcast;
+ logic eth_dst_is_me;
+ logic udp_dst_is_me;
+ logic ipv4_dst_is_me;
+ logic ipv4_protocol_is_udp;
+ logic eth_type_is_ipv4;
+
+ // save the fields
+ always_ff @(posedge eth_rx.clk) begin : field_ff
+ if (eth_rx.rst) begin
+
+ eth_dst_addr_old <= '0;
+ eth_src_addr_old <= '0;
+ ip_protocol_old <= '0;
+ ip_version_old <= '0;
+ ipv4_src_addr_old <= '0;
+ ipv4_dst_addr_old <= '0;
+ udp_src_port_old <= '0;
+ udp_dst_port_old <= '0;
+ eth_type_old <= '0;
+ mac_error_old <= 1'b0;
+ min_packet_error_old <= 1'b0;
+ reached_min_packet_old <= 1'b0;
+
+ // Statemachine Decisions
+ eth_dst_is_broadcast <=1'b0;
+ eth_dst_is_me <=1'b0;
+ udp_dst_is_me <=1'b0;
+ ipv4_dst_is_me <=1'b0;
+ ipv4_protocol_is_udp <=1'b0;
+ eth_type_is_ipv4 <=1'b0;
+ end else begin
+ eth_dst_addr_old <= eth_dst_addr_new;
+ eth_src_addr_old <= eth_src_addr_new;
+ ip_protocol_old <= ip_protocol_new;
+ ip_version_old <= ip_version_new;
+ ipv4_src_addr_old <= ipv4_src_addr_new;
+ ipv4_dst_addr_old <= ipv4_dst_addr_new;
+ udp_src_port_old <= udp_src_port_new;
+ udp_dst_port_old <= udp_dst_port_new;
+ eth_type_old <= eth_type_new;
+
+ if (in0.tvalid && in0.tready) begin
+ eth_dst_is_broadcast <= eth_dst_addr_new == ETH_ADDR_BCAST;
+ eth_dst_is_me <= eth_dst_addr_new == my_mac;
+ udp_dst_is_me <= udp_dst_port_new == my_udp_chdr_port;
+ ipv4_dst_is_me <= ipv4_dst_addr_new == my_ip;
+ ipv4_protocol_is_udp <= ip_protocol_new == IPV4_PROTO_UDP;
+ eth_type_is_ipv4 <= eth_type_new == ETH_TYPE_IPV4;
+ end
+ if (in1.tvalid && in1.tready) begin
+ if (in1.tlast) begin
+ mac_error_old <= 1'b0;
+ min_packet_error_old <= 1'b0;
+ reached_min_packet_old <= 1'b0;
+ end else begin
+ if (mac_error)
+ mac_error_old <= 1'b1;
+ if(min_packet_error)
+ min_packet_error_old <= 1'b1;
+ if(reached_min_packet_new)
+ reached_min_packet_old <= 1'b1;
+ end
+ end
+ end
+ end
+
+ // get the fields - don't use assign. assign will not activate with changes to in0.
+ always_comb begin : get_fields
+ eth_dst_addr_new = in0.get_packet_field48(eth_dst_addr_old,DST_MAC_BYTE,.NETWORK_ORDER(1));
+ eth_src_addr_new = in0.get_packet_field48(eth_src_addr_old,SRC_MAC_BYTE,.NETWORK_ORDER(1));
+ ip_version_new = in0.get_packet_byte(ip_version_old,IP_VERSION_BYTE);
+ ip_protocol_new = in0.get_packet_byte(ip_protocol_old,PROTOCOL_BYTE);
+ ipv4_src_addr_new = in0.get_packet_field32(ipv4_src_addr_old,SRC_IP_BYTE,.NETWORK_ORDER(1));
+ ipv4_dst_addr_new = in0.get_packet_field32(ipv4_dst_addr_old,DST_IP_BYTE,.NETWORK_ORDER(1));
+ udp_src_port_new = in0.get_packet_field16(udp_src_port_old,SRC_PORT_BYTE,.NETWORK_ORDER(1));
+ udp_dst_port_new = in0.get_packet_field16(udp_dst_port_old,DST_PORT_BYTE,.NETWORK_ORDER(1));
+ eth_type_new = in0.get_packet_field16(eth_type_old,ETH_TYPE_BYTE,.NETWORK_ORDER(1));
+ end
+
+
+ always_comb begin : reached_bytes
+ reached_min_packet_new = in1.reached_packet_byte(MIN_PACKET_SIZE_BYTE);
+ reached_end_of_udp = in1.reached_packet_byte(DST_PORT_BYTE+3);// we have enough to decide
+ end
+ assign mac_error = in1.tuser[ERROR_BIT] || mac_error_old;
+ if (DROP_MIN_PACKET) begin
+ assign reached_min_packet = (reached_min_packet_new && in1.tuser[BYTES_MSB:0] ==0) || reached_min_packet_old;
+ assign min_packet_error = (in1.tlast && !reached_min_packet) || min_packet_error_old;
+ end else begin
+ assign reached_min_packet = 1'b1;
+ assign min_packet_error = 1'b0;
+ end
+
+ always_ff @(posedge eth_rx.clk) begin : dispatch_sm_ff
+ if (eth_rx.rst) begin
+ dispatch_state <= ST_IDLE_ETH_L0;
+ end else begin
+ if (in1.tvalid && in1.tready) begin
+ if (in1.tlast)
+ dispatch_state <= ST_IDLE_ETH_L0;
+ else
+ dispatch_state <= next_dispatch_state;
+ end
+ end
+ end
+
+ always_comb begin : dispatch_sm_next_state
+ //defaults
+ next_dispatch_state = dispatch_state;
+ `AXI4S_ASSIGN(in2,in1);
+ in2.tuser[ERROR_BIT] = mac_error || min_packet_error;
+ cpu_error = 1'b0;
+ chdr_error = 1'b0;
+
+ // Statemachine always returns to ST_IDLE_ETH_L0 when tlast is set
+ case (dispatch_state)
+ ST_IDLE_ETH_L0: begin
+ cpu_error = 1'b0;
+ chdr_error = 1'b0;
+ if (mac_error || min_packet_error) begin
+ cpu_error = 1'b1;
+ chdr_error = 1'b1;
+ next_dispatch_state = ST_DROP_TERM;
+ end else if (reached_end_of_udp) begin
+ // all header values are decoded
+ if (eth_dst_is_broadcast) begin
+ // If Eth destination is bcast then fwd to CPU
+ cpu_error = 1'b0;
+ chdr_error = 1'b1;
+ next_dispatch_state = ST_FWD_CPU;
+ end else if (!eth_dst_is_me && DROP_UNKNOWN_MAC) begin
+ // If Eth destination is not us then drop the packet
+ cpu_error = 1'b1;
+ chdr_error = 1'b1;
+ next_dispatch_state = ST_DROP_TERM;
+ end else if (udp_dst_is_me &&
+ ipv4_dst_is_me &&
+ //ip_version_new == IPV4_LEN5 && // NEW CHECK --verify if this is ok
+ ipv4_protocol_is_udp &&
+ eth_type_is_ipv4) begin
+ // The conditions matches CHDR port
+ cpu_error = 1'b1;
+ chdr_error = 1'b0;
+ next_dispatch_state = ST_FWD_CHDR;
+ end else begin
+ // Not the CHDR port. Forward to CPU
+ cpu_error = 1'b0;
+ chdr_error = 1'b1;
+ next_dispatch_state = ST_FWD_CPU;
+ end
+ end
+ end
+
+ // CHDR Payload
+ ST_FWD_CHDR: begin
+ cpu_error = 1'b1;
+ chdr_error = 1'b0;
+ if (mac_error || min_packet_error) begin
+ cpu_error = 1'b1;
+ chdr_error = 1'b1;
+ next_dispatch_state = ST_DROP_TERM;
+ end
+ end
+
+ // NotCHDR Payload: Send to CPU
+ ST_FWD_CPU: begin
+ cpu_error = 1'b0;
+ chdr_error = 1'b1;
+ if (mac_error || min_packet_error) begin
+ cpu_error = 1'b1;
+ chdr_error = 1'b1;
+ next_dispatch_state = ST_DROP_TERM;
+ end
+ end
+
+ // Unwanted Payload: Drop
+ ST_DROP_TERM: begin
+ cpu_error = 1'b1;
+ chdr_error = 1'b1;
+ in2.tlast = 1'b1;
+ in2.tvalid = 1'b1;
+ in1.tready = in2.tready;
+ next_dispatch_state = ST_DROP_WAIT;
+ end
+
+ // Unwanted Payload: wait
+ ST_DROP_WAIT: begin
+ cpu_error = 1'b0;
+ chdr_error = 1'b0;
+ in2.tlast = 1'b0;
+ in2.tvalid = 1'b0;
+ in1.tready = 1'b1;
+ end
+
+ // We should never get here
+ default: begin
+ cpu_error = 1'b0;
+ chdr_error = 1'b0;
+ in1.tready = 1'b1;
+ in2.tvalid = 1'b0;
+ in2.tlast = 1'b0;
+ next_dispatch_state = ST_IDLE_ETH_L0;
+ end
+ endcase
+ end
+
+
+ //---------------------------------------
+ // SPLIT
+ //---------------------------------------
+ always_comb begin : cpu0_assign
+ cpu0.tdata = in2.tdata;
+ cpu0.tuser = in2.tuser;
+ cpu0.tlast = in2.tlast;
+ cpu0.tvalid = in2.tvalid && chdr0.tready;
+ cpu0.tuser[ERROR_BIT] = in2.tuser[ERROR_BIT] || cpu_error;
+
+ chdr0.tdata = in2.tdata;
+ chdr0.tuser = in2.tuser;
+ chdr0.tlast = in2.tlast;
+ chdr0.tvalid = in2.tvalid && cpu0.tready;
+ chdr0_error = in2.tuser[ERROR_BIT] || chdr_error;
+
+ in2.tready = cpu0.tready && chdr0.tready;
+ end
+
+ //---------------------------------------
+ // CPU Output processing
+ //---------------------------------------
+ axi4s_fifo #(
+ .SIZE(1)
+ ) out_reg_cpu_i (
+ .clear(),.space(),.occupied(),
+ .i(cpu0),.o(cpu1)
+ );
+
+ // We cannot make a CHDR/noCHDR routing decision until we are in the middle
+ // of a packet so we use a packet gate for the CPU path because we can rewind
+ // the write pointer and drop the packet in case it's destined for the CHDR
+ // path.
+ // NOTE: This also rejects packets with FCS failures.
+ // NOTE: The SIZE of this FIFO must accommodate a 9000 byte jumbo frame
+ // regardless of the CHDR MTU
+ // SIZED for 11 bit address when using a 64 bit word -> 16KByte
+ // SIZED for 8 bit address when using a 512 bit word -> 16KByte
+ axi4s_packet_gate #(
+ .SIZE(17-$clog2(ENET_W)), .USE_AS_BUFF(0)
+ ) cpu_out_gate_i (
+ .clear(1'b0), .error(cpu1.tuser[ERROR_BIT]),
+ .i(cpu1),.o(cpu2)
+ );
+
+ // The CPU can be slow to respond (relative to packet wirespeed) so
+ // extra buffer for packets destined there so it doesn't back up.
+ axi4s_fifo #(
+ .SIZE(CPU_FIFO_SIZE)
+ ) cpu_fifo_i (
+ .clear(),.space(),.occupied(),
+ .i(cpu2),.o(e2c)
+ );
+
+ // CHDR DATA GATE
+ // SIZED for 11 bit address when using a 64 bit word -> 16KByte
+ // SIZED for 8 bit address when using a 512 bit word -> 16KByte
+ axi4s_packet_gate #(
+ .SIZE(17-$clog2(ENET_W))
+ ) chdr_out_gate_i (
+ .clear(1'b0),.error(chdr0_error),
+ .i(chdr0),.o(e2v)
+ );
+
+endmodule // eth_ipv4_chdr_dispatch
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_interface.sv b/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_interface.sv
new file mode 100644
index 000000000..0c9f33f8c
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_interface.sv
@@ -0,0 +1,228 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Module: eth_ipv4_interface
+//
+// Description:
+// Adapts from internal CHDR to UDP/IPV4 Ethernet packets.
+// Packets not specifically addressed to CHDR are routed
+// to the CPU
+//
+// Parameters:
+// - PROTOVER: RFNoC protocol version {8'd<major>, 8'd<minor>}
+// - MTU: Log2 of the MTU of the packet in 64-bit words
+// - CPU_FIFO_SIZE: Log2 of the FIFO depth (in 64-bit words) for the CPU egress path
+// - RT_TBL_SIZE: Log2 of the depth of the return-address routing table
+// - NODE_INST: The node type to return for a node-info discovery
+// - DROP_UNKNOWN_MAC: Drop packets not addressed to us?
+// - DROP_MIN_PACKET: Drop packets smaller than 64 bytes?
+// - PREAMBLE_BYTES: Number of bytes of Preamble expected
+// - ADD_SOF: Add a SOF indication into the tuser field
+// - SYNC: Set if MAC is not the same as bus_clk
+// - ENET_W: Width of the link to the Ethernet MAC
+// - CPU_W: Width of the CPU interface
+// - CHDR_W: Width of the CHDR interface
+//
+
+module eth_ipv4_interface #(
+ logic [15:0] PROTOVER = {8'd1, 8'd0},
+ int MTU = 10,
+ int CPU_FIFO_SIZE = MTU,
+ int NODE_INST = 0,
+ int RT_TBL_SIZE = 6,
+ int REG_AWIDTH = 14,
+ int BASE = 0,
+ bit DROP_UNKNOWN_MAC = 0,
+ bit DROP_MIN_PACKET = 0,
+ int PREAMBLE_BYTES = 6,
+ bit ADD_SOF = 1,
+ bit SYNC = 0,
+ int ENET_W = 64,
+ int CPU_W = 64,
+ int CHDR_W = 64
+) (
+ input logic bus_clk,
+ input logic bus_rst,
+ input logic [15:0] device_id,
+
+ // Register port: Write port (domain: bus_clk)
+ input logic reg_wr_req,
+ input logic [REG_AWIDTH-1:0] reg_wr_addr,
+ input logic [31:0] reg_wr_data,
+
+ // Register port: Read port (domain: bus_clk)
+ input logic reg_rd_req,
+ input logic [REG_AWIDTH-1:0] reg_rd_addr,
+ output logic reg_rd_resp,
+ output logic [31:0] reg_rd_data,
+
+ // Status ports (domain: bus_clk)
+ output logic [47:0] my_mac,
+ output logic [31:0] my_ip,
+ output logic [15:0] my_udp_chdr_port,
+
+ // Ethernet MAC
+ AxiStreamIf.master eth_tx, // tUser = {1'b0,trailing bytes};
+ AxiStreamIf.slave eth_rx, // tUser = {error,trailing bytes};
+ // CHDR router interface
+ AxiStreamIf.master e2v, // tUser = {*not used*};
+ AxiStreamIf.slave v2e, // tUser = {*not used*};
+ // CPU DMA
+ AxiStreamIf.master e2c, // tUser = {sof,trailing bytes};
+ AxiStreamIf.slave c2e // tUser = {1'b0,trailing bytes};
+
+ );
+
+ localparam [47:0] DEFAULT_MAC_ADDR = {8'h00, 8'h80, 8'h2f, 8'h16, 8'hc5, 8'h2f};
+ localparam [31:0] DEFAULT_IP_ADDR = {8'd192, 8'd168, 8'd10, 8'd2};
+ localparam [31:0] DEFAULT_UDP_PORT = 16'd49153;
+
+ //---------------------------------------------------------
+ // Registers
+ //---------------------------------------------------------
+ // Include for register offsets
+ `include "eth_regs.vh"
+ // Allocate one full page for M
+ // mac_reg: MAC address for the dispatcher module. This value is used to
+ // determine if the packet is meant for this device and should be consumed.
+ //
+ // ip_reg: IP address for the dispatcher module. This value is used to
+ // determine if the packet is addressed to this device
+ //
+ // This module supports two destination ports.
+ logic [47:0] mac_reg = DEFAULT_MAC_ADDR;
+ logic [31:0] ip_reg = DEFAULT_IP_ADDR;
+ logic [15:0] udp_port = DEFAULT_UDP_PORT;
+ logic [47:0] bridge_mac_reg = DEFAULT_MAC_ADDR;
+ logic [31:0] bridge_ip_reg = DEFAULT_IP_ADDR;
+ logic [15:0] bridge_udp_port = DEFAULT_UDP_PORT;
+ logic bridge_en;
+
+ always_comb begin : bridge_mux
+ my_mac = bridge_en ? bridge_mac_reg : mac_reg;
+ my_ip = bridge_en ? bridge_ip_reg : ip_reg;
+ my_udp_chdr_port = bridge_en ? bridge_udp_port : udp_port;
+ end
+
+ always_ff @(posedge bus_clk) begin : reg_wr_ff
+ if (bus_rst) begin
+ mac_reg <= DEFAULT_MAC_ADDR;
+ ip_reg <= DEFAULT_IP_ADDR;
+ udp_port <= DEFAULT_UDP_PORT;
+ bridge_en <= 1'b0;
+ bridge_mac_reg <= DEFAULT_MAC_ADDR;
+ bridge_ip_reg <= DEFAULT_IP_ADDR;
+ bridge_udp_port <= DEFAULT_UDP_PORT;
+ end
+ else begin
+ if (reg_wr_req)
+ case (reg_wr_addr)
+
+ REG_MAC_LSB:
+ mac_reg[31:0] <= reg_wr_data;
+
+ REG_MAC_MSB:
+ mac_reg[47:32] <= reg_wr_data[15:0];
+
+ REG_IP:
+ ip_reg <= reg_wr_data;
+
+ REG_UDP:
+ udp_port <= reg_wr_data[15:0];
+
+ REG_BRIDGE_MAC_LSB:
+ bridge_mac_reg[31:0] <= reg_wr_data;
+
+ REG_BRIDGE_MAC_MSB:
+ bridge_mac_reg[47:32] <= reg_wr_data[15:0];
+
+ REG_BRIDGE_IP:
+ bridge_ip_reg <= reg_wr_data;
+
+ REG_BRIDGE_UDP:
+ bridge_udp_port <= reg_wr_data[15:0];
+
+ REG_BRIDGE_ENABLE:
+ bridge_en <= reg_wr_data[0];
+ endcase
+ end
+ end
+
+ always_ff @ (posedge bus_clk) begin : reg_rd_ff
+ if (bus_rst) begin
+ reg_rd_resp <= 1'b0;
+ reg_rd_data <= 32'd0;
+ end
+ else begin
+ if (reg_rd_req) begin
+ // Assert read response one cycle after read request
+ reg_rd_resp <= 1'b1;
+ case (reg_rd_addr)
+ REG_MAC_LSB:
+ reg_rd_data <= mac_reg[31:0];
+
+ REG_MAC_MSB:
+ reg_rd_data <= {16'b0,mac_reg[47:32]};
+
+ REG_IP:
+ reg_rd_data <= ip_reg;
+
+ REG_UDP:
+ reg_rd_data <= {16'b0, udp_port};
+
+ REG_BRIDGE_MAC_LSB:
+ reg_rd_data <= bridge_mac_reg[31:0];
+
+ REG_BRIDGE_MAC_MSB:
+ reg_rd_data <= {16'b0,bridge_mac_reg[47:32]};
+
+ REG_BRIDGE_IP:
+ reg_rd_data <= bridge_ip_reg;
+
+ REG_BRIDGE_UDP:
+ reg_rd_data <= {16'b0, bridge_udp_port};
+
+ REG_BRIDGE_ENABLE:
+ reg_rd_data <= {31'b0,bridge_en};
+
+ default:
+ reg_rd_resp <= 1'b0;
+ endcase
+ end
+ // Deassert read response after one clock cycle
+ if (reg_rd_resp) begin
+ reg_rd_resp <= 1'b0;
+ end
+ end
+ end
+
+ eth_ipv4_chdr_adapter #(
+ .PROTOVER (PROTOVER),
+ .MTU (MTU),
+ .CPU_FIFO_SIZE (CPU_FIFO_SIZE),
+ .RT_TBL_SIZE (RT_TBL_SIZE),
+ .NODE_INST (NODE_INST),
+ .DROP_UNKNOWN_MAC(DROP_UNKNOWN_MAC),
+ .DROP_MIN_PACKET (DROP_MIN_PACKET),
+ .PREAMBLE_BYTES (PREAMBLE_BYTES),
+ .ADD_SOF (ADD_SOF),
+ .SYNC (SYNC),
+ .ENET_W (ENET_W),
+ .CPU_W (CPU_W),
+ .CHDR_W (CHDR_W)
+ ) eth_adapter_i (
+ .eth_rx (eth_rx ),
+ .eth_tx (eth_tx ),
+ .v2e (v2e ),
+ .e2v (e2v ),
+ .c2e (c2e ),
+ .e2c (e2c ),
+ .device_id (device_id),
+ .my_mac (my_mac ),
+ .my_ip (my_ip ),
+ .my_udp_chdr_port(my_udp_chdr_port )
+ );
+
+
+endmodule : eth_ipv4_interface
diff --git a/fpga/usrp3/lib/rfnoc/xport_sv/eth_regs.vh b/fpga/usrp3/lib/rfnoc/xport_sv/eth_regs.vh
new file mode 100644
index 000000000..354a19414
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/xport_sv/eth_regs.vh
@@ -0,0 +1,29 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: eth_constants (Header File)
+//
+// Description:
+// Holds register offsets for the Ethernet Interface
+// NOTE: These depend on the following parameters
+// Parameters:
+// REG_AWIDTH - How wide the register window is in bits
+// BASE - Base address added to the offsets here
+// REGISTER OFFSETS
+// Allocate one full page for MAC
+localparam [REG_AWIDTH-1:0] REG_MAC_LSB = BASE + 'h0000;
+localparam [REG_AWIDTH-1:0] REG_MAC_MSB = BASE + 'h0004;
+
+// Source IP address
+localparam [REG_AWIDTH-1:0] REG_IP = BASE + 'h1000;
+// Source UDP Port
+localparam [REG_AWIDTH-1:0] REG_UDP = BASE + 'h1004;
+
+// Registers for Internal/Bridge Network Mode in CPU
+localparam [REG_AWIDTH-1:0] REG_BRIDGE_MAC_LSB = BASE + 'h1010;
+localparam [REG_AWIDTH-1:0] REG_BRIDGE_MAC_MSB = BASE + 'h1014;
+localparam [REG_AWIDTH-1:0] REG_BRIDGE_IP = BASE + 'h1018;
+localparam [REG_AWIDTH-1:0] REG_BRIDGE_UDP = BASE + 'h101c;
+localparam [REG_AWIDTH-1:0] REG_BRIDGE_ENABLE = BASE + 'h1020;
diff --git a/fpga/usrp3/tools/utils/testbenches.excludes b/fpga/usrp3/tools/utils/testbenches.excludes
index 79c3bb557..771d8bff2 100644
--- a/fpga/usrp3/tools/utils/testbenches.excludes
+++ b/fpga/usrp3/tools/utils/testbenches.excludes
@@ -17,3 +17,4 @@ top/n3xx/dboards/eiscat/radio/noc_block_radio_core_eiscat_tb
# These testbenches only work in ModelSim
lib/axi4s_sv/axi4s_remove_bytes_tb
lib/axi4s_sv/axi4s_add_bytes_tb
+lib/rfnoc/xport_sv/eth_interface_tb