众所周知,GPU出现的最初目的仅仅是为了图像和视频并行处理的加速,但随着OpenCL 和 NVIDIA 的 CUDA 语言和工具链的出现使 GPU 更易于使用,目前已经成为一种通用的并行加速平台。然而,也正是由于GPU是为图像和视频处理这一类应用而做出来的专用ASIC,显然在非具有图像和视频加速处理特点的其它应用场景下(如计算密集型应用),GPU的加速性能也会大打折扣。在这种情况下,FPGA 与 CPU 结合的加速卡模式应运而生了。RIFFA 并不是将 FPGA 集成到传统软件环境中的第一次尝试,也并非唯一的一种架构,但它开源的巨大优势引起了越来越多的关注。如有学者将RIFFA应用于基因测序的加速(https://github.com/BilkentCom...),做出目前唯一的成本低廉便携式快速基因测试产品。笔者认为,随着各行各业不同加速应用卸载到网卡等设备的需求越来越多,开源的RIFFA架构必将越来越普及。
RIFFA 是一种开源通信架构,它允许通过 PCIe 在用户的 FPGA IP 内核和 CPU 的主存储器之间实时交换数据。为了建立其逻辑通道,RIFFA 在 CPU 端拥有一系列软件库,在 FPGA 端拥有 IP 核。本文主要针对其中的DMA性能(Scatter-Gather DMA)进行测试。
RIFFA是一个用于PCIe设备的可重用集成框架,它的原版Github仓库链接如下:https://github.com/KastnerRG/...
branch的Gitee仓库如下:
https://gitee.com/xu\_mingwei/riffa.git
01
>DMA技术简介
随着人工智能、大数据等领域的兴起,计算机系统的规模和复杂度都有了显著提升。市面上出现了许多高速外设,比如高速固态硬盘、TCP/IP卸载引擎、高速网卡、高性能显卡和高性能GPU等。为了充分发挥高速外设的性能优势,必须确保外设与CPU之间的数据吞吐量大于外设自身的吞吐量,否则CPU接口将成为限制系统性能的瓶颈,所以CPU与外设之间的高速传输成为了亟需解决的问题,而DMA技术成为该问题的重要解决方案之一。
图1:通用处理器架构
如图所示,通用处理器的结构主要由CPU、RC、Host主存、PCIe外设和PCI外设组成。在处理器工作过程中,CPU需要与Host主存和PCIe外设进行频繁的数据交互。CPU与PCIe外设之间有两种通信方式:第一,PIO操作,特点是每次访问的数据量小,一般用于CPU对PCIe外设寄存器空间的读写操作。PIO访问的优势在于操作简单,缺点在于需要占用CPU资源,访问速率低且容易达到瓶颈。在高速外设出现之前,系统性能瓶颈在于外设自身的吞吐量,所以CPU使用PIO操作进行数据传输也不会拖累系统的整体性能。第二,DMA操作,特点是每次访问的数据量大,一般用于CPU和PCIe外设之间的大数据通信,但是通信过程中,CPU需要申请一定大小的Host主存作为DMA缓冲区。DMA访问的优点在于数据搬移过程不需要CPU干预,且能够以极高的吞吐量完成外设与CPU之间的数据传输。
综上所述,DMA技术指的是在外设与主机内存之间的数据传输过程中不需要CPU干预的一种数据传输方式。
>DMA技术分类
随着DMA技术的不断发展和演化,出现了一些成熟的DMA套件,比如Xilinx公司提供的XDMA,套件中包括DMA控制器的硬件逻辑和软件驱动,为DMA传输提供了相对完整的解决方案。这类IP已经将DMA控制器的核心模块封装在IP内部,用户只需要使用IP提供的标准数据总线接口就可以完成DMA读写操作。
DMA套件降低了DMA技术的使用门槛,缩短了项目的开发周期,提高了系统的稳定性,但是套件中提供的DMA控制器不能进行定制化的设计,灵活性较差。在综合考虑项目需求和驱动的开发难度后,最终决定自主研发定制化的DMA控制器。目前常用的DMA技术主要分为两种类型:Block DMA和Scatter-Gather DMA。
· Block DMA
Block DMA,也称阻塞式DMA,Block DMA读操作中驱动与硬件的交互流程如图所示,详细的流程描述如下:
图2:Block DMA读的交互流程
Step1:驱动向内核申请一定大小的主存作为DMA读写缓冲区。需要强调的是,缓冲区的物理地址必须是连续的,不同内核允许分配的连续内存空间的大小是不同的,通常情况下,连续内存空间的申请难度与其大小成正比。
Step2:驱动将应用缓冲区中的数据拷贝至DMA缓冲区。
Step3:驱动配置相关寄存器来启动DMA读,比如基地址寄存器、长度寄存器和控制寄存器等。驱动启动DMA读意味着将总线控制权移交至DMA控制器,同时意味着搬移数据的过程不需要CPU的干预。
Step4:DMA控制器接收到启动DMA读的命令后,根据基地址寄存器和长度寄存器的信息,向DMA缓冲区发送Mrd报文。
Step5:RC将DMA缓冲区数据封装在Cpld报文中并反馈至DMA控制器。DMA控制器接收并解析Cpld报文来获取有效数据,并统计Cpld报文中的有效数据量。
Step6:当DMA控制器接收的有效数据量与Mrd报文申请的数据量相等时,产生硬线INT中断和中断标志位。
Step7:驱动检测到INT中断,并通过PIO读操作读取中断寄存器。
Step8:控制器将中断标志位反馈给驱动,清除硬线INT中断和中断标志位。
Step9:驱动根据中断标志位识别中断类型属于DMA读完成中断。
Step10:驱动清空并释放DMA缓冲区,一次DMA读操作结束。
根据以上流程,总结两点结论:第一,DMA读写缓冲区的物理地址必须是连续的。申请大片连续内存空间的难度大,所以当传输的数据量较大时,驱动需要将数据进行拆分后通过多次DMA来传输,传输次数的增加降低了PCIe的带宽利用率。第二,硬件和驱动之间的交互逻辑简单且具有阻塞式的特点,即一次DMA操作彻底完成之前不能配置下一次DMA操作的参数。控制器在传输数据时,驱动处于空闲状态,而驱动在拷贝数据时,控制器处于等待状态,驱动与控制器无法同时处于工作状态,降低了DMA传输的效率。
· Scatter-Gather DMA
Scatter-Gather DMA,也称分散聚集式DMA,分散聚集指的是它可以将分散的内存空间通过链表的方式聚集在一起。相比于Block DMA,Scatter-Gather DMA的操作流程更加复杂,但是可以提高主机内存的利用率以及DMA传输的效率。
Scatter-Gather DMA读操作中驱动与硬件的交互流程如图所示,其中,驱动作为数据的生产者,DMA控制器作为数据的消费者。详细的流程描述如下:
Step1:驱动根据传输的数据量决定申请DMA缓冲区的大小。需要强调的是,DMA缓冲区可以由分散的连续内存空间拼接而成。比如驱动需要传输64KB的数据,驱动可以申请16个4KB的DMA缓冲区,然后将16个4KB的DMA缓冲区通过链表的方式拼接为一个64KB的DMA缓冲区。
Step2:驱动从上层协议栈接收有效数据,并将数据从应用缓冲区拷贝至分散的DMA缓冲区。
Step3:驱动申请SG缓冲区,该缓冲区负责存放SG链表,缓冲区的大小取决于SG链表中表项的个数。SG链表的组成方式多种多样,比如文献[27]中提出的一种改进的SG描述符。考虑到驱动的开发难度,本文采用一种简易的描述符构造方式:驱动利用每个分散DMA缓冲区的基地址和长度信息构成一条表项,然后将表项依次排列组成SG链表。SG链表中每一条表项都代表了一段物理地址连续的DMA缓冲区,DMA控制器根据SG链表中的表项信息,依次读取DMA缓冲区的数据。
Step4:驱动将构造的SG链表填入SG缓冲区中。
Step5:驱动将SG缓冲区的基地址和长度信息写入寄存器,并配置控制寄存器来启动DMA读操作。
Step6:控制器启动DMA读,向RC发送Mrd报文读取SG链表。
Step7:RC接收到Mrd,将SG链表封装在Cpld报文并反馈给DMA控制器。
Step8:DMA控制器接收SG链表后,产生硬线INT中断和中断标志位。
Step9:驱动接收到硬线INT中断,立即读取中断寄存器。
Step10:控制器反馈中断标志位,清除硬线INT中断和中断标志位。
Step11:驱动根据中断标志位判断中断类型属于SG缓冲区读完成中断,清空并释放SG缓冲区。
Step12:DMA控制器解析SG链表,依次读取每一条表项对应的DMA缓冲区。首先根据第一条表项对应的缓冲区的基地址和长度,向RC发送Mrd读取DMA缓冲区数据,当第一个缓冲区数据全部读取完毕时,再处理第二条表项,以此类推,直到最后一个表项对应的缓冲区数据全部读取完毕。
Step13:RC接收到Mrd,将DMA缓冲区数据封装在Cpld报文并反馈给DMA控制器。
Step14:DMA控制器接收数据并统计数据总量,当接收到的数据总量等于SG链表对应缓冲区的数据总量时,产生INT中断和中断标志位。
Step15:驱动收到INT中断,立即读取中断寄存器。
Step16:DMA控制器将中断标志位反馈给驱动后,立即清除中断和中断标志位。
Step17:驱动根据中断标志位判断中断类型属于DMA读完成中断,清空并释放所有DMA缓冲区,一次DMA读操作结束。
上述步骤就是SG模式下驱动与硬件之间的完整交互流程,相比于Block DMA,一次SG DMA的流程更加复杂。主要包括两个阶段:第一阶段是硬件读取SG链表,第二个阶段是硬件解析SG链表后根据表项读取DMA缓冲区。这样的设计虽然导致驱动与硬件的逻辑设计难度更大,但同时提升了DMA传输的效率,降低了CPU处理中断的负载,提高了CPU内存的利用率等。
图3:Scatter-Gather DMA读的交互流程
RIFFA架构中的DMA即属于Scatter-Gather DMA。
02
RIFFA架构中的FPGA部分最核心的模块是SG DMA Engine,SG DMA Engine的架构如图4所示,其中主要包括9个组件:总线转换模块、报文封装模块、预解析模块、Bar空间模块、中断控制模块、乱序处理模块、DMA RX控制模块、DMA TX控制模块以及PCIe IP。
图4:Scatter-Gather DMA引擎模块组成
RIFFA的Linux驱动文件夹下有6个C源码文件,riffa\_driver.c、riffa\_driver.h、circ\_queue.c、circ\_queue.h、riffa.c、riffa.h。
其中riffa.c和riffa.h不属于驱动源码,它们是系统函数调用驱动封装的一层接口,属于用户应用程序的一部分。
circ\_queue.c和circ\_queue.h是为在内核中使用而编写的消息队列,用于同步中断和进程;riffa\_driver.c和riffa\_driver.h是驱动程序的主体。
注意:RIFFA驱动全程采用面向对象的编程思想。
由于篇幅所限,本公众号后续将会给出RIFFA驱动的详细解读。
03
端系统SG DMA控制器的设计与实现在Xilinx开发平台(具体的FPGA芯片型号:xc7vx690tffg1761-2)上进行测试,该平台具有丰富的逻辑资源和大量高速接口,支持PCIe Gen3x8总线,理论带宽可达64Gbps。
本设计中SG DMA控制器采用PCIe Gen3x4,理论带宽可达32Gbps,上板测试的主要目的有两个:1.测试DMA控制器的基本功能是否能够正常工作;2.测试DMA控制器的读写峰值速率,判断SG DMA控制器的吞吐量能否满足万兆端系统的带宽要求,并根据性能测试结果分析影响带宽的主要因素。
>测试环境
(1)Xilinx Virtex-7开发平台一块,网线一条;
(2)64位Linux系统(内核版本:Ubuntu 16.04 LTS)主机一台、PCIe驱动和功能测试应用;
(3)64位Windows系统调试工程机一台,硬件开发软件Xilinx Vivado 2019.2;
图5:板级测试连接图
板级测试的拓扑结构如图5所示,在64位的Linux系统主机上装载了与SG DMA控制器适配的PCIe驱动和功能测试应用;Virtex-709开发板上运行端系统的硬件代码,并将两个网口直接相连形成闭环;Virtex-709开发板与64位的Linux系统主机之间采用PCIe Gen3x4总线连接;64位的Windows系统调试工程机上运行硬件开发软件Xilinx Vivado 2019.2,利用该软件将端系统的硬件代码通过JTAG接口烧写至Virtex-709开发板上,同时借用软件内部的虚拟逻辑分析仪(ILA)观察端系统运行过程中内部信号波形的变化。
>测试步骤
(1)使用PCIe延长线将Virtex-709开发板与64位的Linux系统主机连接起来,使用网线将Virtex-709开发板的两个网口连接起来;使用JTAG连接线将64位的Windows系统调试工程机与Virtex-709开发板连接起来。
(2)给Virtex-709开发板、Linux系统主机和Windows系统调试工程机供电,在调试工程机上运行Vivado并将硬件代码烧写至Virtex-709开发板,等待烧写完毕后,将Linux系统主机重启,便于主机对PCIe设备进行枚举。
(3)在Linux系统主机上输入lspci命令查看Virtex-709开发板是否被主机识别为PCIe设备;输入lsmod命令检查PCIe驱动是否装载成功;输入dmesg命令查看PCIe设备的详细信息,并核对这些信息的正确性;在功能测试应用中输入命令查看待测PCIe设备的详细信息,并核对这些信息的正确性。
(4)读写配置空间测试:在Linux主机上输入lspci -xxx命令读取PCIe配置空间详细信息,使用setpci命令向PCIe标准配置空间写入数据。
(5)寄存器读写测试:在Linux主机上运行功能测试应用,输入测试参数并启动PIO读写寄存器测试;在调试工程机上使用Vivado内部ILA观察波形。
(6)DMA读写测试:在Linux系统主机上运行功能测试应用,输入测试参数并启动DMA读写功能测试;在调试工程机上使用Vivado内部ILA观察波形;
>功能测试
测试前准备工作:根据图5中的测试拓扑结构,按照测试步骤中的(1)和(2)搭建板级测试环境如下图6所示。
图6:板级测试环境
>性能测试
下面对端系统SG DMA控制器进行性能测试。测试思路:首先,利用测试应用产生不同大小的模拟数据,驱动将模拟数据通过DMA读发送给硬件,硬件将数据通过DMA写回传至驱动;其次,分别在驱动和硬件中设置计时器,对一次DMA读写操作的时长进行计时;最终,根据DMA读写的时间和传输的数据量计算DMA读写的带宽,进而计算PCIe的上下行带宽的利用率。
PCIe带宽的利用率主要与两个因素相关:1.PCIe协议开销,其中主要包括事务层开销、数据链路层开销和物理层开销;2.DMA流程开销,一次完整的DMA读写操作中不仅包括了有效数据传输的过程,而且包括了驱动对硬件的配置操作,以及产生和响应中断的操作,这些操作耗费的时间就是流程开销。
(1)PCIe协议开销
首先说明本设计中PCIe关键的参数信息:MPS=256Byte、MRRS = 512Byte和RCB=128Byte。基于上述参数信息通过建立理论模型计算去除PCIe协议开销后剩余的有效吞吐量:
对于PCIe Gen3x4通道而言,每路通道提供8 GT/s的传输,由于物理层采用128b/130b的编码方式,所以每路通道提供的有效传输为7.87 GT/s,数据链路层的流量控制和ACK/NAK协议消耗了大约8-10%的带宽,最终剩余给事务层的有效带宽只有28.93Gbps,需要说明的是,28.93Gbps是文献[16]中给出的推荐数值。在传输TLP时,除了传输有效数据还需要传输报文开销,这些开销包括三部分:1)事务层添加的TLP头,若TLP头属于Mwr/Mrd,则TLP头长度为16字节(假设64位地址),若TLP头属于Cpld,则TLP头长度为12字节。2)数据链路层添加的序列号和CRC,长度为6字节。3)物理层添加的帧定界符,长度为2字节。
当硬件通过DMA写的方式向驱动传输大小为X的有效数据时,需要使用Mwr报文携带这些有效数据,Mwr报文中存在上述开销,有效数据所占的比例可以按照下面的公式计算得到:
其中X表示通过DMA写传输的有效数据量,以字节为单位;MPS为256字节;Mwr\_Hdr为24字节(包括事务层的16字节,数据链路层的6字节,物理层的2字节)。
当驱动通过DMA读的方式向硬件传输X大小的数据时,硬件不仅需要发送读Mrd,而且需要接收Cpld,Mrd和Cpld报文均存在上述开销,有效数据所占的比例可以按照下面的公式计算得到:
其中X表示通过DMA读传输的有效数据量,以字节为单位;MRRS为512字节;Mrd\_Hdr为24字节(包括事务层的16字节,数据链路层的6字节,物理层的2字节)。Cpld\_Hdr为20字节(包括事务层的12字节,数据链路层的6字节,物理层的2字节)。
图7:考虑PCIe协议开销后的事务层的理论有效带宽
如图7所示,使用上述公式计算去除PCIe协议开销后的有效带宽并绘制折线图,图中的锯齿状体现了发送MPS字节数据的额外开销,当数据量较小时,这个开销将更高(见本公众号之前文章:业界首个NIC中PCIe性能测试基准程序公布!)。需要强调的是,上述模型仅考虑了去除PCIe协议开销后剩余的理论有效带宽,除此之外,CPU的性能、驱动的运行和DMA的配置都会消耗一定的PCIe带宽,所以实际性能一定远低于上述计算结果。
(2)DMA流程开销
1)DMA读性能测试
DMA读性能测试原理:将整个DMA读操作划分为7个阶段,在硬件中分别为每个阶段设置一个计时器,当一次DMA读操作完成时,硬件统计出每个阶段消耗的时间以及一次DMA读操作消耗的总时间。最终根据DMA读操作消耗的时间和传输数据量计算带宽。
传输1KB数据量的DMA读性能测试如图8所示,根据Vivado抓取的计时器信号,可以看出传输过程消耗的总时间是6904纳秒,计算出DMA读带宽为141MB/s。
图8:传输1KB数据量时,统计DMA读操作时间
如图9所示,在Linux主机的终端下输入./testutil 2 0 0 16384命令启动测试应用并产生64KB的模拟数据,驱动将数据通过DMA读传输给硬件。在DMA读传输过程中,通过Vivdo抓取计时器信号,如下图所示,可以看出通过DMA读操作传输64KB数据量消耗的总时间是58432纳秒,计算出DMA读带宽为1070MB/s。
图9:传输64KB数据量,终端打印的信息
图10:传输64KB数据量,统计DMA读操作时间
根据上述实验结果可以得到一个初步结论:DMA读的带宽与一次DMA读传输的数据量相关。基于上述的结论,不断调整DMA读传输的数据量并重复进行测试,根据测试结果得到表1:
表1:DMA读性能测试表
根据表,可以得出结论:在MRRS、RCB参数和TAG个数不变的情况下,可以通过增加传输的数据量提高DMA读带宽。该结论的理论依据如下:通过增加传输的数据量而减小了DMA流程开销,即降低一次DMA读操作中与传输数据无关的操作在整个DMA读操作中的占比,从而提高下行带宽利用率。
2)DMA写性能测试
DMA写性能测试原理:将整个DMA写操作划分为6个阶段,在硬件中分别为每个阶段设置一个计时器,当一次DMA写操作完成时,硬件会统计出每个阶段消耗的时间以及一次DMA写操作消耗的总时间。最终根据DMA写操作的时间和传输的数据量计算DMA写操作的带宽。
传输1KB数据量的DMA写性能测试如图11所示,通过Vivado抓取了计时器信号,可以看出传输过程消耗的总时间是29016纳秒,计算出DMA写带宽为34MB/s。
图11:传输1KB数据量时,统计DMA写操作时间
传输64KB数据量的DMA写性能测试如图12所示,通过Vivado抓取了计时器信号可以看出传输过程消耗的总时间是60552纳秒,进而计算出DMA写带宽为1032MB/s。
图12:传输64KB数据量时,统计DMA写操作时间
根据上述实验结果可以得到一个初步结论:DMA写的带宽大小与一次DMA写传输的数据量大小相关。基于上述实验结论,不断调整DMA写传输的数据量大小并重复进行DMA写性能测试,根据测试结果得到表2:
表2:DMA写性能测试表
根据表,可以得出结论:在MPS参数不变的情况下,可以通过增加传输数据量的大小提高DMA写带宽。该结论的理论依据如下:通过增加数据量大小而减小了DMA流程开销,即降低一次DMA写操作中与传输数据无关的操作时间在整个DMA写操作时间中的占比,从而降低DMA流程开销。
除了增加传输数据量,进一步提升DMA上下行带宽利用率的方式包括:1)增加MRRS、MPS、RCB和TAG个数,降低了PCIe协议开销。2)采用MSIX中断方式,避免驱动读取中断寄存器的操作,降低DMA流程开销。
作者:郑圆圆、殷建飞、徐铭伟 图文排版:祝钊华
责任编辑:潘伟涛
原文链接:网络交换FPGA
推荐阅读
- 一种面向确定性低延迟网络数据应用的处理器-nanoPU
- 具有调节器和非理想时钟的时敏网络中的时间同步问题
- "小爱同学"之类语音唤醒芯片相关技术介绍
- 【干货】FPGA设计中大位宽、高时钟频率时序问题调试经验总结
更多IC设计技术干货请关注IC设计技术专栏。