631 lines
22 KiB
Systemverilog
631 lines
22 KiB
Systemverilog
//
|
|
// 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 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
|
|
// - 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).
|
|
// - 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
|
|
//
|
|
// - my pause set : number of word of fullness on CHDR_FIFO before requesting a pause
|
|
// - my pause clear : number of word of fullness on CHDR_FIFO before clearing a pause request
|
|
//
|
|
|
|
module eth_ipv4_chdr_dispatch #(
|
|
int CPU_FIFO_SIZE = $clog2(8*1024),
|
|
int CHDR_FIFO_SIZE = $clog2(8*1024),
|
|
int PREAMBLE_BYTES = 6,
|
|
int CPU_PREAMBLE = 0,
|
|
int MAX_PACKET_BYTES = 2**16-1,
|
|
bit DROP_UNKNOWN_MAC = 0,
|
|
bit DROP_MIN_PACKET = 0,
|
|
int ENET_W = 64
|
|
)(
|
|
// Clock domain: eth_rx.clk (other interface clocks are unused)
|
|
|
|
// AXI-Stream interfaces
|
|
output logic eth_pause_req,
|
|
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,
|
|
// Pause control
|
|
input logic [15:0] my_pause_set,
|
|
input logic [15:0] my_pause_clear,
|
|
|
|
output logic chdr_dropped,
|
|
output logic cpu_dropped
|
|
);
|
|
|
|
// 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;
|
|
logic [15:0] e_pause_set;
|
|
logic [15:0] e_pause_clear;
|
|
// 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+32),.STAGES(1))
|
|
e_info_sync (.clk(eth_rx.clk),.rst(eth_rx.rst),
|
|
.in({my_mac,my_ip,my_udp_chdr_port,my_pause_set,my_pause_clear}),
|
|
.out({e_my_mac,e_my_ip,e_my_udp_chdr_port,e_pause_set,e_pause_clear}));
|
|
|
|
|
|
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)
|
|
AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),
|
|
.TKEEP(0),.MAX_PACKET_BYTES(MAX_PACKET_BYTES))
|
|
inp(eth_rx.clk,eth_rx.rst);
|
|
// 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_regs
|
|
// 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);
|
|
AxiStreamPacketIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),
|
|
.TKEEP(0),.MAX_PACKET_BYTES(MAX_PACKET_BYTES))
|
|
in2(eth_rx.clk,eth_rx.rst);
|
|
// STATEMACHINE
|
|
// tUser = {error,trailing bytes};
|
|
AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W),.TKEEP(0))
|
|
in3(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);
|
|
// cpu_out_gate - throw away error packets
|
|
// 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_fifo
|
|
// e2c (OUTPUT)
|
|
|
|
// CHDR_Branch
|
|
// tUser = {not used};
|
|
AxiStreamIf #(.DATA_WIDTH(ENET_W),.TKEEP(0),.TUSER(0))
|
|
chdr0(eth_rx.clk,eth_rx.rst);
|
|
// tUser = {not used};
|
|
AxiStreamIf #(.DATA_WIDTH(ENET_W),.TKEEP(0),.TUSER(0))
|
|
chdr1(eth_rx.clk,eth_rx.rst);
|
|
// tUser = {not used};
|
|
AxiStreamIf #(.DATA_WIDTH(ENET_W),.TKEEP(0),.TUSER(0),.MAX_PACKET_BYTES(MAX_PACKET_BYTES))
|
|
chdr2(eth_rx.clk,eth_rx.rst);
|
|
// chdr_out_fifo
|
|
// e2v(OUTPUT)
|
|
|
|
//---------------------------------------
|
|
// Strip Preamble Bytes
|
|
//---------------------------------------
|
|
if (PREAMBLE_BYTES > 0 && !CPU_PREAMBLE) begin : gen_strip_preamble
|
|
// Keep the preamble since the CPU expects it.
|
|
always_comb begin
|
|
`AXI4S_ASSIGN(inp,eth_rx);
|
|
end
|
|
axi4s_remove_bytes #(.REM_START(0),.REM_END(PREAMBLE_BYTES-1)
|
|
) strip_preamble (
|
|
.i(inp),.o(in0)
|
|
);
|
|
end else begin : gen_no_strip_preamble
|
|
// Remove the preamble since CHDR and CPU don't expect it.
|
|
always_comb begin
|
|
`AXI4S_ASSIGN(in0,eth_rx);
|
|
end
|
|
end
|
|
|
|
//---------------------------------------
|
|
// Input pipeline stages
|
|
//---------------------------------------
|
|
axi4s_fifo #(
|
|
.SIZE(1)
|
|
) in_reg_i (
|
|
.clear(1'b0),.space(),.occupied(),
|
|
.i(in0), .o(in1)
|
|
);
|
|
|
|
axi4s_fifo #(
|
|
.SIZE(1)
|
|
) in_reg2_i (
|
|
.clear(1'b0),.space(),.occupied(),
|
|
.i(in1), .o(in2)
|
|
);
|
|
|
|
//---------------------------------------
|
|
// 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 mac_error;
|
|
logic min_packet_error;
|
|
logic reached_min_packet;
|
|
logic cpu_push_error;
|
|
logic chdr_push_error;
|
|
|
|
// 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;
|
|
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 (in1.tvalid && in1.tready) begin
|
|
eth_dst_is_broadcast <= eth_dst_addr_old == ETH_ADDR_BCAST;
|
|
eth_dst_is_me <= eth_dst_addr_old == e_my_mac;
|
|
udp_dst_is_me <= udp_dst_port_old == e_my_udp_chdr_port;
|
|
ipv4_dst_is_me <= ipv4_dst_addr_old == e_my_ip;
|
|
ipv4_protocol_is_udp <= ip_protocol_old == IPV4_PROTO_UDP;
|
|
eth_type_is_ipv4 <= eth_type_old == ETH_TYPE_IPV4;
|
|
end
|
|
end
|
|
end
|
|
|
|
// Get the fields - don't use assign. assign will not activate with changes
|
|
// to in0.
|
|
localparam int OFFSET = CPU_PREAMBLE ? PREAMBLE_BYTES : 0;
|
|
always_comb begin : get_fields
|
|
eth_dst_addr_new = in0.get_packet_field48(eth_dst_addr_old, OFFSET+DST_MAC_BYTE, .NETWORK_ORDER(1));
|
|
eth_src_addr_new = in0.get_packet_field48(eth_src_addr_old, OFFSET+SRC_MAC_BYTE, .NETWORK_ORDER(1));
|
|
ip_version_new = in0.get_packet_byte (ip_version_old, OFFSET+IP_VERSION_BYTE);
|
|
ip_protocol_new = in0.get_packet_byte (ip_protocol_old, OFFSET+PROTOCOL_BYTE );
|
|
ipv4_src_addr_new = in0.get_packet_field32(ipv4_src_addr_old, OFFSET+SRC_IP_BYTE, .NETWORK_ORDER(1));
|
|
ipv4_dst_addr_new = in0.get_packet_field32(ipv4_dst_addr_old, OFFSET+DST_IP_BYTE, .NETWORK_ORDER(1));
|
|
udp_src_port_new = in0.get_packet_field16(udp_src_port_old, OFFSET+SRC_PORT_BYTE, .NETWORK_ORDER(1));
|
|
udp_dst_port_new = in0.get_packet_field16(udp_dst_port_old, OFFSET+DST_PORT_BYTE, .NETWORK_ORDER(1));
|
|
eth_type_new = in0.get_packet_field16(eth_type_old, OFFSET+ETH_TYPE_BYTE, .NETWORK_ORDER(1));
|
|
end
|
|
|
|
always_ff @(posedge eth_rx.clk) begin : reached_bytes
|
|
reached_min_packet_new = in1.reached_packet_byte(OFFSET+MIN_PACKET_SIZE_BYTE);
|
|
reached_end_of_udp = in1.reached_packet_byte(OFFSET+DST_PORT_BYTE+3);// we have enough to decide
|
|
end
|
|
|
|
// calculate error conditions
|
|
assign mac_error = in2.tuser[ERROR_BIT] && in2.tvalid;
|
|
assign cpu_push_error = (in3.tvalid && !cpu0.tready);
|
|
assign chdr_push_error = (in3.tvalid && !chdr0.tready);
|
|
|
|
if (DROP_MIN_PACKET) begin
|
|
assign reached_min_packet = (reached_min_packet_new && in2.tuser[BYTES_MSB:0] ==0) || reached_min_packet_old;
|
|
assign min_packet_error = (in2.tlast && !reached_min_packet);
|
|
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 (in2.tvalid && in2.tready) begin
|
|
if (in2.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(in3,in2);
|
|
cpu_error = 1'b0;
|
|
chdr_error = 1'b0;
|
|
in2.tready = 1'b1; // never hold off
|
|
|
|
// 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 || cpu_push_error || chdr_push_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 || chdr_push_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 || cpu_push_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;
|
|
in3.tlast = 1'b1;
|
|
in3.tvalid = 1'b1;
|
|
next_dispatch_state = ST_DROP_WAIT;
|
|
end
|
|
|
|
// Unwanted Payload: wait
|
|
ST_DROP_WAIT: begin
|
|
cpu_error = 1'b0;
|
|
chdr_error = 1'b0;
|
|
in3.tlast = 1'b0;
|
|
in3.tvalid = 1'b0;
|
|
end
|
|
|
|
// We should never get here
|
|
default: begin
|
|
cpu_error = 1'b0;
|
|
chdr_error = 1'b0;
|
|
in3.tvalid = 1'b0;
|
|
in3.tlast = 1'b0;
|
|
next_dispatch_state = ST_IDLE_ETH_L0;
|
|
end
|
|
endcase
|
|
end
|
|
|
|
|
|
//---------------------------------------
|
|
// SPLIT
|
|
//---------------------------------------
|
|
// differentiating push_errors for reporting
|
|
logic cpu0_push_error, cpu0_push_error_old= 1'b0;
|
|
logic chdr0_push_error, chdr0_push_error_old= 1'b0;
|
|
logic chdr0_error, chdr0_error_old = 1'b0;
|
|
logic cpu0_error, cpu0_error_old = 1'b0;
|
|
|
|
always_comb begin : cpu0_assign
|
|
cpu0_error = (cpu_error && in3.tvalid) || cpu0_error_old;
|
|
cpu0_push_error = (cpu_push_error && in3.tvalid)|| cpu0_push_error_old;
|
|
cpu0.tdata = in3.tdata;
|
|
cpu0.tuser = in3.tuser;
|
|
cpu0.tlast = in3.tlast || cpu0_error;
|
|
cpu0.tvalid = in3.tvalid || cpu0_error;
|
|
|
|
chdr0_error = (chdr_error && in3.tvalid) || chdr0_error_old;
|
|
chdr0_push_error = (chdr_push_error && in3.tvalid) || chdr0_push_error_old;
|
|
chdr0.tdata = in3.tdata;
|
|
chdr0.tuser = in3.tuser;
|
|
chdr0.tlast = in3.tlast || chdr0_error;
|
|
chdr0.tvalid = in3.tvalid || chdr0_error;
|
|
|
|
// If the downstream sections are not ready, then the packet is dropped
|
|
// there isn't really any buffer up stream, so a hold off here would
|
|
// mean pushing back on a mac that doesn't have the capability to slow
|
|
// down, and a data word would be lost.
|
|
in3.tready = 1'b1;
|
|
|
|
end
|
|
|
|
// hold the error bits until the end of the packet
|
|
always_ff @(posedge eth_rx.clk) begin : error_ff
|
|
if (eth_rx.rst) begin
|
|
cpu0_error_old <= 1'b0;
|
|
chdr0_error_old <= 1'b0;
|
|
cpu0_push_error_old <= 1'b0;
|
|
chdr0_push_error_old <= 1'b0;
|
|
chdr_dropped <= 1'b0;
|
|
cpu_dropped <= 1'b0;
|
|
end else begin
|
|
|
|
// report dropped back at the end of packet when push_error is detected.
|
|
// 1st term counts the drop if ready lets up before the end of the packet
|
|
// 2nd term counts the drop if ready is held through then end of the packet
|
|
// NOTE: Drop counts don't have to be perfect. This gets pretty close though.
|
|
// I.e. Don't sweat this more in the future.
|
|
chdr_dropped <= (chdr0_push_error && chdr0.tlast && chdr0.tvalid && chdr0.tready) ||
|
|
(chdr0_push_error && in2.tlast && in2.tvalid);
|
|
cpu_dropped <= (cpu0_push_error && cpu0.tlast && cpu0.tvalid && cpu0.tready) ||
|
|
(cpu0_push_error && in2.tlast && in2.tvalid);
|
|
|
|
// don't clear till we discard a packet
|
|
if (cpu0.tlast && cpu0.tvalid && cpu0.tready) begin
|
|
cpu0_push_error_old <= 1'b0;
|
|
// remember if we saw an error
|
|
end else if (cpu0_push_error && cpu0.tvalid) begin
|
|
cpu0_push_error_old <= 1'b1;
|
|
end
|
|
|
|
// don't clear till we discard a packet
|
|
if (cpu0.tlast && cpu0.tvalid && cpu0.tready) begin
|
|
cpu0_error_old <= 1'b0;
|
|
// remember if we saw an error
|
|
end else if (cpu0_error && cpu0.tvalid) begin
|
|
cpu0_error_old <= 1'b1;
|
|
end
|
|
|
|
// don't clear till we discard a packet
|
|
if (chdr0.tlast && chdr0.tvalid && chdr0.tready) begin
|
|
chdr0_push_error_old <= 1'b0;
|
|
// remember if we saw an error
|
|
end else if (chdr0_push_error && chdr0.tvalid) begin
|
|
chdr0_push_error_old <= 1'b1;
|
|
end
|
|
|
|
// don't clear till we discard a packet
|
|
if (chdr0.tlast && chdr0.tvalid && chdr0.tready) begin
|
|
chdr0_error_old <= 1'b0;
|
|
// remember if we saw an error
|
|
end else if (chdr0_error && chdr0.tvalid) begin
|
|
chdr0_error_old <= 1'b1;
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
// 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
|
|
|
|
//---------------------------------------
|
|
// CPU Output processing
|
|
//---------------------------------------
|
|
// 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(14-$clog2(ENET_W/8)), .USE_AS_BUFF(0)
|
|
) cpu_out_gate_i (
|
|
.clear(1'b0), .error(cpu0_error),
|
|
.i(cpu0),.o(cpu1)
|
|
);
|
|
|
|
// The CPU can be slow to respond (relative to packet wire speed) so
|
|
// extra buffer for packets destined there so it doesn't back up.
|
|
axi4s_fifo #(
|
|
.SIZE(CPU_FIFO_SIZE-$clog2(ENET_W/8))
|
|
) cpu_fifo_i (
|
|
.clear(),.space(),.occupied(),
|
|
.i(cpu1),.o(e2c)
|
|
);
|
|
|
|
//---------------------------------------
|
|
// CHDR Output processing
|
|
//---------------------------------------
|
|
// 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(14-$clog2(ENET_W/8))
|
|
) chdr_out_gate_i (
|
|
.clear(1'b0),.error(chdr0_error),
|
|
.i(chdr0),.o(chdr1)
|
|
);
|
|
|
|
// The transport should hook up to a crossbar downstream, which
|
|
// may back-pressure this module because it is in the middle of
|
|
// transferring a packet. To ensure that upstream logic is not
|
|
// blocked, we instantiate at least one packet of buffering here.
|
|
// The actual size is set by CHDR_FIFO_SIZE.
|
|
logic [15:0] chdr_occupied;
|
|
logic [15:0] chdr_occupied_q;
|
|
localparam CHDR_FIFO_WORD_SIZE = CHDR_FIFO_SIZE-$clog2(ENET_W/8);
|
|
axi4s_fifo #(
|
|
.SIZE(CHDR_FIFO_WORD_SIZE)
|
|
) chdr_fifo_i (
|
|
.clear(1'b0),.space(),.occupied(chdr_occupied),
|
|
.i(chdr1),.o(chdr2)
|
|
);
|
|
|
|
// Remove the preamble on the CHDR path if it was not already removed.
|
|
if (PREAMBLE_BYTES > 0 && CPU_PREAMBLE) begin : gen_strip_chdr_preamble
|
|
axi4s_remove_bytes #(
|
|
.REM_START(0), .REM_END(PREAMBLE_BYTES-1)
|
|
) strip_preamble (
|
|
.i(chdr2), .o(e2v)
|
|
);
|
|
end else begin : gen_no_strip_chdr_preamble
|
|
always_comb begin
|
|
`AXI4S_ASSIGN(e2v, chdr2);
|
|
end
|
|
end
|
|
|
|
// Documentation requires pause requests to be set for a minimum of 16
|
|
// clocks. I'm providing the same guaranteed min time in the set and clear
|
|
// direction.
|
|
logic [3:0] pause_timer;
|
|
typedef enum logic [1:0] {
|
|
ST_IDLE = 2'd0,
|
|
ST_MIN_DELAY_SET = 2'd1,
|
|
ST_REQUESTING = 2'd2,
|
|
ST_MIN_DELAY_CLR = 2'd3
|
|
} pause_state_t;
|
|
pause_state_t pause_state = ST_IDLE;
|
|
|
|
always_ff @(posedge eth_rx.clk) begin : pause_req_ff
|
|
if (eth_rx.rst) begin
|
|
chdr_occupied_q <= 0;
|
|
eth_pause_req <= 1'b0;
|
|
pause_state <= ST_IDLE;
|
|
pause_timer <= 0;
|
|
end else begin
|
|
chdr_occupied_q <= chdr_occupied;
|
|
|
|
case (pause_state)
|
|
|
|
ST_IDLE: begin
|
|
pause_timer <= 0;
|
|
if (chdr_occupied_q >= e_pause_set) begin
|
|
eth_pause_req <= 1'b1;
|
|
pause_state <= ST_MIN_DELAY_SET;
|
|
pause_timer <= pause_timer-1; // Wrap counter to max value
|
|
end
|
|
end
|
|
|
|
ST_MIN_DELAY_SET: begin
|
|
pause_timer <= pause_timer-1;
|
|
if (pause_timer == 1) begin
|
|
pause_state <= ST_REQUESTING;
|
|
end
|
|
end
|
|
|
|
ST_REQUESTING: begin
|
|
pause_timer <= 0;
|
|
if (chdr_occupied_q <= e_pause_clear) begin
|
|
eth_pause_req <= 1'b0;
|
|
pause_state <= ST_MIN_DELAY_CLR;
|
|
pause_timer <= pause_timer-1; // Wrap counter to max value
|
|
end
|
|
end
|
|
|
|
ST_MIN_DELAY_CLR: begin
|
|
pause_timer <= pause_timer-1;
|
|
if (pause_timer == 1) begin
|
|
pause_state <= ST_IDLE;
|
|
end
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
|
|
|
|
endmodule // eth_ipv4_chdr_dispatch
|