news 2026/10/20 9:39:16

FPGA纯Verilog实现千兆百兆自适应UDP以太网协议栈

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA纯Verilog实现千兆百兆自适应UDP以太网协议栈

简介:这份资源面向FPGA与网络通信方向的开发者及学习者,提供一套可在硬件层面运行的千兆/百兆自适应以太网UDP传输方案,用于解决不同链路速率下数据包收发速率动态适配的问题。压缩包共19个文件,约69KB,以Verilog源码为主,辅以Vivado工程相关的xml、xci、tcl与xdc约束文件,覆盖以太网速率检测、GMII与RGMII转换、UDP收发控制、CRC32校验及FIFO缓冲等模块,便于在FPGA上直接综合与验证。资源围绕UDP协议头部处理、IP层封装、端口与校验和机制展开,并涉及动态缓冲与流量控制思路,可帮助读者理解硬件加速下的低延迟实时传输实现。目前已有1301人学习下载,适合作为以太网通信实验、课程设计或项目原型的参考工程,从中获取模块划分方式、接口定义与约束编写经验,快速搭建可复用的UDP传输链路。

1. 千兆百兆自适应以太网 UDP 传输:一块 FPGA 上跑通网络协议栈

很多做 FPGA 的开发者第一次接触以太网,都是从“点灯”式的 PHY 回环测试开始的——PHY 芯片能识别到链路,但数据包就是发不出去,或者发出去抓包工具里全是 CRC 错误。这个项目要解决的就是这个问题:在 FPGA 内部用纯 Verilog 实现一套完整的 UDP 以太网传输链路,支持千兆和百兆速率自适应,不依赖任何软核或硬核处理器。它适合两类人:一类是需要在 FPGA 上做高速数据采集回传的工程师,另一类是正在学习 Verilog 状态机设计、想找一个有实际工程价值的练手项目。整个设计从 PHY 接口时序到 UDP 校验和计算全部自己实现,代码结构清晰,改起来心里有底。

2. 协议栈架构拆解:从 PHY 到 UDP 的分层设计

2.1 整体数据通路与模块划分

这套设计最核心的思路是把以太网协议栈拆成几个独立的状态机模块,每个模块只负责一层协议的处理,模块之间通过 FIFO 或握手信号衔接。数据从 PHY 芯片进来后,先经过 GMII/RGMII 接口转换,再逐层剥离前导码、以太网帧头、IP 头、UDP 头,最终把有效载荷写入接收 FIFO;发送方向则反过来,从 FIFO 读出数据,逐层封装协议头,最后通过 PHY 接口发出去。

整个工程通常包含这几个关键模块:

模块名功能接口类型
phy_interfacePHY 芯片接口时序转换GMII/RGMII
eth_rx以太网帧接收与解析内部握手
ip_rxIP 包头解析与校验内部握手
udp_rxUDP 包头解析与端口过滤内部握手
udp_txUDP 包封装与发送内部握手
ip_txIP 包头封装与校验和计算内部握手
eth_tx以太网帧封装与 CRC 生成内部握手
crc32以太网 FCS 校验码生成组合逻辑

这个划分方式的好处是每一层可以独立仿真验证。比如你只想调 UDP 发送逻辑,可以在 udp_tx 模块的输入端直接灌数据,不需要管 PHY 接口的时序问题。常见做法是在每个模块内部用状态机控制流程,状态机的状态数量控制在 5 到 8 个左右,太多状态会导致时序收敛困难。

2.2 千兆百兆自适应机制

速率自适应是这个项目的一个关键点。千兆模式下 PHY 接口时钟是 125MHz,数据位宽 8bit;百兆模式下时钟是 25MHz,数据位宽 4bit。如果代码里把时钟频率写死,换一个速率就完全跑不起来。

实现自适应的常见做法是:在顶层用一个参数或寄存器来配置当前速率模式,然后 PHY 接口模块根据这个配置选择不同的时钟域和位宽处理逻辑。具体来说,千兆模式用 GMII 接口,8bit 数据在 125MHz 时钟下传输;百兆模式用 MII 接口,4bit 数据在 25MHz 时钟下传输。两种模式的数据吞吐率都是 1000Mbps 和 100Mbps,但接口时序完全不同。

// 速率自适应配置寄存器 reg speed_mode; // 0: 百兆, 1: 千兆 // PHY 接口时钟选择 wire phy_clk; assign phy_clk = speed_mode ? clk_125m : clk_25m; // 数据位宽处理 wire [7:0] gmii_txd; wire [3:0] mii_txd; assign gmii_txd = speed_mode ? tx_data_8bit : {4'b0, tx_data_4bit};

这段代码的逻辑很直接:用一个 speed_mode 寄存器控制时钟选择和数据位宽。实际工程中,这个寄存器通常由外部按键或上位机通过串口配置,也可以在复位时通过检测 PHY 芯片的链路状态自动判断。注意百兆模式下 4bit 数据需要拼成 8bit 再送给上层协议处理,否则 IP 头的解析会出错。

2.3 跨时钟域处理与 FIFO 设计

FPGA 以太网设计里最容易翻车的地方就是跨时钟域。PHY 接口时钟和内部处理时钟往往不是同一个时钟域,如果直接跨时钟域传数据,亚稳态问题会让你的包随机丢失或内容错乱。

这个项目里通常用异步 FIFO 来做时钟域隔离。接收方向上,PHY 时钟域的数据先写入异步 FIFO,内部处理时钟域从 FIFO 读出;发送方向同理。FIFO 的深度一般选 512 或 1024,太浅了容易溢出,太深了浪费 Block RAM 资源。

// 异步 FIFO 例化(接收方向) async_fifo #( .DATA_WIDTH (8), .FIFO_DEPTH (512) ) u_rx_fifo ( .wr_clk (phy_rx_clk), // PHY 接收时钟域 .wr_en (rx_valid), .wr_data (rx_data), .rd_clk (sys_clk), // 系统处理时钟域 .rd_en (fifo_rd_en), .rd_data (fifo_rd_data), .empty (fifo_empty), .full (fifo_full) );

这里 wr_clk 和 rd_clk 分别接两个不同的时钟,FIFO 内部用格雷码指针做跨时钟域同步。参数 DATA_WIDTH 根据速率模式选择 8 或 4,FIFO_DEPTH 根据最大帧长和时钟频率比来算。千兆模式下 125MHz 时钟,一个最大帧 1518 字节,FIFO 深度至少要能缓存一帧的数据,512 深度是够用的。

2.4 UDP 校验和与 IP 校验和计算

UDP 和 IP 头里都有校验和字段,这两个校验和的计算方式不一样,但都是反码求和。IP 校验和只覆盖 IP 头本身,UDP 校验和覆盖 UDP 头加伪头加数据载荷。很多初学者在这里踩坑:UDP 校验和算错了,抓包工具直接显示 checksum incorrect,接收端会静默丢包。

// IP 头校验和计算(反码求和) always @(posedge clk) begin if (ip_hdr_valid) begin sum <= sum + {16'b0, ip_hdr_word0} + {16'b0, ip_hdr_word1} + ...; end end // 最终校验和 = 反码 assign ip_checksum = ~sum[15:0];

这段代码的关键点是:求和过程中如果产生进位,要把进位加回到低 16 位(end-around carry)。UDP 校验和的计算类似,但需要把伪头(源 IP、目的 IP、协议号、UDP 长度)也加进去。常见做法是用一个 32bit 累加器,每来一个 16bit 数据就累加一次,最后把高 16 位和低 16 位相加取反。

注意:UDP 校验和在发送端可以填 0 表示不校验,但很多接收端(尤其是 Linux 协议栈)会强制校验,填 0 会导致包被丢弃。建议还是老老实实算对。

3. 上板跑通:从工程创建到抓包验证的完整流程

3.1 工程目录结构与文件说明

拿到源码包后,先别急着打开综合工具。花五分钟把目录结构看清楚,后面改代码的时候能省很多时间。典型的目录结构是这样的:

udp_ethernet/ ├── rtl/ │ ├── eth/ │ │ ├── phy_interface.v │ │ ├── eth_rx.v │ │ ├── eth_tx.v │ │ └── crc32.v │ ├── ip/ │ │ ├── ip_rx.v │ │ └── ip_tx.v │ ├── udp/ │ │ ├── udp_rx.v │ │ └── udp_tx.v │ └── top.v ├── sim/ │ ├── tb_top.v │ └── udp_packet.txt ├── constr/ │ └── top.xdc └── doc/ └── register_map.md

rtl 目录下按协议层分子目录,这个组织方式很清晰。sim 目录里通常有一个 testbench 和一个 UDP 包数据文件,用来做仿真验证。constr 目录放约束文件,里面有时钟约束和引脚约束。doc 目录如果有寄存器映射文档,二次开发的时候先看这个。

3.2 仿真验证:先用 testbench 跑通再上板

上板之前一定要先跑仿真。直接上板调试以太网,出了问题你连是 PHY 没配好还是协议栈有 bug 都分不清。仿真环境里可以用 testbench 模拟 PHY 接口的时序,给接收模块灌一个标准的 UDP 包,看发送模块能不能正确回环。

// testbench 中模拟 PHY 接收时序 initial begin // 发送前导码 for (i = 0; i < 7; i = i + 1) begin gmii_rxd = 8'h55; gmii_rx_dv = 1; #8; // 125MHz 时钟周期 end // 发送 SFD gmii_rxd = 8'hD5; #8; // 发送以太网帧数据... end

这段 testbench 代码模拟了 PHY 芯片接收数据时的时序:先发 7 个 0x55 前导码,再发 0xD5 作为帧起始定界符,然后才是真正的帧数据。时钟周期 #8 对应 125MHz。仿真的时候重点看几个地方:CRC 校验是否正确、UDP 校验和是否匹配、FIFO 有没有溢出。

3.3 约束文件与引脚分配

约束文件是上板成功的关键。以太网设计里至少需要这几类约束:时钟约束、PHY 接口的输入输出延迟约束、引脚位置约束。

# 时钟约束 create_clock -period 8.000 -name phy_rx_clk [get_ports phy_rx_clk] create_clock -period 8.000 -name phy_tx_clk [get_ports phy_tx_clk] # 输入延迟约束 set_input_delay -clock phy_rx_clk -max 2.0 [get_ports {gmii_rxd[*]}] set_input_delay -clock phy_rx_clk -min 1.0 [get_ports {gmii_rxd[*]}] # 输出延迟约束 set_output_delay -clock phy_tx_clk -max 2.0 [get_ports {gmii_txd[*]}]

输入输出延迟的具体数值要根据 PHY 芯片的数据手册来填。常见做法是先填一个保守值,综合实现后看时序报告,如果 slack 是正的且余量够大,就不用改;如果 slack 是负的,再根据报告调整。引脚约束就是把 gmii_rxd、gmii_txd、phy_rx_clk 这些信号分配到 FPGA 的实际引脚上,这个必须对照开发板的原理图来写,写错了轻则不通重则烧芯片。

3.4 上板调试与抓包验证

板子连好网线,下载比特流之后,先看 PHY 芯片的链路指示灯亮不亮。灯不亮说明 PHY 没协商成功,检查时钟有没有输出、复位有没有释放。灯亮了之后,在 PC 端用 Wireshark 抓包,看能不能收到 FPGA 发出来的 UDP 包。

如果抓不到包,按这个顺序排查:先确认 PC 的防火墙有没有拦截、网卡是不是在同一网段;然后用示波器或逻辑分析仪看 PHY 的发送引脚有没有波形;如果有波形但抓不到包,检查 MAC 地址和 IP 地址有没有配错。常见的一个坑是 PC 的网卡开了节能模式,导致接收异常,关掉节能选项就好了。

提示:调试以太网的时候,把 PC 的 IP 设成和 FPGA 同一网段,子网掩码 255.255.255.0,先 ping 一下看 ARP 能不能通。ARP 通了再调 UDP,能省很多时间。

4. 避坑与常见问题排查

4.1 抓包工具显示 CRC 错误

现象:Wireshark 能抓到包,但每一帧都标红显示 CRC 错误。原因通常是 CRC32 模块的初始值或多项式搞错了。以太网 FCS 用的是 CRC-32/ISO-HDLC,初始值 0xFFFFFFFF,多项式 0x04C11DB7,结果要取反。解决方法是拿一个已知正确的以太网帧数据,在仿真里灌进去,对比 CRC 输出值。如果对不上,检查初始值和输出取反这两步。

4.2 UDP 包发出去但接收端收不到

现象:抓包工具能看到 UDP 包,校验和也正确,但目标主机上的应用程序收不到数据。原因可能是目的端口号写错了,或者目的 IP 地址不在同一网段导致包被路由丢弃。解决方法是先在 PC 上用 netcat 监听对应端口,确认端口号一致;然后检查 IP 地址和子网掩码,确保发送端和接收端在同一广播域内。

4.3 千兆模式下丢包严重

现象:百兆模式跑得好好的,切到千兆模式就大量丢包。原因通常是 FIFO 深度不够或者跨时钟域同步没做好。千兆模式下数据速率是百兆的十倍,如果 FIFO 深度还是按百兆来配,很容易溢出。解决方法是在千兆模式下把 FIFO 深度加倍,同时检查异步 FIFO 的读写时钟频率比是否满足时序要求。

4.4 综合后时序不收敛

现象:综合实现后时序报告显示 setup violation 或 hold violation。原因可能是状态机逻辑太复杂导致组合逻辑路径太长,或者跨时钟域信号没有加时序约束。解决方法是把复杂状态机拆成两级流水线,在跨时钟域路径上加 set_false_path 或 set_max_delay 约束。如果还不行,降低系统时钟频率试试,先保证功能正确再优化时序。

4.5 PHY 芯片识别不到链路

现象:板子上电后 PHY 芯片的链路指示灯不亮,或者灯亮但数据不通。原因可能是 PHY 芯片的复位时序不对,或者 MDIO 接口没有正确配置 PHY 寄存器。解决方法是检查 PHY 复位信号的持续时间是否满足数据手册要求(通常至少 10ms),然后用 MDIO 接口读一下 PHY 的 BMSR 寄存器,看链路状态位是不是 1。

5. 二次开发改造:从固定包长到任意长度传输

这套代码默认可能是固定包长发送,比如每次发 64 字节或 1024 字节。实际项目里往往需要发送任意长度的数据,比如从 ADC 采集回来的数据长度是变化的。改造的思路是在 UDP 发送模块里增加一个长度寄存器,由上层逻辑在启动发送前写入实际数据长度,然后状态机根据这个长度来控制发送字节数。

具体改法是在 udp_tx 模块里加一个 data_len 输入端口,状态机在发送数据阶段用一个计数器从 0 计到 data_len-1,计满后自动跳到发送 CRC 阶段。同时 IP 头的总长度字段和 UDP 头的长度字段都要根据 data_len 动态计算,不能写死。

// 动态长度控制 always @(posedge clk) begin if (tx_start) begin tx_cnt <= 0; state <= SEND_UDP_HDR; end else if (state == SEND_DATA) begin if (tx_cnt == data_len - 1) begin state <= SEND_CRC; end else begin tx_cnt <= tx_cnt + 1; end end end // IP 总长度 = IP头(20) + UDP头(8) + 数据长度 assign ip_total_len = 20 + 8 + data_len;

这段代码的关键是 data_len 要在 tx_start 拉高之前就稳定下来,否则状态机会算错长度。我一般会在上层加一个握手信号,等 data_len 准备好之后再启动发送。另外注意 data_len 的最小值是 0,最大值受限于 FIFO 深度和 MTU,通常不超过 1472 字节(以太网 MTU 1500 减去 IP 头和 UDP 头)。

另一个常见的改造需求是增加 ARP 协议支持。当前设计可能只支持 UDP 发送,但实际网络中发送 UDP 包之前需要先通过 ARP 获取目的 MAC 地址。改造方法是在 eth_tx 模块前面加一个 ARP 请求和应答的状态机,收到 ARP 请求时回复自己的 MAC 地址,发送 UDP 包之前先查 ARP 缓存表,如果缓存里没有目的 IP 对应的 MAC 地址就先发 ARP 请求。

验证改造是否成功的方法很简单:在 PC 端用 Wireshark 抓包,看 ARP 请求和应答是否正常,然后看 UDP 包的目的 MAC 地址是不是 ARP 应答里返回的地址。如果 ARP 通了但 UDP 不通,检查 ARP 缓存表的超时时间是不是设得太短,导致每次发 UDP 都要重新 ARP。

从那以后我每次改完以太网代码,都强制走一遍仿真加抓包的流程,先在仿真里灌标准包验证协议栈逻辑,再上板用 Wireshark 确认实际链路。这套流程虽然多花二十分钟,但能省下半天盲目调试的时间。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 9:38:53

Qwen-Drive-1.0:视觉语言模型如何实现三维道路理解与轨迹生成

1. 从二维感知到三维决策&#xff1a;这个模型到底在解决什么问题视觉语言模型这两年进步飞快&#xff0c;给它一张图&#xff0c;它能告诉你图里有什么、发生了什么、甚至能推理出下一步可能怎样。但如果你把场景换成真实道路&#xff0c;让它直接输出一条可执行的驾驶轨迹&am…

作者头像 李华
网站建设 2026/10/20 9:36:18

多平台短视频解析去水印工具v3.0:源码设计与实操拆解

项目标题里那几个词——“多平台”“v3.0”“源码工具”——凑在一起&#xff0c;基本就是短视频玩家、自媒体运营和爬虫入门者都绕不开的一个需求&#xff1a;怎么把别人作品里的水印去掉&#xff0c;拿到干净的原视频。市面上这类工具多如牛毛&#xff0c;但要么收费&#xf…

作者头像 李华
网站建设 2026/10/20 9:35:57

大模型蒸馏实战:从软标签到特征对齐的完整指南

简介&#xff1a;mattevans-distil 是一个用 Go 语言编写的轻量级内存数据集过滤开源项目&#xff0c;面向需要在程序内对结构化数据做条件筛选的开发者&#xff0c;尤其适合关注查询表达式、谓词组合与过滤逻辑实现的中高级 Go 工程师参考学习。资源包共 40 个文件&#xff0c…

作者头像 李华
网站建设 2026/10/20 9:34:36

DataFun年终大会:数据智能技术人的不躺平指南

1. 一场年终技术大会为什么值得聊"疫情不躺平"这个说法&#xff0c;放在技术圈里其实特别贴切。过去几年&#xff0c;很多线下技术交流被迫中断&#xff0c;不少团队的年终复盘、技术分享都改成了线上&#xff0c;甚至干脆取消。但做技术的人都知道&#xff0c;闭门造…

作者头像 李华
网站建设 2026/10/20 9:34:17

POJO/PO/DTO/VO/BO/Entity:Java后端对象命名与分层

1. 为什么会有这么多后缀&#xff1a;对象命名的底层逻辑如果你写Java&#xff0c;大概在某个加班的深夜&#xff0c;打开某个历史项目&#xff0c;会看到类似User.java、UserDTO.java、UserVO.java、UserBO.java、UserPO.java甚至UserEntity.java同时存在&#xff0c;而且字段…

作者头像 李华
网站建设 2026/10/20 9:31:20

多功能并网逆变器在微电网中的选型统计:六个关键维度与决策方法

做微电网方案这几年&#xff0c;我养成了一个习惯&#xff1a;只要涉及多功能并网逆变器的选型或应用&#xff0c;一定先做一份自己的“统计报告”。不是把厂商彩页上的参数抄一遍&#xff0c;而是把不同产品的数据拉到同一个坐标系下重新整理、归一化、填上现场验证的结果。这…

作者头像 李华