Files
b210-k7-fpga/lib/rfnoc/xport_sv/chdr_xport_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

612 lines
23 KiB
Systemverilog

//
// 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.
// - NET_CHDR_W: CHDR width used over the network connection
//
// 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,
int NET_CHDR_W = 64
) (
// Device info (domain: eth_rx.clk)
input logic [15:0] device_id,
// Device addresses (domain: eth_rx.clk)
input logic [47:0] my_mac,
input logic [31:0] my_ip,
input logic [15:0] my_udp_chdr_port,
// Ethernet (domain: eth_rx.clk)
AxiStreamIf.slave eth_rx, // tUser={*not used*}
AxiStreamIf.master eth_tx, // tUser={1'b0,trailing bytes}
// CHDR (domain: eth_rx.clk)
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
AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.USER_WIDTH(USER_META_W),.TKEEP(0))
e2v_resize(eth_rx.clk,eth_rx.rst);// RX Resize => Management packet handler
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 switches versus when it is needed!
always_comb begin : assign_ph
`AXI4S_ASSIGN(ph,ru4)
end
// ---------------------------------------------------
// Rewrite packets from network to use FPGA CHDR_W
// ---------------------------------------------------
if (NET_CHDR_W != eth_rx.DATA_WIDTH) begin : gen_chdr_resize_e2v
chdr_resize #(
.I_CHDR_W (NET_CHDR_W),
.O_CHDR_W (eth_rx.DATA_WIDTH),
.I_DATA_W (eth_rx.DATA_WIDTH),
.O_DATA_W (eth_rx.DATA_WIDTH),
.USER_W (USER_META_W),
.PIPELINE ("IN")
) chdr_resize_e2v (
.clk (eth_rx.clk),
.rst (eth_rx.rst),
.i_chdr_tdata (ph.tdata),
.i_chdr_tuser (ph.tuser),
.i_chdr_tlast (ph.tlast),
.i_chdr_tvalid (ph.tvalid),
.i_chdr_tready (ph.tready),
.o_chdr_tdata (e2v_resize.tdata),
.o_chdr_tuser (e2v_resize.tuser),
.o_chdr_tlast (e2v_resize.tlast),
.o_chdr_tvalid (e2v_resize.tvalid),
.o_chdr_tready (e2v_resize.tready)
);
end else begin : gen_no_chdr_resize_e2v
always_comb begin
`AXI4S_ASSIGN(e2v_resize, ph);
end
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(e2v_resize.tdata), .s_axis_chdr_tlast(e2v_resize.tlast),
.s_axis_chdr_tvalid(e2v_resize.tvalid), .s_axis_chdr_tready(e2v_resize.tready),
.s_axis_chdr_tuser(e2v_resize.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))
resize_v2e(eth_rx.clk,eth_rx.rst);
AxiStreamIf #(.DATA_WIDTH(eth_rx.DATA_WIDTH),.USER_WIDTH(0),.TKEEP(0))
data_fifo_o(eth_rx.clk,eth_rx.rst);// TX Resize => Management packet handler
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 resize_v2e_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 ()
);
// ---------------------------------------------------
// Rewrite packets from FPGA to use network CHDR_W
// ---------------------------------------------------
if (NET_CHDR_W != eth_rx.DATA_WIDTH) begin : gen_chdr_resize_v2e
chdr_resize #(
.I_CHDR_W (eth_rx.DATA_WIDTH),
.O_CHDR_W (NET_CHDR_W),
.I_DATA_W (eth_rx.DATA_WIDTH),
.O_DATA_W (eth_rx.DATA_WIDTH),
.USER_W (1),
.PIPELINE ("OUT")
) chdr_resize_v2e (
.clk (eth_rx.clk),
.rst (eth_rx.rst),
.i_chdr_tdata (data_fifo_o.tdata),
.i_chdr_tuser (1'b0),
.i_chdr_tlast (data_fifo_o.tlast),
.i_chdr_tvalid (data_fifo_o.tvalid),
.i_chdr_tready (data_fifo_o.tready),
.o_chdr_tdata (resize_v2e.tdata),
.o_chdr_tuser (),
.o_chdr_tlast (resize_v2e.tlast),
.o_chdr_tvalid (resize_v2e.tvalid),
.o_chdr_tready (resize_v2e.tready)
);
end else begin : gen_no_chdr_resize_v2e
always_comb begin
`AXI4S_ASSIGN(resize_v2e, data_fifo_o);
end
end
// 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 resize_v2e word is the start of a new packet
always_ff @(posedge eth_rx.clk) begin : resize_v2e_hdr_ff
if (eth_rx.rst)
resize_v2e_hdr <= 1'b1;
else if (resize_v2e.tvalid && resize_v2e.tready && pass_packet)
resize_v2e_hdr <= resize_v2e.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 (resize_v2e.tvalid && resize_v2e.tready && resize_v2e_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 (resize_v2e.tvalid && resize_v2e.tready && resize_v2e.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 (resize_v2e_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 = resize_v2e.tdata;
au.tlast = resize_v2e.tlast;
au.tvalid = resize_v2e.tvalid & pass_packet;
resize_v2e.tready = au.tready & pass_packet;
end
// Clock Crossing to the ethernet clock domain
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}));
// 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(e_my_mac),
.ip_src(e_my_ip),
.udp_src(e_my_udp_chdr_port),
.mac_dst(au_mac_dst),
.ip_dst(au_ip_dst),
.udp_dst(au_udp_dst)
);
endmodule : chdr_xport_adapter