FPGA以太网通信实战:从RGMII到UDP的全链路设计

发布时间:2026/9/8 19:12:36
FPGA以太网通信实战:从RGMII到UDP的全链路设计 玩FPGA的早晚会碰上网口。如果你从串口转网口会发现完全是两个世界串口波特率115200传一帧640x480的灰度图像要好几秒网口百兆速率下同样一帧只要几毫秒。所以当你的FPGA工程开始处理图像、采集高速ADC或者跑各种算法时网口就是一个躲不开的出口。这也是FPGA网络通信设计最典型的动机——把并行处理能力转化成高速的数据回传能力。这篇part.7面向两类人一是会点灯、会写状态机、跑过串口但还没碰过以太网的FPGA入门朋友二是在FPGA上已经实现了图像处理或信号采集、正被回传带宽卡住的开发者。我会从概念、硬件连线、RTL实现到抓包验证全链路讲一遍把实际踩过的坑和值得注意的地方一起写清楚。1. 整体思路拆解FPGA做网络通信到底图什么1.1 网口在FPGA应用版图里解决什么问题FPGA的强项从来不是“跑逻辑复杂的事务”而是“并行处理海量数据”。但数据从FPGA里出来总得有个出口。在工程上最常见的三个出口是串口、PCIe、以太网。串口最简单但带宽极低115200波特率算下来每秒也就十几KB别说图像哪怕是连续的高速采样数据也顶不住。PCIe带宽高但要么用自带PCIe硬核的高端芯片要么用MGT高速串行收发器硬件布线、驱动、软件配合的复杂度直接拉满不是零基础阶段该碰的东西。以太网恰恰卡在中间百兆以太网有效带宽大概在8-11MB/s千兆能到110MB/s左右对这个阶段的工程来说基本够用硬件上用一颗PHY芯片加RJ45座子就能解决逻辑复杂度也远低于PCIe。所以这一篇专门讲FPGA网络通信解决的就是“中速数据回传和指令下发”这个刚需。你可能只是想在FPGA上采个ADC波形上抛给上位机或者想把板上的图像数据打包发出去或者想通过网口远程配置滤波器系数这些都用得上。1.2 方案选型为什么是“PHY FPGA内部逻辑实现MAC”要做以太网通信必须面对三个层次的东西PHY物理层、MAC链路层、协议栈IP、ARP、UDP/TCP。方案上大体有这几条路方案实现方式优点缺点适合场景FPGA逻辑实现MAC 外部PHY自己写RTL处理RGMII/MII时序和MAC帧逻辑不依赖厂商IP灵活性高能学到协议本质成本低开发周期长调试有难度学习、中低速通用场合厂商MAC IP核 外部PHY用Vivado或Quartus里的Tri-Mode Ethernet MAC IP时序和MAC功能可靠接口标准不买授权有功能限制IP核学习成本不低产品化开发MCU网口 FPGA协同FMC/SPI/并行总线把数据交给带MAC的MCU比如STM32H743处理协议栈成熟开发最快带宽上限受总线接口限制两套固件联调麻烦既有MCU主控的产品升级带MGT的高速方案FPGA的GTP/GTX SFP光口或SerDes转以太网带宽高可达万兆布线、时序、功耗难度大高端板卡、数据中心如果单纯从开发速度看MCU方案可能最快但这一篇选择的是第一行用FPGA里自己写的逻辑实现MAC配合一颗常见的千兆PHY芯片比如RTL8211、YT8531、88E1512之类做物理收发。理由很简单一来零基础阶段自己写一遍MAC对理解以太网协议有不可替代的作用二来以后要做高速多通道采集FPGA自己控制链路层往往更灵活不用依赖MCU的协议栈性能。关键点在于PHY芯片一般不自带MAC功能它只负责把MAC交给它的并/串行数据转换成物理线路上的差分信号反过来再把线路信号还原成数据交给MAC。所以FPGA里必须有一套逻辑去发送和接收符合IEEE 802.3格式的帧这就是MAC层。1.3 内容边界这一篇能做到什么程度先说明白在FPGA上完整跑TCP/IP协议栈是不现实的至少对零基础阶段的工程没有必要。实际工程里绝大多数FPGA网络通信做的是UDP或者加上ARP因为UDP无连接、状态简单、实时性好适合传输采集数据和控制指令。TCP当然也能在FPGA里实现但状态机极其复杂一般会交给软核处理器或者MCU侧处理FPGA侧只管把数据包给出去。所以这篇的网络通信设计范围是RGMII接口收发、以太网MAC帧组包/解包、ARP应答、UDP/IP组包和收发。按这个范围你可实现一个“FPGA当作一个简单网络设备上位机可以通过网口收命令、收数据”的最小可运行系统。这套东西搞通之后再往PCIe或者TCP方向走的路会顺很多。2. 动手前要过的基础概念关2.1 用生活类比理解协议栈分层网络通信对FPGA开发者来说最头疼的往往不是RTL怎么写而是协议栈那一堆名词。其实你可以把它类比成快递发货你网购一个包裹商家打包好货物贴上面单UDP头、IP头快递公司给它套上大袋子写上地址MAC帧头然后快递员骑着车送过去PHY物理传输。每一层只关心自己那一层的事情数据一层层包起来收件后再一层层拆开。放在以太网里就是这个顺序PHY层物理接头、网线、电压差分信号负责比特流的实际传输。MAC层负责把数据装成帧加前导码、目的MAC、源MAC、类型、FCS校验也负责从网线上收帧、校验、剥壳。IP层负责逻辑地址IP地址和跨网络路由先把数据装进IP报文然后再塞进MAC帧里。UDP层负责端口标记这个数据是给哪个应用程序的。FPGA通信设计里你至少要处理MAC层和UDP/IP层。ARP属于IP层的地址解析协议作用是问你“知道某IP对应的MAC是多少吗”这个也必须处理否则上位机第一次发IP包时会卡在找不到MAC地址这一步。2.2 RGMII接口与引脚时序FPGA和PHY之间现在最通用的是RGMII接口Reduced Gigabit Media Independent Interface。它把千兆MAC和PHY之间的数据线从GMII的8位并行降到了4位并行然后在时钟上下沿都采样从而在125MHz时钟下跑出1000Mbps。RGMII的信号不多按功能分三组数据、时钟、控制。信号方向以FPGA侧视角作用eth_rxc / tx_clk输入/输出125MHz/25MHz/2.5MHz 时钟RXC由PHY恢复给MACeth_rx_ctl / eth_tx_ctl输入/输出上下沿分别表示“数据有效”和“数据错误”/“数据有效”eth_rxd[3:0] / eth_txd[3:0]输入/输出上下沿各采4bit拼成8bit数据具体到RGMII时序发送方向FPGA在tx_clk上升沿发送低4位txd[3:0]下降沿发送高4位txd[7:4]同时tx_ctl上升沿表示有数据下降沿表示数据是有效帧的一部分。接收方向类似但时钟是PHY恢复出来的rxcFPGA作为接收端在rxc的上下沿分别取rxd和rx_ctl。时序上有个最值得注意的点发送侧的时钟延迟。很多PHY要求RGMII发送数据相对于时钟有一点延迟否则在高速下建立保持时间不够。Xilinx系的FPGA一般直接在约束里加ODDR来保证源同步时序Altera系需要在PHY接口侧调整I/O延时。这个细节后面第5章再展开。2.3 以太网帧格式收发都得按这个格式来写FPGA网络通信MAC帧格式必须烂熟于心。以太网MAC帧基本格式如下前导码7字节0x55重复7次 帧起始定界符1字节0xD5目的MAC地址6字节源MAC地址6字节类型/长度2字节0x0800表示IPv40x0806表示ARP0x86DD表示IPv6数据载荷46-1500字节FCS校验4字节CRC32FPGA发送时通常可以不必发前导码和FCS由PHY或MAC逻辑自己生成但为了通用性最好自己组帧时把前导码也补上。这里有一个坑CRC32的计算范围是从目的MAC到数据结束不包括前导码和SFD而且是以太网多项式0x04C11DB7的非反射、初始值为全1、结果异或全1的版本。网上CRC生成工具很多默认都用的是反射型CRC32比如常见的zip crc32这里直接拿过来用会算错必须确认采用“Ethernet CRC”的算法形式。3. 实操从零搭建最小以太网收发链路3.1 硬件连接和PHY配置的注意点选型方面入门级板卡上比较常见的是RTL8211E、RTL8211F、YT8531这些千兆PHY芯片基本都支持RGMII模式。拿到原理图先确认几个重要引脚PHY的时钟来源通常需要从FPGA或独立晶振给一个25MHz或125MHz参考时钟、复位引脚极性、RGMII模式配置引脚mode strap。硬件连线上FPGA与PHY之间就是6组信号tx_clk、tx_ctl、txd[3:0]、rx_clk、rx_ctl、rxd[3:0]。有些FPGA开发板会用GMII8位数据125MHz单沿但在百兆/千兆通用设计里RGMII已经是主流我这里以RGMII为例。PHY配置这里说一个非常实用的经验如果只是把网口调通跑UDP初学阶段完全不需要先写MDIO配置逻辑。大部分PHY的默认strap配置已经能工作在RGMII千兆或百兆模式上电复位后链路就能用。真正需要MDIO配置的场景是调速、调整延迟、读取link状态、切换千兆/百兆模式。真到那一步再补一个简单的MDIO主机模块按PHY寄存器手册逐位写就是了。如果板卡上有两个或更多PHY务必注意每个PHY的默认地址可能不同MDIO地址引脚一般由硬件pull-up/pull-down决定调试读不到寄存器时先查这个。3.2 顶层模块划分不要把协议逻辑全揉在一起写FPGA网络通信最容易犯的错误是一个模块里既管RGMII时序又管UDP组包还管FIFO调度最后状态机乱成一锅粥。我建议从一开始就按数据流方向拆成清晰的两条链路并做独立模块。接收链路eth_rgmii_rxRGMII采样→ rx_macMAC帧解析、CRC校验→ rx_protocolARP应答、UDP解包→ rx_fifo数据缓存输出给应用层。发送链路tx_fifo应用层数据缓存→ tx_protocolUDP/IP/ARP组包→ tx_macMAC帧封装、CRC生成→ eth_rgmii_txRGMII发送时序。中间再加一个简单的cmd_parser模块负责根据收到的命令类型去路由数据。比如收到ARP请求就交给ARP应答逻辑收到UDP请求就解析端口和载荷把有效数据写入FIFO并向发送链路发一个“我有数据要发”的信号。这样的架构好处很直接调RGMII时不用关心协议调协议时不用关心物理时序出了问题能快速定位是哪一段。另外这样的模块划分也方便以后换成厂商MAC IP或者加入自定义协议。3.3 RGMII接收模块的RTL写法接收方向的关键是把PHY送来的双沿数据转成单沿的8bit数据流。Xilinx FPGA一般用IDDR原语Altera/国产FPGA也有对应的DDR原语。这里给一个参考性的Verilog写法思路以Xilinx系为例// 伪代码示例RGMII接收核心逻辑 always (posedge eth_rxc or negedge eth_rx_rst_n) begin if (!eth_rx_rst_n) begin rx_data_l 4b0; rx_ctl_l 1b0; end else begin rx_data_l eth_rxd; rx_ctl_l eth_rx_ctl; end end always (posedge eth_rxc or negedge eth_rx_rst_n) begin if (!eth_rx_rst_n) begin rx_data_h 4b0; rx_ctl_h 1b0; end else begin rx_data_h eth_rxd; rx_ctl_h eth_rx_ctl; end end // 根据上下沿组合出8bit数据 assign rx_data_byte {rx_data_h, rx_data_l}; assign rx_data_valid rx_ctl_l; // 高电平表示数据有效这里必须说明的是原语的例化方式和你用的是哪家FPGA强相关——Vivado里是IDDRQuartus里可能是ALTDDIO_IN国产高云、安路的原语又是另一套。但核心逻辑完全一样把上下沿采到的4bit拼成8bit把ctl上下沿的信息解析成data valid和data error然后交给MAC解析模块。另一个关键点是异步时钟域。RGMII的rxc和你的用户逻辑时钟比如125MHz不是同源时钟严格来说收到的数据要先做跨时钟域处理。简单做法是把rxc当地址直接采数据再把同步后的8bit数据用异步FIFO转换到用户时钟域。这一步不做后续协议解析时偶尔会出现偶发数据错乱很难查。3.4 MAC帧解析与组包的状态机设计MAC接收状态机一般是这样IDLE等待前导码和SFD检测到0xD5后进入接收状态RECV按字节收数据存目的MAC、源MAC、type数据写入FIFOCHECK数据收完后比较收到的CRC和本地计算CRC不一致则丢弃DELIVERCRC正确后把帧头信息和数据长度交给上层一个细节很多以太网帧末尾会有几个字节的填充pad为了保证帧长至少64字节。判断一个UDP包是否有效不能只看数据长度还得在校验完CRC后根据IP头里的total_length字段来截取实际UDP数据多余的部分直接丢掉。组包状态机则简单很多IDLE等待发送请求然后按顺序发送前导码、目的MAC、源MAC、type、数据、CRC。发送数据时注意CRC必须是整个帧发送完成后再追加的4个字节不能用“边发边算边输出”的思路因为CRC需要覆盖整个帧的内容。工程上的做法是先把数据存进发送FIFO等FIFO即将读空的最后一个字节发完时再把计算好的CRC连续发出去。3.5 最简单的验证方式先做环回写完RGMII收和发先别急着写ARP和UDP。把接收到的任意MAC帧原封不动地发回PHY然后在电脑上用Wireshark抓包。这样能验证一整条物理链路是否正常同时还能确认PHY配置、时钟、CRC都在工作。操作方法是电脑和FPGA板卡用网线直连如果电脑千兆口不支持自适应可能有兼容问题可以在电脑网卡属性里强制百兆模式先试把FPGA的接收数据直接接到发送数据路径上注意要把接收到的CRC原样保留一起回发。如果Wireshark里能看到自己连续发的包到这一步链路就基本通了。之后再去做协议解析就有底气了。4. 内层协议实现ARP和UDP/IP的硬逻辑写法4.1 ARP应答网络通信里第一个必须写的“对话”当电脑第一次向FPGA的IP发数据时会先发一个ARP请求“谁的IP是192.168.1.10请告诉我你的MAC地址。”如果FPGA不回答后续的UDP/IP包永远不会发出来。ARP请求帧长这样目的MAC为广播地址FF-FF-FF-FF-FF-FF类型0x0806ARP头里硬件类型0x0001、协议类型0x0800、硬件地址长度6、协议地址长度4、操作类型0x0001请求。FPGA收到后要做三件事检查ARP头里的目标IP是不是自己如果不是直接丢弃如果是请求构造一个ARP应答帧应答帧的源MAC填自己目的MAC填请求方的MAC操作类型改成0x0002然后把请求里的发送方IP和MAC对调填进去再封装成MAC帧发出去。这个模块属于纯组合逻辑简单状态机就能干的活在零基础阶段用来练习状态机正合适。注意ARP响应帧不需要CRC计算错——仍然要正确计算并追加FCS否则电脑网卡会丢弃响应包。4.2 UDP组包IP头校验和的计算细节UDP帧的组装顺序是MAC帧头目的MAC、源MAC、type0x0800→ IP头 → UDP头 → UDP数据 → CRC。IP头固定20字节关键字段需要填版本号4、头长度5即20字节、总长度20UDP头8数据长度、标识可随便填、TTL通常填64、协议号填17UDP、源IP、目的IP。IP头校验和是唯一需要算的东西。IP校验和的算法把一个16bit序列所有数加起来进位回卷取反。为了运算简单可以在组包时逐字累加IP头里的前10个字不含校验和字段最后进位相加取反填入。这里有个常见错误IP头校验和不受UDP数据影响但UDP校验和会把伪首部源IP、目的IP、协议号、UDP长度也算进去。如果是在FPGA里做纯硬件组包一个偷懒的办法是把UDP校验和字段填0在局域网或很多简单上位机场景下对方也能接受但这属于不规范实现某些协议栈会丢弃。正规做法是补上UDP校验和计算好在计算过程和IP校验和类似只是在前面加上伪首部的12字节一起算。4.3 CRC32并行计算别用串行移位寄存器以太网FCS使用的是CRC32如果按位串行移位计算一帧1500字节要算12000拍严重拖慢吞吐。实际工程中通常用查表法或并行CRC32核心来实现每拍计算8bit甚至32bit。用LFSR原理推导得到8bit并行CRC32核心后可以把它封装成一个组合逻辑函数输入当前CRC寄存器值和新的8bit数据输出更新后的CRC值。每接收或发送一字节调用一次。具体系数表网上资料很多但一定要先做验证——用已知的以太网帧测一下如果CRC结果不匹配就赶紧换参数配置别等到上板了再抓头。让我分享一个很实用的验证方法不要对着复杂的数学式子硬推先用全0数据帧目的MAC全0、源MAC全0、type全0、数据全0算一次再用全1帧算一次把结果和某个通用CRC计算工具比对。如果全0和全1都能对上基本就说明参数没配错。4.4 缓冲FIFO的深度怎么定高速收发时协议组包模块和用户逻辑的频率不一定匹配必须用FIFO做缓冲。FIFO深度不是随便定的有几个经验值发送方向如果用户逻辑以125MHz写入发送MAC以125MHz读出单包数据最多1500字节不含帧头最小深度取2048即可。但注意考虑背压如果PHY或链路暂时繁忙FIFO要能扛住连续几帧的突发适当加深到4096更稳。接收方向UDP解包后数据被应用逻辑消费如果应用逻辑偶尔停顿FIFO至少存一整包以上才不会丢包通常取2048或者更大。FIFO实现推荐用FPGA内部的Block RAMVivado原语用FIFO36E1Quartus用FIFO IP核国产厂家也有对应IP。同时务必把读写计数、full、empty信号引出来接调试工具这一步在调协议栈时能省大量时间。4.5 一个小型UDP发送时序的参数计算如果要以百兆以太网连续发送图像数据需要算好带宽和帧间隔。百兆有效MAC速率算下来是10MB/s左右但协议头、帧间隔、CRC要吃掉一部分项目字节数以太网帧头类型14IP头20UDP头8实际数据以最大UDP载荷1472为例1472FCS4帧间隔前导码20IFG 12字节 前导码SFd 8字节所以单帧总开销大约是1442038字节有效载荷1472字节实际最大利用率大约97.5%百兆下真实有效数据速率最高大概9.75MB/s。如果你要上抛1080P30灰度视频约62MB/s百兆明显不够得上千兆。千兆RGMII时125MHz时钟、8bit数据宽度最大有效数据速率约120MB/s能勉强够1080P30灰度但如果是彩色RGB888就必须做压缩或者换更高带宽方案。这个算清楚之后再做图像上抛类的应用心里就有底了。5. 常见问题与排查技巧实录5.1 问题速查表现象可能原因排查/解决思路网口link灯不亮PHY供电、复位、时钟缺失先查复位引脚和参考时钟用示波器量PHY时钟是否起振确认PHY模式strap是否处于RGMII模式ping不通FPGAARP没响应、MAC地址不对、FPGA逻辑没跑起来先在PC上用arp -a查看缓存FPGA侧用ILA抓收到的ARP请求看是否进了接收状态机确认目的IP匹配逻辑Wireshark能看到ARP请求但PC收不到应答帧应答帧CRC计算错误、目的MAC错误、回环测试干扰核对CRC32参数确认应答帧目的MAC是否等于请求方MAC确认PHY发送数据是否正常UDP能发出去但上位机收不到数据端口号对不上、IP校验和错误、帧没对齐Wireshark过滤udp.port 端口号检查IP/UDP头字段检查字节序是否大小端颠倒数据偶发错误一两个字节跨时钟域没处理、RGMII时序余量不足检查rxc和用户时钟的异步FIFO是否确实存在确认IO delay或IDDR约束是否违反时序高低温或长跑后断链RGMII时序裕量不足、PHY配置漂移在时序约束中给RGMII路径加适当输出延迟用MDIO读取PHY状态寄存器判断link状态5.2 排查手段ILA和Wireshark配合FPGA调网络通信最痛的点是“不能直接看网络包”所以核心调试手段是FPGA侧用逻辑分析仪Vivado里是ILAQuartus里是SignalTap电脑侧用Wireshark。我的建议是验证三步走第一步只上RGMII回环Wireshark里确认有包第二步上MAC帧解析FPGA里收一个PC发的UDP包用ILA观察MAC帧头是否正确第三步上ARP和UDP组包PC端用Wireshark确认FPGA发出的应答和UDP包格式、校验和都正确。抓包时有个细节电脑网卡的ARP缓存会把之前的IP-MAC映射存很久你改了FPGA里的MAC地址后必须用arp -d命令清理缓存否则PC会一直往旧MAC发数据FPGA那边自然收不到。这个问题我见过不少人卡了很久。5.3 时序约束实践RGMII路径的set_input_delay最后必须提一下时序约束。RGMII的收发都是源同步接口FPGA综合工具并不知道外部PHY的时钟相位关系所以你必须用set_input_delay和set_output_delay告诉工具外部时序参数否则工具可能为了满足内部时序而做出不合理的布局布线板上偶发错数据。最简单的做法是按照PHY数据手册提供的RGMII延迟参数给rx_clk到rxd的输入路径设置set_input_delay给tx输出路径设置set_output_delay。以千兆模式为例RGMII数据在时钟上升沿之后约1-2ns稳定输入延迟大致在-0.5到0.5ns之间具体数值以PHY手册为准。实际操作中不少入门板卡为了让RGMII时序稳妥会选择在PHY侧开启内部延delay通过strap或MDIO配置此时FPGA侧的delay可以设置为0也可以正常约束。这类板卡调起来会省心很多但也意味着你没法靠FPGA侧的约束去修正所有时序问题调板前一定先看板卡原理图确认有没有做延迟处理。6. 学完这一part之后可以往哪里走把FPGA的网络通信链路跑通之后你的FPGA开发能力已经从一个“只会做内部状态机”的阶段升级到了“能和外接芯片、上位机协同工作”的阶段。接下来的路有好几条可以选择往系统级走可以研究在上位机用LabVIEW、Python或者C#写一个实时显示和参数配置界面这样FPGA图像采集网络传输上位机显示就构成一个完整的小项目往性能走可以把百兆升级到千兆并加入多通道UDP切片、Jumbo Frame甚至把一个图像帧拆成多个UDP包分片发送往协议深度走可以去攻坚TCP状态机或者干脆在FPGA里跑一个软核处理器MicroBlaze、Nios II或者国产FPGA里的RISC-V软核把TCP协议栈跑在软核上FPGA逻辑专心做数据搬运。从我个人的经验来看当初卡我最久的并不是RGMII时序或者CRC计算而是对整个数据流的全局理解——容易在一个小模块里耗了太久忘了整个系统是从网线到上位机的一条完整链路。所以如果你是零基础过来的建议先放下细节用顶层视角把这条路看通上位机发一个请求包经过PHY变成比特流FPGA里的RGMII模块恢复成字节MAC模块剥掉帧头协议模块认出这是ARP还是UDP该回什么包然后把回包再一层层套回去。这段流程想明白了后面的工作其实就是填代码而已。最后再分享一个小经验调网络通信时别只看灯亮没亮要把Wireshark里每一帧的字段都读到——目的MAC、源MAC、type、IP、UDP端口、校验和任何一个字段和协议对不上网络栈都不会把包往上送。先学会“用Wireshark读帧”再回来写FPGA逻辑效率会高一倍。这一篇的内容足够搭出一套能用的百兆/千兆UDP数据通信系统往后的扩展方向就是你的应用场景说了算了。

相关新闻