资讯详情

verilog-ethernet:FPGA UDP以太网协议栈入门

📅 2026/9/18 18:24:08 | 华诺云谱 👁 阅读
verilog-ethernet:FPGA UDP以太网协议栈入门
从近似0基础开始啃 FPGA 开发走到第 10 篇终于摸到了网络通信这块硬骨头。前面几篇我们把 Verilog 语法、时序逻辑、状态机、FIFO、跨时钟域这些基础攒得差不多了但真要在板子上跑通一条 UDP 链路光靠手写是远远不够的——你要处理 PHY 接口时序、MAC 帧格式、IP 头校验和、ARP 地址解析、UDP 伪首部校验任何一个环节错了抓包工具里就是一片沉默。这时候把 verilog-ethernet 这个开源工程拿过来读性价比是最高的。它是一个纯 Verilog 实现的以太网协议栈支持 1G/2.5G/10G 速率覆盖 GMII、RGMII、SGMII、XGMII 这些常见 PHY 接口内部用 AXI-Stream 做统一的数据通路把 ARP、ICMP、IPv4、UDP 拆成独立模块哪一层出问题就单独盯哪一层。我写这篇的目的很直接给同样处在入门阶段、手里有一块带网口的 FPGA 板子、想把 UDP 收发跑起来的人一份能照着抄的工程学习笔记。不管你是刚学完 Verilog 语法还是已经能点灯但没碰过网络这篇里的选型思路、模块拆解、仿真流程和踩坑记录应该都能省你几天时间。1. 为什么最后选了 verilog-ethernet 这个工程1.1 从零手写 UDP 协议栈会撞上哪几堵墙刚开始我也想过自己从 MAC 层往上撸一遍觉得不就是拼几个字节嘛。真正动手才发现第一堵墙是 PHY 接口时序RGMII 是 DDR 双边沿采样4 位数据在时钟上下沿各传一次你还得在 FPGA 侧做输出延迟和输入延迟对齐不同厂商的 PHY 芯片要求还不一样差个几百皮秒就开始丢包。第二堵墙是帧结构以太网帧头 14 字节、前导码 7 字节、SFD 1 字节、最小帧 64 字节、CRC32 校验你少填一个字段对面直接当垃圾帧扔掉。第三堵墙是校验和IP 头的 checksum 是 16 位反码求和再取反UDP 还要额外算伪首部涉及到字节序和进位回卷手写特别容易出错。第四堵墙是 ARP你得先发广播问“谁是 192.168.1.100”等对方回了 MAC 地址才能发数据中间还有超时重试和缓存老化。这四堵墙叠在一起没有一两个月根本理不顺而且你调试的时候根本不知道是自己逻辑错了还是对面没收全凭猜。1.2 verilog-ethernet 的定位和分层设计verilog-ethernet 这个工程最舒服的地方在于它的分层特别干净。最底下是 PHY 接口层负责把 RGMII/GMII 的物理信号转换成 8 位或 32 位的内部数据流往上是 MAC 层处理前导码、帧间隔、CRC 生成和校验、最小帧填充再往上是以太网帧解析层把帧头剥掉把 payload 交给上层最上面才是 ARP、ICMP、IPv4、UDP 这几个协议模块。每一层之间统一用 AXI-Stream 连接也就是valid、ready、data、last这四根线的握手机制。这个设计的好处是你可以只关心自己那一层想验证 UDP 收发逻辑就绕开 PHY直接在仿真里往 UDP 模块灌 AXI-Stream 数据想验证 PHY 时序就发固定帧看波形。我第一次跑通 ping 的时候才意识到这种“每层可独立测试”的结构对一个初学者有多重要——它把排错空间从整个链路压缩到了单个模块。1.3 为什么不直接用 lwIP 或软核方案有人会问直接上一个软核跑 lwIP 不香吗何必在 Verilog 里遭这个罪。这里得说清楚场景差异。lwIP 是跑在 CPU 上的软件协议栈适合那些需要 TCP、HTTP、DHCP 这类复杂协议的场合代价是它要占处理器资源和内存吞吐还受 CPU 主频限制。而纯 Verilog 协议栈是硬件并行跑一个时钟周期处理一批数据1Gbps 线速下几乎不占 CPU延迟稳定在微秒级。如果你的板子上本来就没有软核或者你的应用就是高速采集、实时控制这类对延迟敏感的场景那硬件协议栈才是对的选择。verilog-ethernet 还有个优势是纯 RTL、无厂商 IP 依赖Xilinx 和 Altera 都能综合这也让它成了很多教学和开源项目的默认底座。1.4 上手前要先具备哪些前置知识虽然标题写的是“近似 0 基础”但读这个工程还是需要一点铺垫的。我个人建议至少掌握这几样再进来一是能看懂always (posedge clk)和阻塞、非阻塞赋值的行为差异二是理解同步复位和异步复位的区别三是知道 FIFO 是干什么用的、读写指针为什么不能直接拿来当握手信号四是最好在仿真里跑过一个带跨时钟域的小设计。缺了这些直接冲进来看 RGMII 的 DDR 原语会很痛苦因为那部分代码里混着厂商原语例化和时序约束。基础不牢的时候读开源工程最容易犯的错是“看不懂就跳过”结果跳过的全是最关键的握手逻辑最后上板不通又回头补时间反而花得更多。2. 工程目录与核心模块逐个拆解2.1 顶层封装怎么选从 FIFO 版本切入最省事打开工程源码目录你会看到rtl下面按功能排了一长串文件第一次看容易懵。我的建议是从顶层封装文件开始看也就是那些名字里带_fifo或者_64bit后缀的封装。以 1G 速率、RGMII 接口为例eth_mac_1g_rgmii_fifo这个顶层把 PHY 接口、MAC、FIFO 全包进去了对外只暴露一个标准的 AXI-Stream 收发接口。这样你几乎不用管 RGMII 的时序细节只要把tx_axis和rx_axis接上自己的逻辑就行。FIFO 版本里有一堆参数值得注意TARGET用来指定厂商原语Xilinx 填XILINXAltera 填ALTERADATA_WIDTH决定内部数据位宽8 位是标配但可以升到 32 位来降时钟压力ENABLE_PADDING控制小于 60 字节的帧是否自动填充到最小帧长TX_FIFO_DEPTH和RX_FIFO_DEPTH决定缓冲深度跑满 1G 线速的时候这两个值别设太小我一般起始给 4096跑通了再往下调。2.2 PHY 与 MAC 层GMII 和 RGMII 到底差在哪GMII 和 RGMII 是两块最常打交道的接口。GMII 简单粗暴8 位数据配一路 125MHz 时钟上升沿就是上升沿采样逻辑一目了然缺点是引脚多8 根数据加控制信号占一片 IO。RGMII 为了省引脚把数据砍到 4 位同时在时钟的上升沿和下降沿各传一次数据还额外引入了 TXC 和 RXC 两根时钟线。代价是 FPGA 侧必须用 DDR 原语来收发Xilinx 上对应ODDR和IDDR而且要在建立保持时间上做延迟补偿通常通过约束或 PHY 内部延迟来调。工程里对这两种接口都有现成实现我的经验是初学阶段如果你的板子两种都支持优先用 GMII 调试出问题时波形好读等逻辑跑通了再切 RGMII 去省引脚切过去之后只需要重点盯 DDR 原语和延迟约束这两块。理解这一层的关键是搞清楚数据从 PHY 进来时是什么节拍进来之后被拼成了几个字节、什么时候开始有效这个信息直接决定你后面协议层模块的数据流怎么接。2.3 AXI-Stream 握手整个工程的通用语言AXI-Stream 这个接口协议是这个工程的骨架所有模块之间都靠它说话。它其实就四根关键线valid表示发送方数据有效ready表示接收方可以收data是数据总线last表示这是一帧的最后一个数据。握手规则是只有当valid和ready在同一个时钟上升沿同时为高时数据才算真正传输成功。这个规则看起来简单但初学最容易犯的错是把它当普通使能信号用比如只看valid就开始处理数据或者last和valid不同步拉高。正确的心智模型是“一次握手一笔交易”ready没来之前valid和data都要保持不动不能因为时钟走了一拍就把数据换掉。我刚开始写接收逻辑时last位置判断错了一位结果整帧数据永远差一个字节抓包看到长度不对又找不到原因最后对着波形一拍一拍数才定位到。读懂 AXI-Stream这个工程就通了一半。2.4 ARP、IP、UDP、ICMP 四块积木怎么拼协议层这块工程拆得特别清楚最大的一个封装叫udp_complete名字就直接告诉你它把 UDP 相关的东西全装好了。里面主要包含四块arp负责地址解析收到广播请求就回自己的 MAC同时把别人的 IP-MAC 对应关系缓存下来ARP_CACHE_SIZE默认给 8 条够小网络用ip负责 IPv4 头解析和生成包括版本号、头长度、TTL、协议号这些字段ip_tx负责把用户数据加上 IP 头再算校验和udp是核心业务层udp_rx剥掉 UDP 头把 payload 往外送udp_tx加上源端口、目的端口、长度和校验和icmp单独拿出来专门处理 ping 请求回了 ping 包才算网络层通了。这四块拼装的顺序是以太网帧进ipip看协议号分流到udp或icmp。我第一次看的时候不知道从哪下手后来发现只要记住“数据从哪进、往哪出、不匹配的分流到哪”这三件事整个数据流就理顺了。2.5 参数化设计里的几个关键配置这个工程全程参数化很多行为靠参数开关。我挑几个实际会改的说说。UDP_CHECKSUM_ENABLE控制 UDP 校验和是否生成如果你的上位机不校验关掉能省点逻辑但网络里混有校验的设备时最好开着不然对面可能拒收。IPV4_ADDR和IPV4_MAC_ADDR是本机地址必须和上位机配成同一网段我踩过把板子 IP 设成 192.168.1.x 而电脑是 192.168.0.x 的坑ping 了半天才发现网段不通。ARP_REQUEST_RETRY_INTERVAL决定多久重发一次 ARP 请求网络稍微慢点就把它调大。还有MII_*一堆参数跟 PHY 管理接口有关用于配置 PHY 的速率和双工模式如果你的板子用 MDIO 配置 PHY这组参数得对着手册填。3. 实操过程从仿真到上板的完整链路3.1 仿真环境搭建先让数据流在波形里跑起来我强烈建议第一步不要上板先在仿真里把协议层跑通。工程自带的 testbench 通常都放在tb或者sim目录下里面会例化 UDP 顶层然后造一帧数据从tx灌进去再从rx读出来对比。可惜大部分开源工程的 testbench 不完整需要你自己补一点。我的做法是写一个最简单的激励给udp_tx的 AXI-Stream 灌一帧固定内容比如0x11 0x22 0x33 ...同时在udp_rx侧监听输出用$display或者写文件的方式把收到的 payload 打印出来。仿真跑通之后再往上层加 ARP 和 IP。这一步的核心目的是验证你的握手时序理解对不对因为仿真是唯一能看到每一拍信号的地方上板抓波形成本太高。这里有个小技巧用$dumpfile和$dumpvars存 VCD再用 GTKWave 打开把valid、ready、last加进波形组这样握手有没有问题是肉眼可见的。3.2 引脚、时钟、复位三件套怎么接上板前的准备工作里引脚约束、时钟、复位是最容易出问题的三样。先说时钟RGMII 方案通常需要一个 125MHz 的参考时钟给 PHY有的板子由 PHY 芯片回传 RXC有的需要 FPGA 自己产生。如果工程配置成 FPGA 输出时钟那你要在顶层例化一个 MMCM 或 PLL把晶振频率倍频到 125MHz 并且注意相位。引脚约束要严格对着板子原理图来我就因为把 RGMII 的 TXD 和 TXC 顺序写反折腾了一晚上。复位这块工程用的是同步复位所以你不能把外部按键直接接到rst上中间要加两级同步器消抖和打拍否则按键抖动会造成状态机误复位。另外注意上电顺序PHY 芯片需要时间完成内部初始化主逻辑复位释放得太早第一批数据会丢我一般让复位信号保持几百毫秒再释放或者干脆等 PHY 链路上的 RXC 稳定后再放。3.3 与 PC 联调先把 ping 跑通再谈 UDP上板后第一件事不是发 UDP 数据而是 ping。为什么先 ping因为 ping 走的是 ICMP它不需要 ARP 之外的其他解析路径最短能通就说明物理层、MAC 层、IP 层、ARP 全部没问题后面 UDP 出问题就可以只在 UDP 模块里找。联调步骤是这样先把板子和电脑用网线直连电脑端把 IP 设成和板子同网段比如板子是 192.168.1.10电脑设 192.168.1.100掩码 255.255.255.0。然后在电脑上ping 192.168.1.10同时用抓包工具看有没有 ARP 请求发出去。如果 ARP 有请求但板子没回检查IPV4_MAC_ADDR参数是不是填错了如果 ARP 回了但 ping 不通检查 ICMP 模块的校验和计算。我第一次 ping 通的那一刻真的挺激动因为从那之后所有的问题都变成了局部问题。3.4 用 UDP 打流验证数据通路ping 通之后开始测 UDP。最土但最有效的办法是写一个简单的上位机脚本用 Python 的 socket 往板子发数据同时板子把收到的数据原样回发看内容对不对。这里要注意端口号板子的udp_rx侧只认自己配置的目的端口上位机发的目标端口要对上。发送频率也从慢到快先一秒钟一包跑通了再加到线速。如果一开始就满速发缓冲来不及处理会丢包你反而以为是逻辑错了。我实测下来1G 链路上用 32 位数据位宽、FIFO 深度给够丢包率可以做到 0。另外建议在板子侧加一个计数器统计正确收到的包数和校验失败的包数用 LED 或者串口打出来这样你在不看抓包的情况下也能判断链路健康度。3.5 关键的时序约束和跨时钟域处理上板跑通之后如果想跑稳时序约束是绕不过去的。RGMII 的收发路径上PHY 时钟和内部逻辑时钟是不同源的属于典型的跨时钟域场景。工程里通常用异步 FIFO 来做隔离你要做的是给这两个时钟域分别建时钟约束并且给跨域的路径加set_false_path或者set_clock_groups把它们分开否则工具会去优化那些本来不该优化的路径反而引入问题。我第一次没加约束综合报了上百个时序违例后来加了时钟组就好了。另外如果时钟是自己用 MMCM 生成的别忘了给生成的时钟建约束还要检查 MMCM 的锁定信号是否参与了复位释放逻辑。4. 常见问题与排查技巧实录4.1 问题速查表现象可能原因排查方向完全 ping 不通IP 不同网段、MAC 参数错、ARP 没工作抓包看是否有 ARP 请求和回复ARP 通但 ping 不通ICMP 校验和错、TTL 为 0看 ICMP 模块生成的回包字段ping 通但 UDP 收不到目的端口不匹配、UDP 校验和错抓包看 UDP 头是否符合预期偶尔丢包缓冲太浅、时钟约束缺失加大 FIFO 深度、补时序约束收到的数据偏移一位last判断位置错、握手采错拍波形逐拍核对valid/ready/last上电后无反应复位释放时机早、PHY 未初始化检查复位时长和 MDIO 配置这张表是我自己踩坑攒下来的遇到问题先对照着缩小范围比盲目改代码快得多。特别是“收到的数据偏移一位”这类问题八成是握手逻辑写错了先在波形里确认last拉高的那一拍对应的是不是最后一个字节再回头改代码。4.2 波形定位三步法怎么用波形快速定位问题我总结了三步。第一步看顶层的valid和ready如果有一方一直不拉高说明卡在握手上了往下找是哪个模块没准备好。第二步看last一帧数据里last只应该出现一次如果出现多次或者根本没出现说明长度信息有问题。第三步看数据内容的前几个字节正常以太网帧开头应该是目的 MAC 地址如果开头全是 0 或者乱的说明数据流接错了源头。这三步走下来八成的问题都能定位到具体模块。我个人的习惯是把关键信号提前在仿真里打标记上板时再用集成逻辑分析工具抓同样的信号两边对比着看效率会高很多。4.3 几个让我印象深刻的坑第一个坑是字节序。Verilog 里数据总线通常按大端排列但我在拼 IP 头的时候按小端思维把地址填反了结果包发出去了但对面不认。后来改成严格按照字段定义从高字节往低字节摆问题就没了。第二个坑是 UDP 长度字段它指的是“UDP 头 数据”的总长度不包括以太网头和 IP 头我一开始把整个帧长填进去了被抓包工具标红。第三个坑是 ARP 缓存的老化时间默认几分钟就过期长时间不发数据之后突然要发会先卡在 ARP 请求上表现出来就是“隔一会儿第一包丢”。解决方式是让应用层在发送前检查 ARP 状态或者把缓存超时调长。这些坑网上文档基本不讲都是自己撞出来的写出来给后面的人省点时间。4.4 关于仿真授权和工具链的小提醒另外提一句工具链的问题。有些人跑仿真时会遇到授权报错比如拿不到仿真 license这种情况一般是环境变量或者工具配置问题不是代码本身的问题。我的建议是仿真优先用开源工具链配合iverilog或者 Verilator 做功能验证综合和上板再用厂商工具这样能避开一堆授权和版本兼容的坑。verilog-ethernet 本身是纯 RTL跨工具综合能力不错但你要注意它里面用到的厂商原语在仿真时可能需要对应的库支持遇到找不到模块的报错先确认是不是原语库没加到仿真路径里。5. 跑通之后还能往哪扩展把基础 UDP 收发跑通之后这个工程其实是个很好的起点。往性能方向走可以试着把DATA_WIDTH从 8 位提到 32 位甚至 64 位看看吞吐能不能翻倍同时观察 FIFO 深度和时钟频率的配合往协议方向走工程里已经有不少上层接口的预留你可以在 UDP 之上自己定义一个简单的应用层协议加个序号和校验字段做成可靠传输往应用方向走把数据采集逻辑挂到udp_tx前面就变成了一个网络化的采集节点比如把 ADC 数据或者图像数据打 UDP 包发出去实时性比软核方案好很多。我自己下一步打算做的是把 UDP 接收和一个小型状态机结合根据收到的命令字去控制板子上的外设相当于一个最简的网络控制节点。整个过程里最值钱的其实不是代码本身而是排错那套方法先分层、再仿真、再抓波形、最后对照速查表这套流程走到哪都通用。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。