435 lines
15 KiB
Systemverilog
435 lines
15 KiB
Systemverilog
// ethernet MAC with axi stream IO with UDP
|
|
|
|
`timescale 1 ns / 1 ns
|
|
|
|
module axis_mac
|
|
(
|
|
input rst_n,
|
|
input gmii_tx_clk,
|
|
input gmii_rx_clk,
|
|
input gmii_rx_dv,
|
|
input [7:0] gmii_rxd,
|
|
|
|
output reg gmii_tx_en,
|
|
output reg [7:0] gmii_txd,
|
|
|
|
// AXI-stream RX output (clock domain = gmii_rx_clk)
|
|
(* MARK_DEBUG="true" *)output reg [7:0] m_axis_rx_tdata,
|
|
(* MARK_DEBUG="true" *)output reg m_axis_rx_tvalid,
|
|
(* MARK_DEBUG="true" *)input wire m_axis_rx_tready,
|
|
(* MARK_DEBUG="true" *)output reg m_axis_rx_tlast,
|
|
(* MARK_DEBUG="true" *)output [15:0] udp_rec_data_length,
|
|
|
|
// tx part
|
|
(* MARK_DEBUG="true" *)input wire send_req,
|
|
input wire [15:0] data_length,
|
|
(* MARK_DEBUG="true" *)output reg req_ready,
|
|
|
|
(* MARK_DEBUG="true" *)input wire [7:0] s_axis_tx_tdata,
|
|
(* MARK_DEBUG="true" *)input wire s_axis_tx_tvalid,
|
|
(* MARK_DEBUG="true" *)output reg s_axis_tx_tready,
|
|
(* MARK_DEBUG="true" *)input wire s_axis_tx_tlast
|
|
);
|
|
|
|
// ----------------------------------------------------------------
|
|
// GMII RX input registering
|
|
// ----------------------------------------------------------------
|
|
reg gmii_rx_dv_d0;
|
|
reg [7:0] gmii_rxd_d0;
|
|
|
|
always @(posedge gmii_rx_clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
gmii_rx_dv_d0 <= 1'b0;
|
|
gmii_rxd_d0 <= 8'd0;
|
|
end else begin
|
|
gmii_rx_dv_d0 <= gmii_rx_dv;
|
|
gmii_rxd_d0 <= gmii_rxd;
|
|
end
|
|
end
|
|
|
|
// ----------------------------------------------------------------
|
|
// TX path from mac_top
|
|
// ----------------------------------------------------------------
|
|
wire gmii_tx_en_tmp;
|
|
wire [7:0] gmii_txd_tmp;
|
|
|
|
always @(posedge gmii_tx_clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
gmii_tx_en <= 1'b0;
|
|
gmii_txd <= 8'd0;
|
|
end else begin
|
|
gmii_tx_en <= gmii_tx_en_tmp;
|
|
gmii_txd <= gmii_txd_tmp;
|
|
end
|
|
end
|
|
|
|
// TX signals
|
|
reg [7:0] tx_ram_wr_data;
|
|
reg tx_ram_wr_en;
|
|
reg [15:0] udp_send_data_length;
|
|
reg udp_tx_req;
|
|
reg arp_request_req;
|
|
wire mac_send_end;
|
|
|
|
wire udp_ram_data_req;
|
|
wire udp_tx_end;
|
|
wire almost_full;
|
|
(* MARK_DEBUG="true" *)wire arp_found;
|
|
wire mac_not_exist;
|
|
wire [15:0] udp_ram_data_count;
|
|
|
|
// RX signals
|
|
reg [10:0] udp_rec_ram_read_addr;
|
|
wire [7:0] udp_rec_ram_rdata;
|
|
wire [15:0] udp_rec_data_length;
|
|
wire udp_rec_data_valid;
|
|
|
|
mac_top mac_top0 (
|
|
.gmii_tx_clk (gmii_tx_clk),
|
|
.gmii_rx_clk (gmii_rx_clk),
|
|
.rst_n (rst_n),
|
|
|
|
.source_mac_addr (48'h00_0a_35_01_fe_c0),
|
|
.TTL (8'h80),
|
|
.source_ip_addr (32'hc0a80002), // 192.168.0.2
|
|
.destination_ip_addr (32'hc0a80003), // 192.168.0.3
|
|
.udp_send_source_port (16'h1f90), // 8080
|
|
.udp_send_destination_port (16'h1f90), // 8080
|
|
|
|
.ram_wr_data (tx_ram_wr_data),
|
|
.ram_wr_en (tx_ram_wr_en),
|
|
.udp_ram_data_req (udp_ram_data_req),
|
|
.udp_send_data_length (udp_send_data_length),
|
|
.udp_tx_end (udp_tx_end),
|
|
.almost_full (almost_full),
|
|
|
|
.udp_tx_req (udp_tx_req),
|
|
.arp_request_req (arp_request_req),
|
|
|
|
.mac_send_end (mac_send_end),
|
|
.mac_data_valid (gmii_tx_en_tmp),
|
|
.mac_tx_data (gmii_txd_tmp),
|
|
|
|
.rx_dv (gmii_rx_dv_d0),
|
|
.mac_rx_datain (gmii_rxd_d0),
|
|
|
|
.udp_rec_ram_rdata (udp_rec_ram_rdata),
|
|
.udp_rec_ram_read_addr (udp_rec_ram_read_addr),
|
|
.udp_rec_data_length (udp_rec_data_length),
|
|
.udp_rec_data_valid (udp_rec_data_valid),
|
|
|
|
.arp_found (arp_found),
|
|
.mac_not_exist (mac_not_exist)
|
|
);
|
|
|
|
// Detect "new packet ready" on udp_rec_data_valid rising edge
|
|
reg udp_rec_data_valid_d0;
|
|
|
|
always @(posedge gmii_rx_clk or negedge rst_n) begin
|
|
if (!rst_n)
|
|
udp_rec_data_valid_d0 <= 1'b0;
|
|
else
|
|
udp_rec_data_valid_d0 <= udp_rec_data_valid;
|
|
end
|
|
|
|
wire udp_pkt_done = udp_rec_data_valid & ~udp_rec_data_valid_d0;
|
|
|
|
// ----------------------------------------------------------------
|
|
// RX RAM -> AXI-stream bridge
|
|
//
|
|
// Assumption:
|
|
// udp_rec_data_length includes 8-byte UDP header,
|
|
// so payload length = udp_rec_data_length - 8
|
|
//
|
|
// This implementation is simple and safe:
|
|
// - start on udp_pkt_done
|
|
// - read bytes 0 .. payload_len-1 from RX RAM
|
|
// - output them on AXIS
|
|
//
|
|
// ----------------------------------------------------------------
|
|
localparam RX_IDLE = 2'd0;
|
|
localparam RX_NOTREADY = 2'd1;
|
|
localparam RX_START = 2'd2;
|
|
localparam RX_DATA = 2'd3;
|
|
|
|
(* MARK_DEBUG="true" *) reg [1:0] rx_state;
|
|
(* MARK_DEBUG="true" *) reg [15:0] rx_payload_len;
|
|
(* MARK_DEBUG="true" *) reg [15:0] rx_index;
|
|
|
|
always @(posedge gmii_rx_clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
rx_state <= RX_IDLE;
|
|
rx_payload_len <= 16'd0;
|
|
rx_index <= 16'd0;
|
|
udp_rec_ram_read_addr <= 11'd0;
|
|
|
|
m_axis_rx_tdata <= 8'd0;
|
|
m_axis_rx_tvalid <= 1'b0;
|
|
m_axis_rx_tlast <= 1'b0;
|
|
|
|
end else begin
|
|
case (rx_state)
|
|
RX_IDLE: begin
|
|
m_axis_rx_tvalid <= 1'b0;
|
|
m_axis_rx_tlast <= 1'b0;
|
|
rx_index <= 16'd0;
|
|
udp_rec_ram_read_addr <= 11'd0;
|
|
|
|
if (udp_pkt_done) begin
|
|
// protect against pathological short values
|
|
if (udp_rec_data_length > 16'd8) begin
|
|
rx_payload_len <= udp_rec_data_length - 16'd8;
|
|
udp_rec_ram_read_addr <= 11'd0; // issue read for byte 0
|
|
rx_state <= RX_NOTREADY;
|
|
end else begin
|
|
rx_payload_len <= 16'd0;
|
|
rx_state <= RX_IDLE;
|
|
end
|
|
end
|
|
end
|
|
|
|
// one cycle for synchronous BRAM read latency
|
|
RX_NOTREADY: begin
|
|
m_axis_rx_tvalid <= 1'b0;
|
|
m_axis_rx_tlast <= 1'b0;
|
|
|
|
if (m_axis_rx_tready)
|
|
rx_state <= RX_START;
|
|
end
|
|
|
|
RX_START: begin
|
|
if (m_axis_rx_tready) begin
|
|
// put current data
|
|
// end of data?
|
|
if (rx_index == (rx_payload_len - 1)) begin
|
|
rx_state <= RX_IDLE;
|
|
end else begin
|
|
rx_state <= RX_DATA;
|
|
end
|
|
|
|
// always increment pointer
|
|
rx_index <= rx_index + 1'b1;
|
|
udp_rec_ram_read_addr <= rx_index + 1'b1; // next byte
|
|
end else begin
|
|
rx_state <= RX_NOTREADY;
|
|
|
|
end
|
|
end
|
|
|
|
RX_DATA: begin
|
|
// hold valid until accepted
|
|
if (!m_axis_rx_tready) begin
|
|
// take a break while not ready
|
|
m_axis_rx_tvalid <= m_axis_rx_tvalid;
|
|
m_axis_rx_tlast <= m_axis_rx_tlast;
|
|
rx_state <= RX_NOTREADY;
|
|
// reset increment
|
|
rx_index <= rx_index - 1'b1;
|
|
udp_rec_ram_read_addr <= rx_index - 1'b1;
|
|
end else begin
|
|
// present current byte from RAM
|
|
m_axis_rx_tdata <= udp_rec_ram_rdata;
|
|
m_axis_rx_tvalid <= 1'b1;
|
|
m_axis_rx_tlast <= (rx_index == (rx_payload_len));
|
|
if (rx_index == (rx_payload_len)) begin
|
|
// last byte accepted immediately if ready=1,
|
|
// otherwise valid/last remain asserted until ready
|
|
rx_state <= RX_IDLE;
|
|
end
|
|
|
|
rx_index <= rx_index + 1'b1;
|
|
udp_rec_ram_read_addr <= rx_index + 1'b1; // next byte
|
|
end
|
|
end
|
|
|
|
default: begin
|
|
rx_state <= RX_IDLE;
|
|
m_axis_rx_tvalid <= 1'b0;
|
|
m_axis_rx_tlast <= 1'b0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
|
|
// ----------------------------------------------------------------
|
|
// TX FSM
|
|
// Semantics:
|
|
// - send_req/data_length form a packet send request
|
|
// - udp_tx_req is held HIGH until udp_ram_data_req pulses
|
|
// - udp_ram_data_req is a pulse - start feeding payload now to RAM
|
|
// - AXIS ready is asserted only during payload write phase
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
localparam TX_IDLE = 3'd0;
|
|
localparam TX_ARP_REQ = 3'd1;
|
|
localparam TX_ARP_SEND = 3'd2;
|
|
localparam TX_WAIT_ARP = 3'd3;
|
|
localparam TX_WAIT_RAM_REQ = 3'd4;
|
|
localparam TX_STREAM = 3'd5;
|
|
localparam TX_WAIT_DRAIN = 3'd6;
|
|
|
|
(* MARK_DEBUG="true" *)reg [2:0] tx_state;
|
|
|
|
assign arp_request_req = (tx_state == TX_ARP_REQ) ;
|
|
|
|
reg [15:0] tx_req_len;
|
|
reg [15:0] tx_bytes_written;
|
|
reg [15:0] tx_release_threshold;
|
|
|
|
reg tx_req_inflight;
|
|
|
|
// register for long arp timeout, if no got no response
|
|
reg [31:0] arp_delay;
|
|
reg arp_cached;
|
|
|
|
always @(posedge gmii_tx_clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
tx_state <= TX_IDLE;
|
|
|
|
tx_ram_wr_data <= 8'd0;
|
|
arp_cached <= 1'b0;
|
|
tx_ram_wr_en <= 1'b0;
|
|
udp_send_data_length <= 16'd0;
|
|
udp_tx_req <= 1'b0;
|
|
arp_delay <= 32'b0;
|
|
|
|
s_axis_tx_tready <= 1'b0;
|
|
req_ready <= 1'b0;
|
|
|
|
tx_req_len <= 16'd0;
|
|
tx_bytes_written <= 16'd0;
|
|
tx_release_threshold <= 16'd0;
|
|
|
|
tx_req_inflight <= 1'b0;
|
|
end else begin
|
|
// defaults
|
|
tx_ram_wr_en <= 1'b0;
|
|
|
|
case (tx_state)
|
|
// Ready to accept a new packet request
|
|
TX_IDLE: begin
|
|
udp_tx_req <= 1'b0;
|
|
s_axis_tx_tready <= 1'b0;
|
|
tx_bytes_written <= 16'd0;
|
|
tx_req_inflight <= 1'b0;
|
|
|
|
req_ready <= arp_cached && !almost_full;
|
|
|
|
if (send_req && req_ready) begin
|
|
tx_req_len <= data_length;
|
|
udp_send_data_length <= data_length;
|
|
tx_req_inflight <= 1'b1;
|
|
|
|
// threshold for allowing next packet
|
|
// to be written to the RAM
|
|
if (data_length > 16'd16)
|
|
tx_release_threshold <= data_length - 16'd16;
|
|
else
|
|
tx_release_threshold <= 16'd0;
|
|
|
|
tx_state <= TX_WAIT_RAM_REQ;
|
|
end
|
|
|
|
// arp check
|
|
if (!arp_cached) begin
|
|
tx_state <= TX_ARP_REQ;
|
|
end
|
|
end
|
|
|
|
// Pulse ARP request
|
|
TX_ARP_REQ: begin
|
|
req_ready <= 1'b0;
|
|
s_axis_tx_tready <= 1'b0;
|
|
udp_tx_req <= 1'b0;
|
|
|
|
arp_delay <= 32'ha000000;
|
|
tx_state <= TX_ARP_SEND;
|
|
end
|
|
|
|
// Wait until ARP is resolved
|
|
TX_ARP_SEND: begin
|
|
req_ready <= 1'b0;
|
|
s_axis_tx_tready <= 1'b0;
|
|
udp_tx_req <= 1'b0;
|
|
|
|
// sent
|
|
if (mac_send_end)
|
|
tx_state <= TX_WAIT_ARP;
|
|
end
|
|
|
|
// wait for ARP response
|
|
TX_WAIT_ARP: begin
|
|
if (arp_found) begin
|
|
arp_cached <= 1'b1;
|
|
tx_state <= TX_IDLE;
|
|
end
|
|
|
|
// timeout to not spam ARPs
|
|
if (arp_delay == 32'b0) begin
|
|
// re-try
|
|
tx_state <= TX_ARP_REQ;
|
|
end else begin
|
|
// wait
|
|
arp_delay = arp_delay - 32'b1;
|
|
end
|
|
|
|
end
|
|
|
|
// Hold udp_tx_req until udp_ram_data_req pulse arrives
|
|
TX_WAIT_RAM_REQ: begin
|
|
req_ready <= 1'b0;
|
|
udp_tx_req <= 1'b1;
|
|
|
|
if (udp_ram_data_req) begin
|
|
udp_tx_req <= 1'b0;
|
|
s_axis_tx_tready <= 1'b1;
|
|
tx_state <= TX_STREAM;
|
|
end
|
|
end
|
|
|
|
// Accept AXIS bytes and write them into TX RAM
|
|
TX_STREAM: begin
|
|
req_ready <= 1'b0;
|
|
udp_tx_req <= 1'b0;
|
|
|
|
// keep ready high while receiving payload bytes
|
|
s_axis_tx_tready <= (tx_bytes_written < tx_req_len);
|
|
|
|
if (s_axis_tx_tvalid && s_axis_tx_tready) begin
|
|
tx_ram_wr_data <= s_axis_tx_tdata;
|
|
tx_ram_wr_en <= 1'b1;
|
|
|
|
tx_bytes_written <= tx_bytes_written + 1'b1;
|
|
|
|
if (tx_bytes_written + 1'b1 >= tx_req_len) begin
|
|
s_axis_tx_tready <= 1'b0;
|
|
tx_state <= TX_WAIT_DRAIN;
|
|
end
|
|
end
|
|
end
|
|
|
|
// Packet payload is already in RAM.
|
|
// Wait until TX RAM starts draining enough to allow
|
|
// the next request.
|
|
TX_WAIT_DRAIN: begin
|
|
s_axis_tx_tready <= 1'b0;
|
|
udp_tx_req <= 1'b0;
|
|
|
|
if (udp_ram_data_count <= tx_release_threshold)
|
|
tx_state <= TX_IDLE;
|
|
|
|
end
|
|
|
|
default: begin
|
|
tx_state <= TX_IDLE;
|
|
tx_ram_wr_en <= 1'b0;
|
|
udp_tx_req <= 1'b0;
|
|
s_axis_tx_tready <= 1'b0;
|
|
req_ready <= 1'b0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
|
|
endmodule |