diff options
author | Andrew Moch <Andrew.Moch@ni.com> | 2020-06-25 21:49:00 +0100 |
---|---|---|
committer | Wade Fife <wade.fife@ettus.com> | 2020-06-30 10:29:35 -0500 |
commit | eed4988cc266a63370a4332351d02fadedde3a3b (patch) | |
tree | ba8b4d4518153d88d40c835ae5b31192d79ae763 | |
parent | 6d9b174086ddebf49183eb09b779bd2c2658573a (diff) | |
download | uhd-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.
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/Makefile.srcs | 16 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/chdr_xport_adapter.sv | 529 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_constants.vh | 76 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/.gitignore | 3 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/Makefile | 81 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_all_tb.sv | 24 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_synth_test.sv | 161 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_interface_tb/eth_ifc_tb.sv | 1084 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_add_udp.sv | 200 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_adapter.sv | 473 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_chdr_dispatch.sv | 435 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_ipv4_interface.sv | 228 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/xport_sv/eth_regs.vh | 29 | ||||
-rw-r--r-- | fpga/usrp3/tools/utils/testbenches.excludes | 1 |
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 |