近年来“Software Define”软件定义这个词持续火热,全球知名技术研究和咨询公司Gartner早在对2014年最有战略意义的十大技术与趋势做出预测时,便提出了软件定义一切(Software Defined Anything)的概念,他们预测这类技术会在未来三年里拥有巨大潜力,并在同行业中产生重大影响。两年后的今天回顾这一概念和技术的发展,不难看出,Software Define的确成为了行业风向标,其应用可谓无处不在。
一直关注Xilinx技术动向的工程师们对SDx这个词并不陌生,概括来讲,Xilinx的SDx指的是一系列新工具,专为系统和软件工程师而设计,可以使那些只有很少或根本没有FPGA设计经验的研发人员直接使用高级编程语言在强大的可编程硬件上进行设计,并且与那些嵌入芯片内部的或是在片外连接的业界标准处理器例如ARM或x86一起协同工作。 可以看到SDx系列目前有三个主要成员,包括SDSoC、SDAccel和SDNet。
SDSoC开发环境允许嵌入式和应用软件开发人员更广泛的利用Zynq® SoC和MPSoCs的性能,提供超过100倍的软件性能加速。SDAccel™ 开发环境针对的是OpenCL,C和C++的设计应用,与那些CPU和GPU在数据中心和医疗影像等领域的实现方案相比,利用FPGA进行加速可获得高达25倍的性能功耗比提升。SDNet是“软”定义网络的解决方案。具体来说就是SDNet结合Xilinx的全面可编程器件,打造出了“软”定义网络这样的交叉技术,从而将可编程能力和智能化功能从控制层扩展至数据层,不仅支持SDN,而且还可以突破性地支持任何软件定义网络架构。
从以上简要的介绍可以看出SDx系列的三个工具所针对的器件与市场各有侧重,但总体上都是面向软件和系统工程师的全面可编程抽象化设计工具,是Xilinx业界领先技术实力的进一步体现。其中,专门用作提高Xilinx异构Zynq SoC以及MPSoC的设计生产力而生的SDSoC开发环境就是这篇短文将要介绍的重点,在以下的篇幅中,我们会为大家分析SDSoC主要针对的市场和应用,SDSoC的各种创新,应用SDSoC的好处和优势等。
本章主要内容是介绍SDSoC,但在开始之前需要重复介绍一下Vivado HLS这个在高级抽象语言与底层硬件描述语言之间架起了一座桥梁的高阶综合工具。经过了三四年的上市推广,Vivado HLS早已不再是一个全新的工具,Xilinx已经有超过1000家成功的客户。HLS的灵活性和生产力优势是显而易见的,甚至Xilinx的IP开发部门在交付Vivado 2015.1版本及以后的部分视频IP时也都是使用HLS从C/C++语言开始设计。
Vivado HLS可以在很短的时间内生成与手工编码质量相当的RTL代码,并且允许用户将同样在C测试平台生成的测试向量用在C仿真和RTL验证中,从而大幅加速验证过程。对那些使用C / C ++描述规范的算法设计类客户来说,这是理想的解决方案,可以将其已有的各类浮点或定点算法无缝实现到FPGA硬件中,比较典型的应用就是各类视频运算、加解密等DSP算法等等。最近非常火热的FPGA+深度学习的应用,就有很多开发者使用HLS进行开发,并且取得了非常高的性能。
使用Vivado HLS可以实现真正意义上基于C语言的IP,通过HLS,我们可以把用户的C/C++以及System C算法以VHDL或Verilog的形式输出,然后通过Vivado IPI或SysGen等Xilinx的工具,整合到FPGA设计工程中去。使用Vivado HLS可以更便捷高效地完成从高阶抽象语言C/C++到Xilinx FPGA可编程逻辑硬件的设计实现过程。
Vivado HLS不仅是简单的翻译工具或是综合工具,更为重要的是,我们可以将其产生的RTL以IP的形式导出到Vivado IPI中,或者直接调用HLS生成的RTL文件到另一个RTL项目,甚至是输出到DSP设计的SysGen工程中。
之所以要花几页篇幅来介绍Vivado HLS的作用与Zynq的架构,就是为了更清楚地描述全面可编程Zynq SoC的开发流程。对于以C/C++等高阶语言为起点来进行SoC开发的用户来说,由C/C++算法开始,首先需要对软硬件进行划分,选择哪些部分放入PL进行加速,哪些部分仍然在PS用软件实现。对那些被指定放入上PL用于硬件加速的部分,还需要完成C代码到RTL IP的转换。接下来,就是完成软/硬件之间的连接,包括使用怎样的DataMover、PS与PL之间的接口如何配置等,接下来还要完成配套的软件驱动程序。所有这些往往牵扯到数个不同团队和专业人员的通力合作,需要通过数次迭代来探索最佳的实现方案和系统架构。这个流程图很清晰地描绘了SoC的开发流程,每一步都是耗时耗力的工作。
具体到每一步的工作来看,传统的Zynq 设计流程大致分为五个步骤:
首先,系统架构师来决定将哪些部分用于软件实现,哪些部分放入硬件加速,即所谓的软硬件分区。划分为硬件实现的功能将需要使用RTL 代码来开发,或是使用HLS 将C/C++代码综合成Vivado 中可实现的IP。然后,要在Vivado IPI 中搭建DataMover 和接口。后是应用软件和驱动程序的开发。这无疑是一个十分耗时的过程,需要多个部门和团队之间的设计切换。有时候,即便这样完成后的设计可以正常工作,却可能无法满足你在吞吐量、延迟或面积等方面的设计性能要求。此时就可能需要通过修改系统连接来重新搭建硬件架构来对系统性能进一步优化。但这么做,又将导致软件应用程序和驱动程序的变化。因此,你往往需要与多个软件和硬件团队紧密合作,通过多次迭代设计来试着满足最终需求。
在某些情况下,设计不能满足你的性能要求的原因在于软件性能不够,或是硬件的占用率太高。这时候你就需要返回到最初的设计,重新修改软/硬件分区方案,然后前面所说的硬件实现,系统连接,软件驱动等所有步骤都要重新再来一边,这势必要求更多的团队一起配合,改变设计来探索另一种架构,而且可能面临更多次的设计迭代,进一步拉长设计周期。毫无疑问,用在系统优化上的时间对time-to-market 的影响巨大。此外,我们也注意到在Zynq SoC 的设计中,用户最关注的部分往往是算法的最终实现和算法模块的优化,包括IP 或是软件功能块。
SDSoC工具可以自动搭建软硬件之间的通讯部分,包括DataMover、软件驱动程序和硬件连接接口。工具还可以将整个开发过程抽象到C/C++的应用层面,以C/C++为起点来进行算法开发,当然,工具也可以调用以传统的IP方法开发的算法模块,包括已经由Vivado HLS转换后的RTL IP,也包括那些本身就由Verilog或VHDL硬件描述语言编写的IP。在调用这类IP时,只需将其封装为C可调用库的形式即可。在SDSoC中,我们能够轻松进行软件/硬件的划分,用户仅需在图形化界面中用鼠标单击指定那些需要进行硬件加速的模块即可。
相比较传统SoC设计流程,SDSoC通过自动生成硬件连接和软件驱动程序大大简化了Zynq SoC和MPSoC的开发过程。它会自动调用Vivado HLS来将那些用C/C++开发的算法模块转化为Vivado可综合的RTL IP,它也可以将那些已经优化过的HDL IP模块通过C可调用库的方式进行重用。用户可以在软件中通过简单点击某个功能块将其应用到PL上进行加速来迅速修改软/硬件的分区,因此,它也有助于系统架构设计人员运行快速假设性分析来评估系统的性能和面积。在应用SDSoC之后,我们可以非常迅速地将你的设计应用在Zynq系统上,即使第一遍实现后的性能不达标,也可以使用SDSoC快速选择不同的用于硬件加速的功能块,探索不同的硬件/软件分区方案,或是通过pragma等手段来指导工具产生不同的系统配置等方法来进一步优化设计。统计显示,使用SDSoC开发Zynq系统,可以将整体开发时间从原本的数周缩短至数日甚至数个小时。
现在我们稍作总结就会发现,SDSoC开发环境提供了一个大大简化的C/C++编程体验,用户现在可以在嵌入式开发人员所熟悉的基于Eclips的IDE上完成整个Zynq SoC的开发。SDSoC带来了业界首个C/C ++的全系统优化编译器,提供系统级的Profiling特征分析,自动将软件代码放入可编程逻辑中加速,自动产生系统连接和相关的库以加速开发。SDSoC也为用户和第三方平台开发者提供了流程支持,通过提供平台描述文件的手段,可以使他们自己设计的包含有Zynq SoC的开发板在SDSoC开发环境中使用。
应用SDSoC,开发人员可以从整个设计的C/C++代码开始系统级特征分析,从而找出系统设计的瓶颈。然后用户只需选择将那些性能瓶颈的功能块放入PL中加速。SDSoC的全系统优化编译器会使用Vivado HLS自动创建RTL IP,生成最优的系统连接,配置软件驱动程序。最终的结果是FPGA比特流文件,可执行elf文件和软件的引导映像。所有这些,完全由一个基于Eclipse的嵌入式开发环境生成。
SDSoC提供给用户的是一个可以用来完成整个Zynq SoC和MPSoC开发的基于Eclipse的软件环境,这个环境对那些已经在使用DSP芯片、视频SoC 和CPU处理器的嵌入式开发人员来说是在熟悉不过的。在IDE中,用户可以简单地选择用来放入PL中加速的功能块,无需手动创建用于硬件实现的Vivado工程或是软件驱动程序。另外,已经有很多针对FPGA硬件优化过的IP库可以经由Vivado HLS导出,除了Xilinx和ARM,我们也有很多合作伙伴提供更多特定的算法库,包括视频类,加解密,OpenCV等等,帮助用户进一步提高生产力。
关于操作系统,目前的SDSoC版本中已经支持的目标平台Platform大都支持多种OS,包括Linux,FreeRTOS和Standalone,如果用户需要其他操作系统的支持,只要将所需OS打包到所用的目标平台中即可。具体做法涉及SDSoC目标平台的创建,稍后会在其他章节中做详细介绍。
Xilinx提供了很多关于SDSoC的开发文档,非常详细地介绍了SDSoC所有的内容,是学习SDSoC工具最好的资料。但是这些开发文档动辄上百页,想完整地看完需要耗费很长时间,而且并不是所有内容都经常被用到,因此本章挑选了使用SDSoC过程中较常见的操作并且给出了应用SDSoC的一些案例以便读者能够快速入门。本章不仅希望能够使读者快速掌握SDSoC,还希望读者通过查阅Xilinx的开发文档进一步学习SDSoC。
尽管SDSoC支持在Windows主机上开发Linux应用,但是创建SDSoC Platform需要一台Linux主机,本书会在其他部分介绍如何在Ubuntu系统上安装SDSoC。在Windows上安装SDSoC比较简单,因此不做介绍。
第一次打开SDSoC后会弹出一个界面要求指定工作区,SDx工作区是一个文件夹,用于存储项目,源文件和编译时产生的中间结果。您可以为每个项目定义单独的工作区,或者为不同类型的项目分配工作区。勾选Use this as the default and do not ask again指定当前的文件夹为SDSoC的默认工作区,日后可以在File ->Switch Workspace中更改
在Project Explorer的空白处右键New->SDx Project即可新建工程,SDx有三种工程类型:
在这里我们选择Application Project点击Next,然后指定一个工程名称,在这里我们暂定为lab1,点击Next。下面我们就进入选择Platform的对话框。
将我们提供的PYNQ的board_file文件夹复制到XILINX_VIVADO/data/boards/board_files路径下,PYNQ的SDSoC Platform复制到XILINX_SDX/platforms/路径下。一个SDSoC Platform包含DSA(Device Support Archive)文件,定义了硬件平台的设计、接口,以及软件平台,包括OS的boot文件以及runtime文件。在编译过程中SDSoC会调用Vivado从dsa文件读取平台信息复现硬件平台,因此需要提供板卡的board_file,后面会介绍如何自己搭建Platform。在这里我们选择PYNQ platform然后点击Next。
然后进入System Configuration对话框,下面有三个选项。
在SDSoC中打开工程后,可以将源代码导入到工程中,右键src文件夹,点击Import。Import对话框允许您指定获取文件的来源,包括从压缩包,已经存在的工程,文件系统以及Git文件夹。我们在这里选择从文件系统中导入,点击File System,在From directory一栏输入$XILINX_SDX /samples/mmultadd,注意不要导入description.json和Makefile文件,这两个文件工具可以自动生成。
在选择硬件加速函数之前,我们需要大致了解整个算法各部分消耗多少时间,SDSoC工具提供了Profiling工具,可以自动分析算法各部分消耗了多少时间。首先新建一个Application工程命名为profiling,将我们提供的代码导入到src文件夹下,在Debug模式下编译代码。
将PYNQ与电脑连接在同一个路由器下,并将PYNQ的MicroUSB接口连接到电脑。点击SDx界面右下角的Target Connection->Linux TCF Agent-> Linux Agent [default],弹出Target Connection对话框,Host一栏输入PYNQ的IP地址,点击Test Connection,如果看到Connection Successful对话框则说明连接成功。
右键小蜘蛛,选择System Debugger using Debug_Profiling.elf on Linux Agent,点击OK进入Debug模式。
使能TCF Profiler,Windows->Show View->Other->Debug->TCF Profiler
点击TCF下绿色三角箭头,弹出TCF Profiler启动界面,勾选Enable stack tracing,点击OK,按F8启动程序
等待程序运行一段时间,可以看到Profiler显示各函数运行的时间:
创建硬件加速SoC的第一项主要任务是确定应用程序的哪些部分适合在硬件中实现,即将这部分代码放在PL中运行将显著提升整体性能,在上一小节我们介绍了如何找到算法中计算量最大的部分。当您已经决定哪个函数放到硬件进行加速之后,有两种方法指定用于硬件加速函数。
选择硬件功能后,还需要选择硬件加速函数和数据传输网络的时钟频率。硬件加速函数需要通过CPU从DDR中读取数据,这项工作由专门的硬件——DMA来完成,因此也要指定这部分的频率。每个平台都有一个或多个由SDSoC Platform开发人员定义的时钟源,在开发SDSoC Plaform的时候会指定一个默认时钟源,但是无论是硬件加速函数还是数据传输网络,都可以自由选择时钟而不是限定为默认时钟,此外硬件加速函数和数据传输网络可以使用不同的时钟频率,这部分内容会在之后介绍。
指定硬件加速函数和它们的运行频率之后我们就可以编译工程了。默认界面左上角可以选择当前的编译模式,Release还是Debug,二者的优化级别不同,我们这里选择Release,注意更改编译模式后要重新添加硬件加速函数。右键lab1文件夹选择build project,或点击图片中的小锤子按钮。编译过程根据电脑配置会持续二十分钟到六十分钟不定。编译的过程中SDSoC首先调用Vivado HLS工具将硬件加速函数中的代码转化成RTL代码并打包成IP核,在Release/_sds/iprepo/repo文件夹中可以看到硬件加速函数生成的HLS IP核。
然后调用Vivado IP Integrator自动添加DMA模块生成Data motion Network并生成系统的Block Diagram,可以点击Release/_sds/p0/_vpl/ipi/syn/syn.xpr打开Vivado工程,然后Open block diagram查看,不同的SDx生成的Vivado工程路径可能会不同。
SDSoC会调用Vivado生成比特流,同时自动生成Data motion network的驱动程序,并将生成的bitstream与fsbl.elf,u-boot.elf打包在一起生成BOOT.bin文件。每一次编译工程SDSoC都会完整地进行一遍Vivado HLS->Vivado IPI->Vivado的流程,Vivado HLS正常情况下只要几分钟就可以完成,HLS运行时间过长说明代码编写有问题,整个流程中最费时间的是Vivado综合布局布线生成比特流的过程。
将sd_card目录下的lab1.elf,BOOT.BIN,image.ub文件拷贝到FAT32的SD卡中。有两种方法连接到PYNQ板卡。将PYNQ的MicroUSB连接到PC,PC会自动安装驱动。然后找到SDx Terminal右侧的“+”,选择合适的端口。需要注意以上操作的顺序:先上电,然后打开串口,波特率保持默认,端口选择COM4。
启动之后会打印内核启动信息,直到输出PYNQ login,输入用户名root,密码root。
$ mount /dev/mmcblk0p1 /mnt //进入系统后挂载SD卡
$ cd /mnt
$ ./lab1.elf
SDSoC完整地编译一次工程少则二十分钟,多则几个小时,因此Xilinx提供了性能预测模式可以让开发者快速地评估系统性能。勾选Estimate performance选项然后点击编译按钮,这个模式下SDSoC只会进行HLS的流程,并不会产生Block Diagram并调用Vivado产生比特流,整个编译过程不会持续超过10分钟。
编译完成后会弹出一个性能评估报告,包含了资源利用情况以及预测的执行一次硬件加速函数消耗的CPU时钟周期数。
还有一种办法可以得到在CPU上运行程序的实测周期数。设置好TCF Agent后(在10.2.4节有提到)点击上图中的Click Here就会运行lab1.elf文件,得到实测的CPU周期数,并给出性能对比。
在执行算法优化时需要考虑两个不同的方向:应用程序代码优化和硬件功能优化。大多数应用程序开发人员都熟悉针对CPU优化软件。这通常要求程序员分析算法的复杂性,总体系统性能和数据局部性。有许多方法指南和软件工具来指导开发者定位性能瓶颈,这些技能在优化针对SDSoC环境中的硬件加速函数时同样适用。
首先,开发者应该分别优化整个工程中每一个函数的性能,在SDSoC环境下进行优化与传统软件开发的最大不同是有一些算法会被放进PL中进行加速,这要求开发者必须考虑到算法的并行性,数据传输,内存使用以及PL的存在。然后,开发者需要指定硬件加速函数,并尽可能地让硬件加速器在数据传输时保持工作,也就是说要尽可能让硬件加速器计算跟通信同时进行。
SDSoC可以在编译过程中自动生成报告,帮助开发者详细地分析软件应用和硬件加速函数,这些报告在Report视图下可以看到,如下图所示
双击Data Motion Network Report,我们可以看到Data Motion Network的报告,包含了每个硬件加速函数与PS之间的连接信息,如使用了哪种端口(S_AXI_ACP or S_AXI_HP),哪种Datamover(DMA_SIMPLE or DMA_SG),每次运算的数据搬运量,以及用来存储数据的内存是否连续,这些名词的含义会在后面介绍。
双击HLS Report,可以看到HLS的详细信息,包括资源利用率,时钟频率以及周期数等信息,开发者要根据这些信息来优化设计。需要注意的是这里估算出来的时钟周期数与HLS估算出来的时钟周期数是有差别的。CPU端的时钟周期数=PL端时钟周期数×(CPU频率/PL频率),而且HLS估算数据加载的时间与实际数据传输时间是有区别的,因此Estimate performance里预测的CPU时钟周期数与实际情况会有一些区别,但是整体来看预测结果还是比较准确的,可以作为参考。
前面的章节已经介绍了利用HLS进行开发的流程,分成三步,首先在Vivado HLS工具中将C/C++转换成RTL代码并打包成IP核,然后在Vivado IPI中将HLS IP核与Zynq的PS集成在一起,最后在SDK中编写驱动完成整个设计。在这个流程中开发者需要根据应用特点选择合适的接口,合适的DMA,每一次调整都要先在HLS中调整,然后在Vivado更新IP,操作流程十分繁琐。此外,编写驱动进行调试也要花费大量的精力。
SDSoC替开发者完成后两项工作,使开发者可以专注在最有价值的工作当中。SDSoC完成后两步的工作也需要一些约束条件,这些约束条件以SDS pragma的形式给出。SDS pragma根据是否与数据传输相关可以分成两类,第一类SDS pragma决定了算法加速IP连接到PS的哪个端口,调用哪种类型的Data mover,在DDR中的内存如何分配,包括SDS data copy/zero_copy,data access_pattern,data mem_attribute,data sys_port和data_mover,这些指令可以形成不同的组合,不同的组合最终实现的数据传输性能差距很大。第二类SDS pragma包括SDS async/wait,partition,buffer_depth,resource以及trace,这类指令的功能各异,使用频率也不是很高,本节会着重介绍第一类指令应该如何使用。
access_pattern
语法
`#pragma SDS data access_pattern( ArrayName:) `
其中:
pattern:SEQUENTIAL或RANDOM,默认为RANDOM。
如果access_pattern是SEQUENTIAL,Array会被综合成一个streaming接口,如ap_fifo;如果是RANDOM,Array会被综合成一个RAM。
例1
`#pragma SDS data access_pattern(A:SEQUENTIAL) void foo(int A[1024], int B[1024]) `
在这种情况下,数组A会被综合成一个FIFO,只能按照A[0], A[1],…, A[1023]的顺序被访问,而B会被综合成RAM(因为默认是RANDOM),可以按照任意顺序被访问。
例2
`#pragma SDS data access_pattern(A:SEQUENTIAL) void foo(int* A, int B[1024]) `
类似地,指针变量也可以用data access_pattern指令来规定它的访问次序。
在为硬件加速函数添加SDS指令时,有些指令可以省略,但是access_pattern指令绝对不能省略,除非用到zero_copy指令。zero_copy要求在该接口上数据必须顺序传输,此时data access_pattern强制为SEQUENTIAL,是否添加pragma没有影响。
例3
`void mmult_accel(float A[N*N], float B[N*N], float C[N*N], int M) { float A_tmp[N][N], B_tmp[N][N]; for(int i=0; i<M; i++) { for(int j=0; j<M; j++) { #pragma HLS PIPELINE A_tmp[i][j] = A[i*M+j]; B_tmp[i][j] = B[i*M+j]; } } for (int i = 0; i < M; i++) { for (int j = 0; j < M; j++) { float result = 0; for (int k = 0; k < M; k++) { #pragma HLS PIPELINE result += A_tmp[i][k] * B_tmp[k][j]; } C[i*M+j] = result; } } } `
此外,为了提升系统性能,通常算法加速IP需要将运算时用到的数据先加载到PL上再进行计算,如例3代码所示。为了提升性能,将数据从内存搬运到PL的过程访问内存的方式是连续的,因此在本系列文章中所有SDSoC相关实验的assess pattern均设置为SEQUENTIAL。
使用copy指令时SDSoC会综合出一个datamover完成数据传输,而使用zero_copy指令时,SDSoC会将硬件加速器的接口直接连接到PS的S_AXI端口,这部分内容在下一节中会详细分析。
语法:
`#pragma SDS data copy|zero_copy(ArrayName[:])
`
其中:
offset:编译时必须为常量,指定数据存储到数组的第几个元素中,目前offset值必须指定为0。
length:用于告知编译器通过该接口的数据传输量,可以是数学表达式,要确保该函数执行时能够得到确定的结果。
重要!copy指令与zero_copy指令是相互排斥的,绝对不允许在同一个数组/指针上使用。
例1
`#pragma SDS data copy(A[0:1024], B[0:1024]) #pragma SDS data zero_copy(A[0:1024], B[0:1024]) void foo(int* A, int* B); `
例1的所示情况是不被允许的。
例2
`#pragma SDS data copy(A[0:1024]) #pragma SDS data zero_copy(B[0:1024]) void foo(int* A, int* B); `
例2的所示情况是被允许的。
例3
`#pragma SDS data copy(A[0:size*size], B[0:size*size]) void foo(int *A, int *B, int size) { ... } `
size作为传入参数虽然在编译时不能确定,但是在执行时可以确定,因此上面的写法是被允许的。
copy与zero_copy的区别会在后面详细介绍。
默认情况下编译器会分析代码自动选择合适的data_mover,但是开发者可以指定data_mover覆盖掉编译器的默认值。
语法:
`#pragma SDS data data_mover(ArrayName:DataMover[:id])
`
其中:
dataMover:AXIFIFO,AXIDMA_SG或AXIDMA_SIMPLE。
id:可选,必须是一个正整数,如果多个ArrayName指定同一个id,它们会共享同一个data_mover。
例1
`#pragma SDS data data_mover(A:AXIDMA_SG:1, B:AXIDMA_SG:1) void foo(int* A, int* B) `
在这种情况下A和B会共享同一个AXIDMA_SG IP
`pragma SDS data mem_attribute `
对于像支持虚拟内存的操作系统比如Linux,用户空间分配的内存是经过Linux系统虚拟化过的,不能保证在物理上,也就是DDR中是否连续,这可能会影响系统性能。SDSoC提供了API分配物理上连续的内存空间。mem_attribute可用于告诉编译器该参数是否被分配在物理上连续的内存中。
语法:
`#pragma SDS data mem_attribute(ArrayName:contiguity)
`
其中:
Contiguity: PHYSICAL_CONTIGUOUS或NON_PHYSICAL_CONTIGUOUS,默认是后者。
例1
`#pragma SDS data mem_attribute(A:PHYSICAL_CONTIGUOUS) void foo(int* A, int* B) `
在上面的例子里,开发者告诉编译器数组A被分配到了物理上连续的内存空间,编译器会强制选择Datamover为AXIDMA_SIMPLE而不是AXIDMA_SG。
前文介绍过Zynq-7000系列处理器提供了S_AXI_HP,S_AXI_ACP接口用于数据传输。ACP接口全称Accelerator Coherency Port,也叫缓存一致接口,通过该接口PL可以直接访问PS的cache,缓存一致性由SCU保证,延时很低,适合做专用指令加速器模块接口。通过ACP接口传输的数据量有限制,不能超过8MB,如果传输数据量较大只能使用HP接口。
语法:
`#pragma SDS data sys_port(ArrayName:port)
`
其中:
port: ACP,AFI或HPC,AFI对应的是HP接口,ACP对应Zynq-7000系列的缓存一致接口,HPC对应MPSoC系列的缓存一致接口。
例1.
`#pragma SDS data sys_port(A:AFI) void foo(int* A, int* B) `
在该例中,A接口连接到S_AXI_HP端口,B接口视情况而定。
pragma SDS async/wait
ASYNC pragma必须与WAIT pragma搭配使用以手动控制硬件函数同步。在调用硬件函数之前立即指定ASYNC pragma,指示编译器不要根据数据流分析自动生成等待。 WAIT pragma必须插入到程序的适当位置以便CPU等待,直到具有相同ID的关联ASYNC函数调用完成。上面的描述可能比较难以理解,读者可以先看下面的例子。
语法:
`#pragma SDS async() ... #pragma SDS wait(ID) `
其中:ID是用户定义的 ID用于匹配ASYNC/WAIT,必须是正整数
例1.
`{ #pragma SDS async(1) mmult(A, B, C); #pragma SDS async(2) mmult(D, E, F); ... #pragma SDS wait(1) #pragma SDS wait(2) } `
程序运行到mmult(A, B, C)时,先启动数组A和数组B的数据传输,一旦传输完毕立刻返回到主程序中,然后程序开始将数组D和数组E送到mmult硬件加速器中然后立即返回。当程序执行到#pragma SDS wait(1)时,等待输出C就绪,当程序执行到#pragma SDS wait(2)时,等待输出F就绪。使用该指令可以将数组D,E的数据传输时间与数组C的计算时间重叠,提升系统的吞吐率。
上一小节单独介绍每一个指令,但是在实际开发时要将SDS pragma组合起来,本小节通过一个最基本的数据传输的例子,分析不同的指令组合综合出来的数据传输网络有何区别以及最终数据传输性能的差异。
新建一个Application Project,起名为lab2,将我们提供的代码拷贝到src文件夹。这个工程实现的功能非常简单,首先生成一个随机数数组,然后把这个数组从DDR传到PL中,再从PL中传回DDR,最后验证从PL传回来的数据与最初传到PL的数据是否一致。这个例子并没有涉及如何加速在PL中的计算,只是单纯地分析数据传输。
例1. 以下程序并不完整,为了便于说明摘取了部分片段。
`#define N 2048 #pragma SDS data copy(In[0:N],Out[0:N]) #pragma SDS data data_mover(In:AXIDMA_SG, Out:AXIDMA_SG) #pragma SDS data access_pattern(In:SEQUENTIAL, Out:SEQUENTIAL) #pragma SDS data sys_port(In:AFI, Out:AFI) void IO(float* In, float* Out) { float Buffer [N]; for(int i=0;i<N;i++) Buffer[i]=In[i];//将数据从DDR读取到PL中 for(int i=0;i<N;i++) Out[i]=Buffer[i];//将数据从PL传输回DDR中 } int main(int argc, char* argv[]) { float *A, *B; A = (float *)malloc(N * sizeof(float)); B = (float *)malloc(N * sizeof(float)); ... IO(A, B); ... } `
在上面这段程序里,我们用到了四个SDS pragma,分别是copy,data_mover,sys_port以及access_pattern。没有使用mem_attribute是因为编译器会自动判断分配的内存连续与否。上面这段程序使用malloc语句分配内存,那么这段内存就是分页的。类似地,我们也可以用数组来初始化一段内存。
`float A[N]; float B[N]; `
这两种方式分配的内存在物理上都是不连续的,想要分配在物理上连续的内存片段必须使用sds_alloc()语句,内存片段在物理上是否连续对数据传输性能影响很大。
这四条指令加上内存分配语句理论上可以产生2(malloc/sds_alloc) * 2(copy/zero_copy) * 3(DMA_SG/DMA_SIMPLE/FIFO) * 2(RANDOM/ SEQUENTIAL) * 2(AFI/ACP)=48种组合方式,但是各个pragma之间是有依赖关系的,比如,zero_copy和DMA_SIMPLE都必须与sds_alloc()同时使用。此外,RANDOM的assess pattern比较罕见(前面有提到),因此实际上并没有那么多组合。下面我们会比较一下各种能够实现的SDS pragma组合的情况(比如paged +AXIDMA_SIMPLE就无法实现),包括各组合的理论性能,实测性能,资源利用等情况。
内存 | 端口 | Datamover | copy | Setup | Transfer | 实测 | FF | LUT |
---|---|---|---|---|---|---|---|---|
1 | 连续 | ACP | SIMPLE | zero | 518 | 10890 | 48907 | 1197 |
2 | 连续 | AFI | SIMPLE | zero | 11222 | 20675 | 110623 | 1197 |
3 | 连续 | ACP | SG | zero | 518 | 10890 | 48891 | 1197 |
4 | 连续 | AFI | SG | zero | 11222 | 20675 | 105411 | 1197 |
5 | 连续 | ACP | SIMPLE | copy | 1120 | 10337 | 49912 | 130 |
6 | 连续 | AFI | SIMPLE | copy | 12454 | 10546 | 80345 | 130 |
7 | 连续 | ACP | SG | copy | 5202 | 15370 | 72418 | 130 |
8 | 连续 | AFI | SG | copy | 16242 | 15426 | 104536 | 130 |
9 | 分页 | ACP | SG | copy | 28524 | 17532 | 321272 | 130 |
10 | 分页 | AFI | SG | copy | 26624 | 18636 | 351497 | 130 |
11 | 连续 | GP | FIFO | zero | 518 | 10890 | 48941 | 1197 |
12 | 连续 | GP | FIFO | copy | 396836 | 18636 | 580754 | 130 |
13 | 分页 | GP | FIFO | copy | 396836 | 18636 | 573677 | 130 |
其中,表1的内存,端口,Datamover,copy,Setup,Transfer由Data Motion Network Report给出,实测在板上测试给出,LUT与FF的使用情况由HLS Report给出,这些内容在附件里有提供,此外我们还提供了不同指令组合对应的Block Diagram。
连续+ACP+SIMPLE+zero_copy对应原理图
连续+HP+SIMPLE+zero_copy对应原理图
连续+ACP+SG+zero_copy对应原理图
连续+ACP+SIMPLE+copy对应原理图
连续+ACP+SG+copy对应原理图
分页+ACP+SG+copy
综上,分配内存时要使用sds_alloc(),数据传输量较小(<8MB)时选择ACP接口,copy与zero_copy性能差距并不大,AXIDMA_SG与AXIDMA_SIMPLE读者可以根据自己需要选择。
SDSoC Platfrom定义了基本的硬件和软件架构以及应用程序的运行环境,本节从硬件和软件两方面,介绍SDSoC Platform如何在SDSoC中被调用然后说明如何搭建自己的SDSoC Platform。一个SDSoC Platform应该包含如下文件:
上图是PYNQ SDSoC Platform硬件部分的基础平台,里面只有系统复位(Processor System Reset)模块和时钟向导(Clocking Wizard)模块。在SDSoC中可以指定硬件加速器的时钟频率,如100M、142M、166M和200M,这些频率并不是凭空产生的,而是在Platform的时钟向导模块中指定的,如果想要其他频率可以在时钟向导中添加。
上图是reVISION 的案例平台zcu102_rv_ss Platform硬件部分的基础平台,除了包含系统复位模块和时钟向导模块之外,还包含了MIPI接口和HDMI接口。zcu102_rv_ss提供了一个光流算法的案例,输入是MIPI摄像头,输出是DisplayPort接口(DP接口是连接到PS上的),在SDSoC中无法编写MIPI接口的控制逻辑,所以这部分逻辑是在创建SDSoC平台时准备好的。所有用到MIPI接口的工程都可以使用该基础平台,使用SDSoC工具我们可以很容易地在该基础平台上实现不同的算法。
最终加载进PL的比特流由图3所示工程生成的(路径:Debug/sds/p0/_vpl/ipi/syn),该工程在图1的基础上添加了若干IP,包括:
Project Type保持默认RTL Project,点击Next,跳过Add Sources和Add Constraints
Defualt Part界面下选择PYNQ-Z2
点击Next,检查后点击Finish
在Flow Navigator –> IP Integrator, 选择Create Block Design.
在Create Block Design对话框中为Block Design指定一个名称,在这里设置为PYNQ
在Block Design画布右键选择Add IP
搜索框内输入zynq找到ZYNQ7 Processing System IP
在IP catalog中选择ZYNQ7 Processing System IP,按下回车将其添加到Block Design中。
在IP integrator界面下单机Run Block Automation,如下图所示
Run Block Automation对话框打开后如下图所示,这个对话框表示FIXED_IO和DDR接口会被创建,Apply Board Preset选项通用会被勾选。在board file文件夹PYNQ目录下有一个preset.xml文件,里面提供了PS部分的基本配置,如DDR的设置。
点击OK,在IP integrator diagram会出现如下的图形
Running Block Automation 之后Zynq-7000 AP SoC Processing System,注意加载preset之后要检查TTC模块,QSPI模块,UART模块,Eth模块有没有被使能,这些模块是Petalinux正常运行所必需的。
右键Add IP,输入proc sys res找到Processor System Reset,按回车将这些模块添加到Block Diagram中,重复三次该操作,最终在Block Diagram中应该有四个Processor System Reset
相似地,添加Clocking Wizard和Concat IP,以上步骤执行之后Block Diagram应跟下面这张图比较相似
双击Zynq IP打开配置引导界面,在Re-customize IP对话框中执行如下操作
PS-PL Configuration-> AXI Non Secure Enablement-> GP Master AXI Interface取消勾选M AXI GP0 interface
Interrupts->勾选Fabric Interrupts->PL-PS Interrupt Ports,勾选IRQ_F2P[15:0],点击OK退出
双击Clocking Wizard进入配置界面,选择Output Clocks选项卡,勾选clk_out2,3,4并按照下图配置频率,这些频率就是最终在SDSoC平台中可以选择的频率。
在该页面下向下滑动,将Reset Type设置成Active Low,点击OK关闭该界面。
双击Concat IP,将Number of Ports设置为1,Re-customizing IP之后的Block Diagram应如下图所示。
下面开始手动连线
clk_wiz_0/ clk_in1与ZYNQ7 PS/ FCLK_CLK0,clk_wiz_0/clk_out1与proc_sys_reset_0/ slowest_sync_clk相连,以此类推连接所有PSR的slowest。
将ZYNQ7 PS/FCLK_RESET0_N,Clocking Wizard/resetn,所有PSR/ext_reset_in相连
将Clocking Wizard的locked与所有PSR的dcm_locked相连。
将Concat的dout[0:0]与ZYNQ7 PS的IRQ_F2P[0:0]相连。
点击Regenerate Layout按钮重新生成Block Diagram的布局。
右键bd文件,点击Generate Output Products-> Synthesis Options->Global,点击Generate
然后右键bd文件,点击Create HDL Wrapper,生成HDL Wrapper。
完成Vivado设计套件中的硬件平台设计项目后,必须完成添加平台属性(PFM)定义平台名称并配置平台接口如时钟,中断和总线接口。这些属性被设置一次并存储在工程中。平台通常由多个时钟组成。在当前项目中,我们的设计包含四个用Clocking Wizard生成不同的时钟,用户可以在SDSoC中选择要被硬件加速的函数的时钟频率。同理,在SDSoC中被用到的AXI端口也要标注,硬件加速函数会用这些被标注的AXI端口来建立Data motion network。
上图所示的Block Diagram中S_AXI_HP0被两个VDMA占用,那么这个AXI端口就不再可以被SDSoC调用。这些AXI端口在Block Diagram中可能不可见(在该Block Diagram中只能看到S_AXI_HP0),但是只要平台属性里标注好就可以被SDSoC中的硬件加速函数使用。
`set_property PFM_NAME "xilinx.com:PYNQ:PYNQ:1.0" [get_files PYNQ.bd] `
PFM_NAME属性按照下面的格式
`: : : `
`set_property PFM.CLOCK {\ clk_out1 {id "0" is_default "false" proc_sys_reset "proc_sys_reset_0"} \ clk_out2 {id "1" is_default "true" proc_sys_reset "proc_sys_reset_1" } \ clk_out3 {id "2" is_default "false" proc_sys_reset "proc_sys_reset_2"} \ clk_out4 {id "3" is_default "false" proc_sys_reset "proc_sys_reset_3"} \ } [get_bd_cells /clk_wiz_0] `
`set_property PFM.AXI_PORT { \ M_AXI_GP0 {memport "M_AXI_GP"} \ M_AXI_GP1 {memport "M_AXI_GP"} \ S_AXI_ACP {memport "S_AXI_ACP" sptag "ACP" memory "ps7 ACP_DDR_LOWOCM"} \ S_AXI_HP0 {memport "S_AXI_HP" sptag "HP0" memory "ps7 HP0_DDR_LOWOCM"} \ S_AXI_HP1 {memport "S_AXI_HP" sptag "HP1" memory "ps7 HP1_DDR_LOWOCM"} \ S_AXI_HP2 {memport "S_AXI_HP" sptag "HP2" memory "ps7 HP2_DDR_LOWOCM"} \ S_AXI_HP3 {memport "S_AXI_HP" sptag "HP3" memory "ps7 HP3_DDR_LOWOCM"} \ } [get_bd_cells /ps7] `
`set intVar [] for {set i 0} {$i < 16} {incr i} { lappend intVar In$i {} } set_property PFM.IRQ $intVar [get_bd_cells /xlconcat_0] `
`write_dsa –force/PYNQ.dsa -include_bit `
注意,SDSoC Hardware Platform的名称,即PFM_NAME下的要与Vivado工程名,block diagram名以及dsa文件名保持一致以免出现不必要的麻烦,在本教程中所有的名称都是PYNQ。
`validate_dsa/PYNQ.dsa `
Zynq启动时需要的文件包括kernel,device-tree,u-boot和fsbl,如果与PL部分相关,还需要.bit文件,这些文件除了.bit文件都可以由Petalinux生成,其中image.ub文件包含了kernel以及device-tree。
PetaLinux 是Xilinx 提供的工具链, 用于生成Linux 内核映像, 根文件系统和ZYNQ 的内核模块, 例如带有可编程硬件的嵌入式系统(用于FPGA 部分中的不同硬件设计)。使用PetaLinux工具链, 我们可以轻松地为ZYNQ PS 构建内核和模块, 而无需使用单独的交叉编译工具。使用PetaLinux 的一个缺点是, 每个PetaLinux 版本都带有特定的Linux 内核版本。例如PetaLinux2017.4 带有4.9 的默认内核版本。赛灵思提供了一种方法来改变Petalinux 使用的默认内核版本, 读者可以通过百度搜索轻松找到它, 在此不赘述。注意:对于特定的硬件设计,PetaLinux 工具可以生成U-Boot 文件, 第一阶段启动加载程序(FSBL)和BOOT.BIN。使用Xilinx SDK 可以完成同样的事情。
2017.4/2018.2 版本的工具链(包括Vivado,SDSoC,Petalinux)跟之前的版本有很大区别, 因此强烈建议版本与本文保持一致。此外,SDSoC,Vivado,Petalinux必须版本一致,本文在2017.4、2018.2均测试通过。
Petalinux必须安装在Linux系统上,下面提供在Ubuntu16.04上安装的步骤
Petalinux2017.4 版本下载链接:
https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/embedded-designtools/2017-4.html
读者可以直接到Xilinx官网查找
`$ sudo apt-get install gawk xvfb chrpath socat autoconf libtool git texinfo zlib1g-dev zlib1g-dev gcc-multilib libsdl1.2-dev libglib2.0-dev zlib1g-dev libncurses5-dev libssl-dev zlib1g:i386 –y `
安装Petalinux 依赖的库
`$ sudo mkdir -p /opt/pkg/petalinux $ cd /opt/pkg/ $ sudo dpkg-reconfigure bash `
UG1144 中提到所用到的/bin/sh 命令都需要是bash, 而Ubuntu 默认的/bin/sh 是dash, 在弹出界面选“否”来禁用dash, 选择bash
`$ sudo chown USERNAME:users petalinux/ -R `
USERNAME 替换成Ubuntu 的用户名,Petalinux 不能在root 权限下安装,,所以需要chown
`$ cd$ ./petalinux-v2017.4-final-installer.run /opt/pkg/petalinux `
等待一段时间来到了Agreement 部分, 按Enter 进入协议文本, 按q 退出协议文本, 输入y 同意协议, 进入下一条协议, 若干次之后, 就进入安装部分, 再等待一段时间Petalinux 就安装完成了
`$ source /opt/pkg/petalinux/settings64.sh `
每次使用petalinux之前要先设置环境
`$ petalinux-create –type project –template zynq –name PYNQ `
新建Petalinux 工程, 命名为PYNQ,会自动生成一个名为PYNQ的文件夹
`$ cd PYNQ/ `
将第一步中.hdf 文件复制到PYNQ 文件夹下
`$ petalinux-config –get-hw-description ./ `
获取硬件描述文件, 然后会弹出一个图形界面
配置system config
DTG Settings->Kernel Bootargs->generate boot args automatically 取消勾选该选项
手动将bootargs 设置成
`console=ttyPS0,115200 earlyprintk quiet `
`$ petalinux-config -c kernel `
Device Drivers->Generic Driver Options->Size in Mega Bytes(256)
Device Drivers->Staging drivers (ON)->Xilinx APF Accelerator driver (ON)->Xilinx APF
DMA engines support (ON)
保存并退出
手动添加设备树
打开/project-spec/metauser/recipes-bsp/device-tree/files/system-user.dtsi, 添加如下几行
`/{ xlnk { compatible =“xlnx,xlnk-1.0”; }; }; `
`$ petalinux-build `
编译结束后在images/linux下找到zynq_fsbl.elf, u-boot.elf, image.ub, 这些文件之后会用到。
前两节我们已经准备好了搭建自己的SDSoC Platform所需要的文件,下面我们将介绍如何将这些文件组织成一个SDSoC Platform。一个典型的SDSoC Platform文件结构如下
`/* linux */ the_ROM_image: { [bootloader]} `
打开SDSoC新建一个工程,Project type选择Platform Project,然后找到dsa文件,工程会自动命名为dsa文件的名称
打开该工程
SDSoC Platform Project
点击左下角Define System Configuration,添加以下内容
Define System Configuration
boot directory里包含了u-boot.elf,fsbl.elf,这两个文件将与SDSoC生成的.bit文件打包在一起生成BOOT.BIN文件。
Add Processor Group/Domain
Add Processor Group/Domain
这一步完成点击Generate Platform和Add to Custom Repositories将使该Platform可以被SDSoC工具找到。至此我们就完成了搭建最基础的SDSoC Platform的流程。
转载自:https://blog.csdn.net/lulugay/article/details/83241716