From 0b9fb64193d523c29fd4fedf01dc1d8c42fc39cd Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 1 Apr 2026 11:44:46 +0300 Subject: [PATCH] rtl: add axis eth rx prototype --- rtl/ethernet-udp/src/eth/mac/rx/mac_rx_top.v | 16 +- rtl/ethernet-udp/src/eth/mac/tx/mac_tx_top.v | 20 +- rtl/ethernet-udp/tests/eth_axis/Makefile | 47 ++++ .../tests/eth_minimal/axis_mac.sv | 229 ++++++++++++++++++ 4 files changed, 294 insertions(+), 18 deletions(-) create mode 100644 rtl/ethernet-udp/tests/eth_axis/Makefile create mode 100644 rtl/ethernet-udp/tests/eth_minimal/axis_mac.sv diff --git a/rtl/ethernet-udp/src/eth/mac/rx/mac_rx_top.v b/rtl/ethernet-udp/src/eth/mac/rx/mac_rx_top.v index 017475d..46de2fe 100644 --- a/rtl/ethernet-udp/src/eth/mac/rx/mac_rx_top.v +++ b/rtl/ethernet-udp/src/eth/mac/rx/mac_rx_top.v @@ -10,7 +10,7 @@ module mac_rx_top input rst_n, input rx_dv, - input [7:0] mac_rx_datain, + (* MARK_DEBUG="true" *)input [7:0] mac_rx_datain, input [31:0] local_ip_addr, input [47:0] local_mac_addr, @@ -21,14 +21,14 @@ module mac_rx_top output [47:0] arp_rec_source_mac_addr, - output [7:0] udp_rec_ram_rdata , - input [10:0] udp_rec_ram_read_addr, - output [15:0] udp_rec_data_length, - output udp_rec_data_valid, + (* MARK_DEBUG="true" *)output [7:0] udp_rec_ram_rdata , + (* MARK_DEBUG="true" *)input [10:0] udp_rec_ram_read_addr, + (* MARK_DEBUG="true" *)output [15:0] udp_rec_data_length, + (* MARK_DEBUG="true" *)output udp_rec_data_valid, - output [7:0] mac_rx_dataout, - output [15:0] upper_layer_data_length , - output [15:0] ip_total_data_length, + (* MARK_DEBUG="true" *)output [7:0] mac_rx_dataout, + (* MARK_DEBUG="true" *)output [15:0] upper_layer_data_length , + (* MARK_DEBUG="true" *)output [15:0] ip_total_data_length, output icmp_rx_req, output icmp_rev_error, diff --git a/rtl/ethernet-udp/src/eth/mac/tx/mac_tx_top.v b/rtl/ethernet-udp/src/eth/mac/tx/mac_tx_top.v index 71457af..0a230a5 100644 --- a/rtl/ethernet-udp/src/eth/mac/tx/mac_tx_top.v +++ b/rtl/ethernet-udp/src/eth/mac/tx/mac_tx_top.v @@ -24,13 +24,13 @@ module mac_tx_top input arp_request_req, - input [7:0] ram_wr_data, - input ram_wr_en, - input udp_tx_req, - output udp_ram_data_req, - input [15:0] udp_send_data_length, - output udp_tx_end, - output almost_full, + (* MARK_DEBUG="true" *)input [7:0] ram_wr_data, + (* MARK_DEBUG="true" *)input ram_wr_en, + (* MARK_DEBUG="true" *)input udp_tx_req, + (* MARK_DEBUG="true" *)output udp_ram_data_req, + (* MARK_DEBUG="true" *)input [15:0] udp_send_data_length, + (* MARK_DEBUG="true" *)output udp_tx_end, + (* MARK_DEBUG="true" *)output almost_full, output upper_data_req, input icmp_tx_ready, @@ -40,9 +40,9 @@ module mac_tx_top output icmp_tx_ack, input [15:0] icmp_send_data_length, - output mac_data_valid, - output mac_send_end, - output [7:0] mac_tx_data + (* MARK_DEBUG="true" *)output mac_data_valid, + (* MARK_DEBUG="true" *)output mac_send_end, + (* MARK_DEBUG="true" *)output [7:0] mac_tx_data ) ; diff --git a/rtl/ethernet-udp/tests/eth_axis/Makefile b/rtl/ethernet-udp/tests/eth_axis/Makefile new file mode 100644 index 0000000..4c761cc --- /dev/null +++ b/rtl/ethernet-udp/tests/eth_axis/Makefile @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: MIT +# +# Copyright (c) 2025 FPGA Ninja, LLC +# +# Authors: +# - Alex Forencich +# + +# FPGA settings +FPGA_PART = xc7a35tfgg484-1 +FPGA_TOP = ethernet_test_minimal +FPGA_ARCH = artix7 + +RTL_DIR = ../../src + +# Files for synthesis +SYN_FILES = ethernet_test_minimal.v + +include ../../../../scripts/vivado.mk + +SYN_FILES += $(sort $(shell find ../../src -type f \( -name '*.v' -o -name '*.sv' \))) + +XCI_FILES = $(sort $(shell find ../../src -type f -name '*.xci')) + +program: $(PROJECT).bit + echo "open_hw_manager" > program.tcl + echo "connect_hw_server" >> program.tcl + echo "open_hw_target" >> program.tcl + echo "current_hw_device [lindex [get_hw_devices] 0]" >> program.tcl + echo "refresh_hw_device -update_hw_probes false [current_hw_device]" >> program.tcl + echo "set_property PROGRAM.FILE {$(PROJECT).bit} [current_hw_device]" >> program.tcl + echo "program_hw_devices [current_hw_device]" >> program.tcl + echo "exit" >> program.tcl + vivado -nojournal -nolog -mode batch -source program.tcl + +$(PROJECT).mcs $(PROJECT).prm: $(PROJECT).bit + echo "write_cfgmem -force -format mcs -size 16 -interface SPIx4 -loadbit {up 0x0000000 $*.bit} -checksum -file $*.mcs" > generate_mcs.tcl + echo "exit" >> generate_mcs.tcl + vivado -nojournal -nolog -mode batch -source generate_mcs.tcl + mkdir -p rev + COUNT=100; \ + while [ -e rev/$*_rev$$COUNT.bit ]; \ + do COUNT=$$((COUNT+1)); done; \ + COUNT=$$((COUNT-1)); \ + for x in .mcs .prm; \ + do cp $*$$x rev/$*_rev$$COUNT$$x; \ + echo "Output: rev/$*_rev$$COUNT$$x"; done; diff --git a/rtl/ethernet-udp/tests/eth_minimal/axis_mac.sv b/rtl/ethernet-udp/tests/eth_minimal/axis_mac.sv new file mode 100644 index 0000000..f014390 --- /dev/null +++ b/rtl/ethernet-udp/tests/eth_minimal/axis_mac.sv @@ -0,0 +1,229 @@ +`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_ADDR = 2'd1; + localparam RX_DATA = 2'd2; + + (* 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_ADDR; + end else begin + rx_payload_len <= 16'd0; + rx_state <= RX_IDLE; + end + end + end + + // one cycle for synchronous BRAM read latency + RX_ADDR: begin + m_axis_rx_tvalid <= 1'b0; + m_axis_rx_tlast <= 1'b0; + rx_state <= RX_DATA; + end + + RX_DATA: begin + // hold valid until accepted + if (m_axis_rx_tvalid && !m_axis_rx_tready) begin + m_axis_rx_tvalid <= m_axis_rx_tvalid; + m_axis_rx_tlast <= m_axis_rx_tlast; + 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 - 1); + + if (rx_index == rx_payload_len - 1) begin + // last byte accepted immediately if ready=1, + // otherwise valid/last remain asserted until ready + if (m_axis_rx_tready) begin + m_axis_rx_tvalid <= 1'b0; + m_axis_rx_tlast <= 1'b0; + rx_state <= RX_IDLE; + end + end else begin + if (m_axis_rx_tready) begin + rx_index <= rx_index + 1'b1; + udp_rec_ram_read_addr <= rx_index + 1'b1; // next byte + rx_state <= RX_ADDR; + m_axis_rx_tvalid <= 1'b0; + m_axis_rx_tlast <= 1'b0; + end + end + 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