ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

FPGA实现UDP协议栈:verilog-ethernet开源工程学习与上板调试

FPGA实现UDP协议栈:verilog-ethernet开源工程学习与上板调试 从近似0基础开始FPGA开发 -- part.10 verilog-ethernet开源UDP协议栈工程学习我不知道你有没有这种感觉FPGA学了大半年LED、数码管、按键、串口玩了个遍UART收发已经能背下来了甚至IIC、SPI也写过几轮但心里总觉得没底。为什么因为这些接口都是板级的东西好歹是“看得见摸得着”的信号。可一旦要把数据从板子发到电脑、或者从电脑发到板子涉及到网络协议很多初学者就卡住了。我也卡了很久。说实话我卡的原因不是Verilog写不出来而是根本不知道从哪里下手。以太网帧格式、IP头、UDP头、校验和、MAC地址、IP地址、ARP……这些概念每一个单独拿出来都能看懂但要把它们串成一个能跑的硬件逻辑完全不是一回事。直到后来我找到一个开源项目verilog-ethernet才真正把这条路走通了。这篇就记录一下我学习这个工程、把UDP协议栈跑起来的完整过程包括踩过的坑、看代码的顺序、以及最后怎么在板上验证通。1. UDP协议栈到底在解决什么问题1.1 为什么FPGA需要处理网络协议很多做FPGA的人第一反应是我要传数据用串口不就行了吗板子到PC之间一个USB转TTL就搞定了。没错串口确实简单但如果你要传的是高速数据流比如ADC采样数据、图像传感器输出或者想在PC上实时显示FPGA内部的状态波形串口的几Mbps就完全不够看了。千兆以太网能跑到接近线速的吞吐实际有效载荷能到900Mbps以上这个带宽足够应付大多数嵌入式场景。而且以太网的物理层芯片PHY现在价格不高很多开发板本身就集成了RTL8211或者88E1512这样的千兆PHY驱动起来也不算麻烦。但问题在于以太网不等于UDP。以太网链路层只是说“我能在这个局域网里把帧从一台设备送到另一台设备”真正决定数据怎么封装、怎么路由、怎么被应用层识别的是IP协议和传输层协议。UDP就是其中最常用、最适合FPGA实现的传输层协议。1.2 UDP协议栈各层职责回顾为了后面读代码不迷路我先快速回顾一下这个栈里每一层各干哪些活。链路层以太网维护MAC地址负责把IP包封装成以太网帧加上目的MAC、源MAC、类型字段或者反过来从以太网帧里剥离出IP包。如果目标MAC是广播地址FF:FF:FF:FF:FF:FF交换机就会把这个帧广播到所有端口——这是ARP的基础。网络层IP维护IP地址负责把UDP数据报封装成IP包加上IP头、源IP、目的IP、协议号计算IP头校验和也负责解析收到的IP包。它不管数据能不能送到只管封装、寻址和分片重组。传输层UDP维护端口号把应用层数据封装成UDP数据报加上源端口、目的端口、长度、校验和。UDP本身不保证可靠传输没有确认机制、没有重传机制但这恰恰适合FPGA——不需要维护发送缓冲区、不需要超时重传逻辑电路规模小很多。应用层你的用户逻辑只关心数据本身。在FPGA里这一层通常就是你的硬件逻辑通过寄存器或者FIFO接口把数据交给协议栈。所以一个“UDP协议栈”本质上是把这三层协议在硬件里实现一遍对外提供一个简单的业务接口对内处理一大堆协议细节。verilog-ethernet其实就是把这件事做完了而且做得相当彻底。1.3 软核方案和纯硬件方案怎么选在接触verilog-ethernet之前我其实犹豫过要不要用软核。像MicroBlaze或者NIOS II里面跑个lwIPUDP栈就自动有了上层写C代码开发体验和MCU差不多。听起来很完美对吧但我个人的经验是如果不是特别复杂的控制逻辑纯硬件实现UDP协议栈反而更合适。为什么第一是资源占用。一个MicroBlaze或者NIOS II软核指令缓存、数据缓存、总线互联、外设IP塞下去怎么也得占几千个LUT和一堆BRAM。而verilog-ethernet的UDP完整模块优化后大概也就两三千个LUT的量级省下来的资源全都可以留给你的核心业务逻辑。第二是延迟和确定性。软核跑协议栈最怕的是在高负载下响应不及时。硬件栈则是每个时钟周期都在处理数据流水线一旦建立起来吞吐是完全确定的不会出现什么中断抢占导致丢包这种问题。第三是调试复杂度。不带操作系统的软核工程一旦跑飞了想定位问题非常麻烦。纯硬件逻辑则可以用仿真把每一帧数据的流向看得清清楚楚出了bug反而更好查。所以我最终的选择是硬件UDP协议栈开源实现优先考虑verilog-ethernet。这不是说软核方案不好而是说这个开源工程在性能和易用性上的平衡确实做到了位。2. verilog-ethernet工程整体架构学习2.1 仓库结构速览verilog-ethernet这个项目在GitHub上由Alex Forencich维护仓库结构非常清晰。顶层目录下核心代码都放在rtl目录里。第一次进去的时候我差点被文件名劝退——一大堆eth_、udp_、axi_开头的文件。慢慢理下来其实核心模块就那么几个我按功能分组列一下MAC层相关eth_mac_10g、eth_mac_1g等负责以太网MAC功能完整UDP协议栈udp_complete这是把MAC、IP、ARP、UDP全整合在一个模块里的“一键方案”拆开的协议组件eth_udp_tx、eth_udp_rx、eth_ip_tx、eth_ip_rx、eth_arp、arp_cache等通用接口组件axis_fifo、eth_axis_rx、eth_axis_tx等负责把MAC层帧和AXI-Stream数据流互相转换这个仓库还有个好习惯每个模块都自带一个testbench在tb目录下。这意味着你可以直接在Vivado或者ModelSim里对单个模块做仿真不需要先写testbench对学习来说非常友好。2.2 核心设计思路拆开再组合这个工程最值得学习的地方是它的分层设计思路。它不是把所有协议逻辑堆在一个大模块里而是每一层对应一个或几个模块模块之间用标准的AXI-Stream总线连接。链路层eth_mac_1g负责和外部PHY芯片打交道把GMII/RGMII接口的数据转成内部的AXI-Stream流。网络层eth_ip_tx和eth_ip_rx负责IP包的封装和解析eth_arp和arp_cache负责ARP请求和响应维护IP到MAC的映射表。传输层eth_udp_tx和eth_udp_rx负责UDP数据报的封装和解析。最后udp_complete把上面这些全部整合起来对外只暴露两个AXI-Stream接口用户发送、用户接收和一组控制寄存器。这种设计的最大好处是每一层都是独立的你可以单独仿真、单独替换。比如MAC层你要换成一个万兆的网络层以上的逻辑完全不用动。我自己在学习的时候就是按这个层次顺序逐个module读代码的读一个、仿真一个、理解一个最后再整体把udp_complete跑通。2.3 为什么选udp_complete而不是自己拼装仓库里提供了两种使用方式一种是自己把eth_mac、eth_ip、eth_udp、eth_arp这些模块连起来另一种是直接用udp_complete一步到位。我第一次用的时候图省事直接用了udp_complete。用下来发现这个选择是对的。原因很简单udp_complete模块内部已经帮你处理好了所有中间交互的细节包括流控信号握手、ARP缓存管理、错误帧丢弃这些烦人的边界情况。如果你的需求是“尽快把UDP通信跑通”不要自己拼。先把udp_complete用起来等后面需要定制或者升级了再拆开研究内部结构也不迟。这就像学做饭先按成品调料包做一次有感觉了再研究每味调料放多少。3. 把udp_complete接入自己的工程3.1 环境准备与文件清单在把代码加入工程之前建议手头准备好以下这些东西一块带千兆以太网PHY的FPGA开发板我用的是Xilinx Artix-7系列PHY芯片为RTL8211Vivado开发环境一个用于上板调试的PC端工具Wireshark必装用于抓包分析再准备一个网络调试助手用于发包回包测试从repo里需要拷贝的文件如果是千兆以太网至少需要以下几个具体路径在rtl目录下udp_complete.veth_udp_tx.v、eth_udp_rx.veth_ip_tx.v、eth_ip_rx.veth_arp.v、arp_cache.veth_eth_tx.v、eth_eth_rx.veth_mac_1g.v、eth_mac_1g_fifo.vaxis_fifo.veth_axis_rx.v、eth_axis_tx.v如果你用的PHY是RGMII接口的可能还需要看下eth_phy_10g或相关适配模块Xilinx的千兆以太网可以不用单独IP核直接把GMII引脚映射到FPGA的IO上配合RGMII转换逻辑使用。3.2 顶层信号对接udp_complete的顶层接口看起来复杂其实功能分几组就清楚了。我按我的习惯重新分组了一下时钟复位组clk用户时钟通常用PHY的GTX_CLK125MHz、rst异步复位高有效。PHY接口组rgmii_rtl_* 系列在RGMII模式下直接连到PHY芯片引脚如果是GMII模式用gmii_*那组。控制与状态local_mac、local_ip、local_port这三个输入要配成你自己的MAC地址、IP地址和UDP端口号。还有status、debug这些状态输出供逻辑分析仪抓取。用户发送接口input_axis_tdata、input_axis_tvalid、input_axis_tready、input_axis_tlast、input_axis_tkeep这是标准的AXI-Stream从端接口你的业务逻辑往里面喂数据就行。用户接收接口output_axis_tdata、output_axis_tvalid、output_axis_tready、output_axis_tlast、output_axis_tkeep标准AXI-Stream主端接口你从这里把收到的数据读走。如果你之前用过Xilinx的AXI-Stream IP核这套接口应该非常眼熟。有效信号和就绪信号握手tlast表示一帧数据的结束tkeep表示最后一拍的有效字节数。简单说tvalid拉高且tready拉高数据才算真正传输了一个节拍。3.3 AXI-Stream流控时序要点AXI-Stream的握手规则是这个工程里最核心的接口规则我在这里吃过亏多啰嗦两句。基本规则发送方拉高tvalid接收方拉高tready只有在两者同时为高的时钟上升沿数据才被采样。我给一个具体例子。比如你要发送一个UDP数据报数据是64字节那么你的发送逻辑应该这样第一个时钟周期把第一个32位数据放到tdata上拉高tvalid协议栈内部准备好接收时会拉高tready这个节拍数据被取走持续这个过程直到最后一拍同时拉高tlast表示帧结束。如果接收方在中间某个周期没有准备好tready拉低发送方必须保持当前数据不变tvalid也不能拉低直到握手成功。这个规则在AXI协议里叫“valid不能等待ready”你只能等不能丢掉数据。很多自己写逻辑的人第一次在这里翻车数据丢了之后整个帧结构错乱接收端直接丢包。3.4 与PHY芯片的对接细节PHY芯片这边我当时用的是RTL8211工作在RGMII模式。需要注意的无非是以下几点时钟RGMII模式下125MHz的GTX_CLK由FPGA提供给PHYPHY用它来同步发送数据。接收方向PHY会提供一个125MHz的RXC时钟给FPGA。数据线RGMII用4根数据线上升沿和下降沿各采样一次分别对应低4位和高4位。所以FPGA侧要用DDR逻辑来处理把4位变成8位。verilog-ethernet的例程里带了RGMII的收发转换逻辑不用自己写。复位PHY芯片需要一个复位信号。有些板子的PHY复位脚还兼做MDIO配置上电时序比较讲究。如果出现link up不了大概率是复位时序或者配置没做好。自发自收测试很多PHY支持内部回环模式可以通过MDIO寄存器0的bit14来打开。这在上板调试时非常有用可以先确认FPGA到PHY的数据链路没问题再怀疑外部网络对接问题。3.5 地址与端口的分配建议在配置local_mac、local_ip、local_port这三个参数时我踩过一个很搞笑的坑。一开始我把local_ip设成了192.168.1.128结果和公司路由器网段冲突导致网络环境里出现了IP地址冲突电脑都上不了网了。后来换成了192.168.2.128这种冷门网段才消停。经验总结FPGA板子的IP地址尽量选一个和你局域网现有网段不冲突的子网。不要用网关地址、不要和别的设备重复。如果只是为了点对点测试可以把PC的网口IP设成固定地址比如192.168.2.100板子设192.168.2.128然后用一根网线直连不经过交换机这样最干净。MAC地址也注意一下不要用全零不要用广播地址随便编一个像00:11:22:33:44:55这种本地管理的地址就行。UDP端口号建议选1024以上的高位端口避开系统保留端口。4. 关键接口与仿真验证4.1 仿真环境搭建verilog-ethernet仓库的testbench写得非常好但我个人建议先别急着跑官方的tb因为你还不了解内部逻辑跑了也看不懂。我的做法是先搭一个最小仿真环境只测udp_complete一个模块能完整走通“发包-回环-收包”的链路。官方tb里用的mii接口模拟器、axis接口模拟器可以拿来实现用。它们本质上是一些任务函数往接口上塞数据或者从接口上收数据。仿真环境最核心的是要用虚拟的PHY来模拟真实PHY芯片的行为。udp_complete的官方tb例子里就有这样一个虚拟ETH PHY模块它接收FPGA发来的GMII/RGMII数据并原封不动地发回到FPGA的接收端。这样做的目的是不需要依赖外部网络环境就可以验证“发送-回环-接收”的完整流程。我把流程总结一下通过虚拟PHY建立一个回环链路发送端通过AXI-Stream喂一组数据观察接收端AXI-Stream输出了什么数据用ModelSim或者Vivado仿真器抓内部信号对照协议格式逐层校验4.2 用实际抓包对照协议层仿真跑通之后想确认协议格式对不对最好的办法就是抓包看。这个抓包不是说去抓物理网线上的包那得上板才行。在仿真里你可以直接把udp_complete发送方向的信号导出来在Testbench里写一个dumper任务把eth_tx的数据按字节打印到文本文件里做成PCAP格式再导入Wireshark。Wireshark会直接解析出以太网帧、IP头、UDP头和你预期的数据对不对一目了然。我自己在第一次跑通仿真的时候就是用这种方式验证的数据从PC端用Wireshark抓包看到的和FPGA里dumper出来的完全一致。如果你不想折腾PCAP导出还有个更笨但有效的办法在Testbench里把发送方向的轴线数据导成hex文本然后自己去对照UDP包格式手工解析一遍。多做几次这个过程你对协议格式的印象会比看十遍文档都深刻。4.3 接收路径的仿真手段验证接收路径比发送路径麻烦一点因为你要构造一个合法的UDP包发到FPGA里。如果自己造要自己算IP头校验和、UDP长度、填充以太网帧头非常痛苦。我的办法是在PC上直接构造一个UDP包用Wireshark抓下来保存成hex文件然后在Testbench里通过$readmemh把这些数据读进去通过虚拟PHY的接收通道灌给FPGA。这样做的好处是PC那边生成的包一定是“真实世界”的合法包FPGA收到的就是实际网络环境的输入。等FPGA接收解析完成你再检查接收端输出的数据是否符合预期。如果没问题说明接收路径的逻辑也通了。4.4 分析udp_complete内部状态机仿真跑通后我强烈建议你花时间把udp_complete内部的状态机看一遍这是理解整个开源工程的关键步骤比你照着顶层接口盲猜要高效得多。dwfifo和状态机的大致数据流是这样的发送路径用户数据进来经过一个小的FIFO缓冲进入eth_udp_tx在这里被包上UDP头然后送入eth_ip_tx包上IP头再送入eth_arp根据目的IP查ARP缓存找到MAC地址最后送入eth_eth_tx包上以太网头通过MAC送到PHY。接收路径是反过来的eth_eth_rx收到以太网帧剥掉MAC头送到eth_ip_rx解析IP头再送到eth_udp_rx解析UDP头最后数据通过AXI-Stream交给用户逻辑。每一层都有对应的状态机每个状态机通常只有几个状态。你顺着发送路径看一遍状态转移条件就能明白“一帧UDP数据在硬件里是怎么一步一步被包装出来的”。特别是eth_udp_tx的状态机我建议来回看三遍。它处理了数据包空闲、头部封装、数据传输、结尾校验这几个关键阶段状态转移非常清晰看完之后你对AXI-Stream流控的理解会提升一个档次。4.5 官方testbench的利用方法如果你不想自己从零搭仿真环境直接用官方tb也可以有一点注意事项。官方tbudp_complete_tb.v里面其实已经包含了虚拟PHY、任务封装、数据比较逻辑你不需要自己写testbench只需要改一下目标文件路径和宏定义参数比如IP地址、MAC然后直接跑。跑完之后在仿真波形窗口里能很直观地看到数据从发送接口进来穿了一层又一层协议最后从接收接口出来的完整过程。这个tb的做法是先通过发送通道发一个UDP包同时把发出的数据存到一个队列里由于虚拟PHY回环FPGA会收到自己发出的数据再走一遍接收路径并把解析结果输出测试脚本会比对发出的数据和接收到的数据是否一致如果一致就报告测试通过。所以即使你已经能自己写tb了我也建议你先跑一遍官方tb对照它检查一下你自己的仿真环境有没有问题。仿真能过只能说明逻辑行为正确真正能不能和外界通信还得看上板实测。5. 上板调试经验实录5.1 上板前的检查清单上板之前有四个地方值得反复确认这是我踩过无数次坑之后总结出来的时钟是否正确。PHY的GTX_CLK是125MHz必须保证FPGA的时钟约束正确。不要出现“程序里逻辑错了结果跟时序约束有关”这种鬼故事。用Vivado做时序收敛检查跑完Implementation后看时序报告有没有violation。引脚约束是否正确。RGMII的信号引脚、复位引脚、LED等都要仔细对板卡原理图千万别映射错。尤其RGMII的TX和RX是有延迟要求的一般需要在约束文件里加上input delay或output delay否则高速信号很容易采样出错。这部分新手特别容易忽略。PHY芯片配置是否正确。RTL8211上电时默认是千兆模式但具体工作在GMII还是RGMII模式取决于PHY的strap引脚电平配置。如果你的硬件设计上strap没拉对PHY可能跑在别的模式这时候FPGA接收不到正确数据。这种情况用示波器或者逻辑分析仪看引脚电平能很快定位。ARP缓存是否预热。第一次发送UDP包之前FPGA必须知道目的MAC地址这个信息需要通过ARP协议获取。如果ARP缓存为空第一个数据包会被丢弃同时发一个ARP请求出去。你可以先ping一下板子IP让ARP缓存建立起来再发UDP数据。5.2 连线验证PC与FPGA通信上板之后最经典的一步是用网线把FPGA开发板和电脑直连。这一步我建议这样操作电脑有线网卡设置为固定IP和FPGA同一网段。ping FPGA的IP地址看能不能通。如果ping通说明IP层和ARP都正常。如果ping不通先看开发板上的link指示灯亮不亮。不亮就先去查PHY芯片的焊接和配置。用网络调试助手或者自己用Python写一个socket脚本给FPGA的UDP端口发一串数据。FPGA收到后可以通过LED或者串口把数据内容显示出来也可以用ILA抓内部信号确认。PC端发送UDP数据FPGA回传数据。回传的验证方法是用Wireshark抓包看有没有FPGA发出的UDP包。如果PC能收到FPGA发出的UDP包那基本可以认为协议栈的发送路径没有问题如果还能正确解析PC发到板子的数据接收路径也无误。5.3 抓包工具的使用与常见判断Wireshark是网络调试中最好用的工具没有之一。我强烈建议你把过滤语法背下来几个常用的udp、arp、ip.addr 192.168.2.128这种。在抓包的时候有几个典型的现象需要注意现象一ping时Wireshark里有ARP请求的包但没有ARP回复。这说明FPGA收到了ARP请求但没正确回复大概率是MAC地址寄存器配置不对或者协议栈的ARP模块没正常工作也可能是ARP回复的源MAC地址配错导致PC丢弃了。现象二ping通但UDP数据发不出来。这种情况往往是AXI-Stream发送接口的握手有问题或者FIFO没接对。你可以用ILA抓内部信号看tvalid和tready有没有正确握手。现象三收到乱序或者错位的数据。大概率是tlast和tkeep的时序不对。比如发送逻辑在最后一拍没有拉高tlast接收端就会一直等待数据没法flush出去。5.4 ILA在线调试的使用心得上板调试和仿真不一样很多问题只有在真实时钟频率下才暴露。Xilinx的ILA集成逻辑分析仪是DEBUG利器我总结几个实用经验先抓发送方向的信号input_axis_tdata、input_axis_tvalid、input_axis_tready看你的业务逻辑有没有正确地把数据喂进来。这里最容易发现的问题是tvalid已经拉高但tready一直为低——说明协议栈还没准备好接收下一帧可能是上一帧数据还在内部FIFO里没发完。再抓PHY接口的信号rgmii_txd、rgmii_tx_ctl对照以太网帧格式用ILA抓的数据和Wireshark抓到的发送数据对比能快速定位是MAC层封帧错误还是PHY的IO时序问题。ILA的触发条件设置也要用心。如果发的是固定格式的数据包可以设置触发条件为检测到某个特定的报文字节比如以太网帧头的前导字节这样能把抓取窗口对准帧的起始位置看起来会更清晰。5.5 一条实测通过的调试路径我这里给出一条我实际用过的、验证过可行的调试路径照着这个顺序走可以少走很多弯路第一步先用开发板自带的参考工程或者官方demo把PHY芯片跑通确认物理链路up。如果官方demo都ping不通不要怀疑自己先查硬件、查时钟、查引脚。我遇到过用错了PHY芯片的复位时序导致link一直不起来的情况这时候调啥都没用。第二步在FPGA里接一个简单的常发数据源比如计数器每隔一段时间发一个UDP包。接好之后PC端Wireshark看能不能收到。这个阶段不需要PC发数据给板子只需单向验证发送路径。第三步PC端用网络调试助手向板子发数据板子收到后触发一个LED翻转。这个阶段验证接收路径。第四步双向打通之后再挂上你的真实业务逻辑。比如把ADC采样数据打包成UDP帧发到PC或者在PC端下发参数控制FPGA里的寄存器。到这一步UDP协议栈就真正变成你项目的基础设施了和串口、SPI一样属于“默认可用”的模块。6. ARP缓存与IP层处理的深入理解6.1 ARP协议在FPGA里的状态机很多初次接触的人会把ARP当成一个可有可无的东西直到自己调试时发现第一个包永远发不出去、或者PC端ping不通板子才开始正视它。说穿了ARP做的事就是“已知目的IP地址查目的MAC地址”。以太网帧里填的是目的MAC地址而不是IP地址因为链路层只认MAC。交换机转发帧的时候也是看目的MACIP地址对它没有意义。在FPGA里ARP的处理逻辑是一个典型的状态机大致经历这几个状态收到ARP请求查看请求的目标IP是不是自己。如果是则把请求者的IP和MAC记录下来同时构造一个ARP回复应答。这个回复里填上自己的MAC地址和IP以及对方的MAC地址和IP。发出ARP请求目的是获取目标IP对应的MAC。请求发出后等待对应的ARP回复超时后重发。verilog-ethernet的arp_cache模块会维护一个缓存表记录已经解析出来的IP到MAC映射并自动处理过期更新。重要的一点ARP模块只有在协议栈内部有数据要发送、但查不到目的MAC时才会自动发出ARP请求。这个行为是自动的不需要用户逻辑参与。用户逻辑只需要把UDP数据交给协议栈如果目的MAC不在缓存表里协议栈会先把数据包挂起发完ARP请求并收到回复后再把数据包发出去。如果收不到回复数据包就一直在缓存里直到超时被丢弃。6.2 IP头校验和的硬件实现方式IP头校验和的计算方法是UDP协议栈里最容易让新手困惑的地方之一。它不是CRC而是一个“反码求和”的过程。计算方法是把IP头按16位一组划分把这些16位数值的反码求和结果再加回去。写成伪代码就是sum 0 for each 16-bit word in IP header: sum word if sum carries over 16 bits: sum (sum 0xFFFF) 1 checksum ~sum在硬件里这个逻辑可以用一个16位的累加器和进位检测电路来实现每次往FIFO里写入一个16位数据时执行一次累加。verilog-ethernet里实现得比较巧妙它把校验和的计算嵌在了数据包发往FIFO的过程中发送完毕的同时校验和也就算出来了。UDP校验和与IP头校验和有个重要区别UDP校验和不仅覆盖UDP头还覆盖了整个UDP数据部分甚至还包括一个“伪IP头”源IP、目的IP、协议号、UDP长度。伪IP头不参与实际的网络传输它只是参与校验和计算用来防止IP地址错误。这一点在调试数据校验错误时非常容易忽略如果你自己写一个UDP发送逻辑校验和总是算不对九成是伪头部没有参与计算。6.3 校验和错误的典型症状接收端校验和出错时最常见的现象是Wireshark里能抓到包但显示“校验和错误”的红色警告。如果你的FPGA接收端开启了校验和过滤这种情况下包会被直接丢弃用户逻辑根本收不到数据。这个问题的排查思路是先用Wireshark抓包如果在PC发往FPGA的方向出现校验和错误那多半是PC端软件发包工具的问题或者是你构造包时校验和没算对别急着怀疑FPGA。如果是FPGA发往PC的方向出错再用ILA抓FPGA发送方向的数据对照协议格式逐字节检查看是哪一个字段算错了。还有个容易忽略的细节很多PC网卡在发送UDP包时会开启硬件校验和卸载checksum offload由网卡自动计算校验和并填充。这时候你用软件发包工具构造的校验和字段会被网卡覆盖重算属于正常现象。但在FPGA里可没有这个“免费午餐”校验和必须自己算对。7. 常见问题与排查技巧实录7.1 链路层link灯不亮症状板子和电脑网线连接后开发板的以太网指示灯不亮。排查思路先量PHY芯片的供电、时钟。有些PHY芯片需要的时钟频率不对链路就建立不起来。检查PHY的复位信号。很多板子的PHY复位脚被FPGA控制如果你在FPGA逻辑里把它拉低了或者时序不对PHY就一直处于复位状态。解决方法是检查硬件原理图确保复位信号在初始化流程里按正确时序释放。检查PHY芯片的模式配置引脚strap pin。有些PHY通过外部电阻配置工作模式比如强制千兆、强制百兆、自动协商等。如果配置成了错误的模式link就起不来。在软件层面可以通过MDIO接口读取PHY芯片的状态寄存器查看当前链路状态和协商结果。MDIO接口本身也是学习FPGA的不错素材值得花点时间研究。7.2 网络层ping不通症状PC端ping板子IP没有任何回复。排查思路先用Wireshark抓包看PC发出的ARP请求有没有到达交换机如果PC和板子直连看抓包结果里有没有ARP重传。如果没有ARP重传说明ARP请求可能没有被交换机转发出去了板子根本没收到。如果ARP请求已经到达板子侧但PC没收到ARP回复那问题出在FPGA的ARP应答逻辑上。测试方法在板子上用ILA抓eth_arp模块的输入输出看是否收到了ARP请求以及是否输出对应的ARP回复。如果ping通了但UDP不通那问题基本确定在UDP发送或接收逻辑上按前面说的方法去查。7.3 传输层数据发不出或收不到症状ping没问题但PC发到FPGA的UDP数据收不到或者FPGA发到PC的UDP数据看不到。排查思路发送方向用ILA抓input_axis接口确认你的业务逻辑确实把数据交给了协议栈。如果在ILA里看到tvalid拉高、tready始终为低说明协议栈内部FIFO满了或者还在处理上一帧数据。这个问题的概率最大。解决方法是在业务逻辑里做握手机制确保数据在tready为高时才被送入。接收方向用ILA抓output_axis接口确认接收端有没有数据输出。如果没有再往上游看eth_udp_rx模块的状态机看它卡在哪个状态。逐步往前排查最终能定位到是PHY层没收到数据还是MAC层丢弃了帧还是IP/UDP头部解析没通过。一个经验是先检查目的端口号。很多初学者配置local_port的时候没注意字节序导致PC发的数据端口号和FPGA配置的端口号不一致。UDP端口号在帧里是网络字节序大端如果你的配置逻辑没有做endian转换极容易出错。7.4 数据内容错乱或丢字节症状PC能收到FPGA发来的UDP数据但数据内容对不上或者字节顺序和预期不一致。排查思路优先检查AXI-Stream接口的tdata位宽。udp_complete默认的AXI-Stream接口位宽是8字节64bit如果你的业务逻辑是按32bit的节奏发送数据就要在接入前做一些位宽转换比如用axis_adapter或者自己写一个简单的宽度变换模块。检查字节顺序。以太网是网络字节序也就是大端序。如果你的数据在FPGA内部是小端序发出来可能就颠倒了。这个问题在PC上做大小端转换就行但如果你要让FPGA按特定的数据格式发数据就得在逻辑里自己处理。检查tkeep信号。在多字节AXI-Stream接口中每一拍传输的有效字节数由tkeep指定。如果你的发送逻辑没有正确设置tkeep接收端可能把无效字节也当成有效数据接收了。7.5 上板无法复现仿真的问题症状仿真全部通过一上板就完全不工作或者时好时坏。排查思路这类问题多半是时序问题。仿真里默认都是零延迟上板后所有信号都有实际延迟如果时序约束没做对跑在125MHz就可能出现采错信号的状况。优先检查时钟约束GTX_CLK是否正确约束成生成时钟。CLK是否正确地约束为主时钟。检查RGMII的延迟。RGMII的采样时钟和数据线之间有严格的延时关系需要在约束文件里正确设置input delay / output delay。这个不做的话高速信号大概率采错。上板之后不要直接跑业务逻辑先把最简单的LED翻转代码烧进去验证时钟、复位、引脚都是对的再一步一步加复杂逻辑。这个小习惯能帮你省下大量的调试时间。7.6 常见问题速查表问题现象可能原因排查方向Link灯不亮PHY复位、时钟、模式配置错误量硬件信号读MDIO寄存器ping不通ARP回复逻辑有问题抓ARP请求和回复ping通但UDP收不到端口号配置错误或校验和错误检查local_port、抓取接收端状态机UDP发不出去AXI-Stream握手失败ILA抓tvalid/tready信号数据字节错乱字节序、tkeep设置错误对照Wireshark逐字节分析上板后功能异常时序约束缺失、时钟问题检查约束文件、时序报告7.7 调试经验小结最后分享几个我在这个项目上摸爬滚打总结出来的心得希望对你有帮助。第一个心得是“永远先怀疑最简单的环节”。很多看起来像协议栈天大的问题最后定位出来往往是时钟没给对、复位没释放、引脚映射错了。如果上板现象和仿真结果差得很远90%是这类低级问题。所以我会把“先确认时钟复位引脚再纠结协议逻辑”作为固定流程。第二个心得是“日志和抓包是最好的老师”。在调试这个UDP栈的过程中Wireshark帮了大忙。但Wireshark只能看到板子和PC之间的实际网络包看不到FPGA内部的行为。所以ILA和Wireshark配合使用一个看内部一个看外部对照着分析能很快定位出问题。去年我在调试一个千兆图像传输项目时收到的帧每隔几帧就丢一包用ILA抓到tready周期性拉低才发现是AXI-Stream的FIFO深度不够导致反压后来加了深度解决了。第三个心得是“先跑通再优化”。我见过不少初学者走上另一个极端从一开始就研究怎么把BRAM使用降到最少、怎么把延迟降到最低。这种心态可以理解但学习阶段完全没必要。先把协议栈跑起来哪怕资源占用大一点、时序宽松一点等稳定工作了再回头优化也不迟。如果一上来就给自己设定各种约束学习成本会陡增好几倍。8. 后续还能往哪些方向扩展8.1 从UDP到TCP硬件协议的进阶思考UDP学会了之后我猜很多人会和我一样好奇TCP能不能也这样搞TCP和UDP有本质区别。TCP的可靠传输依赖序号确认、超时重传、滑动窗口等机制这些机制在软件里是几行代码的事在硬件里就是巨大的状态机和大量的存储资源。verilog-ethernet这个仓库不直接提供TCP协议栈只有一些支持多连接管理的TCP封装缓存模块。我自己的感受是FPGA里做TCP适合的是TCP卸载引擎TOE这类高端应用普通项目不需要碰它。UDP做到位配合丢包重传机制已经能解决绝大多数实时传输需求了。8.2 从千兆到万兆接口速率升级如果你用的是万兆PHY和FPGA比如Xilinx的10G Ethernet Subsystemverilog-ethernet同样有对应的模块可以移植。核心的地方是把MAC层换成eth_mac_10g上层逻辑基本可以复用。不过万兆和千兆有个非常大的差异在万兆速率下数据是一个钟周期64位打底甚至用256位的总线来搬运数据这在FPGA内部会消耗巨大的逻辑资源。所以如果不做流式大数据传输没必要上万兆。千兆UDP已经能覆盖绝大多数嵌入式场景了。8.3 把UDP模块做成自己的IP等你把verilog-ethernet的代码读透了我推荐你做一件事把这些模块封装成自己的IP核集成到公司的公共IP库或者自己的工程模板里。封装成IP后可以在Vivado的Block Design里拖拽使用下次做项目就不用重复去改代码了。封装时建议把AXI-Stream接口用标准的AXI4-Stream协议来定义这样方便和Xilinx的DMA等IP直接连接复用性会强很多。8.4 在真实项目中如何模块化我自己在后续的项目里普遍采用这样一种模式顶层用Block Design把MicroBlaze、DDR控制器、AXI互联总线、以及udp_complete的IP封装连在一起MicroBlaze负责控制面逻辑比如解析PC下发的一些简单配置命令FPGA侧的逻辑通过AXI-Stream高速发数据。这样做的好处是分工清晰需要灵活应对的复杂流程交给CPU处理需要高吞吐、低延迟的数据通道由硬件协议栈来承担。万一哪天要把协议栈从千兆换成万兆只需要替换MAC层相关模块上层和CPU侧的控制逻辑基本不用动。每次我给别人讲FPGA网络开发总喜欢用“盖房子”来打比方串口是走入户门PCIE是搬货的货车而UDP协议栈则是给你装了一套正规的物流管道。刚开始你可能觉得这套管道又重又麻烦但真正跑起来之后你会发现它能省下的力气远比安装它的成本要高。verilog-ethernet这个开源工程是我认为FPGA学习路上最值得精读的项目之一。它的价值不只是给你一个能用的UDP协议栈更是给你一份“硬件工程师如何做协议处理”的活教材。你把它啃下来之后再去接触其他总线协议、高速接口、网络卸载引擎都会觉得顺很多。最后送大家一句话代码可以抄但总线时序和状态机设计思路必须自己吃透。你把这套栈里每个握手信号、每个状态转移都看懂了才算是真正会了。
返回列表