FPGA实现UDP协议栈:verilog-ethernet开源工程深度解析

发布时间:2026/9/15 3:05:15
FPGA实现UDP协议栈:verilog-ethernet开源工程深度解析 很多FPGA学习者到了网络通信这一步都会卡一阵子点灯、按键、数码管这些基础实验都能玩明白仿真也会写了但一碰到以太网就头大。我今天要拆的这个开源项目是进阶路上非常值得花时间啃的一块硬骨头——verilog-ethernet 开源 UDP 协议栈工程。它把从 MAC、ARP、ICMP、IP 到 UDP 的整套网络协议用可综合的 Verilog 代码完整实现你不需要一颗运行 Linux 的处理器只需要一片 FPGA 加一颗 PHY 芯片就能跟 PC 上的网络调试助手直接收发 UDP 数据延迟还低得感人。这套代码特别适合两类人一类是想搞懂“FPGA 怎么上网”的学生和初学者另一类是项目里需要高速数据传输、又不愿意砸钱买商业 IP 核的工程师。它的学习曲线确实不低但只要按照“先跑仿真、再读接口、最后上板”的节奏走你会发现网络协议栈这件事远没有想象中那么玄乎。今天这篇文章我就把整个工程从目录结构、模块拆解、接口时序到回环实验和常见坑完整过一遍。1. 为什么选 verilog-ethernet 来学 UDP 协议栈1.1 先弄明白 FPGA 上跑 UDP 协议栈到底解决什么问题很多人第一次听到“FPGA 实现 UDP 协议栈”会觉得不太理解协议栈这东西不是应该跑在 CPU 上吗Linux 内核里不是现成的吗确实软件协议栈成熟稳定但在高速数据采集、图像传输、雷达信号处理这类场景里软件有天然的瓶颈。举个最直观的例子一块 200MSPS 的 ADC16bit 采样一个采样点就是 2 字节每秒产生的数据量是 400MB。如果走 CPU 加软件协议栈CPU 要一边搬运数据、一边拼包、一边处理中断好不容易把包发出去万兆网卡可能还能跑满但千兆网卡基本就到极限了而且数据路径上多了好几层拷贝延迟很难压到微秒级。FPGA 做这件事就不一样数据从 ADC 进来经过简单的 FIFO 缓冲直接进入硬件 UDP 协议栈以太网帧、IP 头、UDP 头全是组合逻辑和状态机拼出来的没有任何操作系统调度的不确定性延迟可以做到几微秒以内。UDP 协议本身又特别适合硬件实现。它无连接、无重传、无拥塞控制发送方只需要把用户数据按照固定格式封装成包扔出去就行接收方拆包再把数据交给用户逻辑。相比 TCP 那一堆状态机、序列号、重传定时器、拥塞窗口UDP 的硬件开销小一个数量级。这也是为什么绝大多数科研仪器、图像采集卡、高速数据记录设备都优先选择 UDP 而不是 TCP。1.2 verilog-ethernet 到底包含哪些能力verilog-ethernet 是 GitHub 上一个很有名的开源项目作者是 Alex Forencich。它不是一个简单的“UDP 收发模块”而是一整套以太网相关 IP 的集合覆盖了从千兆到百兆再到万兆、甚至更高速率的以太网 MAC以及建立在 MAC 之上的 ARP、ICMP、IP、UDP 协议处理逻辑。具体来说这个项目能给你这些东西完整的以太网 MAC 实现支持 1G、10G、25G、40G、100G 等多个速率档位内置 UDP 协议栈包括 UDP 收发、IP 头封装与解析、校验和计算内置 ARP 协议处理自动缓存对端 MAC 地址不用你手动填写内置 ICMP 协议回显PC 端可以直接 ping FPGA这对调试链路非常有用对外接口统一采用 AXI-Stream 标准和 Xilinx 自家的 IP 核接口风格一致提供完整的 testbench配套 Makefile支持 iverilog、Verilator、Vivado xsim 等多种仿真方式。另外这个项目的代码风格非常规范每个模块都有清晰的参数列表和信号注释很适合当学习资料来读。它的依赖库也做得比较干净核心依赖是同一个作者的 verilog-axis、verilog-axi、verilog-common 这三个仓库通过相对路径 include 进来。1.3 从“零基础”到“能上手”的学习路径设计如果你是真正意义上的“近似 0 基础”我建议不要一上来就打开 udp_10g.v 这个顶层文件否则很容易被那一大堆端口吓住。合理的学习路径应该是这样第一步先把数电和 Verilog 语法的基础打牢至少能独立写一个状态机和一段简单的 AXI-Stream 逻辑第二步弄懂以太网帧、IP 头、UDP 头的格式知道每个字段占几个 bit、谁负责填什么第三步把 verilog-ethernet 仓库拉下来先跑它自带的仿真观察 testbench 是怎么例化、怎么激励、怎么检查结果的第四步找一小块用户逻辑接上去做成一个最简单的 UDP 回环最后再考虑上板用 Wireshark 抓包验证整个链路。这个顺序是我自己踩了好几次坑才总结出来的。很多人一上来就想着上板调试结果被 ARP 不发、CRC 不对、时序不收敛、PHY 芯片没初始化等问题搞到崩溃。实际上仿真阶段能把协议栈内部的行为看明白上板之后就只剩环境问题而不是逻辑问题了。2. 工程开箱把 verilog-ethernet 跑起来2.1 获取工程与依赖库第一步自然是把代码拿到手。verilog-ethernet 依赖另外三个基础库最好把它们都 clone 到同一个目录下因为源码里的 include 路径是相对路径。我自己习惯这样组织目录my_fpga_work/ ├── verilog-ethernet/ ├── verilog-axis/ ├── verilog-axi/ └── verilog-common/然后进入 verilog-ethernet 目录先别急着改代码花半小时把 rtl 目录下的文件名扫一遍。你会发现模块命名很有规律eth_mac_*是 MAC 层eth_arp_*或arp_*是 ARP 相关ip_*是 IP 层udp_10g、udp_64这类是集成度更高的 UDP 协议栈封装。这里提醒一句verilog-ethernet 的版本迭代比较快不同版本的接口和模块名可能有差异。我自己最早接触的是老版本里面直接用eth_udp_rx、eth_udp_tx两个模块现在新版本则更推荐用udp_10g、udp_64这种封装好的模块。所以看网上教程时如果发现端口对不上不用慌先去仓库里确认一下当前版本的源码一切以代码为准。2.2 目录结构和核心文件定位新手面对一个开源工程最容易犯的错就是想一口气看懂所有文件。正确的做法是先把核心文件挑出来。以我用的版本为例下面这几个文件是学习的重点rtl/udp_10g.v10G 速率的 UDP 协议栈顶层接口完整建议从这里开始读rtl/eth_mac_10g.v10G 以太网 MAC负责成帧、CRC、XGMII 接口rtl/arp_cache.vARP 缓存模块保存 IP 到 MAC 的映射关系rtl/ip_eth_rx.v、rtl/ip_eth_tx.vIP 层接收与发送处理 IP 头、校验和rtl/udp_checksum_gen.vUDP 校验和生成里面是加法树和进位回卷逻辑sim/udp_10g_tb.v对应的 testbench强烈建议认真读一遍。如果你用 1G 的 PHY 和 RGMII 接口可能还需要看rtl/eth_mac_rgmii.v或对应的 PHY 适配模块。总之先定位一个“最高层模块”作为入口再按数据通路一层一层往下追比从头到尾按文件名读要高效得多。2.3 用 iverilog 快速跑通自带的 testbench这个工程自带的 testbench 写得很完整直接用 iverilog 就能跑。我自己用的命令一般是cd verilog-ethernet make -f Makefile.sim iverilog TESTudp_10g_tb ./sim/udp_10g_tb如果环境中没有安装 iverilog可以用系统的包管理器装一个这是个开源的轻量级仿真器速度很快对学习阶段来说完全够用。跑的时候如果提示找不到axis_fifo.v这类模块说明依赖库路径没配对检查一下 Makefile 里的-I参数是不是指向了 verilog-axis 和 verilog-common 的 rtl 目录。仿真跑完后你会看到一串类似PASS或者Test complete的打印信息。testbench 里模拟了 PC 端发包、协议栈收包、再发回给 PC 的完整过程还会自动检查数据内容是否一致。这一步通了说明环境没问题源码基本可读可以继续下一步了。2.4 在 Vivado 里建工程并完成综合如果你最终要在 Xilinx FPGA 上跑建议用 Vivado 建一个工程把多个仓库的 rtl 目录都加入 Design Sources。添加文件时要注意Vivado 默认会自动识别 include 相对路径但为了保险我一般会在 project settings 里把verilog-ethernet\rtl、verilog-axis\rtl、verilog-axi\rtl、verilog-common\rtl都加到 Verilog include 路径里。综合之前先把 testbench 加到 Simulation Sources跑一次行为仿真。这一步的意义很大因为综合环境里如果出现语法错误或者缺少模块仿真阶段就能暴露出来。等仿真通过再跑综合观察资源占用和时序报告。第一次综合如果只跑udp_10g资源占用一般不会太高LUT 大概几千BRAM 若干具体数量取决于参数配置。重点检查两件事一是时钟频率是否满足约束二是有没有跨时钟域警告。verilog-ethernet 内部用了不少异步 FIFO跨时钟域警告只要不影响功能可以暂时忽略但如果出现高扇出或者时序违例的关键路径就要认真对待了。3. UDP 协议栈核心模块拆解3.1 整体分层架构很多初学者一听到“协议栈”就以为里面有很复杂的软件层次其实在 FPGA 里它本质就是一条数据通路加上若干处理状态机。verilog-ethernet 的分层结构大概可以表达成下面这样用户逻辑AXI-Stream 接口 ↓ 发送方向 UDP 封装加上 UDP 头源端口、目的端口、长度、校验和 ↓ IP 封装加上 IP 头版本、头部长度、协议号、源 IP、目的 IP、校验和 ↓ ARP 处理查询对端 MAC必要时发起 ARP 请求 ↓ MAC 封装加上以太网头部目的 MAC、源 MAC、EtherType生成 FCS ↓ PHY 接口XGMII / RGMII / GMII交给外部 PHY 芯片接收方向则是完全相反的过程PHY 先把码流变成并行数据MAC 校验 FCS 后剥掉以太网头IP 层剥掉 IP 头UDP 层剥掉 UDP 头最后把纯用户数据通过 AXI-Stream 交给用户逻辑。这个分层思路和软件协议栈虽然有相似之处但实现方式完全不同。软件协议栈是“函数调用 内存拷贝”FPGA 协议栈是“模块级联 流水线处理”。在 verilog-ethernet 里UDP 模块内部其实例化了 IP 层和 ARP 缓存IP 层又依赖 MAC 层所以对外看起来是一个黑盒子端口不多但内部结构挺丰富。3.2 UDP 模块对外接口逐个理解以udp_10g为例它对外的主要接口可以分成四组时钟复位、用户收发、网络接口、管理配置。用户发送侧的信号一般是s_axis_udp_*接收侧是m_axis_udp_*这一组是标准 AXI-Stream。发送方向你的用户逻辑往s_axis_udp_tdata上放数据然后拉高s_axis_udp_tvalid等s_axis_udp_tready为高时数据就算成功交接接收方向协议栈把收到的数据放在m_axis_udp_tdata上拉高m_axis_udp_tvalid你作为接收方把m_axis_udp_tready拉高数据就被消费掉了。网络接口侧通常是xgmii_txd、xgmii_txc、xgmii_rxd、xgmii_rxc。XGMII 是 10G 以太网的 MAC 与 PHY 之间的接口数据位宽 64bit时钟频率 156.25MHz。如果你的板卡是 1G RGMII 接口对应模块则可能是rgmii_*信号或者通过eth_mac_rgmii桥接。管理配置接口是 AXI-Lite通过它写入本机 MAC、本机 IP、本机端口、目标 IP、目标端口等信息。下面是一个简化版的udp_10g例化模板帮助你先在头脑里建立端口映射udp_10g #( .TARGET (XILINX), .UDP_DATA_WIDTH (64) ) u_udp_10g ( .clk (clk), .rst (rst), // 用户发送侧 .s_axis_udp_tdata (user_tx_tdata), .s_axis_udp_tkeep (user_tx_tkeep), .s_axis_udp_tvalid (user_tx_tvalid), .s_axis_udp_tready (user_tx_tready), .s_axis_udp_tlast (user_tx_tlast), .s_axis_udp_tuser (user_tx_tuser), // 用户接收侧 .m_axis_udp_tdata (user_rx_tdata), .m_axis_udp_tkeep (user_rx_tkeep), .m_axis_udp_tvalid (user_rx_tvalid), .m_axis_udp_tready (user_rx_tready), .m_axis_udp_tlast (user_rx_tlast), .m_axis_udp_tuser (user_rx_tuser), // 网络接口侧这里以 XGMII 为例 .xgmii_txd (xgmii_txd), .xgmii_txc (xgmii_txc), .xgmii_rxd (xgmii_rxd), .xgmii_rxc (xgmii_rxc) );不同版本信号的名称可能略有出入但核心的 AXI-Stream 握手信号是通用的理解规则之后换版本也很快。3.3 AXI-Stream 握手协议贯穿全栈的主线AXI-Stream 是 Xilinx IP 核常用的接口标准verilog-ethernet 从 MAC 层到 UDP 层几乎所有模块之间都靠它连接。理解这个握手协议是读懂整个工程的关键。规则其实就一条当tvalid和tready在同一个时钟上升沿都为高时一个数据传输发生。tdata是数据tkeep表示哪些字节有效tlast表示这是这一帧的最后一拍tuser通常携带错误标志或用户自定义信息。初学者最容易犯的错误是在tvalid还没有准备好的时候就等tready或者反过来在tready未拉高的时候提前拉低了tvalid。正确做法是作为发送方tvalid一旦拉高就必须维持到数据被接收方取走也就是直到tready拉高的那个时钟作为接收方tready可以随时拉高也可以随时拉低但如果tvalid已经拉高拉低tready就是告诉对方“先等一下我上一拍还没处理完”。打个生活中的比方你去快递站寄包裹tvalid就是你告诉工作人员“我这有个包裹要寄”tready就是工作人员说“我现在有空递给我吧”。只有你说“有包裹”同时工作人员说“有空”的那一瞬间包裹才算真正交到对方手上。如果工作人员说“你等一下”你就得一直举着包裹不能放下。在 verilog-ethernet 里UDP 模块发送侧通常会内部处理 AXI-Stream 到 MAC 帧的转换用户不需要自己处理以太网头只需要在s_axis_udp_tvalid上按帧送入数据并在最后一拍拉高tlast即可。3.4 ARP 与 ICMP 在协议栈里的作用很多人在 PC 和 FPGA 之间调 UDP遇到的第一只拦路虎不是 UDP 本身而是 ARP。ARP 的作用是解决“知道对端 IP 地址但不知道对端 MAC 地址”的问题。以太网帧在二层传输时目的 MAC 地址是必须的而 PC 端一般只配置 IPMAC 地址需要通过 ARP 查询获得。verilog-ethernet 内部实现了完整的 ARP 客户端和缓存。当协议栈想给某个 IP 发 UDP 包但缓存里没有对应的 MAC 地址时它会自动发一个 ARP 请求收到应答之后再发送真正的 UDP 数据。这个过程中用户逻辑完全不需要干预但如果配置的目标 IP 不可达或者对端防火墙拦截了 ARPUDP 数据就会一直发不出去。ICMP 是另一个容易被忽略但非常有用的功能。verilog-ethernet 内置了 ICMP 回显支持也就是说只要板卡网络链路正常你在 PC 上 ping 一下 FPGA 的 IP 地址FPGA 能自动回一个 ICMP Reply。这简直是链路调试的“探针”。我自己的习惯是上板测试 UDP 之前先 ping 一下板卡的 IP通了再测 UDP。如果 ping 不通基本不用指望 UDP 能通。3.5 头部校验和的计算与硬件实现UDP 和 IP 头部都有校验和保护verilog-ethernet 内部用纯组合逻辑实现了校验和计算。IP 头校验和覆盖整个 IP 头部UDP 校验和则覆盖“伪头部 UDP 头 数据”。伪头部由源 IP、目的 IP、协议号、UDP 长度组成这些字段在发送端封装时已经确定所以硬件实现并不复杂——本质就是按 16bit 为单位把所有相关的字相加累加过程中如果产生进位就回卷最后取反。硬件实现校验和有两个常见坑。第一个是宽度直接用 32bit 加法器累加 16bit 数据时最后要把高 16bit 的进位再加回低 16bit否则结果会差 1 到 2 个进位第二个是更新时机如果你的应用在发送过程中动态改变数据内容或者源 IP校验和必须重新计算否则抓包会看到 checksum wrong。verilog-ethernet 处理得比较规范UDP 模块内部有独立的校验和生成模块用户只要在 AXI-Stream 上送入数据它会自动完成校验和计算和写入不需要用户干预。但理解原理仍然重要因为抓包时如果看到 checksum 错误至少你能判断是哪里出了问题而不是一头雾水。4. 动手实践实现一个 UDP 回环小系统4.1 需求与顶层设计学习协议栈最好的练手项目就是做一个 UDP 回环PC 上通过网络调试助手发送任意长度的 UDP 数据包给 FPGAFPGA 原封不动地把数据再发回给 PC。这个需求看起来简单但它覆盖了“数据接收、缓存、发送”的完整路径做完这个实验对整个协议栈的用法就有底了。硬件方面如果你手里是常见的 FPGA 开发板一般带 RTL8211、88E1512 这类千兆 PHY 芯片接口是 RGMII。verilog-ethernet 对 RGMII 的支持比较成熟直接用eth_mac_rgmii或对应 PHY 适配模块即可。如果你用的是带 SFP 光口的板卡那可以考虑 10G 链路但调试难度会大不少建议先把千兆回环跑通。顶层设计不需要很复杂FPGA 内部就三块UDP 协议栈、接收 FIFO、发送控制逻辑。协议栈收到的数据直接写入 FIFO同时周期性地从 FIFO 读出并送到发送通道。这样即使 PC 发的数据速率和 FPGA 发送速率不匹配FIFO 也能起到缓冲作用。4.2 用户逻辑侧代码编写最简单的实现方式是把接收通道直接接到发送通道上代码大概是这样assign user_tx_tdata user_rx_tdata; assign user_tx_tkeep user_rx_tkeep; assign user_tx_tvalid user_rx_tvalid; assign user_rx_tready user_tx_tready; assign user_tx_tlast user_rx_tlast;这种直通写法在仿真里往往能跑通但上板后可能会遇到背压问题。因为user_rx_tvalid和user_rx_tdata来自协议栈输出而user_tx_tready来自协议栈输入这两条通路如果组合逻辑拉得太长时序容易紧张而且协议栈接收和发送通道实际上是独立的数据不一定会同时准备好。更稳妥的办法是例化一个 AXI-Stream FIFO 做中间缓冲。verilog-axis 库里就有现成的axis_fifo直接例化即可。比如axis_fifo #( .DEPTH (2048), .DATA_WIDTH (64), .KEEP_ENABLE (1), .KEEP_WIDTH (8) ) u_axis_fifo ( .clk (clk), .rst (rst), .s_axis_tdata (user_rx_tdata), .s_axis_tkeep (user_rx_tkeep), .s_axis_tvalid (user_rx_tvalid), .s_axis_tready (user_rx_tready), .s_axis_tlast (user_rx_tlast), .m_axis_tdata (user_tx_tdata), .m_axis_tkeep (user_tx_tkeep), .m_axis_tvalid (user_tx_tvalid), .m_axis_tready (user_tx_tready), .m_axis_tlast (user_tx_tlast) );FIFO 的好处是解耦了接收和发送速率即使协议栈接收通道和发送通道时钟不同步也不会互相拖累。当然你也可以在 FIFO 之前加一个简单的解析模块只转发特定端口的数据或者修改数据内容这些都可以在熟悉回环之后慢慢加。4.3 PC 端与硬件联调上板之前先把 PC 的 IP 地址和 FPGA 的 IP 地址规划好。假设 PC 是 192.168.1.100FPGA 是 192.168.1.10子网掩码都是 255.255.255.0。然后通过 AXI-Lite 把本机 MAC、本机 IP、本机端口、目标 IP、目标端口写进协议栈的寄存器。寄存器具体偏移以源码为准不同版本可能不同但一般都能在udp_10g_regs.v或者模块顶部的注释里找到。PC 端推荐用 Wireshark 和网络调试助手配合。先打开 Wireshark 抓包然后向 FPGA 的 IP 和端口发送一段固定的数据比如 64 字节的0x5A重复填充。正常情况下Wireshark 里能看到 FPGA 回过来的 UDP 包内容一模一样。如果没收到回包步骤是第一步看 ARP 有没有成功Wireshark 里如果只有 FPGA 发来的 ARP 请求而 PC 不应答大概率是 PC 防火墙拦了第二步看 ICMP在 CMD 里 ping 一下 FPGA 的 IP能通说明链路层和网络层没问题第三步看 UDP 包本身检查源端口、目的端口、校验和是不是都正常。按这个顺序查问题很快就能定位。4.4 用抓包和波形确认问题很多时候网口调试比普通逻辑调试更容易“盲人摸象”因为你不知道数据到底在哪一层丢了。这时候抓包和片上逻辑分析仪结合起来用效率最高。Wireshark 侧重于观察外部行为ILAIntegrated Logic Analyzer则能观察 FPGA 内部信号。我习惯在 AXI-Stream 通路上拉几个关键信号比如user_rx_tvalid、user_rx_tlast、user_tx_tvalid、user_tx_tready。如果 ILA 里能看到接收数据进来了但发送侧一直没有tvalid拉高说明用户逻辑或者 FIFO 那里卡住了如果发送侧有tvalid但tready一直为低说明协议栈发送通道被占满或者配置有问题。另外抓包时注意看校验和字段。Wireshark 里如果显示incorrect checksum先别怀疑是 FPGA 算错也可能是网卡的 checksum offload 功能导致 PC 发出的报文本身校验和没更新。这种情况在通达万兆网卡上尤其常见抓包显示校验和错误但实际设备收到的包是正常的因为校验和在硬件层被重新计算了。5. 常见坑与排查实录5.1 敲黑板时序收敛问题verilog-ethernet 本身设计得比较干净但拿到自己板子上综合难免会遇到时序问题。最典型的是 10G 链路XGMII 接口需要 156.25MHz 的高频时钟如果你只做了行为仿真没有在约束文件里正确声明时钟综合结果大概率是时序违例。我的建议是初学者先从 1G RGMII 上手。RGMII 接口对时序的要求相对宽松PHY 芯片一般自带时钟管理顶层只需要约束好时钟和引脚位置时序收敛容易得多。跑 10G 的话你还需要处理 GT 高速收发器、参考时钟、复位序列这些都是单独的知识点。另外复位信号也很关键。verilog-ethernet 的rst是高有效复位并且内部逻辑大多用同步复位。如果你在顶层做的是异步复位记得要做“异步复位、同步释放”处理否则系统上电后容易进入不确定状态。这个坑我踩过一次现象是板卡偶尔能通、偶尔不能通重启一下可能就好了最后发现是复位释放的毛刺把协议栈内部状态机打乱了。5.2 ARP 缓存和 MAC 地址的迷思很多时候 UDP 不通根源根本不在 UDP而在 ARP。这里有几个我见过无数人栽进去的细节。一是 MAC 地址配置错误。以太网头里的 MAC 地址是 6 字节写成 12 个十六进制字符比如00:11:22:33:44:55。配置到寄存器时要注意字节序是低字节在前还是高字节在前不同版本可能有差异。如果你发现 FPGA 发出的帧里源 MAC 明显不对Wireshark 抓包一看就是乱码那基本就是写入顺序搞反了。二是 ARP 缓存过期。协议栈内部有 ARP 缓存默认保存一段时间超时后会自动重新发起 ARP 查询。这本来是正常机制但在调试时容易造成困惑第一次发包能通隔了十几秒再发包就卡住抓包一看才发现 FPGA 在重新走 ARP 流程。遇到这种情况不用慌说明链路是好的只是 ARP 缓存重新建立需要一点时间。三是 PC 端防火墙。Windows 自带的防火墙在默认设置下会拦截很多 ping 和 UDP 探测包导致 ARP 正常但 ICMP 和 UDP 都不通。调试时最简单的方法是临时关闭防火墙或者给某个端口加放行规则否则你会在一个根本不属于 FPGA 的问题上浪费大量时间。5.3 跨时钟域、位宽转换和 FIFO如果你的用户逻辑时钟和协议栈时钟不是同一个比如协议栈跑 156.25MHz用户逻辑跑 100MHz直接在两个时钟域之间接信号是不行的必须加异步 FIFO。verilog-axis 里的axis_async_fifo就是干这个用的它的例化方式和axis_fifo类似但传入两个时钟写侧一个、读侧一个。还有一个很常见的问题是位宽不匹配。协议栈的 AXI-Stream 数据位宽可能是 64bit 或者 256bit但你的用户逻辑可能只想按字节流处理。这种场景可以用axis_adapter模块做位宽转换它会自动处理tkeep的扩展和压缩还会正确处理tlast的位置。我曾经遇到过一个场景用户逻辑按 32bit 发送数据协议栈是 64bit 接口直接拼接时忘了处理tkeep导致最后一拍的有效字节判断错乱PC 端收到的数据总是比发送的多 4 个字节。后来用axis_adapter一接就正常了。所以涉及位宽转换优先用现成 IP不要自己手搓拼接逻辑。5.4 配置寄存器的大坑local 和 remote 别搞反最后说一个特别容易犯的低级错误配置寄存器里的local_port和remote_port。local_port是 FPGA 这一端的 UDP 端口remote_port是 PC 那一端的 UDP 端口。发送数据时协议栈会把local_port填进 UDP 头的源端口字段把remote_port填进目的端口字段。如果你把这两个值写反了FPGA 发出去的包源端口和目的端口正好调换PC 的网络调试助手如果监听在特定端口就收不到数据。这类问题用 Wireshark 一眼就能看出来抓包看到 UDP 头的源端口和目的端口跟预期不一致就说明配置写反了。还有一种情况是 AXI-Lite 写寄存器没有严格握手导致某些寄存器值没有真正写进去。AXI-Lite 的握手规则和 AXI-Stream 类似写地址通道awvalid和awready、写数据通道wvalid和wready都要同时为高才有效。我自己写初始化代码时会把每次写操作整理成一个函数比如# 伪代码示意 write_reg(0x04, local_mac_low) write_reg(0x08, local_mac_high) write_reg(0x10, local_ip) write_reg(0x14, local_port) write_reg(0x20, remote_ip) write_reg(0x24, remote_port)写完remote_port之后再读一遍确认值确实写入成功。如果读出的是 0 或者乱码说明总线时序有问题需要回头查状态机。结尾写到这里verilog-ethernet 的核心脉络基本过了一遍。我个人学习这个工程时最大的体会是开源项目能不能吃透不在于你把每个模块的每一行代码都背下来而在于你理解了数据是怎么流进去、怎么处理、怎么流出来的。verilog-ethernet 给了你一个特别好的机会因为它的代码规范、层次清晰、testbench 完整你完全可以通过仿真观察数据包的“一生”从 PC 发出 ARP 请求到 FPGA 应答再到 UDP 数据被拆包、重组、回送整个过程都能在波形和抓包里看得清清楚楚。如果你照着这篇文章的思路先从仿真跑通再做回环实验最后再用 Wireshark 抓包验证我相信你收获的不只是“会用这个 IP”而是对 FPGA 网络通信整个体系有了真正扎实的理解。等这个层面打通之后再去看 PCIe、DDR、图像采集和网络传输结合起来的高速系统你会发现很多概念都是相通的。最后再分享一个小技巧遇到问题别急着改代码先问自己“协议栈现在处于哪个状态、哪一层在丢包”带着这个思路去抓包、看波形问题往往比想象中更好定位。