Files
b210-k7-fpga/lib/rfnoc/xport_sv/eth_ipv4_chdr_adapter.sv
T
Wade Fife 032b9677aa fpga: lib: Add NET_CHDR_W parameter to transport adapters
The NET_CHDR_W parameter tells the transport adapter what CHDR width
is used in transport packets, such as Ethernet packets. By default the
CHDR width used by the transport and RFNoC core will be the same. The
NET_CHDR_W parameter can be used in situations where the RFNoC core
expects a CHDR width from the transport adapter that is different from
that used over the network. In this situation, the transport adapter
will rewrite the CHDR packets so that each side gets the expected CHDR
width.


Original-commit: a9e49b7c587ef778ee0fe327e8bdcb15e0b0fc95
2022-08-29 17:01:45 -05:00

498 lines
17 KiB
Systemverilog

//
// 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>}
// - CPU_FIFO_SIZE: Log2 of the FIFO depth (in bytes) for the CPU egress path
// - CHDR_FIFO_SIZE: Log2 of the FIFO depth (in bytes) for the CHDR 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 of the e2c path.
// If false use TKEEP instead of USER.
// - SYNC: Set if the CPU clock domain (c2e, e2c) is not the same as the
// Ethernet clock domain (eth_rx, eth_tx).
// - ENET_W: Width of the link to the Ethernet MAC
// - CPU_W: Width of the CPU interface
// - CHDR_W: CHDR width used by RFNoC on the FPGA
// - NET_CHDR_W: CHDR width used over the network connection
//
// 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 CPU_FIFO_SIZE = $clog2(8*1024),
int CHDR_FIFO_SIZE = $clog2(8*1024),
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,
int NET_CHDR_W = CHDR_W
)(
// 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,
input logic [15:0] my_pause_set,
input logic [15:0] my_pause_clear,
output logic chdr_dropped,
output logic cpu_dropped,
// Ethernet MAC (domain: eth_rx.clk)
output logic eth_pause_req,
AxiStreamIf.master eth_tx, // tUser = {1'b0,trailing bytes};
AxiStreamIf.slave eth_rx, // tUser = {error,trailing bytes};
// CHDR router interface (eth_rx.clk)
AxiStreamIf.master e2v, // tUser = {*not used*};
AxiStreamIf.slave v2e, // tUser = {*not used*};
// CPU DMA
// (domain: e2c.clk if SYNC=0, else eth_rx.clk)
AxiStreamIf.master e2c, // tUser = {sof,trailing bytes};
// (domain: c2e.clk if SYNC=0, else eth_rx.clk)
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 = 0;
`include "eth_constants.vh"
// tUser = {error,trailing_bytes}
AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0)) eth_rx1(eth_rx.clk,eth_rx.rst);
//---------------------------------------
// 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)) e2v3(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);
// The older implementation connected with tUser containing trailing bytes
// The newer implementation brings in TKEEP
// inside this block we expect trailing bytes.
always_comb begin
`AXI4S_ASSIGN(eth_rx1,eth_rx)
if (eth_rx.TKEEP) begin
eth_rx1.tuser = {eth_rx.tuser[ENET_USER_W-1],eth_rx.keep2trailing(eth_rx.tkeep)};
end
end
// Ethernet sink. Inspects packet and dispatches
// to the correct port.
eth_ipv4_chdr_dispatch #(
.CPU_FIFO_SIZE (CPU_FIFO_SIZE),
.CHDR_FIFO_SIZE (CHDR_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_pause_req (eth_pause_req),
.eth_rx (eth_rx1),
.e2v (e2v1),
.e2c (e2c1),
.my_mac (my_mac),
.my_ip (my_ip),
.my_udp_chdr_port (my_udp_chdr_port),
.my_pause_set (my_pause_set),
.my_pause_clear (my_pause_clear),
.chdr_dropped (chdr_dropped),
.cpu_dropped (cpu_dropped)
);
//---------------------------------------
// 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),
.NET_CHDR_W (NET_CHDR_W)
) 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 (e2v3), // 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
//---------------------------------------
if (DEBUG) begin
`AXI4S_DEBUG_ASSIGN(e2v,e2v3)
end else begin
always_comb begin : e2v_direct_assign
`AXI4S_ASSIGN(e2v,e2v3)
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),.MAX_PACKET_BYTES(MAX_PACKET_BYTES)) 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 bandwidth 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
//---------------------------------------
// tUser = {1'b0,trailing bytes}
AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0)) eth_tx1 (eth_rx.clk,eth_rx.rst);
// tUser = {1'b0,trailing bytes}
AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W)) eth_tx2 (eth_rx.clk,eth_rx.rst);
logic c2e3_tready;
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_tx2.tvalid = 1'b1;
eth_tx2.tdata = eth_tx1.tdata;
eth_tx2.tlast = eth_tx1.tlast;
// driving both tuser and tkeep
if (eth_tx1.tlast) begin
eth_tx2.tuser = eth_tx1.tuser;
eth_tx2.tkeep = eth_tx1.trailing2keep(eth_tx1.tuser);
end else begin
eth_tx2.tuser = '0;
eth_tx2.tkeep = '1;
end
end else begin
eth_tx2.tvalid = 1'b0;
eth_tx2.tdata = 'X; // use X so synth will optimize
eth_tx2.tlast = 0;
eth_tx2.tuser = '0;
eth_tx2.tkeep = '1;
end
eth_tx1.tready = eth_tx2.tready;
end
//---------------------------------------
// Output pipeline stage
//---------------------------------------
axi4s_fifo #(
.SIZE(1)
) in_reg_i (
.clear(1'b0),.space(),.occupied(),
.i(eth_tx2), .o(eth_tx)
);
endmodule // eth_ipv4_chdr_adapter