FPGA以太网UDP通信实现:从RGMII时序到纯Verilog协议栈

FPGA以太网UDP通信实现:从RGMII时序到纯Verilog协议栈
FPGA开发最难的一关之一就是网络通信。很多朋友从点灯、按键、UART一路走过来到了以太网这一节会突然发现怎么这么多概念什么PHY、MAC、MII、RGMII、DMA、描述符、ARP、UDP每个单词都认识连在一起完全不知道在说什么。我当年就是从近似0基础的状态硬啃这块的中间踩的坑可以绕实验室三圈。这篇是系列的第7篇专门把FPGA怎么把网络通信跑起来这件事用大白话拆开讲清楚。我不会一上来就丢一堆官方手册而是按照“先用起来再搞懂最后能做自己的UDP核”这个路线把我在实际项目中摸索出来的经验、踩坑记录和值得直接照抄的代码框架完整地分享出来。这篇内容适合下面几类朋友已经会Verilog基本语法能写简单的状态机和FIFO但对网络协议完全没有概念的人用STM32做过W5500或者ENC28J60想搞清楚“不用协议栈芯片直接用FPGA做以太网”是怎么回事的人手里有开发板带千兆PHY芯片比如RTL8211、88E1512但不知道怎么起步的人准备做图像采集传输、高速数据采集卡、或者想把FPGA和上位机打通却卡在“数据怎么从网口出去”这一步的人。这篇的核心目标是让你在看完之后自己能在黑金、正点原子或者任何一块带千兆PHY的FPGA板子上用纯Verilog写一个能收发UDP数据的精简网络接口并且知道怎么排查最常见的坑。1. 方案选型为什么我建议从“裸写UDP”开始很多新手上来就搜“FPGA TCP/IP协议栈”然后被一片源码吓退。我的建议非常明确第一步绝对不要碰TCP先把UDP跑通这等于学会了走路TCP相当于跑步等你能稳定收发UDP了再考虑。1.1 先从“最简可用”的通信链路搭起网络通信说白了就是按照一套约定好的格式把数据从A点搬到B点。这套格式就是协议栈分很多层。我们日常接触到的HTTP网页、微信消息底层是TCP协议而视频监控、语音通话、游戏同步数据很多用的却是UDP协议。原因很简单TCP要保证每条数据都到达且顺序正确效率低UDP不管到没到发了就行速度快。对FPGA来说UDP的好处太明显了协议轻、状态机简单、逻辑资源占用少。你不需要处理复杂的重传机制、超时计时、滑动窗口。在实际的数据采集场景里比如FPGA采完ADC数据直接发给上位机偶尔丢一两包完全能接受甚至很多系统设计时就允许丢包重传。TCP在你写PC端上位机的时候是省事但代价是FPGA侧逻辑复杂度暴涨。所以我的方案是FPGA侧用纯RTL实现一个“精简的千兆UDP传输模块”。它能完成ARP应答和UDP收发支持PC往FPGA发命令、FPGA向PC回高速数据。这个模块跑在Xilinx Artix-7或Intel Cyclone V这个级别的芯片上绰绰有余逻辑占用率通常不超过5%。1.2 聊聊PHY芯片和接口模式GMII还是RGMII这里要弄清楚一个关键概念FPGA里实现的叫MAC媒体访问控制层它负责组帧、解析帧、计算CRC、处理地址过滤而PHY物理层芯片负责把MAC送过来的并行数据变成差分串行信号送到网线上。MAC和PHY之间通过标准接口连接最常见的是MII、GMII、RGMII。MII百兆用的数据位宽4位时钟25MHzGMII千兆用的数据位宽8位时钟125MHz独立时钟线RGMII千兆用的数据位宽4位时钟125MHz时钟双沿采样也就是DDR模式引脚少。绝大多数开发板用的都是RGMII接口因为引脚少、布局布线容易。但RGMII的设计难度在于数据在时钟的双沿采样时序要求比GMII严格需要做约束。新手第一次做RGMII大概率会遇到“能link但收不到数据”或者“跑一段时间就断”的问题后面我会专门讲时序约束怎么做。注意如果你的板子上PHY芯片是RTL8211E、RTL8211F、88E1512、KSZ9031这类常用千兆PHY都可以直接参考本文的思路。但不同芯片的寄存器地址和配置有差别务必先看数据手册确认芯片的PHY地址和想要的模式。1.3 为什么不用Xilinx自带IP核而自己写RTL很多人会问Vivado里不是有现成的AXI Ethernet IP吗直接用IP不就行了我承认量产项目和工程效率优先的场景直接用IP核是更合理的选择。IP核功能全、性能经过验证尤其当你需要做高速传输、多通道、复杂DMA的时候自己写RTL是自找麻烦。但是如果你是学习、做毕业设计、或者想真正理解网络通信的来龙去脉我强烈建议先自己写一遍。原因有三个第一IP核是个黑盒子出问题你根本无从下手尤其是CRC错误、链路层波形异常这类问题不懂内部逻辑连排查方向都没有第二IP核配置复杂什么DMA、描述符、中断、缓存一致性一套下来新手直接劝退第三自己写一套精简RTL逻辑其实并不多也是一个非常好的状态机设计训练做完了你对网络通信的整个流程会有底层级的掌握。等你自己写过一遍再回头用IP核那就是降维打击。2. 整体架构拆解一个精简UDP协议栈是怎么组织的我习惯把整个设计拆成四个模块MAC接收、MAC发送、协议处理、用户接口。四个模块之间用FIFO做缓冲这样每个模块都能独立工作调试的时候也方便用ILA单独抓每个模块的波形。2.1 传统五层协议栈的“迷你版”实现计算机网络教材里那套“OSI七层模型”或者“TCP/IP五层模型”大多数人背完就忘了。但在FPGA里你需要亲手实现所以务必要有个直觉理解。先说帧以太网帧是网络上真正传输的一个“信封”它长这样前导码Preamble8字节用于同步其中前7字节是0x55最后1字节是0xD5目的MAC地址6字节就是要发给谁的地址源MAC地址6字节谁发的类型/长度字段2字节0x0800表示后面跟的是IPv4报文0x0806表示是ARP报文数据最少46字节最大1500字节如果不够46字节要填充FCS帧校验4字节CRC32。IP报文是装在以太网帧的“数据”里的格式是版本首部长度1字节IPv4一般是0x45服务类型TOS1字节置0总长度2字节IP头UDP头数据的总字节数标识、标志、片偏移各字段对UDP通常可以置固定值生存时间TTL一般是64或128协议号1字节UDP对应17首部校验和2字节对整个IP头做校验源IP4字节目的IP4字节。UDP报文装在IP的“数据”里格式是源端口2字节目的端口2字节长度2字节UDP头数据校验和2字节UDP校验和是可选的通常可以置0但严谨的做法还是算上。所以一条完整的UDP数据在网线上是“MAC头 IP头 UDP头 数据 CRC”这样的结构。FPGA发送数据时就是按这个顺序把一个个字节填进去接收数据时就是按顺序把一个个字节解析出来。这个“顺序”非常重要。因为我见过太多人在数据对齐上栽跟头发出去的报文PC端抓包能看到但是Wireshark解析不出来往往是端口号错位或者协议类型没对上。2.2 四个功能模块的职责划分和协作关系我通常把RTL代码按如下分工模块主要职责要点mac_rx接收从PHY过来的数据做CRC校验过滤目的MAC、类型字段输出解析后的ETH/IP/UDP各字段需要一个状态机解析报头数据存入FIFOmac_tx把要从FPGA发出去的数据组装成ETH/IP/UDP报文计算CRC并发送需要一个状态机从FIFO读到数据后拼上各层头部逐字节送出arp_module处理ARP请求、维护ARP缓存表可选收到ARP请求要回ARP应答发UDP前先查ARP表udp_user_interface面向用户的读写接口用户逻辑只要往发送FIFO写数据、配置目的IP和端口即可封装掉网络协议的复杂性这个结构图设计给大家一个直觉用户逻辑根本不关心MAC地址和IP头怎么拼只需要把目标IP地址、目标端口、数据写进接口剩下的脏活累活全由协议模块去处理。2.3 时钟域和跨时钟域数据通路不会因为跨时钟域崩掉网络这个场景天然要多时钟域处理。PHY芯片输出是125MHz的RGMII时钟域用户逻辑可能是100MHz、150MHz甚至更低的50MHz两边频率不同、相位不同数据必须经过异步FIFO转换。我踩过的坑是第一次做的时候图省事把PHY的rx_clk直接分频给用户逻辑用结果数据偶尔错一个字节查了好几天。最后发现是时钟分频产生的毛刺导致边沿采错。后来老老实实用了Xilinx的FIFO IP读时钟用用户逻辑时钟写时钟用PHY的rx_clk问题彻底消失。这里必须提醒一句涉及跨时钟域无条件用异步FIFO不要用自己的代码去拼抢。你觉得自己写的同步器没问题但在时序约束不完整的情况下多比特跨时钟域很容易出现中间状态导致FIFO空满信号误判。3. 实操核心细节RGMII时序约束、CRC计算和状态机设计这一部分全是当年让我熬夜的硬骨头。把它啃完你就已经超过大部分FPGA新手了。3.1 RGMII的时序约束到底是怎么回事怎么约束RGMII本质上就是源同步接口。PHY在发送数据给FPGA时会同时送出一路RX_CLK数据RX_CTL和RX_DATA[3:0]是在这个时钟的双沿上变化的。FPGA接收的时候要采到正确的数据就必须满足建立时间和保持时间的要求。Vivado里常用的做法是在XDC文件里用set_input_delay和set_output_delay来做约束。RGMII接口的典型约束大概长这样以Vivado为例# 接收方向PHY输出数据到FPGA相对于RX_CLK set_input_delay -clock clk_rx -max 2.0 [get_ports {rgmii_rd[3] rgmii_rd[2] rgmii_rd[1] rgmii_rd[0] rgmii_rctl}] set_input_delay -clock clk_rx -min 0.5 [get_ports {rgmii_rd[3] rgmii_rd[2] rgmii_rd[1] rgmii_rd[0] rgmii_rctl}] # 发送方向FPGA输出数据到PHY相对于GTX_CLK set_output_delay -clock clk_gtx -max 2.0 [get_ports {rgmii_td[3] rgmii_td[2] rgmii_td[1] rgmii_td[0] rgmii_tctl}] set_output_delay -clock clk_gtx -min 0.5 [get_ports {rgmii_td[3] rgmii_td[2] rgmii_td[1] rgmii_td[0] rgmii_tctl}]这里数值2.0和0.5只是示例具体要查PHY芯片的数据手册里的tcoclock to output delay参数和PCB走线长度误差。很多PHY芯片还支持通过内部寄存器调整I/O delay比如RTL8211系列的“延时调整寄存器”就是为了解决数据与时钟的相位对齐问题。如果约束没做对症状是什么最常见的现象是链路能连接上网线插上能显示1Gbps但是ping不通或者抓到的包全部是CRC错误。还有一种诡异的情况是常温下一切正常板子一发热或者用手摸一下PHY附近收发就开始丢包错包。这些都是时序余量不足的表现。重要经验如果开发后板子长时间跑不稳定先去看时序报告确认RGMII接口路径的setup和hold slack是不是真的满足。不要盲目去改约束值更不要关掉时序检查硬跑。3.2 CRC32的计算边发边算才能不拖慢速度以太网帧尾的FCS是32位CRC校验标准多项式是0x04C11DB7。新手最常踩的坑是想先缓存整个帧再算CRC结果白白浪费了存储资源还增加了延迟。正确做法是“边发送边计算CRC”。CRC算法的基本原理是对每个字节做一次查表或者移位异或计算。查表法速度快适合软件FPGA里可以直接做并行CRC生成器一个时钟就能完成4位或8位数据的CRC更新。软件查表预先生成256个表项每字节一查方便但慢FPGA并行直接用组合逻辑算4比特或者8比特的CRC一个时钟算一拍适合流水线。由于以太网CRC校验是“先发送低字节最后发送的CRC也要高低位取反后再翻转位序”很多人第一次都搞错字节顺序。一个非常实用的检查技巧是先用Wireshark抓一个已知数据的包对比FPGA发送端的CRC和抓包工具显示的Frame check sequence一致就说明顺序没问题。3.3 发送状态机的关键设计要点拼头、填充、校验一步到位发送状态机的核心逻辑可以描述为“分阶段拼数据流”。我的状态机大致长这样localparam S_IDLE 0; localparam S_PREAMBLE 1; localparam S_ETH_HEADER 2; localparam S_IP_HEADER 3; localparam S_UDP_HEADER 4; localparam S_PAYLOAD 5; localparam S_PADDING 6; localparam S_FCS 7;S_PREAMBLE连续发送8个字节的前导码S_ETH_HEADER发送目的MAC、源MAC、类型0x080012字节S_IP_HEADER发送IP版本、首部长度、总长度、标识、TTL、协议号、源IP、目的IP等20字节S_UDP_HEADER发送源端口、目的端口、长度8字节S_PAYLOAD从发送FIFO里读出用户数据逐字节送出去S_PADDING如果用户数据小于18字节需要填充到46字节这里46 18字节头部 28字节IPUDP头下面详细说S_FCS把之前流水线算好的CRC32输出出去然后回到IDLE等待下一包。这里面最容易出错的两个点第一是总长度的计算。IP头里的总长度字段是“IP头20字节 UDP头8字节 数据长度”UDP长度字段是“UDP头8字节 数据长度”。第二是填充字段Padding。以太网规定帧的数据部分最小是46字节这46字节指的是MAC头之后的“IP报文部分”所以如果IP报加UDP报加数据一共不到46字节你就得往数据后边补零直到凑满46字节。很多人在发短包比如只有几个字节的数据时会遇到一个现象PC端能收到数据但Wireshark显示UDP长度异常或者信息里多了一堆零。十有八九就是Padding没处理好Wireshark把填充数据也显示出来了真正的UDP长度字段又是对的所以看起来很奇怪。3.4 接收状态机比发送容易踩坑的一点是“解析偏移”接收状态机的任务是把对方发过来的帧拆开提取UDP数据。基本状态如下localparam S_IDLE 0; localparam S_PREAMBLE 1; localparam S_MAC_HEADER 2; localparam S_IP_HEADER 3; localparam S_UDP_HEADER 4; localparam S_PAYLOAD 5; localparam S_CHECK_FCS 6;接收时有一个比发送更要注意的细节收到MAC头的时候要通过类型字段判断后面是IP还是ARP。如果是ARP就把这一帧交给ARP处理模块如果是IP还要检查IP头的协议号是不是17UDP。如果都不是大概率是些没必要处理的广播包或者未知协议包直接丢掉别再往后走。IP头里有个“首部长度”字段通常情况下固定是20字节对应0x45其中4表示IPv45表示首部长度是5个32位字乘4就是20字节。虽然绝大多数正常网络包都是20字节IP头但也有带选项字段的极端情况。为了稳妥起见接收模块最好用IHL字段动态计算IP头长度。这不是难事但能避免在特殊环境里丢包。还有一个细节接收的MAC地址过滤。可以过滤成“只接收发给本机MAC的帧、广播帧、多播帧”也可以直接不过滤全部接收。对于调试阶段全接收的模式更容易发现问题但正式做系统时最好还是加上地址过滤减轻主逻辑负担。3.5 ARP模块能不能通全看它在真正的网络环境里你想往PC发UDP至少要过两关——第一关是ARP第二关才是UDP本身。很多新手以为FPGA只要把UDP包发出去了PC就能收到结果根本不通。原因在于FPGA没有ARP缓存不知道PC的MAC地址没法填目的MAC字段。ARP地址解析协议做的事情就是给定一个IP地址问“谁的IP是这个请告诉我你的MAC地址”。当FPGA要向一个IP发数据时如果ARP缓存里没有对应条目就要先发一个ARP请求广播目的MAC填全F内容里带上自己的IP和MAC问目标IP是谁。目标收到之后会回复一个ARP应答里面包含它的MAC地址。FPGA里实现ARP有两种思路最简单的固定把目的MAC写在参数里不动态解析。只要环境不变、IP不变这种够用也是最省事的起步方案正规的做一个小的ARP状态机支持处理ARP请求和应答并且维护一张很小的缓存表。这样换设备、换IP都无所谓逻辑资源增加也不大。我的建议是至少做第二种。你会发现调试的时候方便太多了不用每次改IP都重新综合一遍。而且如果以后要做板子和板子通信没有一个ARP自动解析你可能连接口都通不起来。3.6 用户接口设计怎样设计才能让上层逻辑少操心做个好的用户接口核心思路是“把协议细节封装好”。发送侧我对外提供这样的接口用户写数据到一个发送FIFO用户配置目的IP地址、目的端口写好数据后写一次“发送触发”寄存器协议模块自己负责查ARP缓存、组帧、发送、CRC。接收侧则是协议模块把UDP数据写入接收FIFO同时输出源IP、源端口、数据长度用户逻辑只要关心FIFO里有没有数据可读。这样的设计能让你把网络模块当作一个“异步FIFO”来用对业务逻辑的侵入最少。用过几次你会发现最后调试的重点根本不在协议上了而是FIFO读写时序和背压backpressure逻辑。4. 调试与问题排查实录从灯都不亮到稳定传输的完整过程新手调试网络模块最痛苦的不是代码难写而是出了问题不知道往哪里查。我总结了一套自下而上的排查顺序必须严格遵守跳过一级你就是在浪费时间。4.1 排查链路层用回环模式先确认PHY没毛病第一步先确认PHY芯片工作正常。绝大多数的PHY都有内部回环loopback模式配置寄存器后PHY会把发送方向的数据在内部直接环回给接收方向不走网线。如果FPGA发出数据再收回来收到的和发出的完全一致说明FPGA到PHY的RGMII接口是没有问题的问题只可能在PHY之后。具体操作如下通过MDIO接口读PHY的寄存器确认PHY ID正确说明MDIO配置通路是通的把PHY的“数字回环模式”打开不同芯片的寄存器位不一样常见在0x00寄存器的bit14在FPGA侧不断发送特定的测试数据用ILA抓回环后的接收数据对比发送数据。这个测试通过之后你才能确认链路层时序是OK的再进行下一步。4.2 抓取上电后的RGMII信号ILA是最靠谱的证据不要靠猜。FPGA调试网络通信没有ILA集成逻辑分析仪几乎等于蒙眼开车。尤其是RGMII这种高速双沿接口你肉眼不可能靠逻辑分析仪飞线去看必须用FPGA内部的ILA抓信号。怎么抓在ILA的时钟选择上用PHY送过来的rx_clk作为采样时钟。注意ILA的采样时钟必须是实际的时钟域时钟不可以用其他频率去采否则看到的数据完全不可信。抓的时候关注这些点PHY的RX_DV即RX_CTL有没有正常拉高如果一直拉低说明PHY没正常收数据有可能是网线类型不对、对端没发数据、或者PHY配置有问题前导码0x55、0xD5对不对这个对了说明PHY在正常“看”网线上的信号目的MAC地址是不是全F广播不是广播就是单播若单播而FPGA做了地址过滤有可能收不到数据抓ARP请求发过来时帧里的SRC_MAC和SRC_IP是不是PC的是说明链路通了。4.3 用Wireshark观察数据FPGA侧和PC侧配合判断我调试的时候电脑上永远挂着Wireshark。FPGA往PC发包Wireshark立刻就能看到。根据Wireshark显示的报错类型基本能把问题定位一个大概范围Wireshark提示可能原因排查方向完全没抓到包物理链路不通、PHY没配好、MAC没发出先做PHY回环再抓ILA抓到包但显示“Malformed Packet”报文格式不对各字段偏移错误逐字节对照标准报文格式检查抓到包显示CRC错误RGMII时序问题或者CRC计算/发送顺序错误看时序报告检查CRC位序抓到包能解析但长度不对Padding、总长度、UDP长度字段错误核对IP/UDP总长度字段能ping通但UDP收不到ARP缓存问题或接收侧过滤问题检查ARP应答、MAC地址过滤一个非常实用的小技巧在Wireshark里输入udp ip.addr你的IP作为过滤条件。同时打开“Validate CRC”功能这样每个包的CRC对错一眼就能看到。4.4 典型问题1PC发FPGA收不到FPGA发PC也收不到这种情况大多数是PHY还没正常建立链路。第一步先看板子上的LED灯千兆PHY芯片一般有link灯和active灯插上网线后link灯应该常亮。如果link灯都不亮请检查PHY芯片的供电数字1.0V、1.8V、2.5V、3.3V分别对不对网线本身是否正常换一根交叉线或直连线试试PHY复位时序是否正确。很多PHY要求上电后保持复位至少10ms以上然后释放复位再过一段时间才能访问寄存器MDIO读写是否正常读PHY ID寄存器确认芯片应答。如果link灯亮但收发都不通就看RGMII时序。我强烈建议用ILA抓一下FPGA收到的rx_clk和rx_ctl。如果rx_ctl波形乱七八糟多半是RGMII输入延时约束没做对或者PHY的时钟延迟配置没设对。4.5 典型问题2收包正常但FPGA发的包CRC错收包正常说明FPGA到PHY的接收链路没问题而CRC错出现在发送方向问题就锁定在mac_tx模块。最容易出的错是CRC位序。以太网CRC使用的CRC32计算顺序要求发送时把算出的32位校验值按位倒序再按字节发送而且先发低字节。我给大家一个口诀“算完之后位翻转让给发送逻辑发送逻辑从最低字节先发。”别问为什么IEEE 802.3就是这么规定的照着实现就行。另一个低调但常见的坑是你在组装报文的时候把CRC错放到了数据之后多了一个字节或者少了一个字节导致整个帧长度不对。用ILA抓mac_tx的最后一拍数一下前导码之后到FCS结束一共多少字节是否符合预期。4.6 典型问题3低速通信稳定高速经常丢包如果你一开始用100M模式测试一切正常切到1000M模式就丢包那大概率是时序余量问题而非逻辑问题。千兆模式下RGMII是125MHz DDR建立时间和保持时间余量都相当紧张插拔网线接触不良、板子温漂、电源纹波都可能让时序垮掉。这种问题怎么解决优化XDC约束的set_input_delay / set_output_delay值有时你可以通过ILA观察一段时间内的出错规律粗略判断数据是采样早了还是晚了考虑在PHY寄存器里调整RGMII的TX/RX内部延时配置电源质量也很关键PHY的供电最好由独立的LDO或者低噪声DC-DC提供纹波太大会连带影响时钟抖动实在不行降低速率如果PHY支持10/100/1000M自适应先锁到百兆验证功能再回头处理千兆。4.7 典型问题4重启后就收发异常甚至必须重新插拔网线这种“重启后要插拔网线才恢复”的问题十有八九是PHY复位时序有问题。一般表现为FPGA配置完成后PHY还没准备好FPGA就去访问MDIO初始化PHY结果寄存器配置根本没写进去。解决思路是在PHY的复位释放之后加一个足够长的延时再初始化通常推荐至少50ms到100ms。有些开发板的PHY复位和FPGA配置位流是同一个按钮那就要在逻辑里自己处理“上电延迟”的问题不要让PHY在FPGA配置期间就开始工作。另外还有一种情况PHY芯片在网线断开再插上的过程中link状态恢复很慢导致重连后一段时间内FPGA还在用旧的配置。这种情况可以在代码里加一个“链路检测”模块当PHY的link状态信号变化后延迟复位MAC和发送FIFO重新等待同步。5. 我的工程落地经验参数计算与再扩展网络模块跑通之后接下来要面对的就是工程化问题。这几个点是我在实际项目里一点点攒出来的直接决定你的性能能跑到多高。5.1 带宽计算我这个方案到底能跑多快先说千兆以太网的理论极限物理层线速率是1Gbps也就是125MB/s但这个速度包含了所有帧间隙、前导码、CRC、MAC头IP头UDP头这些开销。算一下实际有效数据速率。以太网最小帧间隔IFG是96ns也就是96个bit时间换算成字节是12字节。一个典型的UDP包假设数据长度是1024字节那么在网线上的总长度是前导码8字节 MAC头14字节 IP头20字节 UDP头8字节 数据1024字节 CRC4字节 帧间隙12字节 1090字节。这1090字节对应的是1090 × 8 8720个比特在千兆速率下需要8.72微秒。那么有效数据吞吐速率就是1024字节 / 8.72微秒 ≈ 117.4 MB/s。千兆链路下UDP协议层有效数据率极限大概在117MB/s左右能跑到110MB/s以上就很不错了。如果你的FPGA内部用户处理逻辑频率是125MHz数据总线是64位那DDR内部数据率是8GB/s网络根本不是瓶颈。瓶颈往往在FIFO深度和背压处理上。5.2 缓冲深度的合理设置避免不必要的丢包发送侧FIFO的深度不是随便填的。如果你要连续发送大量数据发送FIFO的深度至少要能容纳一次“发一整包数据的比特数”。我一般用到2048×8bit或者4096×8bit足够缓冲一两包最大MTU的数据。接收侧也是同理。如果上层逻辑处理速度跟不上接收FIFO满了之后mac_rx模块要么丢弃新到的包要么暂时停止接收。这里涉及一个重要的背压设计思路如果你的上位机逻辑是“边接收边处理”那FIFO深度匹配平均速率就行如果你的上位机逻辑是“攒一批再处理”那FIFO深度必须大于单次最大突发长度如果数据率接近线速边界必须考虑丢包率指标和重传机制。5.3 从UDP到更上层这是ZYNQ/ARM协同工作的入口很多朋友的开发板其实是ZYNQ系列里面有ARM核。如果你想做的系统最终是“Linux发命令 FPGA做高速数据采集”那会走上另一条路PS端的Linux里跑协议栈通过AXI总线与PL侧交互。这时FPGA内部就不再需要自己写UDP协议栈了而是做数据通道和DMA控制。但是这里有个隐藏的坑如果你对网络协议本身的理解不够深即便用了ZYNQ AXI Ethernet IP出了网络问题依然无从下手。反过来当你按照本文的方式自己实现过一遍UDP再去看ZYNQ的AXI DMA Ethernet IP很多概念是秒懂的。所以我的建议是不要试图跳过“自己写RTL协议栈”这一关。哪怕你最终要用的方案是ZYNQ也值得先把纯PL的UDP跑通一次再用IP核。5.4 开发流程的“三板斧”仿真、上板、抓波形的节奏我总结的调试节奏是“仿真先行上板验证ILA定案”。先写一个简单的testbench模拟PHY侧的数据输入验证mac_rx能不能正确解析出UDP数据再写一个testbench模拟用户写入数据验证mac_tx能不能发出正确的报文上板后先用Wireshark抓PC收到的报文看格式是否符合预期如果格式对但CRC报错再用ILA抓mac_tx的CRC输出如果接收有问题用ILA抓PHY的rx_clk、rx_ctl、rx_data核对前导码和MAC头。这样的节奏能帮你把“代码逻辑问题”和“时序物理问题”分离开。很多人一上来就上板发现不行就去瞎改代码改了一整天都不知道问题出在哪就是因为没有一层一层地隔离问题。6. 给我的几点心得写到这儿离整个系列的第7篇该有的干货也差不多了。最后分享一点我个人的体会。如果你正在被RGMII的时序、CRC的位序或者ARP的一堆缓存问题折磨请相信我这很正常。我自己当年光是调通一个“PC能ping通FPGA”就花了两周期间怀疑过代码、怀疑过PHY芯片、怀疑过网线最后发现只是RGMII输出约束一个值设错了。这种“找到原因的豁然开朗”正是FPGA开发最有意思的地方——它逼着你把每一个细节都搞清楚一点也糊弄不过去。还有个小技巧送给大家调试网络类题目永远记得用Wireshark当你的“外置眼睛”用ILA当你的“内置眼睛”两只眼睛一起看问题很多问题其实就藏在你以为绝对不会错的那个字段里。比如源MAC地址填错了、IP校验和算错了、UDP端口是大小端反了这一类问题Wireshark一分钟帮你定位而靠肉眼查代码可能要一下午。FPGA的网络通信这一节确实是个坎但也正因为它是坎迈过去之后你会发现后面很多高速接口PCIe、DDR、高速ADC采集原理上都和网络有相通之处无非是接口时序、协议状态机、数据缓冲、背压控制这些事。所以别急照着这篇文章的顺序一层一层调试你的板子很快就能跑通UDP了。

最新新闻

日新闻

周新闻

月新闻