参考资料:《MPSoc 之 HLS 开发指南——正点原子》
一.HLS 简介
Xilinx 推出的 Vivado HLS 工具可以直接使用 C、C++或 System C 来对 Xilinx 系列的 FPGA 进行编程,从而提高抽象的层级,大大减少了使用传统 RTL 描述进行 FPGA 开发所需的时间。
1.高层综合简介
FPGA 设计过程中的不同抽象层级:
编辑
其中最底层的抽象(结构性的)涉及到对底层硬件单元直接的例化,比如逻辑门,甚至是更 底层的 LUT 或者触发器。设计者更常用的是在“寄存器传输级(Register Transfer Level,RTL)”进行设计, 这个层级的抽象隐藏了底层的细节,是在描述寄存器和寄存器之间可执行的操作。更上层的“行为性的” 描述是对电路的算法描述,也就是描述电路表现出什么样的功能(行为),而不是描述每个寄存器该如何进行操作。
前面介绍的几种抽象层级都是在使用硬件描述语言 HDL 进行设计,可以看出,随着抽象层级的提升, 设计最终在硬件上实现的细节逐渐被弱化。而本章重点介绍的“高层”设计方法则直接使用高级语言,如 C/C++进行设计,然后由 Vivado HLS 编译器将 C 代码综合成 HDL 描述,最后再进行逻辑综合得到网表, 这个网表最终会被映射到具体的 FPGA 器件上。
就像 C 语言或者其他高级语言针对不同的处理器架构有着不同的编译器,Xilinx Vivado High-Level Synthesis(高层综合,HLS)工具同样是一种编译器,只不过它是用来将 C 或者 C++程序部署到 FPGA 上, 而不是部署到传统的处理器上。
C++是一个基于 C 的面向对象的语言,它在 C 的基础上扩展了类、模板、多态和虚函数的概念,还有 一些其他的特性。C++的抽象层次总的来说比 C 要高,能做更精密、灵活的代码开发。另一方面来说,C 的语言特性和编程风格和 C++是兼容的,因此 C++可以认为是 C 的扩展集。总的来说,C++是比 C 更高级 的语言,但是仍保留对低层 C 程序的支持。
严格来说它是 C++的一种扩展。SystemC 能以 C++ 风格的代码来实现 HDL 的以硬件为中心的概念,比如层次结构、并行和周期精确,这些都无法以标准 C++ 的形式来表达。
2.HLS 设计流程
使用 Vivado HLS 进行设计的流程如下图所示:
编辑
HLS 设计的主要输入是一个 C/C++/SystemC 设计,以及一个基于 C 的测试集(TestBench)。我们首先 要知道 C 语言的本质就是函数,那么这个测试集就是用于验证 C 设计中的函数,验证过程需要一个“黄金 参考”。这个“黄金参考”类似于一个标准答案,用来和 C 设计中函数所产生的输出做比对。、
在对 HLS 设计进行综合之前,我们要先对其进行“功能性验证”,也就是 C 仿真,其目的是验证 HLS 输入的 C 代码的功能是否正确。验证的方式就是在 TestBench 中调用 C 设计的函数,然后将其输出与“黄 金参考”进行比对,如果与黄金参考有差异就需要先对 C 设计进行修改调试。
接下来就是对设计进行高层综合,即 HLS 过程本身。该过程涉及到分析和处理基于 C 的代码,加上 用户所给出的指令和约束,来创建 RTL 描述。高层综合结束后会产生一组输出文件,包括以 Verilog 或者 VHDL 语言编写的 RTL 设计文件。
综合过程结束后得到的 RTL 模型,可以在 Vivado HLS 中进行 C/RTL 协同仿真,来进一步验证综合得到的 RTL 设计的正确性。在这个过程中 Vivado HLS 会自动产生一个测试集为 RTL 设计提供输入,然后拿 它的输出与预期的值做比对。C 功能性验证和 C/RTL 协同仿真的区别如下图所示:
编辑
在图左侧的功能性验证(C 仿真)中,原始测试集是用户输入的测试文件 TestBench。而右侧的 C/RTL 协同仿真所需的 RTL 测试集是由 Vivado HLS 自动产生的,这样就不再需要人工创建了,所产生的测 试集包括了原始测试集和被测 RTL 模块之间的数据传递。
在设计被验证了之后,而且实现也满足了期望的设计目标,那么就可以集成进更大的系统里了。我们 可以直接使用 HLS 过程所产生的 RTL 文件(即 VHDL 或 Verilog 代码),更方便的做法是使用 Vivado HLS 的 IP 打包功能。对 Vivado HLS 所产生的输出打包意味着 HLS 设计能够以 IP 核的形式引入其他 Xilinx 工具 中,比如 Vivado 中的 IP 集成器。
编辑
3.接口综合
在做 HLS 的时候,设计者需要分析设计的两个主要方面:
• 设计的接口,也就是它的顶层连接;
• 设计的功能,也就是它所实现的算法;
编辑
在上图中,两端的绿色区域表示设计的输入和输出接口,其中展示了部分接口类型,如 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(),其参数如下图所示:
编辑
这个函数定义包含三个参数,数组“sample”和整数“X”是函数的输入,而 average 作为函数的输出。 因此,简单来说,这三个函数参数要被 HLS 转换成两个输入接口和一个输出接口,如下图所示:
编辑
需要注意的是,图只是一个简化了的接口示意图。根据所用的协议,这些接口可能包括数据端口 自身以外的控制输入或输出,如下图所示:
编辑
图是函数 find_average_of_best_X()经 HLS 综合出来的完整的 RTL 模块的接口图。从图中可以看 到由函数的三个参数所综合出来的接口分别拥有了各自的协议,如 ap_memory 协议、ap_none 协议和 ap_vld 协议。同时模块还多出来了一些端口,如 ap_clk 和 ap_rst 等,它们使用的是 ap_ctrl_hs 协议。这些协议决定 了相应的接口是如何与系统中其他模块进行交互的,至于各协议具体的含义以及如何为接口选择其协议, 我们将在后续的章节中介绍。
4.算法综合
算法综合包括三个主要阶段,依次是:
1.解析出数据通路和控制电路;
2.调度和绑定;
3.优化;
解析出数据通路和控制电路
HLS 的第一个阶段是分析 C/C++/SystemC 代码,并且解释所需的功能。Vivado HLS 从以下几个方面分 析程序:逻辑和算法的运算、条件语句和分支、数组运算和循环等。
所产生的实现会具有一个数据通路元件,一般还会有一个控制元件。需要澄清的是,这里的“数据通路”处理指的是在数据样本上作的运算,而“控制”是需要协同数据流处理所需的电路。算法的本质定义出数据通路和控制元件,设计者可以在 HLS 中采取专门的步骤来最小化控制元件的复杂度。
调度和绑定
HLS 是由两个主要过程组成的:调度(Scheduling)和绑定(Binding)。它们是交替进行的,彼此互相 影响,如下图所示:
编辑
•调度是把由 C 代码解释得到的 RTL 语句翻译成一组运算,每个运算都关联着一定的执行时间,以时 钟周期为单位。这个阶段所作的决策,受时钟频率和不确定度、目标芯片的技术和用户所施加的指令所影响。
•绑定是调度好了的运算和目标芯片上的实际资源联系起来的过程。这些资源的功能和时序特征可能会影响调度,因此绑定信息会反馈给调度过程。比如使用 DSP48x 资源就表明关键路径比采用逻辑资源的方案要短。
优化
有两种方法可以用来调整 HLS 过程的行为,让高层综合朝着设计者的实现目标而努力,从而影响结果:
•约束—设计者可以对设计的某些指标加以限制。比如,可以指定最低的时钟周期。这样就能确保实现结果能够满足要集成进去的系统的要求。类似的,设计者可以选择约束资源的利用情况或其他的指标,从而优化应用的设计。
•指令—设计者可以通过指令对 RTL 的实现参数施加更具体的影响。有各种类型的指令,分别映射在代码的某些特征上,比如让设计者可以指定 HLS 引擎如何处理 C 代码中识别出来的循环或数组,或是某个特定运算的延迟。这能导致 RTL 输出的巨大改变。因此,具有了指令的知识,设计者就可以根据应用的需求来做优化了。
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 的视频图像处理。
6.报错及解决方案
见参考资料
二.LED闪烁实验
数字系统设计加速开发的周期两个方面考虑:(一)设计的重用和(二)抽象层次的提升。Xilinx Vivado 开发套件中的 IP 集成功能可以实现设计的重用,而 Vivado HLS 工具则能够实现对高层次设计的综合。
Vivado High Level Synthesis(即 HLS,高层次综合)工具使用 C、C++或 System C 语言在更抽象的算法层次描述设计,并将 C 代码综合成 RTL 级的 HDL 描述。使用 Vivado HLS 进行高层次综合的过程如下图所示:
编辑
1.HLS设计
<1>创建HLS工程
打开 Vivado HLS 软件**(最新版为 Vitis HLS)**。在 Vivado HLS 的开始界面中点击左“Create New Project”,创建一个新 的工程。然后会弹出新建 Vivado HLS 工程向导界面。
在“Project name”一栏输入工程名“xxx_hls”,然后在“Location”一栏通过点击 右侧的“Browse…”按钮,选择工程路径。需要注意的是,工程名以及路径只能由英文字 母、数字和下划线组成,不能包含中文、空格以及其他特殊字符。 设置好工程名及路径之后,点击“Next”。
编辑
编辑
在“Top Function”一栏设置设计的顶层函数为“led_twinkle”,然后点击“Next”,之后我们可以添加 C 测试文件,这里我们不需要添加,直接点击“Next”
编辑
DFZU2EG/4EV MPSOC 开发板上的芯片有三种型号,XCZU4EV-sfvc784-1-i、XCZU2EG-sfvc784-2-i 和 XCZU2CG-sfvc784-2-i。
创建工程完成后,Vivado HLS 会打开工程界面,如下图所示:
编辑
在工程面板中的“source”目录上点击右键,然后在打开的列表中选择“New File”新建源文件。然后在弹出的对话框中输入源文件的名称“led_twinkle.c”,如图 2.3.11 所示。源文件默认的保存路径 为 HLS 工程目录,为方便源文件的管理,我们在工程目录下新建一个名为“src”的文件下,将源文件保存。输入的源文件的后缀名为“.c”,即使用 C 语言进行设计。如果使用 c++进行设计, 那么后缀名需要设置为“.cpp”。设置好文件名和路径之后,点击“保存”。
在创建了源文件之后,在 Vivado HLS 工程面板中的 Source 目录下可以看到我们新建的源文件: led_twinkle.c,同时信息面板自动打开了该文件的编辑界面,我们可以在里面输入 C 代码。
编辑
输入代码:
1 |
|
在代码的第 1 行,包含了一个名为“ap_cint.h”的头文件,该头文件包含了对任意精度整数类型的支持。 在代码的第 5 行,我们在定义函数参数led 的类型时,就使用了这种数据类型——**“uint1”,它表示 1 位无符号整数*。
C 和 C++语言自带的数据类型是从四个基本数值类型中派生出来的 char、int、float 和 double,它们的 位宽分别是 8 位、32 位、32 位和 64 位。另外还支持 short int 类型(16 位),以及布尔类型(8 位)等。
为了优化硬件实现,用于高层次综合的设计中不应该有多余的位存在,因为那样会导致额外的 硬件开销。比如在本次实验中,我们只需要控制一个 LED,即需要的位宽为 1。如果使用 C 语言自带的 数据类型,即使是将其定义成位宽最小的 char 类型,最终综合出来的模块端口也是 8 位。除了模块的 端口,与之相关的任何寄存器以及其他运算资源也都会超过必须的大小。因此,在 HLS 中需要支持任 意字长来满足电路需要的任意程度的精度。
<2>综合代码
代码输入完成后,按快捷键 Ctrl+S 保存。然后点击工具栏中向右的绿色三角形对 C 代码进行综合,如 下图所示:
编辑
综合完成后,会自动打开综合结果(solution)的报告,给出了设计的性能评估、资源评估以及接口等信息。
编辑
如图所示,在接口信息的各个列中分别给出了设计综合出来的 **RTL 端口(Ports)、方向(Dir)、 位宽(Bits)以及协议(Protocol)**等信息。
从第二行和第三行可以看到,综合之后的模块包含了时钟 ap_clk 和复位 ap_rst 两个端口。在本设计中, 这两个端口是必须的:因为模块的运算无法在一个时钟周期内完成,且模块内部的操作是同步的,因此它 需要一个时钟信号;而模块需要能够从外部被重置,因此需要一个复位信号。
而图中蓝色方框内的一组接口用于实现模块级的协议,即 Protocol 一栏中的 ap_ctrl_hs(Control Handshake,控制握手协议)。所谓的模块级的协议主要是用于控制模块的运行,而协议具体的内容在本次实验中并不必深究,因为我们实现的功能相对比较简单,因此可以使用一个更简单的协议——ap_ctrl_none, 也就是不需要额外的控制端口。
图中红色方框内的一组接口是本次模块的输出端口,其中 led 端口用于控制开发板上的两个 LED, 而 led_ap_vld 则用于实现端口的协议,即 Protocol 一栏中的 ap_vld。对于 led 端口,我们同样不需要额外的 控制信号,因此可以使用 ap_none 协议来替换 ap_vld 协议。替换之后模块的接口会更加简洁明了。
接下来我们就通过插入“指令”(Directive)来指导综合的过程,从而得到使用 ap_ctrl_none 和 ap_none 协议的综合结果。
在右侧的辅助面板中,打开 Directive 标签页,将鼠标放在顶层函数 led_twinkle 上,然后右 击选择“Insert Directive”:
编辑
在弹出的 Vivado HLS 指令编辑框中作如下配置:
1、 在 Directive(指令)一栏选择“INTERFACE”,即选择与接口相关的指令;
2、 在 Destination(目标)一栏选择“Source File”,即将指令插入源文件;
3、 在 Options 下的 mode(模式)一栏选择“ap_ctrl_none”,即接口使用 ap_ctrl_none 协议。
设置完成后点击“OK”,如下图所示:
编辑
插入指令完成后,源代码中多了一行语句。在代码的第 6 行多了一行以#pragma 开头的指令,pragma 的含义是“编译指示”。通过插入指令,设计者可以对 C 源码的实现过程加以控制。指令中的“INTERFACE”表示这是一条 控制接口综合过程的指令,而“ap_ctrl_none”表示相应的端口在综合的过程中使用 ap_ctrl_none 协议。需 要注意的是该指令的最后一部分**“port=return”,它表示该接口指令是模块级的,而不是针对模块上的某一 个端口。接下来我们就要插入一条针对端口**的接口指令。
编辑
按照同样的方法,在信息面板中保持 led_twinkle.c 的编辑页面打开,然后在右侧的辅助面板中,打开 Directive 标签页,将鼠标放在函数的参数 led 上,然后右击选择“Insert Directive”
编辑
编辑
图中红色方框所指示的是新增的指令,与第 7 行的指令类似,它表示综合过程中接口使用的协议 为“ap_none”。与第 7 行指令末尾的“port=return”不同,在该指令末尾的**“port=led”表明该指令是针对 端口 led 的**。
在指令添加完成后,按快捷键 Ctrl+S 保存源文件。然后重新点击工具栏中的绿色三角形,运行 C 综合 过程。综合完成后,会重新打开综合报告,其中接口部分如下图所示:
编辑
在插入了两条综合指令后,HLS 重新综合之后得到的设计的端口简单了很多, 只有时钟(ap_clk)、复位(ap_rst)和 LED 端口。如果需要设置的端口较多,可以先通过上述方式插入一个端口的描述,然后复制粘贴生成的代码修改端口,在辅助设计栏会自动同步约束。
编辑
<3>生成 IP 核
我们需要通过查看综合报告,对高层次综合得到的结果进行评估。如果不满足要求(比如速度或资源 的要求),那么需要返回修改设计,然后再次进行综合。经过反复迭代得到我们满意的实现结果之后,就 来到了高层次综合流程的最后一步,将设计打包成 IP 模块,以供 Vivado 开发套件中的其他工具(如 IP 集 成器)使用。
在工具栏中点击黄色的“田”字按钮(Export RTL),导出 RTL,如下图所示:
编辑
在弹出的对话框中保持默认设置,直接点击“OK”
编辑
设计导出完成后,HLS 设计部分就结束了,我们在 HLS 工程目录下可以找到导出的 IP 核
编辑
IP 核的存储路径如下:
编辑
最后这里补充一下,如果 Vivado HLS 工程关闭后,下次如何打开工程。我们可以重新打开 Vivado HLS 软件,选择“Open Project”,此时会弹出选择 HLS 工程的文件夹,指定 HLS 工程所在的路径即可。
编辑
2.IP 验证
<1> IP 核添加
工程创建完成后,需要先将 HLS 设计过程中导出的 IP 核拷贝到 Vivado 工程目录下。我们在 Vivado 工 程目录下新建一个名为“ip_repo”的文件夹,然后将压缩包拷贝到该文件夹中并解压:
编辑
接下来在 Vivado 中将该 IP 添加到工程的 IP 库中。 点击菜单栏的**“Tools”,选择“Setting”**,如下图所示:
编辑
点击“IP”一栏下的“Repository”,然后点击“+”来添加自定义的 IP 核:
编辑
接下来会弹出添加自定义 IP 核的路径,选择刚解压的文件夹,点击“Select”, 在弹出的界面中可以看到识别到的 IP 核,点击“OK”按钮添加 IP 核:
编辑
<2>IP 核验证
随后即可进行 IP 核的验证,通过 Verilog 语言或者**“Create Block Design”进行验证**:
在 Block Design 中添加所需其他 IP 核并连接,在 Diagram 窗口空白处右击,然后选择“Validate Design” 验证设计。验证完成后弹出对话框提示**“Validation Successful”**表明设计无误,点击“OK”确认。最后按 快捷键“Ctrl+S”保存设计。
接下来在 Source 窗口中右键点击 Block Design 设计文件“system.bd”,然后依次执行**“Generate Output Products”和“Create HDL Wrapper”**。
然后我们还要为设计创建约束文件 system_wrapper.xdc,并在文件中添加以下管脚约束信息。
最后在左侧 Flow Navigator 导航栏中找到 PROGRAM AND DEBUG,点击该选项中的**“Generate Bitstream”**,对设计进行综合、实现、并生成 Bitstream 文件。
三.按键控制 LED 灯实验
在使用 Vivado HLS 工具进行设计的时候,如果设计完成之后发现功能不正确,然后确定问题产生的原 因是非常耗时的。为了提高效率,我们使用测试平台在设计完成之后验证 C 函数在功能上是否正确。
编辑
从图中可以看出在 Vivado HLS 的设计流程中,设计的输入包括 **C/C++设计、Testbench(测试集)编写 以及 Constraints(约束)/directives(指令)的添加。**Vivado HLS 最主要的输出结果是以 VHDL/Verilog 语言 描述的 RTL 实现,并打包成 IP 模块的形式以方便 Xilinx 设计工具使用,如 System Generator 等。
我们可以使用 C testbench 在*综合之前*对 C 函数进行仿真,验证函数输出的正确性,这一过程被称之为 C 仿真,对应于图中的“C Simulation”。它还可以在综合之后对*综合得到的 RTL* 设计进行仿真,这一仿真 过程同样是使用 C testbench 进行的,这一过程被称为 C/RTL 协同仿真,对应于图中的“RTL Simulation”。
1.HLS设计
1 |
|
在代码的第 1 行,包含了一个名为“key_led.h”的头文件,这个是我们自己创建的头文件。“key_led.h” 的创建方式和“key_led.c”的创建方式相同。创建此头文件的目的是为了函数声明,以便在测试文件中引入 此头文件,从而在测试文件中可以调用我们所编写的函数。
头文件“key_led.h”的代码如下:
1 |
|
在工程面板中的“Test Bench”目录上点击右键,然后在打开的列表中选择“New File”新建测试文件, 在弹出的对话框中输入测试文件的名称“key_led_tb.c”,如图 1.3.6 所示。测试文件的保存路径为 src 目录。
编辑
在测试文件中编写如下代码:
1 |
|
在测试文件中,我们通过调用按键控制函数,并给按键赋不同的值,来实现按键控制的仿真。 代码输入完成后,按快捷键 Ctrl+S 保存。然后点击工具栏中向右的绿色三角形对 C 代码进行综合,综合完成后,会自动打开综合结果(solution)的报告,如下图所示:
编辑
从接口信息中,我们可以看到按键 key 的 C 语言类型是变量,在 RTL 级别中被映射为输入端口。led 的 C 语言类型是指针,在 RTL 级别中被映射为输出端口。这是因为 HLS 规定了协议、端口类型和方向之间的相关性,在 Vivado HLS 开发过程中,考虑 C/C++ 函数参数的类型是很重要的。Vivado HLS 规定可以传入/传出 C/C++函数的值有四种不同的数据类型,分别是:变量、指针、数组和引用。这也就是说,一种特定的参数类型只对应于有限的几种协议。比如,传入一个数组作为形参输入,能使用的协议就只有:ap_hs、ap_memory、bram、ap_fifo、ap_bus、axis 和 m_axi, 其中 ap_memory 是默认的。
编辑
本实验中,我们规定接口所使用的协议为 ap_none。图中的“D”表示“default”,表示 Vivado HLS 工 具默认综合出来的接口。“S”表示“support”,表示 Vivado HLS 工具支持综合出来的接口。从图中我们可 以看出,**使用 ap_none 时,当变量作为形参时,接口只能被综合成输入而不能被综合成输出,当指针变量作为形参时,接口可以被综合成输入、输出和双向端口。**本节实验中,我们定义按键 key 的类型为变量,led 的类型指针,从而实现了将按键 key 作为输入接口,led 作为输出接口。
下面我们进行仿真,来测试 HLS 设计是否满足要求。点击工具栏中的仿真按钮,进入仿真,如下图所示:
编辑
弹出仿真配置界面,我们在配置界面中启动 C 调试器,并清除编译残留文件。设置如下图所示:
编辑
点击“OK”按钮,打开 C 调试器,在 C 调试器工具栏中点击单步调试按钮,如下图所示:
编辑
执行单步调试,在右侧变量观察窗口中可以看到当按键值 key 为 1 的时候,*LED 值变为了 1。如下图 所示:
编辑
其他内容同二。
四.呼吸灯实验
学习如何使用 Vivado HLS 工具生成一个带有 AXI4-Lite 总线接口的 IP 核,并学 习 Vivado HLS 工具 C/RTL 协同仿真平台的使用,以及在 Vivado 中对综合结果进行验证的流程。
1.简介
通过 Vivado HLS 生成一个带有 AXI4-Lite 总线接口的 IP 核,然后在 MPSOC PS 端通过 AXI4-Lite 总线来配置此 IP 核。
AXI4 协议支持突发传输,主要用于处理器访问存储器等需要指定地址的高速数据传输场景。AXI4-Lite 为外设提供单个数据传输,主要用于访问一些低速外设中的寄存器。而 AXI-Stream 接口则像 FIFO 一样, 数据传输时不需要地址,在主从设备之间直接连续读写数据,主要用于如视频、高速 AD、PCIe、DMA 接 口等需要高速数据传输的场合。
使用开发板上的 PL LED实现呼吸灯的效果,即由 灭渐亮,然后再由亮渐灭,并且 PS 可以通过 AXI 接口来控制呼吸灯的开关和呼吸的频率。
波形图:
编辑
2.HLS设计
新建一个名为 breath_led 的文件夹,作为本 次实验的工程目录。然后打开 Vivado HLS 工具,创建一个新的工程。设置工程名为“breath_led_hls”……在工程面板中的“source”目录上点击右键,然后在打开的列表中选择“New File” 新建源文件,在弹出的对话框中输入源文件的名称“breath_led.c”。
代码如下:
1 |
|
在source中添加头文件:
1 |
|
在test bench中添加测试文件:
1 |
|
在代码的第 3 行,我们在定义函数参数类型时,使用了“uint1”这种数据类型,它表示 1 位无符号整 数。在代码的第 5 行到代码的第 7 行,#pragma 为 HLS 优化指令,它表示 led 使用的是“ap_none”协议, 呼吸灯开关“sw_ctrl”和频率控制字“freq_step”使用的是 AXI4-Lite 协议。在代码的第 8 行“ap_ctrl_none” 表明没有添加包级别的协议,而是完全在端口接口级别用端口级别协议来做控制。在代码的第 1 行我们引入了“ap_cint.h”的头文件来包含任意精度数据类型。
Vitis HLS相对vivado hls变化
1 |
|
主要区别在相关库的变化和任意精度数据类型的定义。参考用户手册UG1399Vitis High-Level Synthesis User Guide:
编辑
可知如果要定义任意精度数据类型,需要引用头文件ap_int(vivadohls是ap_cint),且定义形式为ap_[u]int
另外可以参考Vitis HLS示例代码:GitHub - Xilinx/Vitis-HLS-Introductory-Examples
3.C/RTL联合仿真
先点击工具栏中向右的绿色三角形对 C 代码进行综合,综合完成后,会自动打开综合结果。
因为 Vivado HLS 工具不支持“ap_ctrl_none”包 级别协议的仿真,所以我们在仿真前先注释所有的优化指令#pragma。然后再点击工具栏中的“Solution”按钮,在打开的列表中选择“Run C/RTL Cosimulation”进入 C/RTL 协同仿真。
编辑
弹出 C/RTL 仿真配置界面,我们在配置界面中选择使用“Vivado Simulator”仿真器,“Dump Trace”选 择“all”表示生成仿真波形。
编辑
仿真结束后会自动打开报告,在工具栏或在侧边栏 flow navigator打开仿真波形图 Vivado HLS 会自动打开 Vivado Simulation 仿真工具(加载需要一定时间,不要手动打开防止崩溃)。
编辑
编辑
将图中所示信号添加到波形窗口中:
编辑
下面我们 将设计打包成 IP 模块,以供 Vivado 开发套件中的其他工具(如 IP 集成器)使用。将之前注释的优化指令重修添加到 Vivado HLS 工程中,在工具栏中点击黄色的“田”字按钮,导出 RTL。
编辑
编辑
到计算机工程目录所指向的文件夹中同样可以看到以ZIP压缩文件形式存在的IP核。
解压压缩包,在相关路径下可也看到HLS给我们生成的c文件:
编辑
其中xbreath_led.h文件包含VITIS所需的函数,文件内容如下:
1 | // ============================================================== |
4.IP验证
创建工程、配置硬件平台(参考VITIS设计二——“AXI GPIO 按键控制 LED 实验”第 3 小节硬件设计部分),添加HLS IP,自动布线。
编辑
右击,然后选择“Validate Design” 验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误。接下来在 Source 窗口中右键点击 Block Design 设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
然后我们还要为设计创建约束文件 system_wrapper.xdc,并添加以下管脚约束信息:
1 | set_property -dict {PACKAGE_PIN AE10 IOSTANDARD LVCMOS33} [get_ports {led}] |
然后点击 RTL ANALYSIS——Schematic,最后在左侧 Flow Navigator 导航栏中找到 PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,对设计进行综合、实现、并生成 Bitstream 文件。
在生成 Bitstream 之后,在菜单栏中选择 File>Export>Export hardware 导出硬件,并在弹出的对话框中, 勾选“Include bitstream”。然后在菜单栏选择 Tools>Launch Vitis,启动 Vitis 软件。
在 Vitis 软件中新建一个 BSP 工程和一个空的应用工程,应用工程名为“breath_led”。然后为应用工程新建一个源文件“main.c”,我们在新建的 main.c 文件中输入本次实验的代码。
1 |
|
第 1 行引入的“xbreath_led.h”即为 Vivado HLS 工具自动为我们生成的头文件(见前),这个头文件包含了初始化 和配置 Vivado HLS 生成 IP 核的函数。
接着下载验证即可,具体见Vitis开发二——FPGA学习笔记。
五.彩条显示实验
使用Vivado HLS设计彩条显示的IP核,并在Vivado中对设计出来的IP 核进行验证。
1.简介
AXI4-Stream 总线协议由 ARM 公司提出,该协议专门针对视频、音频、数组等数据在片内通信设计。 在本章我们将实现彩条显示的功能,来学习如何使用 Vivado HLS 工具生成一个带有 AXI4-Stream 总线接口 的 IP 核,以及在 Vivado 中对综合结果进行验证的流程。
关于 AXI Stream 的基本概念解释如下:
传输(Transfer):通过 AXI4 流接口进行的一个单一数据传输。一个单一数据传输由 TVALID 和 TREADY 握手信号定义。
包(Packet):通过 AXI4 流接口被一起传输的一组字节,包类似于 AXI4 的突发。
帧(Frame):一个 AXI4 流中最高级别的字节编组。一帧可以包含很大数量的字节数,例如,一个完 整的视频帧缓存。
数据流(Data Stream):从一个源设备到一个目标设备传输的数据。
相关详细介绍可以参考《计算机通信》与*进阶设计一(DDR3)——一.5*
TVALID 和 TREADY 信号的握手包含三种情况:TVALID 先于 TREADY 的握手、TREADY 先于 TVALID 的握手、TVALID 和 TREADY 同时发生的握手:
编辑
编辑
编辑
“AXI4-Stream to Video Out”IP核模块中的 AXI4-Stream 接口:
编辑
重点关注图中的“s_axis_video_tlast”和“s_axis_video_tuser”信号,其中“s_axis_video_tlast”是 AXI4-Stream 协议中“TLAST”信号,这个信号设置为高表示一行像素传输结束,“s_axis_video_tuser”是 AXI4-Stream 协议中的“TUSER”信号,这个信号设置为高表示一帧图像传输开始。
编辑
图中的“EOL”表示“End of line”是行传输结束信号,它在一行图像像素传输结束的时候拉高一个时 钟周期;图中的“SOF”表示“Start of frame”是帧传输开始信号。它在一帧图像像素传输开始的时候拉高 一个时钟周期。
2.HLS设计
创建HLS工程lcd_colorbar_hls,指定顶层函数lcd_colorbar,添加src文件lcd_colorbar.c:
1 | //引入数据类型头文件 |
我们这里将 data 置为 24 位,是因为采用 RGB888 格式的数据进行传输,数据位宽为 24。
在代码的第 12 行,定义了顶层函数“lcd_colorbar”,这里我们传入 axi_stream 结构体类型的指针是因 为 vivodo HLS 规定如果用户想要生成带有 AXI4-Stream 接口的 IP 核,只能传入指针或者数组的类型作为输出接口。“rows”表示图像行数,“cols”表示图像列数。这里我们可以通过 MPSOC 的 PS 端来配置这些 参数,从而兼容不同分辨率的 LCD 屏幕。
在代码的第 15 行到第 17 行,“axis”指定了颜色数据传输采用 AXI4-Stream 总线协议,“s_axilite” 指定了行和列控制信号传输采用 AXI4-Lite 总线协议。“ap_ctrl_none”表明没有添加包级别的协议,而是完全在端口接口级别用端口级别协议来做控制。
代码的第 24 行使用 PIPELINE 指令对代码进行优化。HLS 优化设计的最关键指令有两个:一个是流水线(pipeline)指令,一个是数据流(dataflow)指令。正确地使用好这两个指令能够增强算法地并行性,提升吞吐量,降低延迟,但是需要遵循一定的代码风格。Pipeline 指令在循环和函数两个层级都可以使用,通过增 加重复的操作指令(如增加资源使用量等等)来减小初始化间隔。Dataflow 指令是一个任务级别的流水线 指令,从更高的任务层次使得循环或函数可以并行执行,目的在于减小延迟增加吞吐量。
优化指令#pragma HLS PIPELINE 的作用是缩短 C 函数或 C 循环之内的指令触发间隔(initial interval, II)。在不使用该指令的情况下,函数或循环默认的指令触发间隔 II 为 N;使用该指令后,编译器将将对 II 进行优化,默认将其缩短为 1,用户也可以在优化指令中指定期望的 II 值。如果 Vivado HLS 编译器不能将 II 优化为指定值,那么会在默认情况下把 II 优化到最小。
对于本次实验来说,如果不添加 PIPELINE 指令,会影响产生彩条数据的速率,从而无法支持 10 寸 RGB LCD 液晶屏;而添加 PIPELINE 指令后,则可以提升该 IP 核产生彩条的速率,从而可以支持 10 寸 RGB LCD 液晶屏,但与此同时,所消耗的逻辑资源也有所增加。
代码的第 27 行到第 31 行是一个条件判断语句,通过判断像素点的位置来设置“user”和“last”信号 的值。(“x_pos==0”&&“y_pos==0”)表示一帧图像数据开始传输,此时将 user 信号置高一个时钟周期, 在其它情况下将 user 信号置低。代码的第 33 行到第 37 行,“x_pos == (cols – 1)”表示一行像素传输到最 后一个位置,这里我们将 cols 减一是因为 cols 是从零开始计数。当一行数据传输结束的时候将 last 信号置 高一个时钟周期,在其它情况下将 last 信号置低。
在代码的第 39 行到第 52 行,通过比较“x_pos”和“color_edge”的大小,来对像素点的颜色数据进行 赋值。其中“0xff0000”表示 RGB888 格式的数据高 8 位为 1,从而像素点显示红色。
注:VITIS HLS下的代码
1 | //引入数据类型头文件 |
注意axi_stream流数据:
编辑
故对代码做了相关修改,参考:Streaming • Vitis HLS Messaging (UG1448)
点击工具栏进行综合,结果如下:
编辑
图中 Protocol 一栏,“s_axi”和“axis”分别表示 Vivado HLS 生成了一个带有“AXI4-Lite”从接口和 “AXI4-Stream”总线接口的 IP 核。其中“AX4-Lite”总线接口用于控制彩条显示的分辨率,“AXI4-Stream” 总线接口用于传输彩条数据。
编辑
最后导出RTL,HLS设计部分结束。