资讯动态

FPGA以太网通信设计:从PHY到UDP的完整实现

发布时间:2026/9/7 13:16:39 来源:尧图企业网站定制
1. 先搞清楚FPGA做网络通信到底在做一件什么事干FPGA这一行迟早会碰上网口。不管你是做图像采集、高速数据采集还是做工业控制上位机总得和板卡通信而通信方式里以太网是绕不开的一条路。这篇part.7的内容也不是什么黑科技就是老老实实讲清楚FPGA网络通信设计的完整路径从网口硬件长什么样、PHY芯片怎么初始化到MAC帧怎么收发、ARP和UDP怎么处理再到怎么用抓包工具验证整个链路。这次的话题适合谁看已经学过前面几篇、手里有块开发板、想从串口升级到网口的FPGA学习者或者是刚开始接触以太网、被一摞协议文档吓住的嵌入式工程师。先给大家吃个定心丸FPGA里做网络通信不是让你在Verilog里写一个完整的TCP/IP协议栈那既没必要也不现实。绝大多数实际项目里FPGA只扮演一个“会打包、会拆包、速度极快”的角色——从某个数据源拿到数据按照以太网帧格式打包发出去反过来收到上位机发来的网络包解析出来交给下游逻辑。换句话说我们做的是OSI七层模型里最底下的那几层。物理层里的编解码、模拟前端由PHY芯片负责FPGA只和PHY芯片打交道通过RGMII、GMII这类接口传数字信号数据链路层的MAC、部分网络层和传输层工作由FPGA内的逻辑实现。你要做的核心就三件事把PHY芯片配置好把MAC收发逻辑跑通再把UDP或者ARP这类简单协议用状态机实现出来。这里先建立一个整体认知网络通信的调试方式和之前写个流水灯完全不一样它是一个“闭环系统”FPGA内部能看到的东西只是一半另一半在电脑的网卡和协议栈里。这意味着你需要学会用Wireshark抓包、用ping命令探测、用ARP表核对地址这些都会是以后调试的日常。所以我建议把这次任务当成一次系统性的综合练习而不是单纯的某个模块。2. 硬件方案选型与接口速览2.1 FPGA网口到底由哪些芯片组成各自管什么很多人第一次翻开开发板的原理图会被吓一跳FPGA引出几十根信号线中间经过一颗芯片再过网络变压器最后才到RJ45座。实际上这条链路上每个器件分工很明确。FPGA不直接识别网线上的模拟差分信号也不能自己产生符合以太网电气特性的电平所以必须外接一颗PHY芯片。PHY负责物理层的重活把FPGA送来的并行数字信号变成串行差分信号发到网线上同时把网线收到的信号还原成数字信号。常见的PHY芯片型号有瑞昱RTL8211系列、美满Mavell 88E1512、微芯KSZ9031等开发板上出现频率很高。PHY和FPGA之间的接口最常用的是RGMII一边4根数据线加1根时钟加1根控制线DDR模式下时钟上下沿各传一个字节千兆速率下时钟跑125MHz带宽刚好够用。PHY后面接着网络变压器它的作用是隔离共模干扰、匹配阻抗有些设计中它还负责电平转换。变压器之后才是RJ45这个环节原理图上不复杂但焊接和布局布线有讲究一般开发板直接按参考设计画就行学习者不用在变压器选型上花太多精力。MAC逻辑放在FPGA内部可以自己用Verilog写也可以用厂商提供的IP核。MAC要做的事包括生成和识别前导码、目的MAC、源MAC、长度/类型字段计算和校验帧校验序列FCS处理最小帧长、填充字段以及对接收侧的帧进行CRC校验。这个模块是FPGA网络通信的核心后续讲的收发链路都建立在它之上。2.2 RGMII接口的时序逻辑先把引脚关系理顺RGMII这名字直译是“简化吉比特介质独立接口”它在千兆模式下使用TXC、TXD[3:0]、TX_CTL三组信号发送数据RXC、RXD[3:0]、RX_CTL三组信号接收数据。发送侧TXC由FPGA产生频率125MHzTXD和TX_CTL在TXC的上升沿发送低字节、下降沿发送高字节。当传输百兆速率时TXC降为25MHz但数据仍按DDR方式在上下沿各传4位。这里有个容易搞混的地方RXD和RX_CTL是PHY送给FPGA的RXC由PHY提供FPGA接收时必须确保跨时钟域处理正确。PHY一般还会给FPGA提供一套管理接口就是MDC和MDIO用于读写PHY内部寄存器。FPGA通过MDIO寄存器获取链路状态、配置自协商模式、读取协商速率这是FPGA和PHY之间唯一的管理通道。再往下就是一些通用信号PHY的复位信号通常由FPGA控制复位后PHY进入默认工作模式部分PHY芯片还需要配置时钟输入有些用50MHz有源晶振有些用125MHz具体看PHY型号和原理图。开发板自带的有源晶振一般已经把时钟接给PHY了FPGA这边只需要在对应管脚约束好时钟输入即可。画一个RGMII信号方向的简表信号方向作用TXCFPGA - PHY发送时钟125MHz/25MHzTXD[3:0]FPGA - PHY发送数据上下沿各4位TX_CTLFPGA - PHY发送控制高表示数据有效RXCPHY - FPGA接收时钟RXD[3:0]PHY - FPGA接收数据RX_CTLPHY - FPGA接收控制高表示数据有效MDCFPGA - PHY管理时钟MDIOFPGA - PHY管理数据双向只要把这张表的方向和时序搞明白后面写代码、加约束时就不会晕。2.3 开发板选型建议不是板子越贵越好初学者学FPGA网络通信我强烈建议先用手上现有开发板不要为了这个专门去买新板。因为大多数开发板都会带一个或两个网口无论是黑金、正点原子还是其他厂商的板子网口方案大同小异FPGA RTL8211或88E1512 网络变压器 RJ45。板子的PHY芯片型号不同MDIO地址可能不同寄存器定义基本遵循IEEE 802.3标准所以思路和代码是可以迁移的。如果你手头板子上的PHY芯片型号比较冷门或者原理图看得头疼也别慌。去芯片官网下载数据手册重点看寄存器列表和MDIO读写时序一两个小时就能把关键点摸清楚。相比一颗PHY芯片的差异更值得关注的是FPGA型号——如果你用的是Xilinx 7系列或UltraScale系列那么使用厂商的Tri-Mode Ethernet MAC IP核会省去很多底层MAC的编码时间尤其在做高速收发时IP核内部已经处理好了FIFO、CRC引擎和统计计数器比自己造轮子靠谱得多。这里我多说一句学习阶段我建议大家“先IP核后自研”第一次用IP核跑通全链路知道数据从哪进从哪出、信号时序长什么样再尝试自己写简化版MAC。如果一上来就闷头写MAC容易在CRC、状态机这些细节里迷失方向反而不利于理解网络通信的大局。3. 核心实操步骤从PHY初始化到UDP收发完整链路3.1 MDIO读写模块FPGA控制PHY的遥控器PHY芯片内部有一组寄存器FPGA通过MDIO总线读写。MDC是一个由FPGA产生的慢速时钟一般不超过2.5MHzMDIO是双向数据线在MDC上升沿采样。一次MDIO事务的帧格式长这样前导码32个连续1表示一次读写事务开始起始码ST2位固定为01操作码OP2位读操作为10写操作为01PHY地址5位对应PHY芯片的地址一般由原理图上引脚状态决定寄存器地址5位要访问的寄存器编号转换状态TA2位读操作时FPGA释放MDIO总线PHY在第2位驱动数据写操作时FPGA驱动10数据16位读操作时PHY返回寄存器值写操作时FPGA写入寄存器先给一个MDIO读模块的Verilog骨架这个模块用状态机实现重点在于控制双向IO和三态门的使用module mdio_read #( parameter CLK_DIV 50 // 分频系数将系统时钟分频到MDC )( input wire clk, input wire rst_n, input wire [4:0] phy_addr, input wire [4:0] reg_addr, input wire start, output reg [15:0] data_out, output reg done, inout wire mdio, output reg mdc ); // 状态定义 localparam IDLE 3d0, PRE 3d1, ST_OP 3d2, ADDR 3d3, TA 3d4, RD_DATA 3d5; reg [2:0] state; reg [5:0] bit_cnt; reg [15:0] shift_data; reg mdio_out; reg mdio_oe; // 输出使能1表示FPGA驱动MDIO assign mdio mdio_oe ? mdio_out : 1bz; // MDC时钟生成逻辑... always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; data_out 16d0; done 1b0; end else begin case (state) IDLE: begin done 1b0; if (start) state PRE; end // ... 完整状态流程 endcase end end endmodule实际工程中读写寄存器一般不会只在仿真里看波形而是会结合在线逻辑分析仪ILA抓取MDIO总线波形确认读回来的寄存器和数据手册上描述一致。比如读PHY芯片的0x01基本状态寄存器bit5是自协商完成状态bit2是链路状态如果读回来这些位都是期望值说明MDIO链路是通的。注意一个常见问题MDIO总线上可能不止挂一个PHY芯片所以PHY地址必须和原理图上配置一致。很多PHY芯片的地址由引脚的高低电平决定板子默认一般是0x00或0x01但有的厂商主板会改成0x04这时如果你按手册默认地址去读返回的全是0xFFFF看起来就像总线没通。我调试时遇到过这种问题后来是翻原理图确认了PHYAD[4:0]这几个引脚的电平才找到正确的地址。3.2 PHY初始化流程复位、配置自协商、等待链路建立PHY芯片上电后不是立刻就能收发数据需要先做初始化。标准的初始化流程可以归纳成这几步拉低复位引脚保持一段时间PHY手册一般要求至少10ms再拉高完成硬复位等待PHY内部稳定一般需要几十毫秒通过MDIO写寄存器配置工作模式比如使能自协商、设置千兆/百兆速率轮询状态寄存器等待自协商完成链路状态变为up读取协商结果确认最终工作速率和双工模式以RTL8211为例写0x00寄存器基本控制寄存器的值通常设为0x1000表示使能自动协商但不重启如果要软件复位就写0x8000。0x01寄存器是基本状态寄存器bit5表示自协商完成bit2表示链路状态这两个位是调试时最关注的。有些场景下你可能不想用自协商而是强制固定速率和双工模式。比如FPGA和PC直连协商结果一般没问题但如果FPGA对FPGA或者经过交换机强制模式下容易对不上参数导致链路不通。所以我个人建议能自协商就自协商省事且兼容性好。如果你的PHY芯片上电后通过引脚配置就已经工作在想要的状态MDIO初始化步骤甚至可以简化成只读状态寄存器等待链路建立。顺便说一句PHY初始化代码尽量写成参数化、可复用的模块因为不同板子PHY型号不同但MDIO驱动逻辑大差不差。以后换板子时只需要修改PHY地址、寄存器配置常量就能快速移植。3.3 MAC发送链路把一帧数据一步步送上网线MAC层做的事情简单说就是“把数据打包成以太网帧再按位送到PHY”。以太网帧从前往后依次是7字节前导码固定0x55、1字节帧起始定界符SFD0xD5、6字节目的MAC、6字节源MAC、2字节长度/类型、数据载荷46到1500字节不够46字节要填充、4字节FCS帧校验序列。发送时如果数据载荷不足46字节MAC必须自动在数据后面填充0保证整个帧长度不小于64字节。这个填充操作容易忘一旦漏掉PC网卡可能会丢弃短帧表象就是FPGA明明一直在发Wireshark却什么都看不到或者是收到但被标记为“runt”短帧错误。FCS就是以太网CRC32它的计算范围是从目的MAC开始一直到数据末尾包括填充字节不含前导码和SFD。标准CRC32的多项式是0x04C11DB7初值是0xFFFFFFFF计算结果需要再取反而且整个计算是按位、从最低位开始处理的。这导致一个很常见的坑很多教程里的查表法是按字节、高位在前实现的如果直接用在以太网帧上算出来的CRC永远不对PC端会一直报CRC错误。解决方式有两个一是直接用厂商IP核里的CRC引擎二是写一个简化版的逐位CRC计算模块并且在比特顺序上特殊处理。发送状态机的核心逻辑不复杂大致是// 发送主状态机示意 localparam S_PREAMBLE 4d0, S_SFD 4d1, S_MAC 4d2, S_TYPE 4d3, S_DATA 4d4, S_FCS 4d5, S_DONE 4d6; always (posedge tx_clk) begin case (tx_state) S_PREAMBLE: begin // 连续发送7个0x55 if (preamble_cnt 3d7) tx_state S_SFD; end S_SFD: begin // 发送0xD5 tx_state S_MAC; end // 其他状态类似... endcase end发送侧还有一个细节是发送使能信号TX_CTL。RGMII接口上TX_CTL在发送前导码和SFD时是低电平从目的MAC字节开始置为高电平一直保持到最后一个FCS字节结束。PHY就是根据这个信号判断哪些字节是有效数据。这个时序如果弄错PHY会把前导码当有效数据发出去对端设备会直接丢帧。3.4 接收链路从网线上把帧拆出来接收链路是发送链路的镜像FPGA从RGMII接口拿到PHY送过来的RXD、RX_CTL和RXC然后按以太网帧格式解析。接收状态机首先检测前导码和SFD然后逐字节接收MAC地址、类型和载荷数据最后做CRC校验。校验通过后把完整的帧数据写入FIFO同时产生一个接收完成信号通知上层协议处理模块取走数据。接收方向最大的坑在跨时钟域。RXC是PHY提供的采样时钟它的频率可能是125MHz千兆或25MHz百兆这些数据和本地系统时钟完全是两个时钟域。如果你用RXC直接采数据然后系统时钟域再去读必须做异步FIFO否则数据会偶发错位。在RTL设计里一个比较稳妥的处理方式是把RGMII的DDR信号先通过IDDR原语转成单沿数据然后统一以RXC为时钟写入异步FIFO读侧用系统时钟两个时钟域之间通过FIFO隔离。接收方向如果用ILA调试可以重点观察RXC和RXD的捕获时序。很多时候包收不到不是因为数据有问题而是约束没做对导致FPGA采样点刚好落在数据跳变的边沿附近。这时调整输入延迟约束的数值比改RTL代码更有效。3.5 从MAC往上走用状态机实现ARP和UDPMAC收发链路跑通之后通信还停留在数据链路层。要让PC和FPGA能互相ping通、能收发自定义数据还得在网络层和传输层做点事。但别怕这里不需要写一封RFC文档级别的协议栈只需要实现两件事ARP应答和UDP收发。ARP是地址解析协议PC第一次ping一个IP时会发送一个广播的ARP请求“谁的IP是192.168.1.10请告诉我你的MAC地址。”FPGA收到这个请求后如果发现目标IP是自己就要回一个ARP应答把自己的MAC地址告诉PC。这样PC才会把后续的UDP包发到FPGA的MAC地址上。所以做网络通信测试时如果ping不通第一件事就是看ARP通了没有。ARP帧的格式相比普通以太网帧多了一个28字节的ARP头部好在帧类型字段是0x0806接收侧可以根据这个字段判断是不是ARP包。ARP应答状态机的核心逻辑是解析ARP请求比较目标IP和自身IP如果匹配就交换源目的MAC和IP生成应答帧发送出去。这个状态机不复杂注意ARP帧的最小长度是28字节不足时要填充到46字节和普通以太网帧的填充规则一致。UDP更是简单粗暴。UDP头部只有8个字节源端口、目的端口、长度、校验和。IP头部20个字节包含版本、头部长度、总长度、标识、协议字段、源IP、目的IP等。FPGA发送UDP包时把用户数据拼上UDP头再拼上IP头最后套上以太网头交给MAC发送模块。这个流程在代码里体现为几个状态机依次切换从以太网头生成状态机跳到IP头生成状态机再跳到UDP头生成状态机最后跳到数据发送状态机。如果只是做测试IP校验和可以用软件或脚本先算好FPGA端直接填入预先计算的值。但实际项目中IP地址和包长度可能是变量所以最好还是实现一个简单的校验和计算模块。IP校验和是16位累加和取反算法并不复杂把IP头按16位一组求和溢出回卷最后取反。UDP校验和更麻烦一点因为涉及伪头部和整个数据载荷如果不想在FPGA里写大量加法可以在测试阶段将UDP校验和填0因为UDP规定校验和为0表示不校验PC端默认可以接受。我给的实质性建议是第一步先实现“FPGA能响应ARP请求”第二步实现“FPGA能发送固定的UDP包”。这两步通了你已经在电脑上能看到一个活生生的FPGA网络节点了。然后再考虑加接收解析模块让FPGA能处理PC发过来的指令这样双向通信就完成了。3.6 时序约束网络通信能不能稳定跑起来的决定性因素很多初学者把代码写好、综合实现没有报错就以为万事大吉结果上板发现链路时通时不通。对于RGMII接口时序约束是决定性的。RGMII发送方向FPGA作为源输出TXD、TX_CTL同时产生TXC时钟。PHY在TXC的上下沿采样数据所以数据相对于时钟需要满足建立时间和保持时间要求。通常的策略是做约束时告诉工具数据输出相对于时钟的延迟范围create_clock -period 8.000 -name clk_rgmii_tx [get_ports rgmii_txc] set_output_delay -clock clk_rgmii_tx -min -1.0 [get_ports {rgmii_txd[*]}] set_output_delay -clock clk_rgmii_tx -max 2.0 [get_ports {rgmii_txd[*]}]接收方向RXC是PHY输出的源同步时钟数据跟随RXC变化。Xilinx FPGA里通常用IDDR原语在RXC上下沿分别采数据时序约束需要注意数据和时钟的相位关系。RGMII标准中数据是在时钟的正负沿切换通常PHY内部已经做了延迟FPGA端采样时需要加一定的输入延迟约束否则采样窗口覆盖不到稳定的数据中段。时序约束想一次配对的概率不高调试时可以通过改变约束的延迟值来调整采样点。一个有效的手段是用ILA抓RXD信号看看当前采样点是否落在数据稳定的中间位置。如果出现边沿采样导致偶尔错字节可以微调set_input_delay的数值让采样窗口向左侧或右侧移动。这个调试过程有点耐心就觉得很有成就感因为你会真真切切感受到“时序不是玄学而是有依据的计算”。4. 调试实录网络通信经典问题与排查技巧4.1 链路起不来的几种可能性“FPGA程序下载进去了PC网口显示网络电缆被拔出”是最常见的开局问题。先查硬件用万用表量PHY的电源、时钟引脚是不是有波形用示波器看MDC和MDIO有没有跳动。硬件没问题的前提下再查PHY初始化代码——很多时候PHY芯片没有正常复位或者MDIO总线的PHY地址不对FPGA写寄存器根本写不进去。还有一种情况是PHY已经正常工作了但FPGA这边没有输出时钟给PHY或者输出频率不正确。RGMII千兆模式下TXC必须是125MHz百兆模式下是25MHz如果频率不对PHY无法正确采样。检查TX时钟的路由是否和约束一致尤其当TX时钟由MMCM/PLL产生时确认约束里用了正确的输出时钟。链路起来之后又分两种情况PHY状态寄存器显示link up但PCping不通。这种问题多半在MAC发送或协议解析上可以用ILA抓FPGA发出的帧逐字节检查前导码、MAC地址、类型字段和CRC。也可以先在PC上用Wireshark抓包看看有没有收到FPGA发出的任何广播帧。如果Wireshark什么都收不到说明PHY没把数据送上网线重点查TX_CTL时序和TXD引脚约束如果收到了广播帧但标记为坏帧重点查CRC计算和填充逻辑。4.2 ARP通了但UDP不通问题出在哪ARP通说明MAC收发链路、PHY配置都是好的IP和MAC地址的对应关系也写在PC的ARP缓存里了。这时候UDP包发不出去通常是上层协议组包的问题。比如IP头里总长度字段和实际发送字节数不符或者UDP长度字段算错PC协议栈直接丢弃。我调试时习惯先在PC端开Wireshark监听然后用ping命令让PC发ARP请求和ICMP请求再看FPGA侧的回包。如果ICMP回包能通那协议栈基本链路都是好的UDP发不出去大概率是FPGA发送状态机没有正确跳出或者数据源FIFO为空时还在发数据。建议把发送状态机的状态转移图写清楚在关键节点打调试信号交给ILA观察状态跳转是否符合协议预期。另外Wireshark里如果发现FPGA发的UDP包IP校验和错误但ARP是正常的那十有八九是IP校验和模块的位序或翻转写反了。不要急着自己改先在Python里用同样的数据算一遍标准校验和把FPGA的结果和软件计算结果对比很快就能定位问题。4.3 帧结构错误的高频原因速查做一个FPGA网络通信的帧结构问题速查表方便大家排查现象可能原因检查方向Wireshark看不到任何包PHY链路未建立、TXD约束错误PHY寄存器状态、ILA抓TXD看到包但标记为runt数据载荷不足46字节未填充MAC发送状态机的填充逻辑看到包但CRC错误CRC多项式/位序/初值错误对比标准CRC32算法结果ARP请求能收到但无应答ARP应答组包时目的MAC填错检查ARP请求帧里源MAC解析ICMP能通但UDP不通IP校验和错误或UDP长度错误抓包看IP头字段计算ping时通时不痛RGMII采样点不稳定调整set_input_delay约束这个表是我实际调试中一个个踩出来的。最常见的坑是CRC其次是填充。很多人看到Wireshark出现大量CRC错误包第一反应是自己发了错误数据其实往往只是填充字段没有补零、FCS算错了而已。4.4 用ILA和Wireshark双端联调的经验分享FPGA网络通信调试有个特点问题可能出在FPGA内部逻辑也可能出在对端PC协议栈的处理上。只靠FPGA单侧的调试工具往往不够必须学会“双端联调”。我个人的调试习惯是这样先在FPGA侧加ILA抓取MDIO读回的值确认链路状态正常然后FPGA固定发送一个已知内容的以太网帧PC端Wireshark持续监听如果抓到但内容不对就缩回去看MAC发送状态机和数据源如果没抓到说明问题在物理链路或PHY配置。反过来也一样PC端用工具发送自定义包FPGA侧通过ILA看RXD、RX_CTL和解析状态机配合仿真波形对比。强烈建议在FPGA侧加一个简单的计数器每发一帧计数器加一把这个计数器的值映射到某个指示LED上。这样你用肉眼就能判断FPGA是不是在持续发包不用每次跑到仿真里看波形。这个“土办法”在处理链路问题时比ILA还高效因为一眼就能看到活跃状态。5. 总结一下我这段时间做下来的实际感受做FPGA网络通信设计和做串口通信完全是两个难度层次。串口只要波特率对、数据位对基本就成了大半网口涉及PHY配置、MAC时序、协议栈解析、调试工具交叉配合任何一个环节掉链子链路就起不来。但也正因为难当你看到PC端ping通那个回显、或者Wireshark里连续刷出正确的UDP包时成就感是串口通信给不了的。我个人建议如果你想把这个方向的实验做扎实不要满足于“只发一个固定包”。可以试着加一个应用层小任务比如PC端发一个十六进制命令FPGA收到后解析把传感器状态打包通过UDP反馈回去。这样就把接收、解析、组包、发送四个环节全部练到了。有了这个基础以后再走向光口、PCIe、更高带宽的数据通路你会发现核心思路都是一样的搞清楚数据从哪来、经过什么协议、以什么时序送到哪里去剩下的就是选好IP核、写好状态机、配好约束、耐心调试。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价