From c33afac783a7fd7e1baa3ce85b4bdc76ca1ede2c Mon Sep 17 00:00:00 2001 From: Phil Date: Fri, 10 Apr 2026 15:37:19 +0300 Subject: [PATCH] rtl: implement axis UDP TX logic --- rtl/ethernet-udp/src/eth/axis_mac.sv | 239 ++++++++++++++++++++++++--- 1 file changed, 213 insertions(+), 26 deletions(-) diff --git a/rtl/ethernet-udp/src/eth/axis_mac.sv b/rtl/ethernet-udp/src/eth/axis_mac.sv index 1aef3bb..87bb56a 100644 --- a/rtl/ethernet-udp/src/eth/axis_mac.sv +++ b/rtl/ethernet-udp/src/eth/axis_mac.sv @@ -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 \ No newline at end of file