ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA网络通信实战:从零实现UDP收发与RGMII接口调试

FPGA网络通信实战:从零实现UDP收发与RGMII接口调试 FPGA开发学到一定阶段很多人会绕到网络通信这个坎上。嵌入式系统想和上位机交互以前要么走串口要么走USB但这两种方案在数据量上来之后都不太够用。串口速率低USB协议栈在FPGA里写起来又太痛苦。于是百兆、千兆以太网就成了很自然的选择。说实话“近似0基础”做到FPGA网络通信这个跨度听起来挺吓人但如果你拆开看真正需要你在FPGA里实现的东西并没有想象中那么多。很多人被“TCP/IP协议栈”这几个字吓住了以为要在逻辑里写一整套完整的协议栈其实完全不需要。绝大多数项目场景下能把UDP收发调通就已经解决了80%的问题。剩下的ARP应答、ICMP回显这些属于锦上添花但也并没有多难。这篇文章我会从底层帧格式开始讲到PHY芯片怎么选、RGMII接口时序是怎么回事、FPGA内部的收发通道怎么搭、以及怎么用Wireshark抓包验证你自己的UDP收发逻辑。最后再整理一份我日常调试网络通信时踩坑的排查清单。整个路线是我自己走下来觉得最顺的一条适合刚把Vivado跑通、想在板子上把网口点亮的新手。1. 网络通信到底是什么先搞懂FPGA在这里扮演的角色1.1 一个UDP包从PC到FPGA中间经历了什么我见过不少新手拿到网口工程之后第一件事就是找例程、下载bit、然后用网络调试助手发数据。能收到就高兴收不到就懵了。这种状态其实很危险因为你根本没有建立起“这条链路上到底有哪些节点”的认知。我们先想想一个UDP包从PC的软件程序里发出去到FPGA的逻辑模块里被接收这一路上到底经过了什么。PC发送UDP数据先由操作系统协议栈封装成IP包再封装成以太网帧然后交给PC的MAC控制器MAC控制器把它转成某种接口时序发给PHY芯片PHY芯片再做物理层的编码和电平转换通过网线把差分信号发出去。到了FPGA这边板子上的PHY芯片负责接收差分信号、解码、恢复出数据和时钟再通过RGMII之类的接口送到FPGA内部。FPGA里要做的就是实现一个MAC控制器的逻辑——把RGMII接口上的数据流解析成一个个完整的以太网帧然后剥掉MAC帧头、IP头、UDP头把真正的有效载荷取出来交给你的用户逻辑。反过来FPGA要发送数据就是逆着这个过程把有效载荷包上UDP头、IP头、MAC头加上前导码和CRC校验然后通过RGMII接口发给PHYPHY编码后从网线送出去。所以你看FPGA核心要做的其实就三件事第一对PHY芯片进行初始化配置第二实现MAC层的发送和接收状态机第三实现一个最小化的ARP与UDP/IP处理逻辑。这三件事每一件都不算复杂但每一步都有不少细节坑。1.2 为什么FPGA不直接写完整TCP/IP协议栈网上经常有讨论说“FPGA为什么不做TCP”“能不能在FPGA里实现TCP服务器”每次回答都吵成一团。核心矛盾在于TCP是面向连接的可靠传输协议它需要维护连接状态、序号管理、确认重传、滑动窗口、拥塞控制等等。这些事在有操作系统的CPU上都有现成协议栈帮你做完但在FPGA里全部要用状态机和寄存器来实现工作量非常可观。这里我做了一个非常实际的建议如果你的项目只需要数据上报、指令下发、波形采集这类应用优先选用UDP。UDP无连接、无状态头格式简单FPGA实现起来轻松太多。可靠性的问题不要试图在FPGA里解决而是交给上一层协议或者系统架构去解决。比如你可以设计一套简单的应用层应答机制FPGA发完数据等待上位机回一个确认帧超时未收到就重发。这比你硬磕TCP要省事得多而且可调试性也更好。等UDP这整条链路你已经轻车熟路了再决定要不要去碰TCP不迟。2. 前置知识以太网帧、ARP与UDP缺一不可2.1 看数据先看帧一帧以太网数据到底长什么样做FPGA网络通信最大的感受就是所有东西都变得非常“字节化”。你写C语言时不需要关心数据在网络上怎么编排的但在FPGA里每一比特的先后顺序都是你自己拼出来的所以帧格式必须烂熟于心。先看以太网帧不含VLAN标签的基本结构前导码Preamble7字节内容固定为0x55用于时钟同步。帧起始定界符SFD1字节固定为0xD5。目的MAC地址6字节。源MAC地址6字节。以太网类型/长度2字节。对于IPv4这个值为0x0800对于ARP值为0x0806。有效载荷46~1500字节。FCS校验4字节是CRC32校验值。这里有几个细节容易让人犯迷糊。前导码和SFD其实不参与以太网帧的CRC校验很多PHY芯片会自己生成或剥离前导码你在FPGA侧处理时就要搞清楚你的PHY到底给了你什么。有些PHY工作在MII模式时会把前导码直接发给你有些在RGMII模式下会由MAC自己生成。FCS校验的范围是从目的MAC地址开始一直到有效载荷结束不包括前导码和SFD。一个常见的坑是很多FPGA教程里的“最小以太网帧”是只看IP层以前的头部但物理层最小帧长其实是64字节从目的MAC到FCS。如果你的UDP数据特别短凑不够这个长度MAC层会自动把有效载荷填充到46字节。在接收端解析时你要知道这些padding不是真正的数据。2.2 ARP与UDP先通ARP才能谈其他ARPAddress Resolution Protocol主要解决一个问题我只知道对方的IP地址怎么知道对方的MAC地址在以太网里二层传输只认MAC地址所以发送方必须通过ARP把IP解析成MAC。FPGA上电之后你要和PC通信第一步大概率就是PC发一个ARP请求谁的IP是192.168.1.10请告诉我你的MAC地址。如果FPGA不回复ARPPC会认为这个IP不可达后面UDP包根本不会发过来。很多新手遇到的“上位机发UDP给FPGAFPGA收不到”根源其实在ARP这一层就已经断了。所以你的FPGA逻辑里至少要实现一个ARP应答模块。ARP请求帧的格式是一个标准的以太网帧类型字段是0x0806有效载荷有28字节的ARP头。你要做的处理流程是判断帧类型是否为0x0806。解析ARP头的操作码。值为1表示ARP请求值为2表示ARP应答。如果收到ARP请求检查目标IP是否等于本FPGA的IP等则把源IP、源MAC填入应答帧操作码改为2并交换发送端与目标端字段重新计算FCS发回去。这个过程说起来简单但状态机写起来大概几十行。如果你先跳过ARP直接发UDP给PCPC是能收到的因为PC主动发UDP时已经填充了自己的MAC地址FPGA回复UDP时直接把PC的MAC填入目的MAC即可。但PC要在上位机软件里先填写“目的MAC地址”或者通过ARP表拿到FPGA的MAC就很麻烦。所以正规做法还是一上来就把两边IP与MAC绑定关系处理好。ICMPping包同理。FPGA如果实现了ICMP echo应答PC的ping命令就能确认链路通不通这对调试非常友好。实现ICMP其实也不难ping包的本质就是一个ICMP请求把类型字段改成0、重新计算ICMP校验和然后原样发回去就行。我在工程里通常会加上这个功能成本不高但调试效率提升非常大。2.3 UDP报文格式说实话这些字段大部分是固定值FPGA里实现UDP比实现ARP更简单因为UDP头的大部分字段是可以写死的。标准UDP头有8字节源端口号2字节。目的端口号2字节。UDP长度2字节UDP头数据。UDP校验和2字节。在FPGA中源端口和目的端口可以在参数里直接定义。UDP长度根据发送数据长度动态计算。UDP校验和是可选的在IPv4下可以为0很多简单的FPGA实现就直接写0PC端照样能收到数据。但如果较真一点UDP校验和还是应该算一下特别是你要跟一些严格的协议栈对接时。IP头就更套路了。标准IPv4头不带选项时是20字节版本4位值固定为4。头长度4位值固定为5。区分服务1字节可以写0。总长度2字节需动态计算。标识2字节可以递增也可以写死。标志与段偏移2字节一般非分片模式下写0x4000。生存时间TTL 1字节写64或128都行。协议1字节UDP固定为17。头校验和2字节需要计算。源IP地址4字节。目的IP地址4字节。IP头校验和算法是16位反码求和FPGA实现起来也算简单只是要注意校验和计算时要把校验和字段本身当作0来算。网上有各种参考代码照着写一遍再用抓包软件一验证基本不会出错。所以一个最小可用的FPGA UDP发送模块本质上就是在固定的模板里动态填充三个长度字段和一个校验和然后加上MAC帧头再加上CRC最后按字节流发出去。整个逻辑量并不大核心就是状态机组织好。3. 硬件方案选型与接口从PHY芯片到RGMII总线的完整链路3.1 PHY芯片怎么选别只看价格要看接口和驱动难度FPGA和以太网之间的物理层芯片业界叫PHY。FPGA内部实现的是MACPHY负责把MAC输出的并行数据编码成串行差分信号跑在网线上同时负责链路协商、自动翻转等物理层杂事。我做过的板卡里比较常见的有这几颗RTL8211E/RTL8211EG瑞昱的千兆PHY绝大部分开发板都在用资料极多RGMII接口无论是正点原子、黑金还是野火基本都有配套例程。KSZ9031RNXMicrochip原Micrel的千兆PHY稳定性口碑好工业场景用得比较多也是RGMII接口但寄存器地址和RTL8211略有差异。YT8531国产裕太微的千兆PHY性价比高近年很多新项目在选尤其是需要国产器件的场合会优先考虑。选型时主要看几个点。第一是接口速率百兆MII/RMII千兆就得GMII/RGMII/SGMII。第二是工作电压RGMII接口电压可能是3.3V、2.5V或1.8V跟FPGA的IO bank电平要匹配不然轻则时序不稳重则烧引脚。第三是配置方式大部分PHY用MDIO接口配置寄存器有的有硬件引脚可以配置工作模式比如PHY地址、RGMII还是MII这些引脚在硬件设计时就已经拉死你在软件里只能通过MDIO去改寄存器来改变工作模式。个人建议第一块板子选RTL8211E配套资料最多遇到问题搜索一下基本都能解决。3.2 MII、GMII、RGMII这些接口名字到底差在哪MAC和PHY之间的接口有MII、RMII、GMII、RGMII、SGMII等好几种它们的区别主要在数据位宽和时钟频率上。初学时不需要每种都深入但至少要明白RGMII为什么在千兆方案里最流行。MII是百兆时代的接口发送和接收各4根数据线加上各自的时钟、使能、错误指示信号时钟频率25MHz100Mbps、2.5MHz10Mbps数据在时钟上升沿采样。GMII是千兆接口发送和接收各8根数据线时钟125MHz数据还是时钟上升沿采样。优点是时序简单缺点是引脚太多88再加上控制信号一个PHY要占二十几个引脚对FPGA的IO资源压力不小。RGMII是Reduced GMII千兆时把数据线压到发送4根、接收4根时钟还是125MHz但是数据在时钟的上升沿和下降沿各采一次DDR模式这样4根线等效8根线的吞吐量。控制信号也与数据信号复用TX_EN和TX_ER合到了TX_CTL一根线上接收同理。这样一来引脚占用少了一半以上所以目前几乎所有千兆以太网的MAC-PHY接口都是RGMII。RGMII这个名字你还会经常看到Vivado里有个RGMII IP核这个IP核帮你处理DDR采样和时钟对齐说白了就是帮你把RGMII的信号转换成内部更容易处理的字节流。用FPGA做RGMII接口自己直接采样DDR数据也是可以的但没有IP核的话时序极难收敛强烈建议直接用Xilinx的RGMII IP或参考厂商例程。3.3 RGMII时序到底怎么理解定了RGMII接口之后时序是一个绕不开的话题。RGMII v2.0标准的要点是发送方向MAC在TX_CLK的上升沿和下降沿同时输出数据且时钟和数据的相位是对齐的也就是说数据变化沿和时钟沿在同一时刻接收方向PHY会输出RX_CLK和RXDPHY内部做了延迟保证时钟边沿位于数据窗口中央也就是数据相对于时钟会有约2ns的延迟。这两个方向的区别一定要搞清楚不然你约束会写反。TX方向你在FPGA内部要保证数据建立保持时间满足PHY的要求通常需要在输出路径上做一个90度相移来补偿PCB走线和FPGA内部延迟RX方向则基本是PHY已经把时序处理好了你只需要在FPGA内部用IDELAY或直接逻辑采样即可。在Vivado里怎么约束RGMII一般做法是用约束文件创建RX_CLK和TX_CLK的时序约束再对RGMII数据线加set_input_delay/set_output_delay。新手在这里最常犯的错误是漏了对RX_CLK和TX_CLK的关系做约束导致综合实现后时序报告里时序违规一大片跑在板子上时好时坏。我的经验是先用厂商的example design跑通不要一上来就自己手写RGMII接口约束。Vivado自带Tri Mode Ethernet MAC的example中含RGMII的时序约束模板直接参考并改管脚约束比自己摸黑尝试靠谱得多。3.4 MDIO接口PHY芯片的“后台管理通道”很多刚接触PHY的人不知道MDIO是干什么的怎么配置PHY、怎么读取PHY状态寄存器全都要靠它。MDIOManagement Data Input/Output是一根双线管理接口一根时钟线MDC一根数据线MDIO用于CPU/MAC访问PHY的内部寄存器。PHY内部寄存器空间有很多地址0到15是标准寄存器比如寄存器0和1存放芯片型号和版本寄存器2~4是PHY ID寄存器5是厂商自定义寄存器0是基本控制寄存器寄存器1是基本状态寄存器。通过读取状态寄存器的bit 1或bit 2你可以判断网线是否插好、是否协商到千兆全双工。这个信息对调试非常有用。MDIO的时序协议有点像I2C但不完全相同。MDC由主机产生MDIO数据在MDC上升沿采样写操作需要32位的帧格式包括前导码、起始码、操作码、PHY地址、寄存器地址、数据。在FPGA里实现一个简单的MDIO主机并不难几十行状态机搞定。但绝大多数情况下PHY通过硬件引脚配置已经工作在默认模式了你可以先跳过MDIO直接测试链路是否通等链路不通时再回头查PHY的寄存器状态。4. FPGA内部逻辑设计从MAC层控制到收发通道的实现思路4.1 发送通路怎么搭用户数据到RGMII串行流FPGA内部的UDP发送通路我习惯把它拆成三块来看第一块是用户接口与FIFO。用户逻辑把要发送的数据写入FIFOFIFO输出侧由MAC发送状态机控制。用FIFO的好处一是缓冲当MAC在发送一帧数据时用户逻辑可以继续往FIFO里写下一帧二是隔离跨时钟域用户时钟和MAC时钟往往不是一个频率。第二块是发送状态机这是核心。它负责按顺序完成发送前导码、SFD、目的MAC、源MAC、类型、IP头、UDP头、数据、FCS、帧间隙。第三块是CRC计算模块。CRC32的算法可以用LFSR实现Vivado里也有免费的CRC IP核。如果你不想用IP核可以用网上流传很广的并行CRC32 Verilog代码注意把数据位宽和CRC初始值对应好就行。我常用的发送状态机状态顺序是IDLE - PREAMBLE - MAC_HDR - IP_HDR - UDP_HDR - PAYLOAD - CRC - GAP刚开始写的时候最烦的是这些头字段长度不一样有的4字节有的6字节有的20字节。我的处理方式很直接用状态机逐字节发送每个状态里维护一个计数器计数器到边界就跳下一个状态。虽然看起来代码有点长但逻辑清晰出了问题也好定位。4.2 接收通路怎么搭从字节流到完整数据包接收方向的解析比发送复杂一些因为你不知道对端什么时候发数据也不知道数据多长只能一帧一帧从RGMII字节流里解析。接收通路同样有三块第一块是RGMII接口数据对齐和形态转换从DDR模式的4位数据转成单沿的8位数据。这一步通常由RGMII IP核完成。第二块是接收状态机它要负责检测前导码、定位帧头、然后按字节解析MAC帧里的目的MAC、源MAC、类型字段。根据类型字段判断是ARP、ICMP还是UDP然后分别进入不同的分支去解析。第三块是FIFO缓冲和用户接口。接收方向的数据会从一个时钟域进到另一个时钟域而且用户逻辑处理数据的速度往往跟不上网口速度所以必须用FIFO缓存。接收状态机还需要注意一个点非整帧接收。如果一帧数据里只有一部分是我们关心的UDP净荷其他是填充字节你要能通过帧总长度字段判断有效数据边界。IP头里的总长度字段可以告诉你IP包总长度UDP头里的长度字段可以告诉你UDP报文长度。所以收到UDP头之后只需要按长度字段读取对应数量的字节到FIFO里剩下的填充字节直接丢弃就行。4.3 跨时钟域与复位设计网络通信里最容易翻车的地方跨时钟域问题在FPGA网络通信里太常见了。RGMII接口的RX时钟是从PHY恢复出来的它的频率和相位与FPGA内部工作时钟没有任何关系。你从RX时钟域解析出的数据最终要交给用户逻辑的时钟域处理这两者之间不能直接打拍同步。我的建议是所有跨时钟域的数据交换统一用异步FIFO解决。Xilinx在Vivado里有XPM_FIFO原语可以直接配置替代了以前需要手动例化FIFO IP核的方式。XPM_FIFO天然支持异步读写时钟输入输出数据位宽可以不同使用起来很方便。复位设计也是个容易被忽略的坑。网络通信的复位不能随便搞发送前导码和状态机的初始状态如果在复位释放瞬间没有准备好第一帧数据大概率会发错。我一般会把复位信号做异步复位同步释放处理并且让MAC发送状态机和RGMII接口使用同一个复位源避免两块逻辑复位时序不一致导致启动异常。5. 上手实操在Vivado中搭建一个可用的UDP收发工程5.1 第一步确定硬件和工具链我这边用的是黑金AX7A035开发板加RTL8211E的网口Vivado 2021.2。你的板子不同没关系关键是要知道你板子上PHY的型号、PHY地址通常为0b00000或0b00111、RGMII对应FPGA引脚约束文件。开发板厂商一般都会给管脚约束照着用即可。工程结构我建议模块化不要所有逻辑怼在一个文件里。至少分成这几个层级top.v芯片顶层例化PHY的RGMII接口、FIFO、UDP协议栈模块。udp_stack.v包含arp_rx、arp_tx、icmp_rx、icmp_tx、udp_rx、udp_tx等子模块。mac_tx.v、mac_rx.v负责以太网帧的封装与解析。crc32.vCRC校验模块。async_fifo用XPM_FIFO例化。这样分的好处是如果调试中发现ARP不回包你直接看arp_tx模块就行不用在几千行的top文件里翻来翻去找。5.2 第二步把UDP发送模块写出来我先给一个简化的UDP发送模块核心代码帮助你理解整体结构。完整代码太长这里只贴状态机和帧组装的关键部分。module udp_tx #( parameter SRC_MAC 48h00_11_22_33_44_55, parameter DST_MAC 48hff_ff_ff_ff_ff_ff, parameter SRC_IP 32hc0_a8_01_0a, // 192.168.1.10 parameter DST_IP 32hc0_a8_01_64, // 192.168.1.100 parameter SRC_PORT 16d4000, parameter DST_PORT 16d4001 )( input wire clk, input wire rst_n, // user interface input wire tx_valid, input wire [7:0] tx_data, input wire tx_last, output wire tx_ready, // mac interface input wire gmii_tx_clk, output reg [7:0] gmii_txd, output reg gmii_tx_en, output reg gmii_tx_er );由于收发时钟可能不同实际工程中tx_valid/tx_data这一侧的时钟是用户时钟而gmii_txd一侧的时钟是GMII时钟。两者之间的数据交接建议通过FIFO完成。这里为了简洁不写FIFO方便你理解帧组装逻辑。具体状态机结构可以这样设计localparam IDLE 4d0; localparam PREAMBLE 4d1; localparam MAC_HDR 4d2; localparam IP_HDR 4d3; localparam UDP_HDR 4d4; localparam PAYLOAD 4d5; localparam CRC 4d6; localparam GAP 4d7; reg [3:0] state; reg [4:0] cnt; always (posedge gmii_tx_clk or negedge rst_n) begin if (!rst_n) begin state IDLE; gmii_txd 8h00; gmii_tx_en 1b0; gmii_tx_er 1b0; end else begin case (state) IDLE: begin if (tx_valid) begin state PREAMBLE; cnt 5d0; end end PREAMBLE: begin gmii_tx_en 1b1; gmii_txd (cnt 5d7) ? 8hD5 : 8h55; cnt cnt 1b1; if (cnt 5d7) state MAC_HDR; end MAC_HDR: begin // 根据cnt依次发送DST_MAC[47:0], SRC_MAC[47:0], 0x0800 // cnt0~5 - DST_MAC, cnt6~11 - SRC_MAC, cnt12~13 - 0x0800 state IP_HDR; end IP_HDR: begin // 发送20字节IP头注意总长度、标识、校验和 state UDP_HDR; end UDP_HDR: begin // 发送8字节UDP头长度字段按16payload_len计算 state PAYLOAD; end PAYLOAD: begin // 读取FIFO或用户数据按字节发送 if (tx_last) begin state CRC; cnt 5d0; end end CRC: begin // 发送4字节CRC注意字节序 if (cnt 5d3) begin state GAP; gmii_tx_en 1b0; gmii_txd 8h00; end end GAP: begin if (cnt 5d11) begin state IDLE; end end endcase end end代码里我故意省略了每个分支的cnt计数与数据拼接细节因为不同帧头内各字段的计算方式不一样一行行写清楚会超过博客篇幅。关键是你要能看到状态机的骨架一次完整的帧发送就是根据状态逐字节输出每类头部数据计算出对应的字节内容。具体到各个头部的字节内容我建议你先把一个标准UDP包用Wireshark抓下来对照着每一字节自己数一遍这对理解帧格式帮助极大。不要直接抄一段完整代码跑通就完事那样下次遇到问题还是不会调试。5.3 第三步用Wireshark验证你的发送逻辑工程烧进板子PC和FPGA用网线直连。把PC的IP配成192.168.1.100FPGA的IP设为192.168.1.10。然后打开Wireshark监听PC的以太网卡再从FPGA侧触发一次UDP发送。正常情况下你应该在Wireshark里看到这样一个包目的MAC是你的PC网卡MAC源MAC是FPGA的MAC以太网类型0x0800IP协议号17源IP是192.168.1.10目的IP是192.168.1.100源端口4000目的端口4001payload是你发的那几个字节。如果能看到这个完整结构恭喜你你的UDP发送通路已经通了。如果看不到或者看到的是CRC错误标记的包按这个顺序查目的MAC是否和PC网卡MAC一致。你可以用ipconfig /all查看网卡物理地址我经常看到有人把MAC地址的字节序写反的。IP头的总长度字段是不是算多了或算少了。FCS是否正确。Wireshark如果标记为“CRC: 0x12345678 (incorrect)”说明你的CRC算错了。5.4 第四步把ARP和ICMP加上让链路更健壮UDP发送通了之后下一步是实现ARP应答和ICMP echo。ARP应答逻辑相对独立它的核心是判断收到帧的以太网类型是否是0x0806再判断ARP操作码是否为1再做源MAC、IP字段交换。写完之后你可以可以先ping一下FPGA的IP确认能ping通。ping通的意义特别大它说明你整条链路上二层的收发都通了ARP的收发也都通了。这时候你已经拿到了一个“可靠的二层链路”再往上做UDP、做自定义协议信心就完全不一样了。还有一个小技巧很多工程里会把FPGA的MAC和IP做成可配置的寄存器通过MDIO或自定义寄存器读写接口去修改这样同一份bit文件就能适应不同网络环境避免每次都要重新综合。我自己做项目时一般会把MAC、IP做成寄存器同时用EEPROM保存默认值上电读出来再用。这个可能在part.8里具体展开讲先挖个坑。6. 调不通网络通信的常见问题排查速查表6.1 链路层排查link灯亮不亮PHY状态对不对网口调试遇到问题第一件事不是看代码而是确认物理链路是否正常。观察开发板网口RJ45旁边的指示灯Link灯亮起来表示PHY之间协商成功了Active灯闪烁表示有数据在传输。如果Link灯不亮那大概率是硬件问题而不是逻辑问题。硬件层面的常见问题有几个网线是直连还是交叉线现在很多PHY支持自动翻转交叉线直连线都行但老一点或不支持自动翻转的设备可能需要交叉线RJ45座子和PHY之间的网络变压器虚焊、引脚连错PHY的时钟晶体是否起振部分PHY用25MHz无源晶振晶振不起振时PHY完全无响应PHY地址和复位引脚的电平是否正常如果PHY地址被拉错MDIO访问会失败。排查硬件问题需要示波器没有示波器的话至少要把PHY的状态寄存器读出来看看。通过MDIO读取PHY的寄存器1bit 2是链路状态如果读出来是0说明链路没协商成功。这个信息非常有价值能帮你快速判断问题出在PHY之前还是之后。6.2 协议层排查Wireshark带你定位问题链路层OK之后如果还是收发不通就轮到Wireshark出场了。先说FPGA发数据PC收不到的场景。在PC上开Wireshark抓包过滤器填“udp port 4001”然后让FPGA发数据。如果什么都抓不到问题在发送端你要查MAC地址、IP头、UDP头的字节是否正确如果抓到了但标记为坏包优先检查CRC和帧长度如果帧格式完全正确但应用层软件接收不到检查PC本机防火墙是否拦了对应端口的入站包。再说PC发数据FPGA收不到的场景。在PC上用网络调试助手或自定义软件给FPGA发一段数据Wireshark监听这一侧确认包确实发到了网卡。如果Wireshark里能看到发包记录说明PC协议栈这层没问题问题出在FPGA接收逻辑上。打开Vivado ILA逻辑分析仪抓RGMII接收总线的信号先看PHY是否把RX_CLK和RXD正常输出了再看状态机是否成功识别出了以太网帧头。如果PHY输出了数据但FPGA状态机没动作多半是前导码检测逻辑写的有问题。常见的接收端细节坑RGMII的RX_DV信号是拉高表示有有效数据有些人会把RX_ER误当作RX_DV去用接收状态机在检测SFD时多等了一拍或少等了一拍导致字节错位目的MAC地址比较时用了“全等比较”但FPGA里的mac地址存在字节序问题IP头校验和算法不对导致整个IP包被丢弃。这些问题万变不离其宗用好ILA抓信号一层一层看很快就能定位。6.3 我踩过的一些坑和心得最后分享几个我在网络通信调试中印象比较深的坑希望对你有帮助。第一个坑是字节序。网络上传输的数据是“大端序”但你FPGA内部处理时可能用了小端序。尤其是在拼接MAC地址和IP地址时如果高位在前还是低位在前搞错了包出去了别人根本不认。我的解决办法是写一个简单的参数定义把MAC和IP按大端方式以字符串形式注释出来然后在拼接时逐个字节赋值不要用一行“拼接”完事。第二个坑是时序约束。RGMII接口处理不好时最容易出现的情况是“上电偶尔通复位不太行跑几分钟就断”。这是典型的时序不收敛或时序裕量不足。解决的办法是严格按厂商时序约束模板去设置并且在实现后看时序报告里RGMII分组有没有violation。如果有微调IDELAY或者调整约束值不要盲目加逻辑。第三个坑是Vivado的Tri Mode Ethernet MAC IP核。我刚开始偷懒直接用这个IP核省去了自己写MAC逻辑结果被IP核的配置选项折腾得够呛。后来我发现如果是学习目的用纯逻辑自己写一个简单的MAC收发状态机反而对帧格式理解更深刻。等你理解了再回头看IP核的配置就完全知道每个选项是什么意思了。这也符合“近似0基础”学习路径的定位先自己实现一遍再用IP核提效。第四个坑是防火墙。PC端应用层软件收不到数据十有八九是Windows防火墙拦截了UDP入站。这个坑千万不要在FPGA工程里查半天先直接把防火墙关掉试试或者把网络调试助手加进白名单。很多时候你以为是自己逻辑写错了结果只是系统防护把包拦了。我做网络通信模块的实际体会是FPGA网络通信的难点不在于某个单独的模块有多难写而在于从物理层到协议层这条链路很长任何一个环节出错表象都是“不通”。所以一定要养成习惯逐层排查不要一上来就怀疑FPGA逻辑。先用Wireshark看PC侧收发情况再用ILA抓FPGA内部信号把问题定位到某一段再动手改代码这样效率最高。
返回列表