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 /fpga/usrp3 | |
| 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.
Diffstat (limited to 'fpga/usrp3')
| -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  | 
