ARTICLE DETAIL

资讯详情

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

FPGA上AXI到PCIe桥接实战:IP核配置、地址映射与DMA性能优化

FPGA上AXI到PCIe桥接实战:IP核配置、地址映射与DMA性能优化 1. 项目缘起与核心需求拆解1.1 为什么要在FPGA上做AXI到PCIe的桥接手里有一块FPGA开发板板子上有个PCIe金手指或者M.2接口想让它跟主机通信最直接的路径就是走PCIe总线。但FPGA内部逻辑如果用AXI总线来组织数据流就必然面临一个协议转换的问题——AXI是片上总线PCIe是板间总线两者的事务模型、地址映射方式、流控机制完全不一样。Xilinx现在叫AMD提供的AXI Memory Mapped to PCI ExpressIP核就是干这个事的它把AXI4内存映射事务翻译成PCIe的TLP包反过来也把PCIe的读写请求翻译成AXI事务。这个IP核解决的核心问题是让FPGA内部的AXI从设备或主设备能够通过PCIe总线被主机CPU访问或者让FPGA主动发起对主机内存的读写。典型应用场景包括数据采集卡、高速图像传输、雷达信号处理、金融加速卡等。适合谁看如果你已经做过基本的FPGA逻辑设计用过Vivado对AXI协议有初步了解但第一次上手PCIe IP核那这篇内容就是给你写的。1.2 这个IP核到底能做什么简单说它提供了三种接口模式AXI Memory Mapped Slave主机通过PCIe读写FPGA内部寄存器或BRAM、AXI Memory Mapped MasterFPGA主动读写主机DDR内存、以及AXI Stream可选用于高吞吐量数据流。配置的时候你可以选择只开Slave、只开Master或者两个都开。PCIe链路方面支持x1、x2、x4、x8、x16的Lane宽度速率支持Gen12.5GT/s、Gen25GT/s、Gen38GT/s具体能跑多快取决于你的FPGA型号和板卡布线。我见过不少新手一上来就想跑Gen3 x8结果板子布线不支持或者参考时钟抖动太大链路根本训练不起来。所以第一步不是急着配置IP而是先翻开发板手册确认PCIe接口的物理规格。1.3 常见误区与前置知识很多人以为配置好IP核、生成比特流、下载进去就能在系统里看到设备了实际上远没那么简单。PCIe设备需要主机BIOS/UEFI枚举需要正确的参考时钟需要PERST#复位信号还需要配置空间里的Vendor ID和Device ID跟驱动匹配。任何一个环节出问题你在系统里都看不到设备。另外AXI和PCIe的地址空间是两套体系。AXI侧有32位或64位地址PCIe侧有BARBase Address Register空间。IP核内部有一个地址翻译表你需要把PCIe的BAR地址映射到AXI地址上。这个映射关系搞错了主机读到的就是全F或者全0。提示在动手配置之前先把开发板的PCIe参考时钟频率、复位信号极性、Lane映射关系搞清楚这三个信息缺一不可。2. IP核配置参数逐项拆解2.1 基础模式选择Slave、Master还是都要打开Vivado的IP Catalog搜索“AXI Memory Mapped to PCI Express”双击进去。第一个页面就是模式选择。AXI Memory Mapped Slave模式下FPGA作为PCIe端点主机可以读写FPGA内部的AXI地址空间。AXI Memory Mapped Master模式下FPGA可以主动发起对主机内存的读写。两个都勾选的话IP核会同时提供两个AXI接口。我的建议是先只开Slave跑通枚举和基本读写再开Master。因为Master模式涉及到主机内存地址的获取需要驱动配合分配DMA缓冲区调试复杂度高一个量级。一上来就两个都开出了问题你分不清是Slave侧配置错了还是Master侧地址没对上。2.2 PCIe链路参数Lane宽度与速率Lane宽度和链路速率是绑定的。Gen1 x1的理论带宽是250MB/s考虑8b/10b编码开销后Gen2 x1是500MB/sGen3 x1是985MB/s128b/130b编码。如果你需要1GB/s以上的吞吐量至少得Gen2 x4或者Gen3 x2。但这里有个坑FPGA的PCIe硬核GT资源是固定的。比如Artix-7 XC7A100T只有1个PCIe硬核支持Gen2 x4。Kintex-7 XC7K325T有2个可以拆成两个x4或者一个x8。UltraScale系列支持Gen3 x16。选型的时候就要算清楚带宽需求。链路配置理论带宽实际可用带宽约适用场景Gen1 x1250MB/s200MB/s低速控制Gen2 x42GB/s1.6GB/s中速采集Gen3 x87.88GB/s6.5GB/s高速图像Gen3 x1615.75GB/s13GB/s雷达/金融实际可用带宽打八折是因为TLP包头开销、流控信用更新、以及主机侧DMA效率的影响。我实测Gen3 x8跑DMA读写能稳定在6GB/s左右再往上就很难了瓶颈通常在主机内存控制器。2.3 BAR配置与地址映射BARBase Address Register是PCIe配置空间里最重要的东西。IP核允许你配置BAR0到BAR5每个BAR可以设置成32位或64位可以映射到AXI地址空间也可以映射到内部寄存器。BAR0通常用来映射控制寄存器大小设成64KB或128KB就够了。BAR1或BAR2用来映射大块数据缓冲区比如DDR内存或者BRAM大小根据实际需求设。注意BAR的大小必须是2的幂次方而且要和AXI地址空间对齐。地址翻译方面IP核内部有一个AXI to PCIe Translation表。比如你把BAR0映射到AXI地址0x0000_0000那么主机访问BAR0基址0x100实际访问的就是AXI地址0x0000_0100。这个映射关系在IP配置界面里叫“PCIe to AXI Translation”填错了主机读出来的数据就全错。注意64位BAR要占用两个BAR寄存器位置比如BAR0和BAR1一起构成一个64位BAR。配置的时候别把BAR1又单独设成别的功能会冲突。2.4 中断与MSI/MSI-X配置PCIe设备向主机通知事件的方式有两种Legacy中断INTx和MSI/MSI-X。Legacy中断是电平触发共享中断线效率低。MSI是消息信号中断通过写特定地址的TLP包来触发支持多个向量。MSI-X更灵活支持更多向量和独立的地址数据对。IP核支持MSI和MSI-X。建议至少开MSI向量数设成4或8。如果你做的是多通道数据采集每个通道一个中断向量那就开MSI-X向量数设成通道数。中断配置不对的话驱动里request_irq会失败或者中断来了但ISR不执行。2.5 参考时钟与复位参考时钟频率通常是100MHz或125MHz具体看板卡设计。参考时钟的抖动要求很严Gen3的话RMS抖动要小于1ps。如果你用的晶振质量一般链路训练可能时好时坏。复位信号方面PERST#是主机发给设备的全局复位低有效。IP核还有一个AXI复位用来复位AXI接口逻辑。这两个复位要分开处理PERST#复位整个PCIe硬核AXI复位只复位用户逻辑。我见过有人把两个复位接在一起结果主机一复位用户逻辑也跟着复位DMA传输直接断掉。3. 实操过程与关键环节实现3.1 Vivado工程创建与IP核例化先建一个Vivado工程选对FPGA型号。然后Add IP搜索“AXI Memory Mapped to PCI Express”。双击配置按上一章说的选好模式、Lane宽度、速率、BAR、中断。配置完成后IP核会生成一个示例设计Example Design。我强烈建议先跑一遍示例设计看看它的顶层是怎么连的时钟怎么接的复位怎么处理的。示例设计里通常包含一个AXI BRAM Controller和一个BRAM主机可以通过PCIe读写这个BRAM。跑通示例设计你就成功了一半。例化IP核的时候注意时钟域。PCIe硬核输出一个user_clk通常是125MHz或250MHzAXI接口就跑在这个时钟下。你的用户逻辑如果跑在别的时钟域需要加CDC跨时钟域处理。我一般会在AXI接口后面加一个AXI Register Slice或者AXI Clock Converter把时钟域隔离开。3.2 地址翻译与BAR空间分配实战假设我们要做一个数据采集卡FPGA内部有一块BRAM用来存采样数据主机要能读取。配置如下BAR064KB32位映射到AXI地址0x0000_0000用来放控制寄存器BAR11MB64位映射到AXI地址0x0010_0000用来映射BRAM在IP配置界面的“PCIe to AXI Translation”里BAR0的翻译地址填0x0000_0000BAR1的翻译地址填0x0010_0000。这样主机访问BAR1基址0x0实际访问的就是AXI地址0x0010_0000。主机侧驱动里用pci_resource_start(pdev, 1)获取BAR1的物理地址然后ioremap到内核虚拟地址再readl/writel就能读写BRAM了。// 驱动片段示例 bar1_start pci_resource_start(pdev, 1); bar1_len pci_resource_len(pdev, 1); bar1_virt ioremap(bar1_start, bar1_len); // 读取偏移0x100处的数据 u32 data readl(bar1_virt 0x100);3.3 主机枚举与驱动加载验证比特流下载进去之后在Linux下执行lspci -v应该能看到你的设备。如果看不到先检查dmesg里有没有链路训练失败的报错。常见报错是“Link training failed”或者“No link”说明物理层有问题。看到设备后检查BAR空间是否分配成功。lspci -vv里会显示每个BAR的基址和大小。如果BAR显示为“Region not assigned”说明BIOS没有分配资源可能需要在内核启动参数里加pcirealloc。驱动加载后用devmem工具直接读写BAR地址验证数据通路。比如devmem 0xbar1_start 32读一个32位数据。如果读出来是全F说明地址翻译错了或者AXI侧没有响应。3.4 DMA读写性能测试与优化Master模式下的DMA读写是性能关键。IP核提供了AXI Memory Mapped Master接口你需要自己写一个DMA引擎或者用Xilinx的XDMAIP核它内部就用了这个IP。DMA引擎的核心是描述符环主机驱动把描述符源地址、目的地址、长度写到FPGAFPGA根据描述符发起读写。性能优化有几个点描述符批量提交一次提交多个描述符减少主机与FPGA的交互次数大TLP包PCIe的Max Payload Size设成256字节或512字节减少包头开销读写分离读和写用不同的描述符环避免相互阻塞中断合并多个描述符完成后再发一个中断减少中断次数我实测Gen3 x8下用XDMA跑DMA读单次传输1MB能到6.2GB/s。如果把Max Payload Size从128改成256带宽能提升约15%。4. 常见问题与排查技巧实录4.1 链路训练失败怎么办链路训练失败是最常见的问题。排查顺序参考时钟用示波器量参考时钟频率和幅度100MHz的话峰峰值应该在800mV以上PERST#信号确认主机侧PERST#已经释放FPGA侧收到的是高电平Lane映射确认TX/RX差分对没有接反Lane0对应Lane0电源PCIe接口的3.3V和12V供电是否正常如果dmesg里报“Link training failed”但参考时钟和复位都正常那很可能是信号完整性问题。Gen3对走线要求很高差分对阻抗要控制在85欧姆长度匹配要在5mil以内。板子设计不好的话只能降速到Gen2试试。4.2 主机读不到设备或BAR分配失败设备在lspci里能看到但BAR显示“not assigned”。这种情况通常是BIOS资源分配不足。可以在内核启动参数里加pcirealloc让内核重新分配BAR空间。或者进BIOS设置把“Above 4G Decoding”打开让64位BAR能分配到4GB以上的地址空间。还有一种情况是Vendor ID和Device ID跟驱动不匹配。IP核默认的Vendor ID是0x10EEXilinxDevice ID是0x7024或0x8018之类的。你的驱动里pci_device_id表要跟这个对上否则驱动不会probe。4.3 AXI读写超时或数据错误主机能读写BAR但数据不对。排查思路地址翻译确认IP核里的翻译地址和驱动里的偏移量一致AXI响应用ILA抓AXI接口的RRESP和BRESP看是不是SLVERR或DECERR位宽匹配AXI数据位宽和PCIe数据位宽要匹配64位AXI对64位PCIe字节序PCIe是小端AXI也是小端一般不用转换但如果你中间加了字节交换逻辑就要检查我遇到过一次主机读出来的数据高16位和低16位反了查了半天发现是AXI Interconnect的位宽转换配错了。4.4 中断收不到或频繁触发中断收不到先检查MSI使能位有没有置上。在配置空间里MSI Control Register的bit0是MSI Enable驱动里要写1。然后检查MSI Address和MSI Data有没有正确写入。频繁触发的话通常是中断清除逻辑有问题。FPGA侧的中断源要手动清除否则中断线一直拉着主机就会一直进ISR。我一般会在ISR里先清FPGA的中断标志再清主机的中断状态。问题现象可能原因排查方法lspci看不到设备链路训练失败查dmesg量参考时钟BAR not assignedBIOS资源不足加pcirealloc开Above 4G读数据全F地址翻译错误检查IP核翻译表中断收不到MSI未使能查配置空间MSI EnableDMA带宽低TLP包太小改Max Payload Size4.5 独家避坑经验坑一参考时钟用了普通晶振。Gen3对参考时钟抖动要求是RMS 1ps普通晶振可能到3ps链路训练时好时坏。换专用时钟芯片或者降速到Gen2。坑二AXI复位和PERST#接在一起。主机一复位DMA传输就断驱动里要重新初始化。正确做法是分开PERST#只复位PCIe硬核AXI复位由用户逻辑控制。坑三BAR空间设得太大。有人把BAR1设成256MB结果BIOS分配不了设备直接枚举失败。BAR空间按实际需求设别贪大。坑四忘了加AXI Register Slice。AXI接口直接连到用户逻辑时序跑不过Vivado报timing violation。加一级Register Slice时序立马改善。坑五DMA描述符地址没对齐。PCIe DMA要求描述符地址4KB对齐没对齐的话DMA引擎直接挂死。驱动里用dma_alloc_coherent分配的内存天然对齐自己malloc的要手动对齐。5. 性能优化与进阶技巧5.1 提升DMA吞吐量的几个手段除了前面说的增大Max Payload Size和批量提交描述符还有几个手段使用C2H和H2C独立通道XDMA IP核支持独立的C2HCard to Host和H2CHost to Card通道读写互不干扰开启Descriptor Bypass小数据传输时绕过描述符直接写寄存器触发降低延迟调整Outstanding能力增加AXI Outstanding事务数让更多请求同时在途使用AXI Stream接口如果数据是流式的用AXI Stream比Memory Mapped效率更高我做过一个图像采集项目从Sensor到FPGA到主机用AXI Stream DMAGen3 x4能跑到3.2GB/s足够4K 60fps传输。5.2 地址映射的灵活运用有时候我们需要动态改变地址映射。比如FPGA内部有多个BRAM块主机想访问不同的块。可以在IP核的翻译表里预留多个映射项或者用AXI Interconnect做地址解码把不同的AXI地址段路由到不同的BRAM。还有一种做法是用BAR0做窗口BAR1做数据。主机先写BAR0的窗口寄存器选择要访问的BRAM块然后通过BAR1读写数据。这样只需要一个BAR就能访问多个块节省BAR资源。5.3 与XDMA IP核的配合使用Xilinx的XDMA IP核内部就例化了AXI Memory Mapped to PCI Express但它封装了DMA引擎、描述符管理、中断处理用起来更方便。如果你不需要自己写DMA引擎直接用XDMA就行。XDMA的配置界面里PCIe参数和AXI参数是分开的。PCIe侧选Lane宽度和速率AXI侧选接口位宽和时钟频率。AXI时钟频率建议至少125MHz低了带宽上不去。XDMA还支持AXI Stream接口做视频流传输很方便。5.4 调试工具与ILA使用技巧调试PCIe和AXIILA是必不可少的。我一般会在以下几个点插ILAAXI接口抓AWVALID、AWREADY、WVALID、WREADY、BVALID、BREADY看事务是否完成中断信号抓中断触发和清除的时序DMA描述符抓描述符的读写地址和长度ILA的采样深度设大一点比如8192不然抓不到完整的事务。触发条件设成AWVALID AWREADY这样每次写事务都能抓到。提示ILA的时钟要用user_clk别用系统时钟否则跨时钟域抓不到。6. 从项目实战中积累的经验6.1 一个数据采集卡项目的完整复盘去年做了一个8通道数据采集卡FPGA用Kintex-7 XC7K325TPCIe Gen2 x4主机是Linux。FPGA内部8个ADC通道每个通道1MSPS16位。数据先存到BRAM然后通过DMA传到主机。配置IP核的时候BAR0映射控制寄存器BAR1映射BRAM。DMA用XDMAC2H通道。中断用MSI8个向量每个通道一个。调试过程中遇到的最大问题是DMA带宽上不去只有800MB/s远低于Gen2 x4的理论值2GB/s。后来发现是描述符提交太慢驱动里一次只提交一个描述符等DMA完成中断再提交下一个。改成一次提交64个描述符带宽直接到1.6GB/s。另一个问题是中断延迟大8个通道轮流触发中断主机CPU占用率高。后来改成中断合并8个通道的数据都准备好后发一个中断驱动里一次读取所有通道数据。CPU占用率从30%降到5%。6.2 新手最容易忽略的细节细节一PCIe配置空间的只读寄存器。Vendor ID和Device ID是只读的IP核里设好之后就不能改了。Class Code也要设对不然驱动匹配不上。细节二AXI接口的ID信号。AXI的AWID、ARID、BID、RID要处理好多事务并发时ID用来区分响应。如果ID没接对响应会乱序。细节三复位后的初始化序列。PERST#释放后PCIe硬核需要一段时间做链路训练这段时间AXI接口不能发事务。IP核会输出一个user_reset信号用户逻辑要等这个信号释放后再开始工作。细节四电源管理。PCIe有L0、L1、L2等电源状态主机可能会让设备进入低功耗状态。如果你的设备不支持要在配置空间里把Power Management Capability的相应位关掉。6.3 后续扩展方向这个IP核跑通之后可以往几个方向扩展多设备级联用PCIe Switch连接多个FPGA做分布式采集SR-IOV虚拟化场景下一个物理设备虚拟出多个VF每个VF独立DMACXL如果FPGA支持CXL可以用CXL协议做内存扩展延迟比PCIe更低高速接口融合PCIe 100G以太网做异构计算加速卡我个人觉得SR-IOV是下一个值得投入的方向。云游戏、云AI推理都需要硬件虚拟化SR-IOV能让多个虚拟机共享一个FPGA加速卡资源利用率高。6.4 一些实用的调试命令Linux下调试PCIe设备这几个命令很常用# 查看PCIe设备列表 lspci -vv # 查看设备树 ls /sys/bus/pci/devices/ # 查看BAR空间分配 cat /sys/bus/pci/devices/0000:01:00.0/resource # 读写配置空间 setpci -s 01:00.0 COMMAND # 查看中断 cat /proc/interrupts | grep 设备名 # 查看DMA映射 cat /sys/kernel/debug/dma-api/dumpsetpci可以读写配置空间调试的时候很有用。比如setpci -s 01:00.0 0x04.w0x0006可以把Command寄存器的Memory Space和Bus Master使能打开。6.5 最后分享几个小技巧技巧一用devmem快速验证BAR读写。不用写驱动直接devmem 0xbar_addr 32就能读devmem 0xbar_addr 32 0x12345678就能写。快速验证地址翻译对不对。技巧二ILA触发条件用组合。比如AWVALID AWREADY AWADDR 32h1000只在特定地址写的时候触发减少无用数据。技巧三Vivado的Report PCIe。综合实现之后Vivado会生成一个PCIe报告里面有时序余量、链路训练状态、功耗估算。跑完implementation之后一定要看这个报告。技巧四参考时钟用差分探头量。单端探头量差分时钟波形会失真看不出真实的抖动。用差分探头带宽至少1GHz。技巧五驱动里加pci_set_master。忘了调这个函数DMA根本发不出去。pci_set_master会设置Command寄存器的Bus Master位必须调。这些经验都是我在实际项目中踩坑踩出来的有些坑踩一次就记住了有些坑反复踩。希望你看完能少走点弯路。PCIe和AXI的调试确实复杂但一旦跑通后面就是复制粘贴的事了。
返回列表