
1. 为什么非要跟FPGA较劲UDP协议栈1.1 什么场景下需要FPGA直接收发UDP先交代一下背景。这个系列走到第10篇前面的内容基本都在打地基GPIO、UART、数码管、PLL、FIFO这些说白了都是在芯片内部或者低速接口上折腾。到了网口这一块很多人会觉得FPGA搞以太网是不是有点过度设计ARM上跑个lwIP不香吗我自己一开始也是这个想法直到真的遇到了下面两类场景才改观。第一类是高速数据采集。ADC采样率一旦上了几百兆每秒产生的数据量就是几百MB甚至上GBARM的中断处理能力根本扛不住。而FPGA可以在采集端直接用状态机把数据打包成UDP报文通过千兆网口实时送出去CPU只需要在电脑端收包、存盘、做后处理。第二类是低延迟控制链路。比如一些仪器仪表、运动控制设备要求从传感器到上位机的延迟控制在微秒级跑协议栈的CPU因为操作系统调度和中断延迟很难做到稳定低延迟。FPGA用纯硬件逻辑管线处理报文延迟是可确定的这在很多工控现场就是硬需求。我自己做这个项目其实是为了给一块高速ADC板卡配一个上位机接口数据量大约是持续400Mbps左右。ARM方案算了一下中断加上协议栈开销跑到100Mbps就开始丢包了所以才下决心在FPGA里把UDP这条链路打通。这篇文章就把我学习verilog-ethernet这个开源工程的整个过程、踩过的坑、以及最终跑通的最小回环工程完整记录下来给后面想走同一条路的同学一个参考。1.2 为什么选verilog-ethernet而不是lwIP或自己写选型的时候其实纠结过一阵子。lwIP是软件协议栈跑在CPU上verilog-ethernet是纯RTL实现的MAC和UDP/IP协议栈直接在FPGA逻辑里完成链路层和网络层的处理。两者定位完全不同选谁取决于你的场景。如果要跑TCP、要复杂的连接管理、要跑HTTP这类应用层协议那老老实实用软核lwIP但如果是UDP简单收发、追求吞吐和低延迟FPGA里直接做协议栈明显更合适。verilog-ethernet这个工程在GitHub上由Alex Forencich维护大家可以直接搜到。它并不是一个单一模块而是一整套以太网组件库从MAC层到IP、UDP、ARP、DDR3接口都有而且是BSD风格的开源许可商用也友好。我选它的理由有三个一是接口用了标准的AXI-Stream跟我前几篇学的FIFO、AXI总线知识能无缝衔接二是参数化做得很好可以只挑需要的模块出来用三是有配套的testbench和文档对新手比对着一堆时序图自己猜要友好得多。当然也有人问为什么不自己写一个UDP发送模块我的建议是MAC层真的别自己写。以太网MAC涉及CRC32、前导码、冲突检测半双工模式下、帧间隙这些细节自己从零写很容易写出一堆边界case的bug而MAC层一旦出错抓包看到的都是垃圾帧排查起来非常痛苦。verilog-ethernet把MAC层封装好了你的核心精力可以放在怎么组织数据、怎么对接自己的业务逻辑上。2. 先搞懂UDP从网线到FPGA的一条路2.1 帧格式从MAC帧到UDP净荷很多同学上来就把UDP协议栈当成一个黑盒子直接按时序把数据塞进去就以为完事了。这样短时间可能能跑通但一旦出了问题你连抓包结果都看不懂。所以我建议先花半天时间把帧格式彻底搞清楚后面所有调试都会顺畅很多。一条完整的UDP报文从物理层到应用层依次是前导码8字节用于时钟同步、MAC目的地址6字节、MAC源地址6字节、可选VLAN标签4字节、以太网类型字段2字节IPv4是0x0800、IP头部标准20字节、UDP头部8字节、UDP数据载荷、FCS校验4字节CRC32一般由MAC层硬件生成和校验。在verilog-ethernet里MAC层处理的是从以太网类型字段之后到FCS之前的所有内容。如果是标准IPv4 UDP报文MAC层把以太网类型识别为0x0800后会把整帧IP数据包交给上层反过来发送时MAC层接收一个完整的IP数据包自动加上前导码、MAC地址和CRC后从PHY发出去。IP头部里面有几个字段要特别留意源/目的IP地址、协议字段UDP是17、总长度、头校验和。IP头校验和不是可选项路由器或PC网卡收到后都会做校验错了直接丢弃。UDP头部则是源端口、目的端口、长度UDP头和载荷的长度8字节头载荷最小是8、校验和。我做loopback回环的时候为了让PC端能正确识别数据源IP、目的IP、源MAC、目的MAC这些字段都要认真填。最简单的做法是让源MAC和源IP设成FPGA板卡的固定值目的MAC和目的IP设成PC网卡的值端口自己约定一个比如8080。2.2 校验和唯一需要动手算的东西verilog-ethernet这个工程里MAC层和IP层的大部分工作都是自动完成的唯一需要用户理解透的就是IP和UDP校验和。我当时在这里卡了差不多一整天最后发现是UDP校验和的伪头部算错了。IP头校验和的计算方法把IP头按16位一组累加如果累加过程中产生进位就把进位回卷到低位继续加最终结果取反。算法本身很简单但在Verilog里写的时候要防止溢出代码写出来大概长这样// 简化的IP头校验和计算 function [15:0] ip_checksum; input [159:0] header; // 20字节IP头 integer i; reg [31:0] sum; begin sum 0; for (i 0; i 10; i i 1) begin sum sum header[i*16 : 16]; if (sum[31:16] ! 0) begin sum {16b0, sum[15:0]} {16b0, sum[31:16]}; end end ip_checksum ~sum[15:0]; end endfunctionUDP校验和比IP头校验和多一个伪头部pseudo header它不是UDP报文的一部分只是在计算校验和时临时拼出来的12字节源IP4字节、目的IP4字节、协议号1字节UDP为17补1字节0、UDP长度2字节。过程是把伪头部 UDP头 UDP数据一起按16位累加取反。IPv4下UDP校验和是可以省略的置0即可但不推荐特别是在跨网段传输时部分路由器会对UDP校验和做检查丢了很冤。我在实际工程里直接把UDP校验和也做了因为verilog-ethernet里提供了eth_udp_tx模块的校验和选项设置为自动计算就行。但理解原理仍然有必要——万一你拿到一个别人写的简化版代码它把校验和跳过甚至算错了你就要能一眼看出来问题在哪。2.3 跨层看verilog-ethernet的模块地图verilog-ethernet整个工程工程目录很大初学者进去容易懵。我建议不要从头到尾读代码而是先找到自己路径上的几个关键模块。以最常用的1G RGMII接口为例数据通路是这样的eth_mac_1g_rgmii_fifo最外层自带FIFO缓冲的完整MAC→eth_mac_1g纯MAC核心→ RGMII接口信号连线到PHY芯片。在MAC之上用户数据进出发送/接收通路分别是eth_udp_tx和eth_udp_rx。eth_udp_tx负责把你要发的UDP数据从AXI-Stream接口收进来自动封装UDP头、IP头然后通过m_axis_ip_tx接口把完整的IP包发给MAC层eth_udp_rx则是从MAC层收到IP包解析出UDP层数据通过s_axis_ip_rx接口收进来然后通过AXI-Stream接口把纯载荷数据吐出去。我最初以为要先控制MAC层再自己处理IP和UDP结果发现有了eth_udp_tx/rx这两个模块之后IP和UDP层的组包拆包都被封装好了我只需要关心三件事一是AXI-Stream接口的时序tvalid/tready/tlast/tdata怎么配合二是配置端口参数和IP地址参数三是怎么把RX通路和TX通路接到自己的业务逻辑上。模块关系我用一个简表列出来方便大家对照源码理解模块名作用用户需要关注的接口eth_udp_tx封装UDP/IP头并发送s_axis_udp_tx (AXI-Stream输入)eth_udp_rx解析UDP/IP头并接收m_axis_udp_rx (AXI-Stream输出)eth_ip_tx / eth_ip_rx封装/解析IP头由udp模块自动连接eth_mac_1g_rgmii_fifoMAC层RGMII内部FIFOgtx_clk、rgmii接口axis_fifoAXI-Stream FIFO缓存数据缓冲、跨时钟域3. 把verilog-ethernet跑起来最小回环工程3.1 工程结构准备与文件清单我第一次用这个工程的时候犯了一个错误直接把整个GitHub仓库全部Add到Vivado工程里结果编译报了几十个错查了半天发现是有些模块面向其他平台或者依赖特定IP核。正确做法是先看example目录找到最接近你板卡的示例工程然后只添加需要的文件。以我用的Xilinx Artix-7 35T板卡、RGMII接口为例需要添加的核心文件大概是这些eth_mac_1g_rgmii_fifo.v、eth_mac_1g.v、eth_mac_1g_rgmii.v、eth_udp_tx.v、eth_udp_rx.v、eth_ip_tx.v、eth_ip_rx.v、eth_axis_tx.v、eth_axis_rx.v、axis_fifo.v、eth_clock_generator.v、lfsr.v以及reset_sync.v这类公共模块。如果代码里用到MIIMMDIO管理接口还需要eth_mdio.v。确定文件依赖最快的方式是打开example工程里的.xpr或.tcl脚本看它包含了哪些源文件。我当时的做法是先把整个仓库同步下来然后新建空白工程用一个空的顶层模块把所有verilog-ethernet的.v文件一股脑加进去编译看报错根据报错逐个排除不需要的模块。这个方法笨但有效能让你对模块依赖有一个直观感受。另外要特别说一下IP核的问题。eth_mac_1g_rgmii_fifo内部如果配置成使用Xilinx原生三速MAC软核就会依赖Xilinx的tri_mode_eth_macIP这是需要单独生成并添加到工程的。但如果直接用verilog-ethernet自带的纯RTL MAC实现就不需要额外生成IP核。默认配置下一般用的是自带RTL实现这个对新手更友好不会突然冒出一堆IP生成窗口。3.2 RGMII接口与时钟最容易翻车的地方RGMII接口是Reduced GMII的缩写数据线从8位减到4位时钟从单沿采样改成DDR双沿采样。在125MHz时钟下DDR模式等效数据率是1000Mbps也就是千兆网。RGMII的发送时钟由FPGA产生并送给PHY接收时钟由PHY恢复后送给FPGA两者都是125MHz。在Vivado里做RGMII约束时有两个容易踩的坑。第一RGMII的接收时钟rgmii_rxc是要作为时钟信号进FPGA的必须用create_clock约束并且在他进入逻辑之前最好经过BUFG或BUFIO第二RGMII的数据线和控制线相对于时钟有大约2ns的延迟PCB上一般有等长约束但依然会有偏差所以需要做input delay约束。很多板卡的参考设计里直接用了IDELAYE2对输入数据做可调延迟这套东西配置起来很麻烦但效果也确实最稳。我自己的做法是先用板卡厂商提供的参考设计跑通一遍确认PHY芯片的寄存器配置和时钟约束没问题然后再替换成自己的逻辑。如果你手里的板卡没有现成参考设计至少要把rgmii_rxc的时钟约束写对例如create_clock -period 8.000 -name rgmii_rxc [get_ports rgmii_rxc] set_input_delay -clock rgmii_rxc -max 2.000 [get_ports {rgmii_rxd[*] rgmii_rx_ctl}] set_input_delay -clock rgmii_rxc -min 0.500 [get_ports {rgmii_rxd[*] rgmii_rx_ctl}]这里period 8.000对应125MHz。具体延迟值要参考PHY芯片数据手册和PCB走线长度来定我这边演示用的值存在一定经验成分实际项目里需要通过scan测试确定最优延迟。3.3 顶层代码把RX和TX用FIFO串起来最小回环工程的目标很朴素PC发一个UDP包给FPGAFPGA把包里的数据原封不动再发回给PC。这样可以验证整条链路通不通而不涉及其它业务逻辑。我用的顶层结构很简单eth_udp_rx收到的AXI-Stream数据直接进一个axis_fifoFIFO输出端接eth_udp_tx的输入端。这样做的原因是RX和TX两侧的tvalid/tready节奏不同不能直接硬连必须加缓冲。关键连线代码大概是这样// 简化示意时钟复位和参数省略 axis_fifo #( .DATA_WIDTH(8), .ADDR_WIDTH(10) ) udp_loopback_fifo ( .clk(clk_125m), .rst(rst), .s_axis_tdata(rx_udp_tdata), .s_axis_tvalid(rx_udp_tvalid), .s_axis_tlast(rx_udp_tlast), .s_axis_tready(rx_udp_tready), .m_axis_tdata(tx_udp_tdata), .m_axis_tvalid(tx_udp_tvalid), .m_axis_tlast(tx_udp_tlast), .m_axis_tready(tx_udp_tready) ); eth_udp_tx #( .TARGET_IP_ADDR({8d192, 8d168, 8d1, 8d10}), .TARGET_MAC_ADDR(48h00_11_22_33_44_55), .SOURCE_IP_ADDR({8d192, 8d168, 8d1, 8d20}), .SOURCE_MAC_ADDR(48hAA_BB_CC_DD_EE_FF), .UDP_SOURCE_PORT(16d8080), .UDP_DESTINATION_PORT(16d8080) ) u_udp_tx ( .clk(clk_125m), .rst(rst), .s_axis_udp_tdata(tx_udp_tdata), .s_axis_udp_tvalid(tx_udp_tvalid), .s_axis_udp_tlast(tx_udp_tlast), .s_axis_udp_tready(tx_udp_tready), // 其余信号连到MAC层 );注意eth_udp_tx源地址和目的地址都做成了模块参数编译后直接固化不能在运行时更改。如果你要做动态修改需要走它的AXI4-Lite配置接口这属于后面进阶的玩法回环demo阶段用参数就够了。还有一个细节eth_udp_tx支持发送侧自动添加UDP长度、IP长度、校验和但这些功能对应的参数要打开否则发出去的包可能缺字段。3.4 用网络调试助手和Wireshark验证回环硬件工程编译烧录之后就到了最兴奋也最容易失望的联调环节。我在Windows电脑上把IP地址设为192.168.1.10子网掩码255.255.255.0用网线直连FPGA板卡然后打开两个工具一个网络调试助手负责发UDP数据包一个Wireshark负责抓包。网络调试助手选UDP模式设置目标IP为FPGA的IP我设为192.168.1.20目标端口8080本地端口随便填一个。发送十六进制数据比如01 02 03 04 05 06 07 08然后立刻看Wireshark里有没有回包。如果回包出来了说明RX链路、MAC、UDP解析、FIFO、TX链路全部正常那一刻确实挺有成就感的。Wireshark里有两个地方要重点看。一是帧格式对不对源MAC应该变成FPGA板卡的MAC目的MAC变成电脑的MAC源IP、目的IP、端口都应该是我们在参数里设置的值。二是校验和在Wireshark里展开IP头部如果显示Header checksum: 0xXXXX (correct)说明IP校验和正确展开UDP头部UDP校验和如果是0x0000IPv4允许或者正确就说明UDP层没问题。如果显示incorrect, should be 0x...那问题基本都出在校验和计算逻辑上后面会细说。补一个当时困扰我的小细节电脑很可能开机后自动配置了防火墙规则会把非预期端口的入站UDP包挡掉。我用的网络调试助手发数据时没问题但回包一直没反应最后发现是Windows防火墙拦了8080端口的入站流量在防火墙高级规则里放行对应端口就好了。另外要确认Wireshark抓的是正确的网卡如果电脑上有虚拟网卡比如装了虚拟机软件抓错网卡会浪费很长时间。4. 翻车现场我在调试中踩过的坑4.1 回环不通先查哪个信号回环不通用Wireshark抓不到任何包时不要一上来就怀疑代码逻辑先按物理层、MAC层、上层顺序排查。最有效的方法是让FPGA板卡发一个固定数据包比如在顶层模块里用一个计数器定时触发eth_udp_tx发送Hello FPGA几个字节然后去电脑上用Wireshark看能不能收到。如果这都收不到问题大概率出在PHY配置或RGMII接口上而不是UDP逻辑。PHY芯片的问题常见有三种复位时序不对、MDIO配置没写对、时钟没起来。RGMII PHY一般在硬件复位之后有100ms左右的稳定时间FPGA逻辑里要用一个足够长的计数器或者复位管理模块来延迟启动。MDIO配置则主要是配置PHY的工作模式为千兆全双工、启用RGMII接口、关闭环回测试模式等。我当时用的PHY芯片默认是百兆模式不通过MDIO写寄存器改成千兆全双工就一直只能收到错误状态的包。这个排查起来比较枯燥但一旦过了PHY这一关后面的软件层问题反而好定位。如果FPGA能自发包且电脑能收到但电脑发给FPGA的包回不来那问题就出在RX路径。最常用的定位思路是在eth_udp_rx的输出端拉一个信号去点亮LED每当收到一个有效UDP包就翻转一次LED状态。配合网络调试助手从电脑端发包看LED是否变化就能快速判断RX链路是否工作。这种硬件打点的调试法比看仿真波形直观得多强烈推荐在板级调试时多用。4.2 校验和不对导致PC端收不到我跑通自发包之后一度以为已经大功告成结果一测回环发现PC端只能收第一包后面全部石沉大海。抓包一看IP头的校验和标红为incorrectUDP校验和也是incorrect。最开始我以为是verilog-ethernet的bug后来仔细读代码才发现它在eth_udp_tx里默认会把IP校验和和UDP校验和的计算作为可选项如果顶层例化时没有对应参数开启输出的是全0或者错误值。开启方式很简单参数里通常有ENABLE_IP_CHECKSUM和ENABLE_UDP_CHECKSUM这类开关设置成1即可。但这里有一个非常隐蔽的坑如果你同时开启了UDP校验和计算它要求输入的IP地址等参数在数据从FIFO发出之前就已经稳定而UDP长度字段在发送前必须正确写入。如果发送端tlast来得比预期晚长度字段算出的值和实际发送字节数不一致校验和也会跟着错。排查校验和问题时我建议在仿真阶段就把它验掉。verilog-ethernet仓库里自带的testbench很完善把回环工程放到Vivado Simulator里仿真JK能看到每个包的IP头字段、校验和、UDP长度是否正常。我一开始偷懒跳过仿真直接上板结果一个校验和问题花了三个小时才定位要是先跑一遍仿真十分钟就能查出来。所以强烈建议大家上板前先仿真这个习惯能帮你省下大量时间。4.3 接口约束和跨时钟域的坑再讲一个时序相关的坑。我最初写回环的时候MAC层的时钟直接用了板载125MHz晶振提供的gtx_clk然后把PHY的RX时钟也当成同一个时钟域来用结果偶尔会出现包能通但长时间跑会丢包的诡异现象。后来细想明白了一个问题gtx_clk是本地晶振时钟而RG MII的接收数据是和rgmii_rxc由PHY恢复出来对齐的这个时钟和本地晶振虽然都是125MHz但频率和相位并不完全一致是两个不同的时钟域所以RX侧拿到的数据必须做异步处理。verilog-ethernet里的eth_mac_1g_rgmii_fifo其实已经把接收数据用内部的异步FIFO做了跨时钟域处理MAC层输出到上层的接口会被同步到gtx_clk域。但要注意这个异步FIFO的深度有限如果上层RX链路处理不够快FIFO溢出就会丢包。我当时在回环代码里RX到TX之间只加了一个很浅的FIFO遇到突发流量直接溢出丢包后来把FIFO深度加大到2K字节才算稳下来。跨时钟域还有一个隐含要求复位同步器。不同时钟域的复位信号不能直接互相驱动否则容易导致亚稳态。verilog-ethernet里自带的reset_sync模块就是干这个的例化时要把MAC核心、RX侧业务逻辑、TX侧业务逻辑的复位分开处理不能图省事共用一个异步复位。这块在仿真里看不出来只有上板长期跑才会暴露。4.4 抓包经验Wireshark筛选和统计小技巧调试UDP回环时Wireshark的过滤表达式是效率神器。我当时PC上可能同时有ARP、ICMP、各种广播包直接从一堆包里面找UDP回环报文非常痛苦。建议用这几条过滤规则配合查看udp.port 8080只看指定端口的UDP流量最常用。ip.addr 192.168.1.10 udp只看跟FPGA通信的UDP包。frame.time_delta_displayed这是时间差字段鼠标右键设置成列显示可以看到相邻两帧的时间间隔用于判断回环延迟。如果回环通了想验证吞吐和稳定性可以用iperf3做UDP打流测试。注意iperf3的UDP模式需要指定带宽参数例如iperf3 -u -c 192.168.1.10 -b 400M -t 10然后在FPGA侧做回环回到PC端观察Jitter和Lost值。我实测下来一个最简单的回环工程能跑到300Mbps左右不掉包瓶颈反而不是MAC或UDP逻辑而是我的FIFO深度和PC网卡的中断处理能力。如果要做高吞吐FIFO深度、M/AXI位宽、接收侧的处理节拍都要重新调优。另外提一个Wireshark的细节如果抓包发现UDP包大小和网络调试助手里设置的不一致很可能是长度字段错了eth_udp_tx的UDP长度字段必须等于8 payload_bytesIP总长度必须等于20 8 payload_bytes。这些字段在verilog-ethernet里一般会根据tlast自动计算但如果你自己改过发送逻辑一定要检查长度字段有没有被正确刷新。5. 从demo走向实战还有哪些路要走5.1 模块裁剪和位宽选择回环跑通只是第一步真正常用了才发现有很多优化空间。最典型的就是AXI-Stream数据位宽。我在demo里用了8位数据位宽因为对初学者来说分组和校验都直观但8位在125MHz时钟下理论吞吐只有1Gbps实际因为控制开销只能跑到600-700Mbps有点浪费。verilog-ethernet的eth_udp_tx/rx都支持配置成32位甚至64位数据位宽配合MAC层的32位接口可以在较低时钟下跑满千兆。位宽改大之后需要同步调整的是tkeep信号。8位数据时tkeep只有1位32位时是4位表示当前拍哪些字节有效。tlast出现在最后一个有效字节所在的那一拍tkeep要保证尾拍字节数正确。很多第一次改32位的人会在最后一个短包上出错因为尾拍不一定是4字节对齐。处理方法是看tkeep的高位有没有多余字节如果有要置0丢弃。这个逻辑不复杂但写错很隐蔽。模块裁剪上也要注意如果你的应用只需要发不需要收完全可以只例化eth_udp_tx和MAC的发送通路RX通路和相关的eth_udp_rx模块可以全部去掉能省不少LUT。反过来只收不发也是同理。另外ARP模块在纯UDP点对点场景下其实可以不用但如果PC端要自动发现FPGA、或者FPGA要响应PINGARP就是必须的。verilog-ethernet里的eth_arp模块在1G MAC链路中通常需要例化进去否则Windows有时会找不到目标设备通信会失败。5.2 性能测试超出回环的实战验证回环验证的是正确性但真正上项目之前最好做一轮系统的性能测试。我用的工具是iperf3这在前面提过但这里再说一下测试方法PC端作为客户端发送UDP流FPGA收到后原样回传PC端作为iperf3的服务端接收回包这样就能同时测出上行是否有丢包、下行是否正确、吞吐能达到多少。实测结果很有参考价值。最开始的8位宽、浅FIFO版本UDP打流到150Mbps就开始出现间歇性丢包把FIFO深度调到4K字节、数据位宽改成32位后400Mbps持续打流能稳定不掉包再高就开始受限于PC网卡的接收能力了。这个数字和你用的PHY芯片、板卡PCB质量、PC配置都有关仅供参考但方法论是通用的——用iperf3逼出瓶颈再用Wireshark确认丢包发生在哪个方向。另一边要测试的是延迟。FPGA回环的延迟理论上非常低我的实测值大概在几个微秒量级不含PC协议栈时间。测试方法是用Wireshark抓包看包从PC发出到被FPGA回传回来的frame.time_delta_displayed字段通常远小于1毫秒比任何软件协议栈都快很多。这也印证了FPGA做UDP低延迟链路的优势。5.3 与采集链路结合从能收发到能做事跑通UDP回环之后我自己最大的感受是协议栈本身只是传输管道真正的价值在于你把什么数据放进管道里。我的高速ADC项目是把ADC采样的数据通过DMA或FIFO先缓存下来然后按一定的帧格式封装成UDP包发送。这里有两个关键设计要点第一是分包策略网络传输单位是MTU对标准以太网是1500字节扣掉IP头20字节和UDP头8字节实际UDP载荷最大是1472字节。如果你的业务数据超过这个值就要在FPGA里做拆分一个大包拆成多个UDP包并为每个包增加序列号这样接收端可以检查顺序、判断丢包。verilog-ethernet本身不负责拆包组包这需要在你的数据通路里自己写状态机。第二是背压机制数据采集端的速率通常不稳定而网口的发送速率是相对稳定的。如果你的采集速率瞬间超过发送能力就必须有FIFO或者暂停采集的逻辑否则只能丢数据。我在工程中用了axis_fifo加水位信号当FIFO快满时给采集端发一个暂停信号优先保证已缓存的数据能完整发出去。这个机制在回环demo里用不上但做真实数据链路时是必须的。再往后可以扩展的方向包括加入ARP自动应答让上位机开机后能自动发现FPGA设备加入DDR3缓冲实现大数据量的先缓存后发送或者把协议栈从UDP升级到TCP——不过verilog-ethernet本身主要支持UDP官方还有一个verilog-tcp仓库按同样思路可以去啃但TCP的状态机和重传逻辑明显更复杂建议先把UDP链路做稳再做TCP。我个人在实际操作中的体会是学协议栈这类东西最重要的是把帧结构和时序接口两块基石打牢。帧结构决定你发的包能被对端识别时序接口决定你的数据能在系统里流动起来。verilog-ethernet把这两块的实现细节封装好了让你能站在别人的肩膀上快速跑通全链路但千万不要因为有了它就跳过抓包分析这个基本功——Wireshark里的每一个字段将来都可能是在现场救你命的线索。