FPGA光口直连方案:AXI Ethernet Subsystem+SFP实现高速UDP通信
1. 项目概述与核心思路做FPGA开发的朋友应该都有过这种体验调试以太网通信时板子上密密麻麻的PHY芯片、变压器、RJ45座子占了一大片PCB面积遇到信号完整性问题还得反复调整布线光是搞定千兆PHY的寄存器配置就要翻大几十页的datasheet。如果只是想在FPGA和主机之间建立一条高速UDP链路做数据交互这套传统方案显得相当笨重。我在一个高速数据采集项目里遇到了同样的困扰板卡面积紧张但需要把ADC采样数据实时回传上位机带宽需求约800Mbps千兆以太网刚好够用。最初方案是MACPHYRJ45的标准链路但PCB Layout阶段发现PHY芯片的外围电路——特别是差分走线和阻抗匹配——在有限板面积里实在不好处理。后来我把目光投向了Xilinx的AXI 1G/2.5G Ethernet Subsystem IP核配合SFP光模块直接做光口通信彻底甩掉了独立PHY芯片。这个方案的本质是把传统PHY芯片负责的PCS物理编码子层和PMA物理介质附接子层功能直接集成到FPGA内部的IP核里完成。SFP光模块本身就是个光电解耦器件它负责把FPGA输出的CML电流模式逻辑差分信号转成光信号反过来再把光信号转回CML电平。也就是说FPGA的GTP/GTX高速串行收发器直接和SFP模块对接中间不需要额外的PHY芯片做介质转换。这样做的好处相当直接省掉一颗PHY芯片和相关外围电路简化PCB设计降低BOM成本同时还能获得光纤通信固有的电气隔离和抗干扰能力。这个方案适合谁如果你正在做FPGA图像采集传输、高速数据采集、嵌入式网络通信或者单纯想学习Xilinx的高速串行收发器应用这篇梳理都能给你一个可以直接落地的参考路径。我自己在这套方案上实际跑通过用iperf打流实测UDP吞吐量稳定在940Mbps左右千兆模式CPU占用率也很低。下文会把整个方案的每个环节拆开讲清楚包括IP核配置、时钟设计、UDP协议栈实现、时序约束以及实际调试中的坑。2. 光口直连方案的整体设计与选型考量2.1 为什么选择AXI 1G/2.5G Ethernet Subsystem在Xilinx的IP库里做以太网相关实现有几种选择老一点的Tri-Mode Ethernet MACTEMAC新的AXI 1G/2.5G Ethernet Subsystem也叫AxiEth以及面向专业网络设备的Vitis Networking P4。TEMAC其实是AXI Ethernet的前身两者IP核结构相似但AXI Ethernet把MAC和PCS/PMA集成得更完整而且支持1G和2.5G两种速率自适应这对后续系统升级预留了很大空间。核心区别在物理层的集成度。传统方案里MAC在FPGA内部PHY在FPGA外部两者通过RGMII或GMII接口连接需要额外的引脚和时序约束。而AXI 1G/2.5G Ethernet Subsystem如果配置成SGMII模式它会直接例化一个内部的PCS/PMA层物理接口变成高速串行收发器GTP/GTX这意味着你只需要在FPGA顶层引出几对差分信号到SFP座子即可。物理层已经从芯片级简化到了FPGA内部IP级。这就相当于把原来挂在板子上的独立PHY搬进了FPGA里面。实际上Xilinx这个IP核确实包含了完整的千兆PHY功能包括8B/10B编解码、时钟恢复、自动协商Auto-Negotiation、链路状态监测等。光模块对外呈现的就是一个符合SFF-8472标准的可插拔器件FPGA只需提供电源、串行管理接口I2C和高速差分信号即可。2.2 与传统MACPHYRJ45方案的对比为了说清楚这个方案的价值我把两种方式的主要差异列个表对比维度MACExternal PHYRJ45AXI Eth SubsystemSFP光口PCB面积较大PHY芯片、变压器、电阻电容、RJ45座较小SFP座少量退耦电容BOM成本较高PHY芯片单价几十到上百元较低FPGA IP免费SFP模块根据速率几十元起电磁干扰铜线传输易受干扰需要Layout严格把控光纤天然抗电磁干扰电气隔离传输距离100米左右Cat5e/Cat6多模光纤550米单模可达10公里以上调试复杂度需要配置PHY寄存器处理RGMII时序收敛只需关注FPGA内部时钟约束速率升级换PHY芯片重新设计硬件2.5G模式只需改IP配置和GTX速率从我实际体验来看最直观的感受就是调试工作量大幅下降。传统RGMII方案里往往要花大量时间处理Tsu/Th的时序约束因为数据线是DDR双沿采样3.125ns的周期里要保证建立保持时间满足要求稍有偏差就可能出现偶发CRC错误。而SGMII方案走的是FPGA内部SerdesGTP/GTX收发器的时钟管理由内部CDR时钟数据恢复完成硬件设计层面只要保证参考时钟干净、电源纹波达标稳定性远高于并行接口。2.3 三种速率模式的适用场景AXI 1G/2.5G Ethernet Subsystem支持三种速率模式1Gbps SGMII、2.5Gbps SGMII、1Gbps 1000BASE-X。这三种模式在IP配置界面里可以通过勾选选项来支持但物理层的处理方式有明显差异1000BASE-X模式最接近传统千兆光纤以太网的标准模式物理层直接使用8B/10B编码不需要自动协商过程对端设备如交换机、另一块FPGA板卡也必须配置为1000BASE-X模式。SGMII 1G模式兼容性最好的模式物理层同样走Serdes但在协议层支持与标准千兆PHY的自动协商可以对接普通交换机或者电脑主板上的千兆网口通过SFP转RJ45模块。SGMII 2.5G模式速率提升到2.5Gbps需要使用支持2.5G的SFP模块物理层编码改为8B/10B但频率更高这个模式主要用于FPGA之间的高速互联无法直接接标准千兆交换机除非交换机有2.5G口。我实际项目里选的是1Gbps SGMII因为要和上位机的千兆网卡互通。如果你的场景是板间高速互联2.5G模式甚至可以通过QSFP实现10G级别的扩展这也是这套IP核设计上的前瞻性所在。但一定要明白速率模式的选定必须在IP配置阶段就确定后续切换需要重新生成IP、调整GTX参考时钟频率硬件改动较大。3. SFP光口直连的关键技术细节3.1 SFP光模块的硬件连接要点SFP光模块的电气接口其实很简单电源3.3V、I2C管理接口SCL/SDA、LOS信号丢失告警、TX_FAULT发送故障、TX_Disable发送禁用、以及两对高速差分信号TX±和RX±。在FPGA直连方案里真正需要重点处理的是高速差分对和电源去耦。SFP座子的TX±和RX±直接连接到FPGA的GTP/GTX收发器引脚。这里尤其要注意SFP模块内部的激光驱动器输入端TX±需要交流耦合FPGA的GTP/GTX输出也是交流耦合两者之间不需要额外的耦合电容但必须保证差分阻抗为100欧姆。PCB布线时这两对差分线从FPGA引出到SFP座子的距离越短越好尽量控制在1英寸以内避免过孔换层保持完整的参考地平面。电源方面SFP模块对3.3V电源的纹波较为敏感特别是激光驱动器的电源纹波太大会直接导致光眼图质量下降误码率升高。建议使用独立的LDO或小功率DC-DC给SFP供电并且在SFP座子附近放置10uF100nF1nF三级电容去耦。我遇到过一个问题刚开始把SFP供电和FPGA的3.3V共用同一路电源实测误码率在1E-9左右偶尔还能调到1E-8后来单独供电后稳定在1E-12以下。这个细节对长期运行的可靠性影响还是非常大的。3.2 AXI 1G/2.5G Ethernet Subsystem的IP配置在Vivado里创建AXI 1G/2.5G Ethernet Subsystem IP核后需要按要求配置几个关键参数。我以Vivado 2021.1版本为例把每个选项的含义和推荐配置列出来Component Name设置IP实例名最好用soc_eth1这种语义化的名称后续地址映射和代码引用时能减少混淆。Shared Logic选项有三个子选项Include shared logic in core、Include shared logic in example design、Both include shared logic in core and example design。这里我推荐选Include shared logic in core因为IP核内部的GT参考时钟和复位逻辑会直接集成到IP里减少了顶层额外处理。如果选成example design的方式IP会暴露更多的接口信号需要自己管理GT参考时钟的例化和复位顺序容易出错。Physical Interface选择SGMII模式时速率选项勾选1Gbps即可。如果项目可能需要2.5G可以考虑直接勾选支持两个速率但代价是占用的GT资源会更多。MAC Options里RX和TX的接口类型有AXI4-Stream、AXI4-Lite、AXI4等选项。推荐选AXI4-Stream因为这是数据通路的最高效接口配合DMA或者自研协议栈都很方便。AXI4-Lite主要用于配置管理寄存器也可以开放出来。CRC Checking/Generation默认勾选即可MAC层自动处理CRC校验。配置界面里还有一个很关键的选项叫Clocking Options这里需要根据你的系统时钟情况选择是让IP内部自己管理时钟还是从外部输入参考时钟。我的建议是如果板上有独立的156.25MHz或125MHz GT参考时钟就选外部输入如果没有可以用系统时钟分频得到。不过GT参考时钟必须满足Serdes的精度要求最好是专用的参考时钟引脚输入。3.3 GT参考时钟与复位逻辑设计GT参考时钟是整个方案最容易踩坑的地方。AXI 1G/2.5G Ethernet Subsystem在1G SGMII模式下内部GT参考时钟要求是125MHz因为SGMII的线速率是1.25Gbps包含8B/10B开销对应参考时钟是1.25GHz/10125MHz。如果使用2.5G SGMII则线速率是3.125Gbps参考时钟就需要156.25MHz。这里的“参考时钟”不是直接给IP用的用户时钟而是GTP/GTX收发器的模拟参考时钟用于内部锁相环生成高速串行时钟。Vivado的约束文件里必须对GT参考时钟引脚做时钟约束否则时序分析会报错。最稳妥的做法是在XDC里创建一个create_clock约束定义GT参考时钟频率为125MHz或156.25MHz然后让IP核内部的BUFG_GT把低速时钟引入全局时钟网络供MAC侧使用。复位逻辑也有讲究。AXI Ethernet IP的复位信号需要在初始化完成之后才能释放。IP核有一个gt_rxresetdone和gt_txresetdone信号表示GT收发器已完成复位。我的经验是在顶层把这些信号与系统复位逻辑组合形成一个“GT复位完成后再释放MAC复位”的复位链。不按这个顺序操作IP的寄存器可能写不进去数据通路也会异常。4. UDP数据通路的搭建与协议栈实现4.1 FPGA侧UDP协议栈的分层设计AXI Ethernet Subsystem的接口是AXI4-Stream它对外的数据格式是完整的以太网帧包括前导码、目的MAC、源MAC、EtherType、Payload和CRC。这意味着UDP协议栈需要自己做从ARP到IP到UDP的三层处理。对于这种需求通常的做法是在内部实现一个轻量级的协议栈包含ARP请求/响应、IP层校验和、UDP层端口过滤。我实现的方式是这样的内部使用一个简单的状态机数据流从MAC RX FIFO开始先用ARP模块处理ARP请求和应答然后进入IP层解析模块判断IP报文的协议类型是UDP还是ARP再根据IP地址和端口号做过滤。匹配成功的UDP数据写入用户FIFO供业务逻辑读取。发送方向则相反业务逻辑把数据打包成UDP报文加上IP头、MAC头送入MAC TX FIFO。整个协议栈的逻辑量并不大大概两三千行Verilog但设计时有一个点需要注意UDP校验和Checksum在IPv4下是可以省略的填0但很多网络调试工具会拒绝接收校验和为0的UDP报文。我自己实现在发送端计算UDP校验和其中包括伪头部源IP、目的IP、UDP长度、协议号这样兼容性最好。4.2 UDP收发状态的时序配合用AXI4-Stream接口收发以太网帧时有几个时序细节容易搞错。AXI4-Stream的协议要求tvalid和tready同时为高时数据才有效传输但如果发送端一直拉高tvalid而不等接收端的tready接收端FIFO满了就会导致数据丢失。AXI Ethernet Subsystem的TX接口要求数据帧之间必须有至少一个时钟周期的间隔即tlast拉高后在下一帧前需要拉低tvalid否则IP核内部状态机会出现错误。我在实际调试中遇到的典型现象是小包64字节连续大量发送时如果TPG测试包发生器的FIFO深度不够会出现ARP请求丢失的情况表现为上位机ping不通FPGA。排查了半天最后发现是发送端在IP层还没组装完整个UDP报文时就先把tvalid拉高了导致MAC层收到一个不完整的帧头。这个问题后来通过在协议栈里增加一个“帧组装完成再拉高tvalid”的状态位解决了。4.3 上位机与FPGA的UDP对接上位机调试工具的选型也能影响效率。我自己用的比较多的是Python的socket库和通用网络调试助手。用Python做回环测试非常方便几十行代码就能完成大数据包的吞吐测试import socket import time # 上位机接收FPGA发送的UDP数据 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((0.0.0.0, 8080)) sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024) total_bytes 0 start_time time.time() while True: data, addr sock.recvfrom(4096) total_bytes len(data) elapsed time.time() - start_time if elapsed 5: rate_mbps (total_bytes * 8) / (elapsed * 1e6) print(f接收速率: {rate_mbps:.2f} Mbps) total_bytes 0 start_time time.time()这里有一个容易忽略的点操作系统默认的UDP接收缓冲区比较小通常几十KB如果FPGA发送速率超过上位机处理能力操作系统会直接丢弃来不及处理的UDP包导致测出来的吞吐量明显偏低。所以测试前要调大SO_RCVBUF并且在代码里用独立的接收线程持续读socket。另外Windows系统和Linux系统的UDP协议栈性能差异还挺大Linux下经过优化后可以轻松跑满千兆Windows则需要做更多调优比如卸载不必要的网络协议驱动。5. 工程落地中的时序约束与常见坑排查5.1 XDC约束的完整写法参考Xilinx IP核生成时通常会自动带一部分约束但外部接口和时钟约束还是需要自己写。以下是我在这套方案里实测过的完整XDC约束关键片段直接拿去改改就能用# SFP光口GT参考时钟约束125MHz, 连接到GTP/GTX的refclk引脚 create_clock -name gt_refclk -period 8.000 [get_ports gt_refclk_p] # 用户侧MAC时钟约束IP核内部生成的rx_mac_aclk, 输入到用户逻辑 # 该时钟由IP核内部自动产生, 需要在约束里声明为生成的时钟 create_generated_clock -name rx_mac_clk \ -source [get_pins soc_eth1/inst/gt_rxoutclk] \ -divide_by 1 [get_pins soc_eth1/inst/rx_mac_aclk] # 异步FIFO跨时钟域约束 set_clock_groups -asynchronous \ -group [get_clocks -include_generated_clocks gt_refclk] \ -group [get_clocks -include_generated_clocks [list tx_mac_clk rx_mac_clk]]严格来说GT参考时钟如果在硬件上连接到专用参考时钟引脚如MGTREFCLK0Vivado会自动识别并创建时钟但如果走的是普通IO必须手动create_clock。另外跨时钟域处理一定要在代码层面用异步FIFO隔离不要在约束层面强行set_false_path跳过CDC检查这样的代码很容易在生产环境出问题。5.2 调试中遇到的典型问题速查把我在这个项目中遇到过的故障整理成了速查表方便排查时对照现象可能原因排查方法解决方案上位机ping不通FPGAARP没有得到响应ILA抓ARP报文看FPGA是否收到ARP请求检查MAC地址过滤逻辑和IP层状态机链路指示灯亮但无法通信自动协商失败或SGMII配置不匹配查看IP核状态寄存器值确保对端设备工作在相同模式1000BASE-X/SGMII吞吐量低500Mbps上位机UDP接收缓冲不足用iperf3测试对比调大SO_RCVBUF用多线程接收偶发CRC错误PCB差分布线阻抗不匹配查看信号完整性问题优化Layout减少过孔保证100欧差分阻抗光模块插上后LOS告警光模块不兼容或光功率太低用光功率计测试检查SFP管理寄存器更换模块检查光纤连接FPGA复位后一片空白GT复位序列不对检查gt_rxresetdone/gt_txresetdone时序按正确顺序释放MAC复位还有一个容易忽略的问题SFP模块的I2C管理接口虽然不影响主数据通路但如果完全不初始化某些模块可能默认不使能光发射TX_Disable拉高导致光口完全无光输出。处理方式是上电后在FPGA里写一个简单I2C控制器读SFP的A0地址的寄存器0x02确认TX_Disable状态必要时写入使能值。或者在硬件上将TX_Disable引脚接地强制使能。5.3 资源占用与性能评估我用的芯片是Xilinx Artix-7 XC7A100T系列这套AXI Ethernet Subsystem 轻量UDP协议栈配置下资源占用情况如下资源类型占用数量利用率LUT32185.1%FF24561.9%BRAM128.5%GTP112.5%BUFG46.2%可以看到这套以太网方案的资源开销不大剩余的BRAM和LUT可以腾出来做业务处理逻辑比如图像缩放、协议转换等。如果你想降低BRAM占用可以把FIFO深度从默认的16KB调小到4KB但要注意在突发大数据包时可能会丢包需要根据实际业务特性权衡。关于性能1G SGMII模式下理论最大线速率为1Gbps减去前导码、帧间隙、帧头和CRC等协议开销实际UDP有效载荷吞吐率大概在940Mbps左右实测iperf3测试结果与理论值基本吻合。5.4 21套工程源码的组织方式考虑到这个方案做出来后会把源码分享给后来者我在工程组织上花了一点心思。源码包里一共包含了21套完整的Vivado工程它们不是简单复制而是按使用场景做了区分基础入门部分第1到第4套工程是从最小系统开始分别对应Loopback测试、ARP测试、UDP接收测试、UDP发送测试适合第一次接触这套方案的朋友逐步上手。带用户逻辑的参考设计第5到第12套工程分别在UDP数据通路前后挂接了不同类型的外设比如ADC数据采集、FIFO转UART、GPIO远程控制、图像帧缓存等每个工程都包含完整的约束文件和测试脚本。多速率与扩展方案第13到第21套工程覆盖了不同芯片型号Artix-7、Kintex-7、Zynq-7000、不同GT速率1G/2.5G、以及结合DMA和嵌入式处理的更高阶用法。这套工程的目录结构建议按Vivado标准流程组织每个工程目录下都包含sources、constraints、sim三个子目录。sources里是RTL源码和IP核导出文件constraints里是XDC约束sim里是Testbench。读取工程时直接用Vivado打开.xpr文件即可但要注意版本兼容性这些工程我是在Vivado 2021.1下创建的如果是更高版本打开可能会提示升级IP核升级后需要重新进行综合实现。6. 源码包的快速上手与核心代码选读6.1 如何用最短时间跑通一个最小工程对于新手我强烈建议不要一上来就看最完整的第12套工程带图像缓存那种而是先从第1套Loopback工程开始。这套工程的设计思路是FPGA通过UDP收到什么数据就原封不动地发回给上位机。逻辑量最少排查链路故障最容易。具体操作流程是这样的用Vivado打开eth_loopback/eth_loopback.xpr工程文件。如果当前使用的Vivado版本高于2021.1弹出的IP升级提示先忽略直接进入Run Synthesis。综合完成后用Open Hardware Manager连接开发板下载bit文件。把SFP光模块插入SFP座用光纤跳线连接到另一台设备的SFP口或者通过SFP转RJ45模块接到交换机。在上位机上配置一个静态IP地址如192.168.1.10用网络调试助手往FPGA的IP如192.168.1.20和端口如8080发送任意数据观察有没有回包。如果回包正常说明从GT收发器到MAC、从MAC到UDP协议栈、从协议栈到上位机的整条通路是通的。接下来就可以替换成数据采集或视频发送的工程了。6.2 核心代码模块的阅读建议在工程里我最建议你花时间精读的模块是udp_tx.v和arp_rx.v这两个文件。udp_tx.v实现了完整的UDP报文组帧逻辑包括MAC头部填充、IP头部校验和计算、UDP头部填充、用户数据长度填充、以及AXI4-Stream的时序控制。这里重点看两个细节一是IP总长度的计算是在帧开始时确定通过寄存器寄存用户数据长度还是采用BFM的方式实时计算二是在填充IP头校验和时用了什么加法器结构——好的实现会把16位反码和校验用generate循环展开减少组合逻辑级数。arp_rx.v的关键点在于它如何判定“收到的ARP请求是不是发给自己的”常见的坑是把MAC地址和IP地址混在一起比较导致条件判断错误。正确逻辑是IP地址匹配且ARP操作码为1请求时才回复ARP应答应答内容要正确拷贝请求者IP和MAC到对应字段。如果你对AXI4-Stream接口不太熟悉我强烈建议先用ILAIntegrated Logic Analyzer抓一次真实的以太网帧波形。把tdata、tvalid、tlast、tkeep这几个信号加进ILA触发条件设为tvalid 1就能看到完整的以太网帧传输过程。我第一次看这个波形时顿时明白了为什么发送端必须在tlast拉高后再空出至少一个时钟周期——MAC核正是利用这个间隔来重置内部状态机。6.3 基于源码包的二次开发方向源码包不是终点你可以在此基础上按需扩展。这里说几个我实践过的扩展方向希望对大家有启发大文件分包传输原工程的UDP payload是定长1024字节如果直接传超过MTU1500字节的数据需要自己做分片。可以参考TCP的分片策略但UDP没有序号确认机制应用层需要自行加序号和总包数字段接收方根据这些信息重组。多通道并发如果数据源有多个通道比如多路ADC可以在UDP协议栈之上做通道号标记把不同通道的数据打上不同的目的端口上位机按端口分发。FPGA间互联两个FPGA板卡都采用这套方案用一根光纤直连两边都配置为1000BASE-X模式可以实现无交换机的高带宽互联。注意两端速率必须一致否则链路起不来。Zynq平台移植如果换用Zynq系列可以在PL侧保留这套UDP方案同时用PS侧跑Linux操作系统通过AXI接口把FPGA收到的数据直接映射成Linux网络设备实现软硬结合的更灵活方案。我在一个后续项目里就把这套方案移植到了Zynq UltraScale平台上把PL侧接收到的UDP数据直接通过DMA搬到DDR应用程序再从DDR读出来做实时频谱分析。整个系统处理1000Mbps数据的CPU占用率不到5%性能相当可观。7. 写在最后的一些实操体会折腾这个方案断断续续花了快三周时间最大的体会是在FPGA上直接做光口通信并没有想象中那么神秘。轴心在于对GT收发器的理解——它本质上就是一套带8B/10B编码的高速串行收发链路AXI Ethernet Subsystem把MAC和PCS/PMA整合后用户面对的只是一个简化过的AXI4-Stream接口。真正需要花时间的反而是看起来不那么“技术”的地方IP核的配置选项理解、复位时序的把握、跨时钟域的细节。有一点必须提醒如果你手头的板子没有板载SFP座子而是只有RJ45PHY这套方案就无法直接套用需要先把PHY改成通过SGMII接口连接到FPGA的GT引脚或者重新设计硬件。这也是为什么软件思想上理解很简单但实际落地时硬件平台是绕不开的前提条件。最后分享一个小技巧在调试UDP链路时我习惯在FPGA内部加一个udp_debug_register它是一组可读写的寄存器上位机通过指定的UDP端口往寄存器写值FPGA再把当前状态链路up/down、累计收发帧数、错误计数返回值上位机。这样即使数据通路出现问题也能通过这个“带外管理通道”快速定位是链路层还是应用层的问题比反复抓ILA波形高效得多。如果有需要这一步务必要加在每一个工程里它能帮你省下大量排查时间。