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.


Original-commit: eed4988cc266a63370a4332351d02fadedde3a3b
This commit is contained in:
Andrew Moch
2020-06-30 10:29:35 -05:00
committed by Wade Fife
parent cbe5e99e17
commit 05eb1bc2f1
14 changed files with 3340 additions and 0 deletions
+16
View File
@@ -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 \
))
+529
View File
@@ -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
+76
View File
@@ -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;
@@ -0,0 +1,3 @@
wave.do
*.dcp
*.rpt
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large Load Diff
+200
View File
@@ -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
+473
View File
@@ -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
@@ -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
+228
View File
@@ -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
+29
View File
@@ -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;