rtl: implement axis UDP TX logic

This commit is contained in:
Phil
2026-04-10 15:37:19 +03:00
parent 26c627c988
commit c33afac783

View File

@ -1,3 +1,5 @@
// ethernet MAC with axi stream IO with UDP
`timescale 1 ns / 1 ns
module axis_mac
@ -14,8 +16,19 @@ module axis_mac
// 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,
input wire m_axis_rx_tready,
(* MARK_DEBUG="true" *)output reg m_axis_rx_tlast
(* 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
);
// ----------------------------------------------------------------
@ -50,29 +63,27 @@ module axis_mac
end
end
// ----------------------------------------------------------------
// Unused user TX path into mac_top
// We disable user UDP TX for now.
// ARP replies and ICMP replies inside mac_top still work.
// ----------------------------------------------------------------
wire udp_ram_data_req;
wire udp_tx_end;
wire almost_full;
wire mac_send_end;
wire arp_found;
wire mac_not_exist;
// 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 [7:0] udp_rec_ram_rdata;
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;
wire [7:0] tx_ram_wr_data = 8'd0;
wire tx_ram_wr_en = 1'b0;
wire [15:0] udp_send_data_length = 16'd0;
wire udp_tx_req = 1'b0;
wire arp_request_req = 1'b0;
mac_top mac_top0 (
.gmii_tx_clk (gmii_tx_clk),
.gmii_rx_clk (gmii_rx_clk),
@ -81,7 +92,7 @@ module axis_mac
.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 (не используется для user TX пока)
.destination_ip_addr (32'hc0a80003), // 192.168.0.3
.udp_send_source_port (16'h1f90), // 8080
.udp_send_destination_port (16'h1f90), // 8080
@ -111,9 +122,7 @@ module axis_mac
.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
@ -137,8 +146,6 @@ module axis_mac
// - read bytes 0 .. payload_len-1 from RX RAM
// - output them on AXIS
//
// Because the BRAM read port is synchronous, this bridge may insert
// bubbles between bytes. For first bring-up this is fine.
// ----------------------------------------------------------------
localparam RX_IDLE = 2'd0;
localparam RX_NOTREADY = 2'd1;
@ -230,7 +237,6 @@ module axis_mac
rx_state <= RX_IDLE;
end
rx_index <= rx_index + 1'b1;
udp_rec_ram_read_addr <= rx_index + 1'b1; // next byte
end
@ -245,4 +251,185 @@ module axis_mac
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