ARTICLE DETAIL

资讯详情

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

FPGA PCIe XDMA从配置到上板:链路调试与带宽优化实战

FPGA PCIe XDMA从配置到上板:链路调试与带宽优化实战 PCIe XDMA 这个名词干 FPGA 的人应该都不陌生。它能把 FPGA 和主机之间的数据搬运速度拉到几个 GB/s 的级别而且不占 CPU。最近我正好给一个数据采集项目搭了完整的 XDMA 工程从 Vivado 里的 Block Design 一路做到 Linux 下的上板测试中间踩了不少坑。这篇就把整个流程从零到一拆开写清楚包括 XDMA IP 怎么配、Block Design 怎么连、驱动怎么加载、带宽怎么测以及链路起不来、DMA 超时这类问题怎么排查适合刚接触 PCIe 开发的同学也适合被 XDMA 各种报错折磨过的老手查缺补漏。先说个容易劝退新人的点PCIe 协议本身非常庞杂但 XDMA 这个 IP 已经帮你把最难的传输层和事务层封装好了。你真正要关心的是“把数据放到哪个地址”“让 DMA 引擎去搬”这两件事。搞清楚这个逻辑后面所有配置和调试都会顺很多。1. 动手之前先想清楚XDMA 到底在帮你干什么很多人一上来就开 Vivado 配 IP配到一半就晕了因为界面里参数太多每个都像很重要。其实问题不在参数而在你对整个数据通路的理解。先花十分钟把架构捋顺比什么都值。1.1 一句话理解 PCIe 与 XDMA 的配合逻辑PCIe 本质上是一条高速串行总线主机 CPU 通过它访问外设外设也能通过它访问主机内存。你在 FPGA 里看到的 PCIe IP 核解决的是物理层和链路层的握手、报文封装、流量控制这些问题。而 XDMA 这个 IP 在此基础上更进一步它内部直接集成了一个 DMA 引擎可以替 CPU 完成“把数据从 FPGA 搬到主机内存”或者“从主机内存搬到 FPGA”这件事。打个比方PCIe 是一条高速公路XDMA 是一辆自动驾驶货车。CPU 只需要告诉货车“从哪装货、卸到哪”剩下的运输过程全由货车自己完成。这个“告诉货车”的动作在工程里就是配置描述符而描述符本身放在主机内存里XDMA 硬件会自动去取。所以 XDMA 工程的核心链路是这样的主机 CPU 通过 BAR 空间访问 XDMA 的寄存器把源地址、目的地址、传输长度这些信息写成描述符然后通知 DMA 引擎开始搬运搬完再通过中断告诉 CPU“活干完了”。你在 Block Design 里要做的就是把 XDMA 的 AXI 接口接到 DDR 或者其他数据源上让这条链路有数据可搬。1.2 软硬件环境准备做 XDMA 工程需要的东西其实不多但版本匹配问题会浪费你大量时间。我这次的环境供你参考FPGA 平台Xilinx VCU118 开发板XCZU28DR 芯片带 PCIe x16 金手指支持 Gen3。如果你用自己的板子注意 PCIe 引脚分配和参考时钟要提前确认。Vivado 版本2023.1。XDMA IP 在 2022.2 之后界面和默认行为有些变化用新版本能少踩一些坑。主机一台普通 x86 服务器Ubuntu 20.04内核 5.4。Xilinx 的 XDMA 驱动在较新内核上可能需要打补丁这个后面细说。工具Xilinx 官方 XDMA 驱动源码GitHub 上 xilinx/dma_ip_drivers 仓库。如果你用的是 Windows 主机流程也类似只是驱动要装 Win10 版本测试手段会少一些。我个人强烈建议用 Linux命令行工具和驱动调试都方便太多。硬件上还要注意一点开发板的 PCIe 参考时钟是 100MHz这个时钟必须由主板提供不是 FPGA 自己生成的。很多第一次上板的朋友在这个问题上折腾半天最后发现是转接卡没把 REFCLK 引出来。2. Block Design 搭建全流程环境准备好之后真正干活的时间就到了。整个 Block Design 搭建过程可以分为三步创建工程和配置 XDMA IP、连接外围接口和时钟复位、生成比特流前做检查。2.1 创建工程与 XDMA IP 配置在 Vivado 里新建一个工程器件选对之后打开 IP Catalog搜索 XDMA找到 “DMA/Bridge Subsystem for PCI Express (XDMA)”双击打开配置界面。这里有几个参数决定了后面所有行为逐个说清楚Lane Width 和 Maximum Link Speed这是 PCIe 物理层参数必须和你的硬件实际能力一致。PCIe x8 Gen3 的理论带宽大约是 8GB/s单向实际可用带宽因为 8b/10b 编码Gen3 是 128b/130b和协议开销大概在 6.5GB/s 左右。配置成 x4 Gen3 时理论带宽约 4GB/s实测能达到 3GB/s 以上就算不错了。DMA InterfaceXDMA 提供 AXI4 Memory Mapped 和 AXI4-Stream 两种接口。Memory Mapped 适合搬运整块数据到指定地址比如把 FPGA 数据写到主机内存指定缓冲区Stream 接口适合连续流式数据比如 ADC 采集的数据流。我这次用的是 AXI4 Memory Mapped因为它对地址控制更灵活测试也直观。Number of DMA Read/Write Channel默认是 2 读 2 写。这里不是越多越好每个通道都会占用 FPGA 逻辑资源而且中断和描述符管理会变复杂。普通应用用默认的 2 通道就够了。PCIe BARsXDMA 默认有 BAR0映射到 AXI4-Lite 控制寄存器和 BAR1映射到 AXI Memory Mapped 接口。BAR0 是必需的控制寄存器都在里面。BAR1 的大小决定主机能看到多大的 FPGA 地址空间我配置的是 1MB对控制和状态寄存器来说足够了。如果你要让主机直接访问 FPGA 内部的 BRAMBAR1 要留够空间。配置完这些另一个关键选项是 “AXI Address Width”。这决定 DMA 引擎能访问的主机内存地址范围一般默认 64 位就行不要改小否则大内存机器上高地址内存用不了。2.2 时钟复位与自动连接XDMA IP 配置完成后进入 Block Design 画布会看到这个 IP 有一堆接口。你不需要手动连每一个大部分可以靠 “Run Connection Automation” 自动完成。需要手动确认的是几个关键连接PCIe 的参考时钟和复位开发板上通常有固定的引脚比如 VCU118 的 REFCLK 是 MGTREFCLK0。在 Block Design 里添加一个 IBUFDS_GTE4 原语把差分参考时钟转成单端再送给 XDMA IP 的 REFCLK 引脚。这一部如果错了上板后链路永远起不来。user clockXDMA IP 会输出一个 user clock频率由 PCIe 链路速率决定。比如 Gen3 x8 时是 250MHz。这个时钟要接到 FPGA 逻辑的全局时钟网络保证 DMA 引擎和用户逻辑同步。AXI 接口M_AXI 接口是 DMA 引擎访问 FPGA 端内存的控制口要接到你希望被访问的 DDR 控制器或者 BRAM 控制器上。S_AXI 接口有时也叫 AXI4-Lite Slave是主机 CPU 访问 FPGA 寄存器的口一般也要连出来挂在 AXI Interconnect 上。连接自动化有时候会把连线搞得很乱我的习惯是先 Run Connection Automation 生成基础连接然后再手动删除不需要的接口检查时钟域是否匹配。比如 XDMA 的 M_AXI 总线是 128 位 250MHz而你 DDR 控制器的接口是 512 位 300MHz中间必须加一个 Clock Converter 和 Data Width Converter否则地址和数据带宽对不上。2.3 生成比特流前的检查清单Block Design 画完之后先别急着 Generate Bitstream。花五分钟做这几件小事能帮你省下一整天的调试时间Validate DesignVivado 会检查连接是否完整、地址是否冲突。我遇到最多的报错是 AXI 地址没分配双击 Address Editor 手动分配一下就行。检查时钟约束确认所有跨时钟域都有对应的约束文件。XDMA IP 的 example design 自带约束但你自己的用户逻辑如果没加约束时序收敛时会很痛苦。确认复位逻辑XDMA IP 的复位输出应该接一个同步复位逻辑再分发给用户逻辑。直接拿 pcie_perst_n 当全局复位用会引发各种奇怪的初始化问题。生成例化文件在 Sources 标签页右键 Block Design选择 “Create HDL Wrapper”生成顶层 Verilog 文件。检查顶层文件的端口列表确保 pcie 的差分对、参考时钟、复位引脚都连到了顶层。这些检查做完就可以确认生成比特流了。如果你板子上还有 DDR 或者其他外设我建议先只把 PCIe 和必要的逻辑烧进去做一个最小系统验证而不是一次把所有模块都加上。3. 驱动加载与上位机衔接比特流烧进去之后插到主机 PCIe 插槽开机。这个时候还没有任何驱动但主机的 BIOS 和操作系统应该能枚举到这个设备。验证链路是否建立是上板测试的第一步。3.1 Linux 端驱动安装与设备枚举在终端执行lspci如果看到类似这样的输出说明链路已经协商成功02:00.0 Memory controller: Xilinx Corporation Device 9038注意 Vendor ID 是 10eeXilinxDevice ID 可能是 9038、903f 等取决于 XDMA IP 配置时选的 Device ID。如果 lspci 里什么都看不到先别急着查驱动这往往是硬件链路问题后面专门说。接下来编译 Xilinx 官方驱动。从 GitHub 拉取代码git clone https://github.com/xilinx/dma_ip_drivers.git cd dma_ip_drivers/XDMA/linux-kernel make编译过程可能会报错尤其是在较新的内核上。最常见的错误是implicit declaration of function pci_alloc_irq_vectors这类解决方式是在 Makefile 或源码里加上缺失的头文件引用。如果实在编不过可以试一下内核 5.4 以下的发行版或者用 Xilinx 官方提供的预编译驱动包。编译成功后加载模块sudo insmod xdma.ko加载完再次用lspci -v查看设备确认 Kernel driver in use 是 xdma。另外执行ls /dev/xdma*正常情况下会出现设备节点/dev/xdma0_control # 控制寄存器接口 /dev/xdma0_c2h_0 # 通道0 从FPGA到主机 /dev/xdma0_h2c_0 # 通道0 从主机到FPGA3.2 BAR 空间与寄存器验证设备节点有了先别急着测 DMA。先验证 BAR 空间能不能正确访问。XDMA 的控制寄存器是一组偏移地址比如0x0000是 Device ID0x0004是版本号。用 devmem 工具读取sudo devmem2 0xf0000000注意地址要从lspci -v里查到的 BAR0 映射地址来填不是你随便猜的。比如我的设备 BAR0 映射到Memory at f0000000那读取0xf0000000应该能看到非 0 值。如果读出来全是 0xFFFFFFFF说明 BAR 空间没有正常映射问题大概率在链路层或者 BIOS 配置上。寄存器读出来正常说明 PCIe 链路和 BAR 映射没问题这时候才进入 DMA 测试阶段。3.3 DMA 读写测试XDMA 驱动自带一个测试工具源码通常在驱动目录的tests下面。编译后用几个简单命令就能验证 DMA 通道# 通道0 读测试从FPGA到主机传输1MB数据 sudo ./dma_from_device -d /dev/xdma0_c2h_0 -s 0x100000 -c 1 # 通道0 写测试从主机到FPGA传输1MB数据 sudo ./dma_to_device -d /dev/xdma0_h2c_0 -s 0x100000 -c 1dma_from_device 的执行过程是FPGA 端准备好数据 → DMA 引擎把数据搬到主机内存 → 驱动把内存里的数据写到文件或者终端。如果 FPGA 端没有主动发数据这个命令会一直等着所以测试前先确保用户逻辑有数据源。我一般在 FPGA 里写一个简单的计数器每个时钟加一然后把计数值灌到 AXI4-Stream 接口这样收到的数据就有规律能直观判断传输是否正确。-s参数是传输大小建议从 1KB 开始确认通路没问题再加大到 1MB、100MB避免一上来就因为描述符或地址问题把链路搞挂。4. 上板调试与性能实测DMA 通了说明最小系统没问题。但实际项目对带宽是有要求的这时候就要做性能测试和优化。这一章我把链路协商和带宽实测放在一起讲因为这两个问题在实际调试中是交织在一起的。4.1 链路协商排查从 LTSSM 看问题PCIe 链路建立是一个状态机过程叫 LTSSM依次经历 Detect、Polling、Configuration、L0 等状态。L0 是正常工作状态。如果链路起不来你往往看不到任何软件层面的错误因为操作系统根本没枚举到设备。排查链路问题第一件事是确认参考时钟。XDMA IP 的调试接口里有一个 LTSSM 状态寄存器通过 Vivado 的 ILA集成逻辑分析仪可以抓取。把 ILA 接到user_lnk_up信号上如果这个信号一直为低说明链路状态机没走到 L0。常见原因有三种参考时钟没给、差分对极性接反、PCIe 复位时序不对。参考时钟没给是最常见的。用示波器量 FPGA 的 REFCLK 引脚应该是 100MHz 的差分时钟。如果量不到检查主板 PCIe 插槽和转接卡的走线。差分对极性接反的话链路会卡在 Polling 状态。Xilinx IP 有自动极性翻转功能默认是开启的但如果你用自己的约束或者板子走了长线偶尔会有问题。这时可以尝试在 IP 配置里检查 “Polarity Inversion” 选项。复位时序问题通常发生在自研板卡上pcie_perst_n 信号没有按规范保持低电平至少 100ms。这个问题在开发板上很少见但自己做板子时一定要按照 PCIe 规范来。链路起来了再看速率。lspci -vv里能看到 LnkSta 的速率和宽度LnkSta: Speed 8GT/s, Width x8如果显示 2.5GT/sGen1而你配置的是 Gen3说明链路协商没达到最高速率。常见原因是信号完整性差需要检查 PCB 走线、连接器质量或者降低速率要求比如改用 Gen2。4.2 DMA 带宽实测与性能优化链路正常后开始测带宽。测量方法很简单dma_from_device做一次大块传输记录耗时# 传输1GB数据打印MD5和耗时 sudo ./dma_from_device -d /dev/xdma0_c2h_0 -s 0x40000000 -c 1 -m我实测的结果是x8 Gen3 链路下单通道单向传输能达到 5.2GB/s 左右接近理论峰值。如果你测出来只有几百 MB/s优先检查以下几个点第一描述符数量。XDMA 默认每个通道的描述符环有 64 个描述符每个描述符最大传输 4MB。如果你的传输请求超过了描述符环容量驱动会拆成多次请求每次请求之间都有延迟。把描述符数量调到 256 或 512性能会有明显提升。第二中断方式。XDMA 支持 MSI-X 中断驱动默认启用。但有些主板对 MSI-X 支持不好会导致中断风暴或延迟增加。可以尝试用 MSI 或传统 INTx 中断对比一下。我遇到过一个主板MSI-X 下只有 3GB/s换成 MSI 后反而跑到 5GB/s。第三CPU 亲和性。DMA 完成中断和 memcpy 操作如果落在不同的 CPU 核上会有额外的 cache 同步开销。用taskset把测试进程和中断绑到同一个 NUMA 节点性能能提升 10% 左右。如果目标是最大化带宽还要注意一个点XDMA 引擎处理大块连续传输的效率最高所以尽量让驱动分配大块连续内存。xdma 驱动默认使用dma_alloc_coherent已经保证了物理连续。但如果你自己写用户态程序用posix_memalign分配内存后传给驱动驱动内部会做映射性能可能不如驱动内部的预分配缓冲区。5. 常见问题与排查技巧实录这一章是多年经验里踩坑最多的部分。我把高频问题和排查方法整理成速查表再挑几个值得单独展开的深坑细说。5.1 问题速查表现象可能原因排查方法与解决思路lspci 看不到设备链路没起来、参考时钟未接、复位时序异常检查 REFCLK 是否 100MHz检查 pcie_perst_n用 ILA 抓 user_lnk_up链路只有 Gen1 速率PCB 信号完整性差、插槽接触不良重新插拔板卡检查金手指必要时降级使用 Gen2BAR 空间读全 FBAR 映射失败、AXI 接口无响应确认 lspci -v 的 BAR 地址验证 Block Design 里地址分配有效DMA 传输超时描述符地址错误、源地址无数据、DDR 未初始化用dmesg查看驱动报错检查 FPGA 端 DDR 控制器状态确认 AXI 总线连接驱动加载失败内核版本不兼容、设备 ID 不匹配查看/var/log/kern.log在 xdma 驱动源码里添加自己的 Device ID中断风暴导致 CPU 占用高MSI-X 配置异常、通道中断没清理临时关闭 MSI-X改用 MSI 中断验证检查用户逻辑是否正确清除中断标志传输数据出错校验不一致AXI 数据位宽转换错误、跨时钟域亚稳态检查 AXI Data Width Converter 配置确保两端 endianness 一致5.2 一个值得重视的坑描述符内存一致性XDMA 驱动在 DMA 传输中、主机侧的描述符环是驱动分配的内核缓冲区DMA 引擎会从这里读取传输任务。问题在于CPU 缓存和 DMA 引擎之间的数据一致性处理。Linux 内核的 DMA API 框架已经帮你处理了 cache coherence。dma_alloc_coherent分配的内存是 uncached 映射CPU 写入的修改会直接落到内存不会被缓存滞留。这块通常没问题。具体到 XDMA 驱动它使用的是dma_pool来管理描述符缓冲区逻辑上也是安全的。但在实际调试中我遇到过一个诡异的现象dma_to_device写数据的时候偶尔会有几个字节的数据不对。后来发现是我在用户态程序里用mmap直接映射了/dev/xdma0_h2c_0设备文件绕过了驱动内部的安全映射机制导致 cache 一致性问题。换成驱动自带的测试工具通过 read/write 系统调用问题立刻消失。所以调试阶段尽量用官方测试工具别一开始就自己写 mmap 版本。5.3 中断不触发的排查思路DMA 传输完成了但 CPU 那边中断没来驱动就会一直等到超时。排查步骤可以这样先用cat /proc/interrupts看 xdma 设备的中断号分配情况。如果显示PCI-MSI说明 MSI-X 已经启用如果中断计数始终是 0说明中断没有真正上报。接着检查 FPGA 侧。XDMA 的 User Interrupt 寄存器偏移0x0040左右可以软件触发中断往这个寄存器写 1观察主机侧中断计数是否变化。如果软件触发中断正常说明中断链路是通的问题在 DMA 完成中断配置上检查 “Number of DMA Read/Write Channel” 和中断映射配置。还有一种情况比较隐蔽XDMA IP 里有一个全局中断使能位必需同时使能 IP 级和通道级中断。驱动加载时通常会处理但如果你改过驱动源码或者手动触发过中断相关寄存器很容易漏掉某一层。排查时对照 Xilinx PG195 文档里的中断寄存器表逐位核对。写在最后的一个实操经验这套流程走下来我最大的体会是XDMA 工程百分之七十的问题发生在链路建立之前剩下的百分之二十在地址映射和 DMA 描述符上真正复杂的协议处理几乎都被 IP 核封装掉了。所以遇到问题别一头扎进代码里先回头确认物理层和行为链路Link 有没有起来、BAR 能不能访问、AXI 通不通、数据对不对一级一级往上查绝大多数故障都能在半小时内定位。另一个想重点提醒的是如果你要自己改板子或者改 Block Design尽量在第一次上板时就做一个最小系统只包含 XDMA、必要的时钟复位、一个简单的数据源比如计数器或者 BRAM不要并行调试 DDR、以太网这些复杂模块。我之前就因为同时接了 DDR4 和 Ethernet链路没起来的时候根本分不清是 XDMA 配置的问题还是 DDR 初始化的问题白白浪费了两天。最后给一个小工具推荐调试链路状态时Xilinx 的 XDMA IP 自带一个 AXI4-Lite 接口可以直接读取 LTSSM 状态正常态值是0x11L0。你可以用 devmem 读这个寄存器快速判断链路状态比每次都上 ILA 抓波形省事得多。这个技巧在产线验证、批量调试板卡的时候特别有用值得记下来。
返回列表