544 lines
21 KiB
Systemverilog
544 lines
21 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.
|
|
//
|
|
// 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 (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
|
|
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
|
|
|
|
// 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
|