555 lines
19 KiB
Systemverilog
555 lines
19 KiB
Systemverilog
//
|
|
// Copyright 2020 Ettus Research, a National Instruments Brand
|
|
//
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
//
|
|
// Module: eth_ipv4_chdr_adapter
|
|
//
|
|
// Description:
|
|
//
|
|
// An Eth+IP+UDP transport adapter for RFNoC. In the RFNoC-to-Eth direction,
|
|
// this module encapsulates CHDR packets (or just the CHDR packet's payload)
|
|
// inside UDP/IP/Eth packets to be sent onto the network. In the other
|
|
// direction, this module looks at the UDP port in the packet to determine if
|
|
// it's a CHDR packet destined for RFNoC or a packet destined for the CPU,
|
|
// then routes the packet accordingly. If it's destined for RFNoC, the
|
|
// Eth/UDP/IP headers are stripped off.
|
|
//
|
|
// Traffic to/from the CPU can only go from/to the Eth interface. Traffic
|
|
// to/from RFNoC can only go from/to the Eth interface. There's no path
|
|
// between the CPU and RFNoC.
|
|
//
|
|
// (CPU)
|
|
// e2c c2e
|
|
// ▲ │
|
|
// │ │
|
|
// │ ▼
|
|
// ┌───┼───┼───┐
|
|
// │ │ │ |
|
|
// eth_tx ◄────┼───┼───█───┼◄──── v2e
|
|
// (UDP/IP/Eth) │ │ │ (RFNoC CHDR)
|
|
// eth_rx ────►┼───█───────┼────► e2v
|
|
// | |
|
|
// └───────────┘
|
|
// Transport Adapter
|
|
//
|
|
// 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 on Ethernet interface
|
|
// CPU_PREAMBLE : Set to 1 to use PREAMBLE_BYTES on CPU interface
|
|
// (for ZPU) or set to 0 to remove preamble on CPU
|
|
// interface (for ARM CPU).
|
|
// ADD_SOF : Add a SOF indication into the tuser field of the e2c
|
|
// path. If false use TKEEP instead of USER.
|
|
// SYNC : Set to 1 if the c2e/e2c, v2e/e2v, and eth_rx/eth_tx are
|
|
// synchronous to each other. Set to 0 to insert clock
|
|
// crossing logic.
|
|
// 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
|
|
// EN_RX_RAW_PAYD : Enable CHDR header removal (raw payload) on v2e path
|
|
//
|
|
// 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
|
|
//
|
|
|
|
`default_nettype none
|
|
|
|
|
|
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 CPU_PREAMBLE = 0,
|
|
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,
|
|
bit EN_RX_RAW_PYLD = 1
|
|
)(
|
|
|
|
// Device info
|
|
input wire [15:0] device_id,
|
|
|
|
// Device addresses
|
|
input wire [47:0] my_mac,
|
|
input wire [31:0] my_ip,
|
|
input wire [15:0] my_udp_chdr_port,
|
|
input wire [15:0] my_pause_set,
|
|
input wire [15:0] my_pause_clear,
|
|
|
|
// Key-value map interface
|
|
input wire kv_stb,
|
|
output wire kv_busy,
|
|
input wire [47:0] kv_mac_addr,
|
|
input wire [31:0] kv_ip_addr,
|
|
input wire [15:0] kv_udp_port,
|
|
input wire [15:0] kv_dst_epid,
|
|
input wire kv_raw_udp,
|
|
|
|
// Dropped packet debug values
|
|
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),
|
|
.CPU_PREAMBLE (CPU_PREAMBLE),
|
|
.MAX_PACKET_BYTES (MAX_PACKET_BYTES),
|
|
.DROP_UNKNOWN_MAC (DROP_UNKNOWN_MAC),
|
|
.DROP_MIN_PACKET (DROP_MIN_PACKET),
|
|
.ENET_W (ENET_W)
|
|
) eth_ipv4_chdr_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(CHDR_USER_W),.TKEEP(0))
|
|
v2e1D(v2e.clk,v2e.rst);
|
|
// tUser = {*not used*}
|
|
AxiStreamIf #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(CHDR_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 ),
|
|
.EN_RX_RAW_PYLD (EN_RX_RAW_PYLD )
|
|
) chdr_xport_adapter_i (
|
|
.device_id (device_id ),
|
|
.my_mac (my_mac ),
|
|
.my_ip (my_ip ),
|
|
.my_udp_chdr_port(my_udp_chdr_port),
|
|
.kv_stb (kv_stb ),
|
|
.kv_busy (kv_busy ),
|
|
.kv_dst_epid (kv_dst_epid ),
|
|
.kv_data ({kv_raw_udp,
|
|
kv_udp_port,
|
|
kv_ip_addr,
|
|
kv_mac_addr }),
|
|
.eth_rx (e2v2 ), // from Ethernet
|
|
.e2v (e2v3 ), // to CHDR
|
|
.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
|
|
// Set gate depth to 16 KiB
|
|
localparam SIZE = 17-$clog2(ENET_W);
|
|
// Buffer up to 2**N packets, by setting MIN_PKT_SIZE to SIZE-N.
|
|
localparam MIN_PKT_SIZE = SIZE-5;
|
|
axi4s_packet_gate #(.SIZE(SIZE), .USE_AS_BUFF(1), .MIN_PKT_SIZE(MIN_PKT_SIZE))
|
|
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 && !CPU_PREAMBLE) begin : gen_c2e_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)
|
|
) axi4s_add_bytes_c2e (
|
|
.i(c2e1), .o(c2e2)
|
|
);
|
|
end else begin : gen_c2e_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(1), .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
|
|
|
|
|
|
`default_nettype wire
|