我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

FPGA千兆以太网通信实战:从UDP回环到RGMII接口的完整入门指南

FPGA千兆以太网通信实战:从UDP回环到RGMII接口的完整入门指南 1. 为什么FPGA要碰网络通信上一次聊FPGA的时候评论区有个常做图像采集的朋友留言说现在他手里的板子数据吞吐已经到了瓶颈USB 2.0扛不住DDR读写又绕不开上位机问我FPGA做网络通信是不是很麻烦。这个问题其实很多搞FPGA的人都会遇到传感器采集、高速ADC、图像数据预处理数据量都不小但是把这些数据送给PC或者其它设备时传统串口或者USB要么速度不够要么协议栈太复杂FPGA的实时性优势根本发挥不出来。千兆以太网这个接口带宽有1Gbps换算下来大概125MB/s很多工业场景和实验场景都够用了。相比PCIe、RapidIO这些高速接口以太网又有一个绝对优势设备几乎人手一个网口不需要专用驱动插上就能跑协议栈在CPU侧有成熟的实现。FPGA这边只负责把MAC层和PHY层管好把数据包从一个简单的FIFO搬进搬出整体设计思路比做PCIe简单太多了。这个系列的定位是从近似0基础开始所以这一篇我默认你已经会了Verilog的基本语法、知道怎么写一个带FIFO的状态机、跑通过Vivado或者Quartus的工程但是对TCP/IP、对以太网协议可能一头雾水。没关系这篇就是把这些网络协议的“黑话”拆开结合FPGA的视角重新组织一遍最终目标是在一块开发板上把一个网口真正“跑起来”能收到PC发来的UDP包也能把FPGA内部的数据送给PC。本文适合想给FPGA加网络能力的人不论你后面要做高速数据采集、图像传输、还是工业控制这篇铺的路都能用得上。核心思路是用“最小可用的UDP回环设计”作为主线一步步把PHY芯片、MAC内核、FIFO缓冲、UDP协议处理这些部件串起来。2. 网络分层和FPGA的交集2.1 从PC到FPGA数据到底是怎么过去的如果你只是想调用socket接口发几个字节的数据估计根本不会关心这包数据是如何从网线变成电压然后进入FPGA的。但做FPGA网络通信这些层级必须搞清楚因为FPGA要承担的是其中最底层的、对时间敏感度最高的那些工作。以太网通信的完整协议栈分好几层。最上面是应用层比如你写的Python程序通过socket发包。往下是传输层和网络层TCP、UDP、IP地址、路由这些概念都在这一层。再往下是链路层负责把IP包封装成以太网帧加上MAC地址同时做CRC校验。最底层是物理层负责电压、编码、时钟同步这些模拟域的细节。从PC网卡发出去的数据实际在网线上是以差分信号传输的接收端PHY芯片负责把差分信号恢复成数字比特流。FPGA在系统里一般扮演两个角色一是接管MAC层以上的协议处理二是直接控制PHY芯片、完成链路层和媒体访问控制。换句话说FPGA不需要管网线上CS信号怎么调制的那是PHY芯片的事但FPGA必须知道怎么把MAC帧塞给PHY芯片。FPGA通常不直接处理TCP这种复杂协议因为状态机太复杂一般会把TCP卸载到CPU或者干脆选择UDP。用生活类比来理解PHY芯片就像快递运输车负责把货物在高速公路上运到目的地MAC层是分拣中心把货物按地址分装而FPGA就是仓库管理员负责决定什么货发出去、收到的货放哪里。CPU可以远程指示仓库管理员但每辆车的装卸细节CPU如果全都亲自管效率就很低。2.2 FPGA实现网络的关键组件搭建FPGA网络通信系统核心组件就四部分PHY芯片、MAC控制器、FIFO/存储缓冲、协议处理逻辑。PHY芯片在开发板上一般长这样一颗带变压器的RJ45网口旁边总会有一颗小芯片可能是Realtek、Marvell或者Microchip。现在主流FPGA开发板常用RTL8211、88E1111等PHY芯片它们通过RGMII接口和FPGA相连。这个接口很简单数据线加控制信号一共十几个引脚频率125MHzDDR双边沿采样能做到千兆速率。MAC控制器在FPGA里实现可以自己写Verilog也可以直接用厂商IP核。自己写看起来酷炫实际很容易踩坑尤其CRC、帧间隙、冲突检测这些细节非常繁琐。用IP核速度快很多。Xilinx把Tri-Mode Ethernet MAC封装成了IP英特尔FPGA阵营有对应的TSE IP。IP核的配置界面填一下工作模式、MDIO配置、数据接口宽度就可以得一个MAC。FIFO放在MAC和用户逻辑中间做缓冲。网络是突发性的数据到达不均匀FIFO负责平滑流量。更深层的原因是跨时钟域。PHY侧125MHz、MAC核内部频率和用户逻辑时钟往往不在同一个域FIFO天然解决CDC问题。协议处理逻辑属于用户自定义部分负责按UDP/IP格式提取或者填充数据。PC发来的UDP包打入FIFO后你要能解析出目标端口、数据长度、载荷内容反过来要把ADC采样数据发送出去你需要按格式把IP头、UDP头组好再算好校验和。2.3 为什么纯逻辑方案优先选UDP而不是TCP这是很多刚入门的人问的第一个问题。既然要跟PC通信自然想直接用TCP因为上位机编程简单、数据可靠。但TCP的可靠传输依赖确认重传、滑动窗口、序号管理、超时计时这些在CPU上就是一堆状态在FPGA里实现要么吃掉大量LUT和Block RAM要么写出来的逻辑让人头皮发麻。UDP就简单太多了无连接、无确认、无重传总共就8字节头加在IP头后面内容简单直白。丢包可以接受。如果你做的是图像采集、高速数据流实时监控偶尔丢一帧数据画面可能闪一下但如果因为TCP重传导致数据顺序错乱、延迟抖动反而是大问题。所以FPGA里做网络通信应用层首选UDP。这不是说FPGA完全做不了TCP。工业级场合ZYNQ这类SoC芯片里跑Linux硬件卸载引擎可以加速TCP。但那就是另一个量级的复杂度了。纯FPGA逻辑实现TCP的也有比如FPGA开源社区的Verilog-TCP/IP项目但体验过的人都知道光调通就够写几篇博客。3. 硬件平台与工具选型3.1 开发板与PHY芯片的选择做FPGA网络通信开发板最好自带千兆网口。我用过的几块板子里Xilinx Artix-7系列配RTL8211的板子最多教程也多遇到问题好查。Zynq开发板也几乎都带网口但如果是纯PL实现网络Zynq和Artix在逻辑用法上没太大差别。如果手头板子没有网口可以通过扩展板加。市面上很多以太网扩展板给FPGA留了RGMII接口但注意核对引脚和电源域RGMII电平标准是2.5V或者3.3V要确保和FPGA bank电压匹配否则烧坏引脚就麻烦了。PHY芯片的选择最常见的有几个芯片型号厂商接口类型特点RTL8211ERealtekRGMII廉价、开发板最爱、资料多88E1111MarvellRGMII/SGMII老牌、兼容性好、工业设备常见KSZ9031MicrochipRGMII低功耗、集成度高DP83867TIRGMII/SGMII工业级、抗干扰强选PHY没什么玄学核心看资料和例程。RTL8211E几乎每个FPGA开发板商家都能给你调好的例程优先选它。3.2 工具链Vivado和IP核这篇的所有实操代码基于Vivado 2020.1IP核使用Xilinx Tri-Mode Ethernet MAC。用Vivado的好处是IP核配置界面对新手很友好把PHY芯片型号、接口模式填进去RGMII引脚约束也基本是点选完成。英特尔阵营的Quartus也有类似IP核配置逻辑差不多。如果是国产FPGA比如紫光、安路、高云目前也有MAC IP只是生态相对没那么成熟需要更多手写时序的功夫。但底层原理是通用的。配套工具方面Wireshark抓包、Python构造UDP报文、以及串口调试助手可以作为辅助角色。我的建议是先把网络抓包环境准备好做到“发什么、收什么”全部可视化不然纯看FPGA内部逻辑信号排查问题非常痛苦。4. 核心链路拆解RGMII接口与MAC核4.1 RGMII接口时序到底怎么回事RGMII全称是Reduced Gigabit Media Independent Interface是千兆以太网最常使用的MAC到PHY之间的接口。它把原本GMII接口的8位数据线缩减到4位用DDR技术让数据在时钟上升沿和下降沿各采样一次因此125MHz时钟下就能跑出千兆速率。具体来说RGMII有四组信号TXD[3:0]和RXD[3:0]分别表示发送和接收数据TX_CTL和RX_CTL是控制信号还有TX_CLK和RX_CLK时钟线。在千兆模式下时钟频率125MHz数据分别在上升沿传低4位、下降沿传高4位。举个例子发送一个字节0xAB上升沿发0x0B下降沿发0x0A。控制信号在千兆模式下也复用上升沿表示TX_EN有效下降沿表示TX_ERR。所以最终在FPGA内实现RGMII时发送侧逻辑非常简单把8位数据拆成两个4位分别在时钟的两个边沿送出去控制信号也拆开。接收侧需要把上升沿和下降沿的数据拼成8位按照内部时钟对齐。很多新手在这里犯的错误是忘记DDR的建立时间。RGMII标准规定PHY输出的RX_CLK和RXD数据之间是有相位关系的但PCB布线、温度变化会导致相位偏移。低端方案直接用IDDR原语采样一旦时序裕量不足就会偶发错误。更好的做法是使用OSERDES/ISERDES并且可以在RX_CLK上加可调相位的MMCM/PLL来寻找最佳采样点。Vivado里用IDELAYE2的话可以在线调整延迟值调试时用这个扫描最佳tap点。4.2 把Tri-Mode Ethernet MAC核用起来Xilinx TEMAC核封装很完整内部实现包含MAC状态机、CRC校验、流控管理用户主要关心数据接口侧。TEMAC支持多种数据接口比较常用的是AXI4-Stream接口和GMII接口。AXI4-Stream用起来更现代配合AXI DMA可以做很灵活的搬运如果只是实现UDP收发我建议数据接口选简单的FIFO接口也就是LocalLink或者简单的收发FIFO接口这样逻辑直白不会引入额外的AXI协议理解成本。在Vivado中添加TEMAC IP核后需要配置的参数主要有PHY接口模式选RGMII否则IP默认可能导出GMII引脚需要另外加转换逻辑容易绕晕。速率模式选1Gbps全双工。千兆场景基本不需要协商到百兆除非你的应用对功耗敏感。MDIO管理接口勾选用来配置PHY芯片寄存器。虽然有些PHY上电后默认就是千兆模式但通过MDIO读取寄存器状态确认link up是调试时必备手段。数据接口选8位FIFO接口收发通道分开。CRC与前导码选择由MAC核生成。配置完成后IP会产出一堆引脚其中和PHY相连的引脚需要在约束文件里分配物理管脚。不同开发板管脚不一样总之对照原理图一个个填。4.3 MDIO配置PHY寄存器的小技巧MDIO管理接口非常有用它用两根线MDC和MDIO就能读写PHY内部寄存器。通过MDIO可以读取PHY的基本状态比如是否检测到网线、链路速率是多少、是否全双工。开发调试的时候这些信息比示波器还直观。常见寄存器地址和安全操作如下寄存器地址功能说明0x00控制寄存器bit15软件复位bit13速率选择bit8双工模式0x01状态寄存器bit2链路建立状态bit5自动协商完成0x04自动协商广告能力配置支持的速率和双工模式0x00_1FPHY特定状态不同芯片定义不同RTL8211可以读它获取实际协商结果复位顺序建议是FPGA配置完成后先通过MDIO读PHY ID确认MDIO通路是通的然后软复位PHY等待自协商完成最后读状态寄存器确认link up。链路状态对应开发板网口的LED如果用示波器看RX_CLK有没有翻转也是一个直接的判断手段。5. 从MAC到UDP协议栈的最小实现5.1 以太网帧格式速览MAC层收到的是一整包完整的以太网帧。标准格式是前导码7字节帧起始定界符1字节目的MAC6字节源MAC6字节 EtherType2字节载荷46~1500字节 FCS4字节。前导码和FCS一般由MAC核生成和校验用户侧看到的数据从目的MAC开始EtherType常见值是0x0800代表IPv40x86DD代表IPv60x0806是ARP。写解析逻辑时要点是搞清楚字节顺序。以太网是大端模式就是说帧头先到的字节是高位。比如目的MAC第一个字节如果从网线上看到0x00在FPGA侧接收到的第一个数据也是0x00。FCS是CRC32由MAC硬件计算无需用户逻辑干预但一定要留出足够的缓冲空间因为MAC核通常在FCS计算完成后才把有效数据标记好。5.2 从帧到UDP的字段提取一个UDP包在以太网帧里是这样嵌套的以太网头14字节后面跟着IP头20字节不含选项紧接着UDP头8字节最后是数据。如果PC发送一个UDP包FPGA侧从MAC FIFO里读出来的数据流看起来就是目的MAC | 源MAC | EtherType | IP头 | UDP头 | UDP数据 | 填充 | FCS所以要做的事情很清楚状态机从MAC输出FIFO读到数据后先数14字节跳过以太网头然后检查EtherType是否为0x0800如果是继续解析IP头。IP头解析重点其实是两个信息一是协议字段偏移9字节处值为17表示UDP二是源IP地址偏移12字节处的4个字节以及目的IP地址偏移16字节处的4个字节。因为我们是接收方发送回复时必须把源IP和目的IP对调。UDP头就简单了源端口2字节、目的端口2字节、长度2字节、校验和2字节IPv4下UDP校验和是可选的。对于接收方向只需要关心目的端口只有目的端口匹配应用配置的端口号数据才有效。5.3 发送方向的数据组帧组帧比解析稍微省心一点因为PC端的协议栈会自动处理发来的UDP包我们只需要构造格式正确的包发出去。发送一个UDP数据包的顺序是先发目的MAC6字节、源MAC6字节、EtherType0x0800然后IP头20字节依次是版本号头长度0x45、服务类型0x00、总长度IP头UDP头数据长度、标识随意、标志位和偏移0x0000、TTL0x40、协议号0x11、IP头校验和、源IP、目的IP。接着是UDP头8字节再往后是实际数据。IP头校验和需要实时计算。算法很简单把IP头按16位累加然后按位取反。数据量不大时可以在组帧的同时用组合逻辑算好。实际项目里也可以让MAC核替我们算CRC但IP头校验和MAC不管必须自己处理。一个小经验如果不知道源MAC怎么填可以填开发板PHY对应的MAC地址一般建议用私有地址段比如02-00-00-00-00-01避免跟真实网卡冲突。目的MAC在纯局域网通信时填PC网卡的MAC地址即可也可以用广播地址FF-FF-FF-FF-FF-FF。5.4 收发状态机怎么设计才不跑飞FPGA里写协议解析最怕状态机出错一旦进入错误状态整条链路就卡死了。我建议把收发状态机设计成“主状态字节计数器”的结构主状态只管帧开始、帧结束、帧异常字节计数器负责在当前状态下数到指定位置就跳转。接收方向整体流程可以拆成IDLE状态等待MAC FIFO非空有数据后读取第一个字节进入解析状态。解析以太网头计数到14时检查EtherType如果不是IPv4直接丢弃并回到IDLE。解析IP头计数到34时读出协议号到38时记录源IP到42时记录目的IP。解析UDP头计数到50时读出目的端口匹配则进入数据接收状态。数据接收状态把后续数据写入用户FIFO直到收到MAC核给出的帧结束信号。发送方向用类似的计数结构把之前说好的固定字段按顺序发送数据区从用户FIFO里读出直到长度计数到达UDP长度字段指定的数值然后进入等待CRC的状态。这里有个细节容易忽略MAC核输出数据时会同时给出一个字节有效信号比如RX_DATA_VALID。千万别只靠帧起始和帧结束来控制计数万一出现CRC错误MAC核往往会直接终止正在接收的帧此时帧结束信号会提早或异常到来。需要在状态机里对异常终止做处理否则残留的半包数据会污染下一次接收。6. 实操完整流程以Artix-7 RTL8211E为例6.1 工程环境与基本配置我用的是Xilinx Artix-7 XC7A35T开发板板载RTL8211E千兆PHY芯片。Vivado版本2020.1。如果你用别的型号IP核流程差距不大。新建工程后第一步添加TEMAC IP核。在IP Catalog里搜索“Tri-Mode Ethernet MAC”双击打开配置界面选择“RGMII”作为物理接口。数据接口选择“8-bit FIFO”。勾选“MDIO Management”接口。速率选择1Gbps全双工。把“Statistics Counters”关闭这个功能会让IP核导出很多统计信号对于新手来说用不到。配置完IP核后会得到一个example design可以右键IP核选择“Open IP Example Design”Vivado会生成一个包含PHY芯片RTL8211芯片约束文件的完整工程。这个例程工程非常值得研究里面包含了MDIO初始化、引脚约束、收发测试逻辑哪怕不用它直接烧到板子上光看代码思路也能学到很多。6.2 例程工程里的MDIO初始化打开example design找到mdio_if或者phy_reset相关的模块里面是核心初始化流程。RTL8211E的初始化步骤大体是拉高PHY的复位引脚至少10ms等待PHY内部上电稳定。写寄存器0x00设置bit15为1触发软件复位。等待软件复位完成读寄存器0x00确认bit15回0。写寄存器0x04设置自协商广告使能1000M全双工和100M全双工。设置寄存器0x00的bit12为1使能自动协商。循环读寄存器0x01的bit5等待自协商完成。读寄存器0x00确认速率位能得到最终的协商结果。MDIO时序本身不复杂就是MDC时钟下发送访问帧32位前导码、2位操作码、5位PHY地址、5位寄存器地址、2位转向码、16位数据。写操作和读操作的区别在于操作码不同。用状态机实现即可。6.3 收发通路仿真与板上实测在跑板上实验之前强烈建议先做仿真。Vivado仿真里没有PHY芯片但TEMAC IP核提供了仿真模型而且example design自带的testbench可以直接用。仿真里发送一个已知UDP包检查接收状态机的输出是否和预期一致。我用ModelSim跑通收发仿真后在板子上做了两个小实验。第一个实验是UDP回环测试。把FPGA收到的任意UDP数据原封不动地发回去源MAC和目的MAC对调源IP和目的IP对调源端口和目的端口对调。PC端用Python写个socket脚本因为这个实验能让PC和FPGA互发数据非常直观地验证整个链路是否通。Python测试脚本的核心逻辑是import socket # 创建一个UDP套接字 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定PC的端口 sock.bind((192.168.1.100, 9000)) # 设定超时 sock.settimeout(2.0) # 发送数据到FPGA fpga_addr (192.168.1.10, 8000) sock.sendto(bhello fpga!, fpga_addr) # 等待回环数据 try: data, addr sock.recvfrom(1024) print(received:, data, from, addr) except socket.timeout: print(timeout, no reply)FPGA侧收到的数据如果回环成功PC上会打印出“hello fpga!”。第二个实验是MAC地址过滤功能验证。修改接收状态机只保留目的MAC等于自己MAC地址的帧其它全部丢弃。这个功能在真实项目中很有用因为局域网里可能有大量广播帧和无关数据如果FPGA不做MAC过滤FIFO会被垃圾数据填满。6.4 用Wireshark看FPGA发出的数据调试时除了PC收包还可以用Wireshark抓包看看FPGA发出的UDP包格式对不对。具体做法是设置PC的Wireshark在对应网卡上抓包然后给FPGA一个触发信号让它主动发送一包UDP数据。抓到包后重点检查以太网头目的MAC是否填对。IP头版本号、总长度是否合理。IP头校验和是否通过。Windows或者Wireshark会直接标记校验和错误如果错误检查发送状态机里的求和逻辑。UDP校验和是否为0如果为0Wireshark也会提示“UDP checksum offload”多数情况下可以被接受因为UDP校验和可选。一个很常见的坑是IP总长度字段或者UDP长度字段填错。比如IP头总长度表示IP头加UDP头加数据的全部长度应该等于20加8加数据长度。有些新手只填了数据长度PC收到包后协议栈直接丢弃表现为上位机recvfrom永远超时而Wireshark又确实能看到包到了网卡。出现这种情况立刻核查长度字段。7. 时钟、FIFO与跨时钟域设计7.1 网络通信里的三个时钟域FPGA网络通信设计里时钟域问题比普通逻辑多得多很多人做第一版代码全都能仿出来但一上板子就乱多半就是时钟域没处理好。整个系统至少有三个时钟域PHY芯片提供125MHz的RX_CLK进入FPGA后接收数据的同步逻辑必须用这个时钟同时MAC核收数据也在这个时钟域下。TEMAC核的发送时钟TX_CLK在RGMII模式下由FPGA内部生成通常来自一个125MHz时钟源经过BUFG后驱动发送逻辑。用户逻辑时钟域也就是FIFO后面的应用逻辑可能跑100MHz、150MHz甚至200MHz完全取决于具体应用。接收方向数据从PHY的RX_CLK域进入MAC核MAC核内部会把数据同步到它自己的处理时钟域。如果你用的是TEMAC核的FIFO接口IP核内部已经做了异步FIFO同步用户侧只需要把MAC输出的数据当成普通的同步FIFO来读即可。发送方向则相反用户逻辑把UDP包写入FIFOMAC核用它的发送时钟读取FIFO。所以用户逻辑和MAC之间天然被FIFO隔开了不需要额外处理跨时钟域问题。但要小心FIFO如果深度不够突发数据一来就溢出丢包。网络数据是突发性非常强的尤其在PC用socket发送时数据是打包发出来的。比如一个网卡发送突发流量可以瞬间达到几百字节甚至上千字节而FPGA端的数据消费速率受用户逻辑处理速度限制如果消费不过来FIFO就会溢出。7.2 FIFO深度估算与流量控制FIFO深度怎么选其实有讲究。以UDP接收为例最大的UDP包不超过1472字节1500 MTU减去IP头20字节、UDP头8字节如果用户逻辑是每收到一整包就开启处理处理期间不接收新数据那FIFO深度至少要能容纳一个半最大的包也就是约2200字节建议直接选4KB。反过来如果用户逻辑持续消费数据只是偶尔被其它任务打断那么FIFO深度可以考虑按“最大打断时间”乘以“平均速率”来估算。比如用户逻辑每1ms中断一次每次中断50us接收速率125MB/s那FIFO至少需要50us*125MB/s约6.25KB加裕量选8KB。实际开发中为了简化设计我通常直接把FIFO深度设置为16KB用Block RAM实现。Artix-7的BRAM资源不算贵16KB FIFO大概占一两个BRAM完全可接受。如果碰上极端的大包场景比如Jumbo Frame到9KB那FIFO深度选32KB。7.3 复位同步和时钟稳定问题网络通信里复位设计特别容易踩坑。PHY芯片的复位引脚下拉时间必须足够否则芯片可能处于未知状态FPGA逻辑复位又分同步复位和异步复位MAC核内部有自己的复位要求。Vivado里TEMAC IP核的复位接口要求使用同步复位并且需要一定宽度的复位脉冲释放后才能正常工作。很多工程出现“模块有时正常有时不正常”的现象多半是复位释放时MAC核还没准备好。我惯用的做法是做一个带延时的上电复位模块FPGA配置完成后使用计数器延时100us释放用户逻辑复位而PHY芯片的外部复位引脚由单独的IO控制先拉低至少10ms再释放。注意这个过程和FPGA配置完成信号同步不要上电就开始拉低防止FPGA还没配置完成IO状态不确定。8. 常见问题与排查技巧实录8.1 板子link灯不亮先别急看FPGA逻辑用万用表量一下PHY芯片供电电压是否正常然后检查时钟芯片是否起振。这两个没问题的前提下再用MDIO读PHY状态寄存器看PHY是否成功检测到对端网线。如果MDIO能读到ID但状态寄存器link bit始终为0大概率是PHY配置问题。比如寄存器0x00的bit13速率设置不对或者自动协商没使能此时手动写入推荐值即可。一个容易被忽略的坑RJ45座的变压器中心抽头有时需要外部偏置电阻部分开发板设计偷懒省略了导致信号幅度不够link建立困难。这种问题只能换板子软件调不掉。8.2 抓包能看到FPGA发出来的帧但PC收不到这个现象很经典。Wireshark能捕获到说明物理层和MAC层都是通的PC收不到多半是协议层字段错误或者是IP层发给PC的IP地址不对。最常见原因目的MAC地址填成了广播但是同网段有多个主机数据被别的机器截获了你的PC自然收不到。IP头总长度字段错误Windows协议栈会直接丢弃异常包。IP校验和计算错误Wireshark会提示bad checksum。FPGA发出的源IP和PC不在同一网段PC认为数据不归自己管不交给socket。UDP目的端口和PC绑定的监听端口不一致。排查方式是逐层检查。先用Wireshark看以太网头是否正常然后展开IP层看IP头字段有没有红块异常最后看UDP层目的端口是否匹配。要么问题一目了然要么逐个修正。8.3 偶发丢包偶发丢包是最让人抓狂的。如果链路稳定、自协商正常、数据大部分能通但时不时丢一包重点排查这几个地方FIFO深度不足突发流量瞬间把FIFO灌满。接收状态机在异常帧终止时没有正确回退导致一包数据没处理完就丢失。时钟抖动。PHY输出的RX_CLK如果质量不好和FPGA内部125MHz时钟不同源采样容易踩到亚稳态。用IDELAYE2调整接收数据采样相位可以显著改善。MAC核配置里流控开关没打开。千兆模式下如果PHY发出暂停帧MAC核没响应就会硬生生丢帧。TEMAC核里把Flow Control选项打开可以缓解。8.4 用ILA在线调试信号Vivado里ILAIntegrated Logic Analyzer是排查FPGA网络通信问题的大杀器。不要一开始就全信号都加进去那样综合时间长、布线资源紧张。建议把关键接口信号分成几组分批上ILA。接收方向优先抓MAC核输出的帧起始RX_DATA_VALID、RX_DATA_GOOD、数据总线、状态机当前状态。如果帧起始都抓不到问题在MAC配置帧起始能抓到但状态机卡住问题在解析逻辑。发送方向优先抓用户FIFO的读使能、数据输出、MAC核的发送FIFO满信号。发送FIFO满信号长时间为高说明MAC核消费速度跟不上要么MAC时钟没起来要么PHY reset没释放。调试时ILA触发条件设置为帧开始时触发捕抓深度选1024就够深了布线容易出问题。状态机的状态信号用独热码还是二进制编码ILA里看二进制编码不够直观可以把状态信号设为枚举类型Vivado里能看到状态名而不是裸数字。9. 扩展从UDP到完整应用到这里一个能收发UDP的最小系统已经落地了。但真实项目往往需要在此基础上扩展。我自己在图像传输和高速数据采集项目里做过的几个常用扩展分享一下思路。9.1 上行数据定长发送如果FPGA要持续向PC发送数据比如ADC采样或者摄像头图像通常不是随时发、想发就发而是按固定帧率、固定长度发送。这个设计核心是一个数据搬运状态机等待应用FIFO积攒到指定长度然后触发UDP发送逻辑。这里有个细节很多人忽略以太网帧最小长度是64字节。如果数据不到46字节MAC层会自动补零。所以哪怕你只发一个1字节的数据PC拿到的UDP包载荷仍然是1字节但以太网帧被填充过上层协议栈会自动把填充部分剥掉所以不用管只是链路上浪费一部分开销。9.2 ARP协议要不要做如果PC通过局域网访问FPGA而你准备让FPGA发送数据给PC时填的是静态IP和MAC不做ARP也可以。只要IP和MAC对应关系正确交换机只管按MAC地址转发。但很多应用场景里PC端不知道FPGA设备的MAC地址这种情况要么在工程里加一个静态ARP表项要么老老实实把ARP应答逻辑写进FPGA。简单ARP应答逻辑并不复杂接收广播ARP请求如果请求的IP地址等于FPGA的IP地址就回一个ARP应答包应答包里带上FPGA的MAC地址。以太网头、ARP头总共需要的逻辑量很小状态机在MAC层帧解析上扩展几个状态就行。我之前在纯逻辑方案里加上ARP应答PC端ping FPGA开始能通所以这个功能非常值得实现。9.3 多通道数据打包数据采集场景经常是多个ADC通道并行每通道采样率也不同。一种做法是FPGA内部先把多通道数据写入一个数据缓存按固定周期打包成一个UDP包发送。打包时在UDP载荷前面加一个自定义包头包含通道号、计时戳、数据长度PC端按协议解析。这样做的好处是主动减少了UDP包数量。UDP包本身有最小帧间隙96bit如果每来一个采样点就发一个UDP包效率极低。比如48kHz采样率每通道每秒48000个采样点如果每个采样点发一个UDP包PC根本处理不过来。按每1ms打包一次每包96个通道采样值效率就高很多。9.4 和STM32配合做FMC通信的联动评论区常看到有人问STM32H743和FPGA做FMC通信时FPGA侧的网络怎么协调。其实这种架构很合理STM32通过FMC总线把命令和参数写入FPGA的寄存器FPGA做高速数据采集通路网络侧只负责传输数据流。FPGA收到PC发来的UDP包时除了数据通路流转还可以解析控制位把控制信息通过寄存器告诉STM32反过来STM32设置的启动、停止采集命令由FPGA通过UDP发状态报文给PC。这种方案的妙处在于网络协议栈的压力全部在FPGA侧解决STM32只关心业务逻辑不用在RTOS里维护TCP/IP协议栈系统实时性更好。FMC说到底就是一组并行总线和寄存器读写的控制时序FPGA侧用状态机挂在AXI-Lite总线上即可两边共享同一组寄存器。10. 写在最后这条路走下来的一点体会FPGA网络通信从“近似0基础”到能独立完成UDP收发确实要跨过不少坎但跨过去以后后面做图像传输、数据采集、或者做PCIE扩展很多思想都是通用的。我感触最深的是网络通信不像LED灯或者呼吸灯那样写完代码就能看效果它涉及上位机、协议栈、PHY芯片、时钟、复位多方面的配合任何一环出问题都会让整个系统沉默。所以调试时一定要沉住气从物理链路一层层往上查先确认Link灯亮再确认MDIO能读寄存器再确认Wireshark能抓到包最后才检查协议字段是否正确。另一个经验是如果发现FPGA发出的包在Wireshark里校验和不对不要怀疑是MAC核的BUG。MAC核只负责以太网FCSIP校验和和UDP校验和都是用户逻辑自己算的。并且IP发送逻辑里最容易被忽略的就是总长度字段这个字段错了PC端的协议栈会直接丢弃数据包表现为Wireshark能看到包但应用层收不到。最后再分享一个小技巧调试时给FPGA的设备设一个固定的IP和端口比如192.168.1.10:8000PC的网卡IP设成192.168.1.100禁用防火墙。很多新手第一步就卡在防火墙拦截了UDP包白白查了半天FPGA逻辑。这个系列接下来还可以继续往两个方向深入一个是把收发通路里的性能拉满做到线速收发包同时加入ARP、ICMP应答让整个网络模块看起来像一个标准的网络设备另一个是跟DDR或者图像采集链路联动把真正的高速数据流通过千兆网送到PC。无论哪条路这篇的基础都是绕不开的。
返回列表