From 3a58119960f73daa3d614c52841506eb01220ab9 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 1 Apr 2026 18:03:47 +0300 Subject: [PATCH] rtl: eth udp rx -> axis --- rtl/ethernet-udp/src/eth/axis_mac.sv | 248 +++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 rtl/ethernet-udp/src/eth/axis_mac.sv diff --git a/rtl/ethernet-udp/src/eth/axis_mac.sv b/rtl/ethernet-udp/src/eth/axis_mac.sv new file mode 100644 index 0000000..1aef3bb --- /dev/null +++ b/rtl/ethernet-udp/src/eth/axis_mac.sv @@ -0,0 +1,248 @@ +`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, + input wire m_axis_rx_tready, + (* MARK_DEBUG="true" *)output reg m_axis_rx_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 + + // ---------------------------------------------------------------- + // 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; + + wire [7:0] udp_rec_ram_rdata; + reg [10:0] udp_rec_ram_read_addr; + 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), + .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 (не используется для user TX пока) + .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 + // + // 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; + 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 + +endmodule \ No newline at end of file