ARTICLE DETAIL

资讯详情

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

FPGA与PC互联的PCIe DMA架构:XDMA原理、配置与调试实战

FPGA与PC互联的PCIe DMA架构:XDMA原理、配置与调试实战 1. 为什么选XDMAPCIE桥接与DMA的架构取舍1.1 XDMA IP到底帮你解决了什么做FPGA和PC互联的人基本都绕不开Xilinx的XDMA全称是DMA/Bridge Subsystem for PCI Express。这几年我前后在多个项目里用过它从最初的Zynq-7000到后来的Kintex UltraScale从PCIe Gen2 x4到Gen3 x8也算踩了不少坑。这篇笔记就把我对XDMA的理解、配置流程和调试经验整理一遍给正要入坑或者卡在某一步的朋友做个参考。先说说XDMA解决了什么问题。PCIE接口本身只是一个物理层和事务层协议它定义了数据怎么在PC和FPGA之间搬运但搬运完之后数据往哪儿放、怎么通知软件、怎么管理多个传输请求这些事协议不管。传统做法是在FPGA里自己写一个PCIE硬核的封装逻辑再写一套DMA控制器工作量相当大。XDMA这个IP做的事情就是把PCIE硬核、DMA引擎、描述符管理、中断控制全部打包成一套现成的方案你只需要关心数据从AXI接口进来之后怎么处理。用XDMA的时候你实际上得到了两条路一条是AXI-MM模式PC端发起读写FPGA端看过去就是一块内存映射的区域PC的CPU可以直接读写这块空间另一条是AXI-Stream模式数据以流的方式进出适合视频、网络包这种天然流式的数据。这两条路的本质区别决定了你整个FPGA侧的逻辑架构怎么搭。1.2 理解AXI接口Bridge模式与DMA模式的差异XDMA内部挂了一个PCIe硬核然后通过AXI接口往外接用户逻辑。这个AXI接口支持AXI4-MM和AXI4-Stream两种你在配置IP的时候可以二选一也可以把AXI4-Lite单独分出来做控制寄存器访问。Bridge模式也就是不使能DMA引擎的模式PC对FPGA的访问就像访问一块内存条。FPGA侧提供一个AXI Slave接口PC的CPU发起读或写请求事务层封包发到FPGAXDMA把它转成AXI的读写请求。这个模式适合哪些场景呢比如你想在FPGA里做一块共享缓存、一个寄存器堆或者跑一个软核PC直接对着这块地址空间读写就行。最大优点就是延迟低不需要软件维护描述符CPU的每次读写都是一次实时的访问。DMA模式则是另外一回事。PC软件只需要把数据在内存中的地址、长度告诉XDMA剩下的搬运由硬件引擎完成。这里有个关键概念叫描述符DMA引擎本身不知道数据在哪儿它从一个环形缓冲里读取描述符每个描述符记录了一段数据的源地址、目的地址和长度。XDMA内部有专门的描述符抓取逻辑它会自己去PC的内存里把描述符读回来然后发起搬运。搬运完成后XDMA写一个完成状态到描述符里然后通过中断告诉软件。这两个模式的选择要根据数据量来定。如果PC和FPGA之间只是偶尔读写几百字节的寄存器用Bridge模式就够简单可靠如果是要持续传输视频流、高速采样数据那就必须上DMA。我在一个数据采集项目里刚开始用Bridge模式结果PC端CPU轮询读数据的开销高得离谱后来切到DMA模式吞吐直接翻了几倍。这个教训就是别嫌DMA麻烦大数据量传输它才是正道。2. IP配置与硬件搭建从Vivado到板卡2.1 XDMA配置要点与实战参数在Vivado里例化XDMA第一眼看到那个配置界面可能会有点懵选项非常多。挑几个影响全局的选项说一下。第一个是PCIe协议配置。根据板卡上的PCIe硬核版本选择Gen2还是Gen3通道数选x1/x2/x4/x8。这里要注意通道数和速率决定了理论带宽但最终跑不跑得满还取决于DMA配置和用户逻辑。我实测过Gen3 x8的理论带宽接近64Gbps但实际AXI侧跑满也就40Gbps左右瓶颈通常在DMA描述符的处理效率和AXI总线的仲裁上。第二个是DMA接口模式。前面说的AXI-MM和AXI-Stream二选一。AXI-MM适合做数据缓存、共享内存、寄存器读写统一编址AXI-Stream适合流式数据比如ADC采样输出、以太网帧。如果做传输层协议处理我倾向于选AXI-MM因为可以借用PC的虚拟地址管理来做大块数据搬运软件侧也会省心很多。第三个是描述符和队列的配置。XDMA支持H2CHost to Card和C2HCard to Host两个方向每个方向又可以配多个DMA通道。每个通道都有独立的描述符环描述符环的大小直接决定了PC软件一次能提交多少个传输请求。我把描述符环深度调到1024实测在高并发小包传输时有明显优势因为软件提交一批描述符后DMA引擎可以连续抓取不需要频繁和CPU交互。第四个是中断配置。XDMA支持MSI和MSI-X。MSI-X比MSI好在哪儿它支持多个中断向量软件可以把不同DMA通道的中断绑定到不同CPU核上减少中断处理竞争。我建议在Linux驱动里优先打开MSI-X。XDMA的Legacy INTx在共享中断线的情况下容易出问题我现在基本不用。IP配置完成后会产生几个关键文件例化模板、PCIe的约束文件、以及一个叫xdma的驱动参考目录。这个目录里带了Linux下的驱动源码后面调试很有用。2.2 BAR空间数据通路和控制通路的布局XDMA在PC侧会透出若干BAR空间BAR空间就是PC软件能看到的FPGA窗口。一般配置下XDMA会占用两个BAR一个映射到AXI-Lite从接口用来访问XDMA自己的配置寄存器另一个映射到AXI-MM从接口用来访问FPGA侧的内存映射区域。BAR空间的大小和地址映射需要注意几个地方。首先是BAR0的大小。我遇到过板卡在BIOS里识别正常但进系统后驱动报BAR资源不足问题就出在Vivado里把BAR0大小设得太大而主板的PCIe地址空间有限。一般BAR0给64KB或1MB就够用不要把整个DDR的地址空间都映射上去否则在32位系统下会直接分配失败。其次是BAR空间的类型。XDMA的DMA引擎需要的是Memory类型的BAR不能设成IO类型。IO空间在现代系统里兼容性很差Linux下很多驱动甚至不轮询IO BAR。最后是多BAR的编址关系。如果FPGA侧挂了DDR4想让PC直接读写DDR里的数据可以把AXI-MM从接口接到DDR控制器上然后把对应的BAR空间配大。但这里有个隐含的坑PC发起的访问会穿过XDMA转成AXI事务访问延迟比PC访问自己的内存高很多。做性能测试时别拿这个直接当DMA的替代品只适合做小数据量的寄存器级交互。2.3 硬件连接与常见布局误区板卡硬件层面的坑往往比逻辑层面的更难排查因为一旦做错了只能改板重投。这里结合我自己的经验提醒几个点。PCIE差分信号对需要做交流耦合耦合电容通常放在发送端或接收端附近。很多硬件工程师容易忽略的是电容的摆放位置和型号选择。电容放置过远会让回流路径变长信号质量下降电容值选得不当会直接影响PCIE链路的眼图。实测中0.1uF的0402封装是常见选择放在连接器到FPGA之间的串行位置尽量靠近发送端。此外PCIE参考时钟的100MHz差分对也要注意走线质量参考时钟抖动过大会导致链路无法协商到最高速率。PCIE的PERST信号也就是复位信号必须由主板控制不能FPGA自己一直拉低或者悬空。我在一块板卡上发现PCIE枚举时有时成功有时失败最后查出来就是PERST信号的下拉电阻太大复位释放时间超过了主板预期导致PC在枚举时FPGA还没准备好。解决方法是按PCIE规范要求PERST释放后至少延迟100ms再让FPGA开始做链路训练。还有PCIE的电源滤波。PCIE插槽提供的12V和3.3V纹波太大会直接影响SerDes的抖动指标。我见过一块板卡跑Gen3就是不稳定跑Gen2就正常后来发现是FPGA的PCIE供电网络上的去耦电容数量不足补上电容后问题消失。这类问题在逻辑上完全看不出来只能靠硬件排查建议第一次做PCIE板卡时预留足够的调试接口。3. 驱动与DMA传输流程让数据真正跑起来3.1 Linux下XDMA驱动的加载与验证硬件调通之后第一件事是看PCIE枚举是否成功。在Linux下执行lspci如果看到一个Vendor ID为10EEXilinx的Device说明PCIe物理链路已经通了。这里有个常见现象lspci能看到设备但驱动加载时报错说明枚举成功但驱动和硬件没配对好。Xilinx官方提供了xdma驱动源码编译成内核模块后insmod加载即可。驱动加载后会创建一组字符设备节点比如/dev/xdma0_c2h_0、/dev/xdma0_h2c_0还有用户中断节点/dev/xdma0_user。c2h代表Card to Host也就是FPGA往PC写数据h2c代表Host to Card是PC往FPGA写数据。看到这几个节点说明驱动已经对DMA通道完成了初始化。驱动加载成功后先别急着写复杂的数据通路先用最简单的读写验证BAR映射是否正常。用devmem2或者dd命令读写BAR空间映射出来的地址比如读回XDMA的版本寄存器如果能读到预期的值说明PC和FPGA之间最底层的地址通路是通的。这一步虽然简单但能把问题范围一下子缩小到DMA引擎或用户逻辑。接下来用官方提供的dma_test工具做一轮基础传输测试。这个工具会分配一块用户态内存发起一次DMA搬运然后比对数据。如果c2h和h2c都能通过说明驱动、描述符环、DMA引擎都是好的。我在新板卡调XDMA时固定流程就是lspci确认设备、加载驱动确认节点、dma_test确认通路。三步走完逻辑层面的排查才算真正开始。3.2 DMA传输的本质描述符与搬运流程很多刚开始接触XDMA的人会问一个问题DMA传输到底是谁把数据从A搬到B的答案是把活交给了硬件DMA引擎但发起和管理是由软件完成的。XDMA的DMA引擎工作时软件要维护一块内存区域这块区域分成多个描述符条目每个条目包含数据缓冲区的物理地址、字节数、控制位和状态字段。软件把传输请求填入描述符后写一个门铃寄存器通知硬件“有活干了”。硬件抓取描述符解析里面的地址和长度然后发起AXI读写事务。搬运完成后硬件更新描述符里的状态字段再通过中断或轮询机制告诉软件“这单干完了”。这里最关键的概念是描述符环。描述符环是一段连续的内存软件在环里写入描述符硬件按顺序读取。环的首尾指针分别由软件和硬件维护软件写描述符到尾部硬件读完头部再释放。如果软件填描述符的速度快于硬件消费的速度环会满如果硬件处理完了而软件没及时补充新描述符环会空。调试时如果DMA卡住不动优先检查描述符环的状态寄存器和门铃值判断卡在哪个环节。XDMA的Scatter/Gather特性也值得单独说。传统DMA要求物理内存连续但Linux内核分配大块连续物理内存很难。Scatter/Gather把一个大块数据拆成多个不连续的内存段每段用一个描述符表示硬件依次搬运后数据在逻辑上还是连续的。XDMA在驱动里会把用户的虚拟地址转换成物理页再拆成多个描述符对应用程序完全是透明的。用户写数据、读数据像操作一块连续缓冲区底层是散列的物理页。3.3 中断与轮询两种完成通知方式的取舍DMA传输完成的通知方式一种是中断一种是轮询。XDMA驱动默认使用中断模式硬件完成一次传输后通过MSI-X给CPU发中断驱动在中断处理函数里唤醒等待数据的应用程序。中断方式延迟低CPU占用低适合大多数应用。但在某些高性能场景下中断反而成了瓶颈。如果数据包很小、速率又极高每秒可能触发几十万次中断CPU忙于处理中断应用层反而跑不满。我做过一个实验在FPGA持续向PC发送小包数据时中断模式只能跑到约60%的带宽切到轮询模式后能跑到90%以上。这里的轮询模式不是CPU死循环读状态寄存器而是驱动在DMA完成后不立即通知用户而是累积到一定数量或超时后再批量通知减少上下文切换和中断处理的开销。在实际项目里我通常的做法是默认开中断因为写起来简单、调试方便。等性能测试发现中断占用过高时再在驱动里开一个轮询模式的参数运行期切换两边都不耽误。XDMA的中断配置还支持合并可以把多个DMA通道的完成中断聚合成一个减少中断线占用的资源。3.4 设备树和地址映射的隐藏问题在Zynq平台或者带MicroBlaze的FPGA上跑Linux时XDMA的驱动还需要正确配置设备树。设备树里要声明XDMA对应的PCIe节点、DMA通道号、中断号、BAR地址等。很多人在Zynq上把XDMA挂在PS侧的PCIE控制器下发现FPGA侧逻辑明明对但驱动就是识别不到大部分原因是设备树的compatible字段或dma-channel的个数和IP配置对不上。还有一个和地址映射相关的坑XDMA驱动在申请DMA缓冲区时用的是dma_alloc_coherent这块缓冲区是物理连续的但如果板卡上DDR的地址空间和PCIE的地址空间有重叠或映射冲突会出现DMA写进去的数据读出来是错的。这个问题的典型表现是dma_test前几百字节通过后面的数据全部错乱。排查时要确认FPGA侧DDR控制器的地址分配没有和PCIE AXI窗口冲突可能需要在地址映射里绕开一段空间。如果用的是Zynq平台还要注意PCIE和DDR之间的一致性。Zynq PS侧的DDR在PC访问时可能存在缓存一致性问题体现为FPGA写进DDR的数据PC读出来是旧的。这种情况需要在驱动里加内存屏障或者把DMA缓冲区标记为非缓存区。我在Zynq上就遇到过这个疑难杂症查了很久最后是看XDMA文档里提到Zynq的PCIE从接口默认不参与Snoop才知道要在地址空间属性里关闭Cacheable。4. 调试实战与性能优化心得4.1 常见问题排查记录调试XDMA多了很多问题其实有固定套路。我整理了几个高频问题和对应的排查方法供大家参考。现象一lspci看不到设备。这时PCIE链路训练失败的概率最大。先用示波器量PCIE参考时钟是否起振再检查PERST复位时序是否满足规范要求。如果硬件都没问题再检查FPGA配置烧写是否成功——常见现象是JTAG能连上但bit文件没烧进去PCIE硬核根本没起来。另外也要确认Vivado里PCIE的位置约束是否准确如果管脚分配和原理图不对应也会导致链路训练失败。现象二lspci能看到设备但驱动加载时报DMA通道初始化失败。这个问题多半是描述符环的物理内存分配失败。Linux内核在内存碎片化严重时dma_alloc_coherent分配大块连续内存会失败。解决办法是开机早期就加载驱动或者在系统中预留DMA内存用memmap启动参数保留一段物理内存然后让驱动从预留区分配。现象三dma_test c2h数据全零。说明DMA传输动作本身没执行或者数据没有从FPGA写出来。排查方法是用ILA抓Xilinx内部的AXI接口信号看AXI总线上的写事务是否发起。如果事务没发起说明描述符抓取或门铃配置有问题如果事务发起但数据全零说明FPGA侧用户逻辑没往AXI接口送数据。我遇到过一种情况是FPGA侧逻辑复位没释放AXI总线一直处于复位状态DMA事务自然无法完成。现象四DMA传输出错但偶尔又能传几笔。这种偶发性问题最容易让人抓狂。建议优先怀疑时钟跨域问题特别是AXI接口时钟和用户逻辑时钟不一致时跨时钟域处理没做好数据就偶发丢失。其次是背压处理AXI的ready信号和valid信号握手逻辑不完善在FIFO满或数据不足时会导致总线挂死。这类问题在仿真阶段不容易暴露因为仿真环境的时序和真实硬件有差异。现象五数据吞吐远低于理论值。先别怀疑PCIE带宽多数情况是软件侧的拷贝开销太大。Linux下驱动从DMA缓冲区把数据拷贝到用户态是一笔不小的开销。实测同样的硬件用mmap方式零拷贝比read系统调用要高出一截。如果数据需要频繁搬运建议在驱动里加mmap支持省掉一次memcpy。4.2 性能瓶颈分析与调优XDMA的性能优化我从软件和FPGA两侧都做了一些工作这里按瓶颈位置拆开说。软件侧的第一个瓶颈是描述符提交方式。如果每传一个小包就写一次门铃寄存器门铃写操作本身是PCIE上的一个MMIO写事务有一定延迟。我在Linux驱动里把多个描述符攒一批一次性写门铃实测小包场景吞吐提升非常明显。这个技术叫Doorbell Coalescing本质上是减少PCIE事务次数让DMA引擎连续干活。软件侧的第二个瓶颈是中断处理开销。前面提过小包高频传输场景下中断会压制性能。除了切轮询模式还可以考虑用NAPI机制把多个中断合并成一次软中断批量处理。XDMA驱动原生不支持NAPI我自己改过一版改动不大就是把中断处理函数里的唤醒逻辑改成挂在softirq里延迟处理收益明显。FPGA侧的第一个瓶颈是AXI总线的位宽和时钟频率。XDMA的AXI接口有128位和256位可选在相同时钟下256位数据位宽能提供翻倍的带宽。如果DDR控制器的AXI端口位宽是512位但XDMA只配了128位DMA带宽就会被卡在AXI接口上。建议在布线资源允许的情况下尽量选大位宽同时保证AXI时钟频率不低于PCIE的用户时钟频率。FPGA侧的第二个瓶颈是DDR控制器的Bank冲突。如果PC连续读写DDR的同一Bank区域行切换开销会拉低实际带宽。解决方法是做地址交错或加缓存让访问比较均匀地分布在不同Bank上。你在Vivado里打开DDR控制器的性能计数器能看到每条Bank的激活次数和预充电次数如果某一条特别高就要考虑调整地址映射策略了。还有一个容易被忽略的瓶颈是PCIE的Max Payload Size和Max Read Request Size。BIOS会设置这两个参数默认通常是256B或512B。如果软件在DMA描述符里没有正确对齐这些边界性能会打折扣。Linux驱动里可以通过PCIe配置空间把MRRS改成更大值但这需要驱动在初始化阶段做一次写配置空间的操作。4.3 XDMA没工作时的定位流程我不知道别人怎么定位XDMA问题我自己的习惯是遵循一套固定的定位顺序。第一步确认PCIE链路层是否稳定。lspci能看到设备只是第一步还要确认链路速率和带宽是否符合预期。在Linux下查看/sys/bus/pci/devices下对应设备的current_link_speed和current_link_width如果显示2.5GT/s或x1说明链路退化严重先解决硬件问题再往下查。第二步确认BAR空间映射是否正确。用lspci -v查看设备的BAR信息和Vivado里的配置对比。如果BAR地址不对说明PCIE资源分配异常检查BIOS设置或系统地址空间。第三步抓AXI接口的事务。Vivado里例化ILA或者用内部逻辑分析仪抓AXI总线的写地址通道和写数据通道。看事务是否发起、是否完成握手、响应信号是OKAY还是SLVERR。如果看到SLVERR说明AXI从端没有正确响应问题在用户逻辑而不在DMA引擎。第四步检查驱动层的状态。看dmesg里驱动的初始化日志确认DMA通道、中断、描述符环都初始化成功。再读XDMA的内部寄存器确认门铃值、头部和尾部指针是否在正常更新。如果硬件寄存器在搬迁但应用层没收到数据那问题就在驱动到应用的通知路径上。这套流程走下来绝大多数XDMA问题都能定位到具体环节。Xilinx的XDMA技术文档其实写得还算清楚但内容太分散我这里相当于帮你把整个排查路径压成一条直线。调试PCIE这种东西切忌上来就怀疑硬件或者怀疑驱动按层次逐层剥开反而是最快的方法。5. 关于XDMA后续扩展的一些方向XDMA本身用起来不难难的是把它和具体的业务场景结合起来。遇到大数据传输需求我一般会先想清楚数据形态是流式的还是随机存取的然后再决定用Stream模式还是Memory Map模式。Stream模式做协议转换很自然比如把UDP包或者图像行缓存直接灌给DMAMemory Map模式做共享内存交互则更直接。有些场景下XDMA还可以和Xilinx的其它IP协同工作。比如在FPGA里同时挂DPD IP核做信号处理再通过XDMA把处理结果搬回PC整个链路就变成了一台软件无线电的雏形。或者接100G以太网用XDMA直接做网卡卸载性能表现也相当不错。这些方向不是我胡说的都是Xilinx生态里成熟的做法很多项目里已经用起来了。我个人的建议是如果你想深入掌握XDMA不要只停留在跑通demo尽量自己改一遍驱动代码亲手在FPGA侧写一套基于AXI接口的用户逻辑。把描述符看看懂把门铃写明白把Scatter/Gather的地址拆解跑通这是整个XDMA体系里最有含金量的部分。走过这一遍你对PCIE和DMA的理解就不是停留在概念层面了而是能真正支撑起一整个项目的那种地基。
返回列表