本帖最后由 正点原子01 于 2020-8-28 16:37 编辑
1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入群聊:
5)关注正点原子公众号,获取最新资料更新
第一章 HLS简介
为了尽快把新产品推向市场,数字系统的设计者需要考虑如何加速设计开发的周期。设计加速主要可以从“设计的重用”和“抽象层级的提升”这两个方面来考虑。Xilinx推出的Vivado HLS工具可以直接使用C、C++或System C来对Xilinx系列的FPGA进行编程,从而提高抽象的层级,大大减少了使用传统RTL描述进行FPGA开发所需的时间。 本章包括以下几个部分: 1.1 高层综合简介 1.2 HLS设计流程 1.3 接口综合 1.4 算法综合 1.5 HLS库
1.1 高层综合简介 在介绍HLS之前,我们先来了解一下FPGA设计过程中的不同抽象层级,如下图所示:
如图 1.1.1所示,FPGA设计中从底层向上一共存在着四种抽象层级,依次为:结构性的、RTL、行为性的和高层。其中最底层的抽象(结构性的)涉及到对底层硬件单元直接的例化,比如逻辑门,甚至是更底层的LUT或者触发器。设计者更常用的是在“寄存器传输级(Register TransferLevel,RTL)” 进行设计,这个层级的抽象隐藏了底层的细节,是在描述寄存器和寄存器之间可执行的操作。更上层的“行为性的”描述是对电路的算法描述,也就是描述电路表现出什么样的功能(行为),而不是描述每个寄存器该如何进行操作。 前面介绍的几种抽象层级都是在使用硬件描述语言HDL进行设计,可以看出,随着抽象层级的提升,设计最终在硬件上实现的细节逐渐被弱化。而本章重点介绍的“高层”设计方法则直接使用高级语言,如C/C++进行设计,然后由Vivado HLS编译器将C代码综合成HDL描述,最后再进行逻辑综合得到网表,这个网表最终会被映射到具体的FPGA器件上。 就像C语言或者其他高级语言针对不同的处理器架构有着不同的编译器,Xilinx Vivado High-Level Synthesis(高层综合,HLS)工具同样是一种编译器,只不过它是用来将C或者C++程序部署到FPGA上,而不是部署到传统的处理器上。 在Vivado HLS中可以使用三种语言进行设计开发,分别是 C、C++ 和 SystemC。其中C语言是一种非常通用的面向过程的编程语言,我们在《正点原子ZYNQ嵌入式开发指南》中均是使用C语言进行嵌入式设计。 C++是一个基于C的面向对象的语言,它在C的基础上扩展了类、模板、多态和虚函数的概念,还有一些其他的特性。C++的抽象层次总的来说比C要高,能做更精密、灵活的代码开发。另一方面来说,C的语言特性和编程风格和C++是兼容的,因此C++可以认为是C的扩展集。总的来说,C++是比C更高级的语言,但是仍保留对低层C程序的支持。在《正点原子ZYNQ HLS开发指南》中,C和C++这两种语言均有涉及。 在这里我们把 SystemC 也当作一种独立的语言,但是严格来说它是 C++ 的一种扩展。SystemC 能以 C++ 风格的代码来实现 HDL 的以硬件为中心的概念,比如层次结构、并行和周期精确,这些都无法以标准 C++ 的形式来表达。因为在本教程中不涉及使用SystemC进行设计开发,在此我们不多作介绍。 1.2 HLS设计流程 Vivado HLS 的功能简单地来说就是把 C、C++ 或 SystemC 的设计转换成 RTL 实现,然后就可以在 Xilinx FPGA 或 Zynq 芯片的可编程逻辑中综合并实现了。需要注意的是,这里我们说的使用C/C++完成的设计与运行在处理器(ZYNQ中的ARM处理器或MicroBlaze软核处理器)中的软件代码是截然不同的。在HLS中,所有的C设计都是要在可编程逻辑中实现的,也就是说,我们仍然是在进行硬件设计,只不过使用的不再是硬件描述语言。 使用Vivado HLS进行设计的流程如下图所示:
HLS设计的主要输入是一个 C/C++/SystemC 设计,以及一个基于 C 的测试集(TestBench)。我们首先要知道C语言的本质就是函数,那么这个测试集就是用于验证C设计中的函数,验证过程需要一个“黄金参考”。这个“黄金参考”类似于一个标准答案,用来和C设计中函数所产生的输出做比对。 在对HLS设计进行综合之前,我们要先对其进行“功能性验证”,也就是C仿真,其目的是验证HLS 输入的C代码的功能是否正确。验证的方式就是在TestBench中调用C设计的函数,然后将其输出与“黄金参考”进行比对,如果与黄金参考有差异就需要先对C设计进行修改调试。 接下来就是对设计进行高层综合,即HLS过程本身。该过程涉及到分析和处理基于 C 的代码,加上用户所给出的指令和约束,来创建RTL描述。高层综合结束后会产生一组输出文件,包括以Veilog或者VHDL语言编写的RTL设计文件。 综合过程结束后得到的RTL模型,可以在 Vivado HLS 中进行 C/RTL 协同仿真,来进一步验证综合得到的RTL设计的正确性。在这个过程中Vivao HLS会自动产生一个测试集为RTL设计提供输入,然后拿它的输出与预期的值做比对。C功能性验证和C/RTL协同仿真的区别如下图所示:
图 1.2.2 C 功能性验证和 C/RTL 协同仿真 在图 1.2.2左侧的功能性验证(C仿真)中,原始测试集是用户输入的测试文件TestBench。而右侧的C/RTL协同仿真所需的RTL测试集是由Vivado HLS 自动产生的,这样就不再需要人工创建了,所产生的测试集包括了原始测试集和被测RTL模块之间的数据传递。 除了对功能进行验证,我们还要评估 RTL设计的实现和性能。比如,在FPGA中所需的资源的数量,设计的延迟、所支持的最高时钟频率等是否满足要求。如果不满足要求,那么就需要设计者通过修改指令和约束,然后再次进行高层综合,如图 1.2.1中右侧的回路所示。一个设计可能要做多次HLS设计迭代,来找到“最佳 ”的解决方案。如果有必要,设计者也可以返回修改C设计代码,然后从头开始重新对设计进行验证。 在设计被验证了之后,而且实现也满足了期望的设计目标,那么就可以集成进更大的系统里了。我们可以直接使用 HLS 过程所产生的RTL文件(即VHDL或 Verilog 代码),更方便的做法是使用 VivadoHLS 的 IP 打包功能。对Vivado HLS 所产生的输出打包意味着 HLS 设计能够以IP核的形式引入其他Xilinx 工具中,比如Vivado中的IP 集成器。这两种类型的输出如下图所示:
1.3 接口综合 在做 HLS 的时候,设计者需要分析设计的两个主要方面: • 设计的接口,也就是它的顶层连接; • 设计的功能,也就是它所实现的算法; 我们给出一个HLS设计中接口和功能的概念图,如图1.3.1所示。
在上图中,两端的绿色区域表示设计的输入和输出接口,其中展示了部分接口类型,如RAM接口、FIFO接口,以及总线类型的接口等。这些接口可以是工具从代码中通过接口综合(Interface Synthesis)得到的,也可以由设计者手动指定具体的接口类型。 图中间黄色的区域表示HLS设计具体能够实现的功能,对于不同的应用,其功能也各不相同。在 Vivado HLS 设计中,功能是从输入的代码中,经过算法综合(Algorithm Synthesis)的过程得到的。 在这里我们先简单介绍一下接口综合。顾名思义,Interface Synthesis指的是 HLS 设计中对接口的综合,综合出来的接口能够与系统中的其他模块通信,还有可能需要与系统中的处理器进行通信。 这里接口的概念既包括端口(port),也包含所使用的协议。所有端口的细节(如类型、位宽和方向)是从 C/C++ 文件中顶层函数的参数和返回值里推断出来的;而协议是从端口的表现(行为)推断出来的。比如,最简单的接口可以是一条 1 比特的线(wire),而更复杂的接口,可能要用总线或 RAM 接口。接口综合能够推断出来的接口类型包括:线、寄存器、单向和双向握手信号、FIFO、存储器和总线等。 下面我们给出一个简单的C设计的顶层函数,函数名为find_average_of_best_X(),其参数如下图所示:
图1.3.2中函数内部工作的详细情况无关紧要,不过每个参数的读/写操作将决定综合出来的端口的方向。这个函数定义包含三个参数,数组“sample”和整数“X”是函数的输入,而average作为函数的输出。因此,简单来说,这三个函数参数要被 HLS 转换成两个输入接口和一个输出接口,如下图所示:
图 1.3.3 函数find_average_of_best_X()综合后的简化接口 需要注意的是,图1.3.3只是一个简化了的接口示意图。根据所用的协议,这些接口可能包括数据端口自身以外的控制输入或输出,如下图所示:
图 1.3.4 函数find_average_of_best_X()的RTL接口图 图1.3.4是函数find_average_of_best_X()经HLS综合出来的完整的RTL模块的接口图。从图中可以看到由函数的三个参数所综合出来的接口分别拥有了各自的协议,如ap_memory协议、ap_none协议和ap_vld协议。同时模块还多出来了一些端口,如ap_clk和ap_rst等,它们使用的是ap_ctrl_hs协议。这些协议决定了相应的接口是如何与系统中其他模块进行交互的,至于各协议具体的含义以及如何为接口选择其协议,我们将在后续的章节中介绍。 1.4 算法综合 算法综合关注的是设计的功能,即设计所期望的行为,它是由输入的 C设计所描述的。算法综合从代码中推出各种运算操作,然后转换成一组 RTL 语句。 算法综合包括三个主要阶段,依次是: 1. 解析出数据通路和控制电路; 2. 调度和绑定; 3. 优化; 解析出数据通路和控制电路 HLS 的第一个阶段是分析 C/C++/SystemC 代码,并且解释所需的功能。Vivado HL从以下几个方面分析程序:逻辑和算法的运算、条件语句和分支、数组运算和循环等。 所产生的实现会具有一个数据通路元件,一般还会有一个控制元件。需要澄清的是,这里的“数据通路”处理指的是在数据样本上作的运算,而“控制”是需要协同数据流处理所需的电路。算法的本质定义出数据通路和控制元件,设计者可以在 HLS 中采取专门的步骤来最小化控制元件的复杂度。 调度和绑定 HLS 是由两个主要过程组成的:调度(Scheduling)和绑定(Binding)。它们是交替进行的,彼此互相影响,如下图所示:
• 调度是把由 C代码解释得到的 RTL 语句翻译成一组运算,每个运算都关联着一定的执行时间,以时钟周期为单位。这个阶段所作的决策,受时钟频率和不确定度、目标芯片的技术和用户所施加的指令所影响。 • 绑定是调度好了的运算和目标芯片上的实际资源联系起来的过程。这些资源的功能和时序特征可能会影响调度,因此绑定信息会反馈给调度过程。比如使用DSP48x 资源就表明关键路径比采用逻辑资源的方案要短。 比如,如果综合出来的算法需要做一组算术运算,HLS 过程就必须根据目标的时钟频率和不确定度来决定如何调度这些运算(要分配多少个时钟周期来完成),以及如何绑定这些运算(也就是如何把运算映射到 PL 上的可计算资源里)。C源码并不能表达或指定硬件架构,但是通过施加指令,源码确实可以产生不同的架构。 优化 有两种方法可以用来调整 HLS 过程的行为,让高层综合朝着设计者的实现目标而努力,从而影响结果: • 约束 — 设计者可以对设计的某些指标加以限制。比如,可以指定最低的时钟周期。这样就能确保实现结果能够满足要集成进去的系统的要求。类似的,设计者可以选择约束资源的利用情况或其他的指标,从而优化应用的设计。 • 指令 — 设计者可以通过指令对 RTL 的实现参数施加更具体的影响。有各种类型的指令,分别映射在代码的某些特征上,比如让设计者可以指定 HLS 引擎如何处理 C 代码中识别出来的循环或数组,或是某个特定运算的延迟。这能导致RTL 输出的巨大改变。因此,具有了指令的知识,设计者就可以根据应用的需求来做优化了。 1.5 HLS库 Vivado HLS中包含了一系列的C库(包括C和C++),方便对一些常用的硬件结构或功能使用C/C++进行建模,并且能够综合成RTL。在Vivado HLS中提供的C库有下面几种类型: 1、任意精度数据类型库 2、HLS Stream库 3、HLS数学库 4、HLS视频库 5、HLS IP库 6、HLS线性代数库 在HLS设计中调用库中的函数可以大大提高开发效率,比如在本教程中我们用到了大量的“HLS视频库”中的函数,来进行基于HLS的视频图像处理。对于上面列出的各个库,我们同样会在后续章节中用到时候再进行介绍。
|