FPGA学习笔记(20)——Xilinx_HLS开发

参考资料:《MPSoc 之 HLS 开发指南——正点原子》

一.HLS 简介

​ Xilinx 推出的 Vivado HLS 工具可以直接使用 C、C++或 System C 来对 Xilinx 系列的 FPGA 进行编程,从而提高抽象的层级,大大减少了使用传统 RTL 描述进行 FPGA 开发所需的时间。

1.高层综合简介

FPGA 设计过程中的不同抽象层级:

img

点击并拖拽以移动编辑

​ 其中最底层的抽象(结构性的)涉及到对底层硬件单元直接的例化,比如逻辑门,甚至是更 底层的 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 进行设计的流程如下图所示:

img

点击并拖拽以移动编辑

​ 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 协同仿真的区别如下图所示:

img

点击并拖拽以移动编辑

​ 在图左侧的功能性验证(C 仿真)中,原始测试集是用户输入的测试文件 TestBench。而右侧的 C/RTL 协同仿真所需的 RTL 测试集是由 Vivado HLS 自动产生的,这样就不再需要人工创建了,所产生的测 试集包括了原始测试集和被测 RTL 模块之间的数据传递。

​ 在设计被验证了之后,而且实现也满足了期望的设计目标,那么就可以集成进更大的系统里了。我们 可以直接使用 HLS 过程所产生的 RTL 文件(即 VHDL 或 Verilog 代码),更方便的做法是使用 Vivado HLS 的 IP 打包功能。对 Vivado HLS 所产生的输出打包意味着 HLS 设计能够以 IP 核的形式引入其他 Xilinx 工具 中,比如 Vivado 中的 IP 集成器。

img

点击并拖拽以移动编辑

3.接口综合

在做 HLS 的时候,设计者需要分析设计的两个主要方面:

• 设计的接口,也就是它的顶层连接;

• 设计的功能,也就是它所实现的算法;

img

点击并拖拽以移动编辑

​ 在上图中,两端的绿色区域表示设计的输入和输出接口,其中展示了部分接口类型,如 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(),其参数如下图所示:

img

点击并拖拽以移动编辑

​ 这个函数定义包含三个参数,数组“sample”和整数“X”是函数的输入,而 average 作为函数的输出。 因此,简单来说,这三个函数参数要被 HLS 转换成两个输入接口和一个输出接口,如下图所示:

img

点击并拖拽以移动编辑

​ 需要注意的是,图只是一个简化了的接口示意图。根据所用的协议,这些接口可能包括数据端口 自身以外的控制输入或输出,如下图所示:

img

点击并拖拽以移动编辑

​ 图是函数 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)。它们是交替进行的,彼此互相 影响,如下图所示:

img

点击并拖拽以移动编辑

调度是把由 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 进行高层次综合的过程如下图所示:

img

点击并拖拽以移动编辑

1.HLS设计

<1>创建HLS工程

​ 打开 Vivado HLS 软件**(最新版为 Vitis HLS)**。在 Vivado HLS 的开始界面中点击左“Create New Project”,创建一个新 的工程。然后会弹出新建 Vivado HLS 工程向导界面。

​ 在“Project name”一栏输入工程名“xxx_hls”,然后在“Location”一栏通过点击 右侧的“Browse…”按钮,选择工程路径。需要注意的是,工程名以及路径只能由英文字 母、数字和下划线组成,不能包含中文、空格以及其他特殊字符。 设置好工程名及路径之后,点击“Next”。

img

点击并拖拽以移动编辑

img

点击并拖拽以移动编辑

​ 在“Top Function”一栏设置设计的顶层函数为“led_twinkle”,然后点击“Next”,之后我们可以添加 C 测试文件,这里我们不需要添加,直接点击“Next”

img

点击并拖拽以移动编辑

​ DFZU2EG/4EV MPSOC 开发板上的芯片有三种型号,XCZU4EV-sfvc784-1-i、XCZU2EG-sfvc784-2-i 和 XCZU2CG-sfvc784-2-i。

​ 创建工程完成后,Vivado HLS 会打开工程界面,如下图所示:

img

点击并拖拽以移动编辑

​ 在工程面板中的“source”目录上点击右键,然后在打开的列表中选择“New File”新建源文件。然后在弹出的对话框中输入源文件的名称“led_twinkle.c”,如图 2.3.11 所示。源文件默认的保存路径 为 HLS 工程目录,为方便源文件的管理,我们在工程目录下新建一个名为“src”的文件下,将源文件保存。输入的源文件的后缀名为“.c”,即使用 C 语言进行设计。如果使用 c++进行设计, 那么后缀名需要设置为“.cpp”。设置好文件名和路径之后,点击“保存”。

​ 在创建了源文件之后,在 Vivado HLS 工程面板中的 Source 目录下可以看到我们新建的源文件: led_twinkle.c,同时信息面板自动打开了该文件的编辑界面,我们可以在里面输入 C 代码。

img

点击并拖拽以移动编辑

输入代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <ap_cint.h>

#define DELAY 100000000

void led_twinkle(uint1 *led){
int i = 0;

for(i = 0; i < DELAY; i++){
if(i < DELAY/2)
*led = 1;
else
*led = 0;
}
}

点击并拖拽以移动

​ 在代码的第 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 代码进行综合,如 下图所示:

img

点击并拖拽以移动编辑

​ 综合完成后,会自动打开综合结果(solution)的报告,给出了设计的性能评估、资源评估以及接口等信息。

img

点击并拖拽以移动编辑

​ 如图所示,在接口信息的各个列中分别给出了设计综合出来的 **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”:

img

点击并拖拽以移动编辑

在弹出的 Vivado HLS 指令编辑框中作如下配置:

1、 在 Directive(指令)一栏选择“INTERFACE”,即选择与接口相关的指令

2、 在 Destination(目标)一栏选择“Source File”,即将指令插入源文件

3、 在 Options 下的 mode(模式)一栏选择“ap_ctrl_none”,即接口使用 ap_ctrl_none 协议。

设置完成后点击“OK”,如下图所示:

img

点击并拖拽以移动编辑

插入指令完成后,源代码中多了一行语句。在代码的第 6 行多了一行以#pragma 开头的指令,pragma 的含义是“编译指示”。通过插入指令,设计者可以对 C 源码的实现过程加以控制。指令中的“INTERFACE”表示这是一条 控制接口综合过程的指令,而“ap_ctrl_none”表示相应的端口在综合的过程中使用 ap_ctrl_none 协议。需 要注意的是该指令的最后一部分**“port=return”,它表示该接口指令是模块级的,而不是针对模块上的某一 个端口。接下来我们就要插入一条针对端口**的接口指令。

img

点击并拖拽以移动编辑

​ 按照同样的方法,在信息面板中保持 led_twinkle.c 的编辑页面打开,然后在右侧的辅助面板中,打开 Directive 标签页,将鼠标放在函数的参数 led 上,然后右击选择“Insert Directive”

img

点击并拖拽以移动编辑

img

点击并拖拽以移动编辑

​ 图中红色方框所指示的是新增的指令,与第 7 行的指令类似,它表示综合过程中接口使用的协议 为“ap_none”。与第 7 行指令末尾的“port=return”不同,在该指令末尾的**“port=led”表明该指令是针对 端口 led 的**。

​ 在指令添加完成后,按快捷键 Ctrl+S 保存源文件。然后重新点击工具栏中的绿色三角形,运行 C 综合 过程。综合完成后,会重新打开综合报告,其中接口部分如下图所示:

img

点击并拖拽以移动编辑

​ 在插入了两条综合指令后,HLS 重新综合之后得到的设计的端口简单了很多, 只有时钟(ap_clk)、复位(ap_rst)和 LED 端口。如果需要设置的端口较多,可以先通过上述方式插入一个端口的描述,然后复制粘贴生成的代码修改端口,在辅助设计栏会自动同步约束。

img

点击并拖拽以移动编辑

<3>生成 IP 核

​ 我们需要通过查看综合报告,对高层次综合得到的结果进行评估。如果不满足要求(比如速度或资源 的要求),那么需要返回修改设计,然后再次进行综合。经过反复迭代得到我们满意的实现结果之后,就 来到了高层次综合流程的最后一步,将设计打包成 IP 模块,以供 Vivado 开发套件中的其他工具(如 IP 集 成器)使用。

​ 在工具栏中点击黄色的“田”字按钮(Export RTL),导出 RTL,如下图所示:

img

点击并拖拽以移动编辑

​ 在弹出的对话框中保持默认设置,直接点击“OK”

img

点击并拖拽以移动编辑

​ 设计导出完成后,HLS 设计部分就结束了,我们在 HLS 工程目录下可以找到导出的 IP 核

img

点击并拖拽以移动编辑

​ IP 核的存储路径如下:

img

点击并拖拽以移动编辑

​ 最后这里补充一下,如果 Vivado HLS 工程关闭后,下次如何打开工程。我们可以重新打开 Vivado HLS 软件,选择“Open Project”,此时会弹出选择 HLS 工程的文件夹,指定 HLS 工程所在的路径即可

img

点击并拖拽以移动编辑

2.IP 验证

<1> IP 核添加

​ 工程创建完成后,需要先将 HLS 设计过程中导出的 IP 核拷贝到 Vivado 工程目录下。我们在 Vivado 工 程目录下新建一个名为“ip_repo”的文件夹,然后将压缩包拷贝到该文件夹中并解压:

img

点击并拖拽以移动编辑

​ 接下来在 Vivado 中将该 IP 添加到工程的 IP 库中。 点击菜单栏的**“Tools”,选择“Setting”**,如下图所示:

img

点击并拖拽以移动编辑

​ 点击“IP”一栏下的“Repository”,然后点击“+”来添加自定义的 IP 核:

img

点击并拖拽以移动编辑

​ 接下来会弹出添加自定义 IP 核的路径,选择刚解压的文件夹,点击“Select”, 在弹出的界面中可以看到识别到的 IP 核,点击“OK”按钮添加 IP 核:

img

点击并拖拽以移动编辑

<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 函数在功能上是否正确。

img

点击并拖拽以移动编辑

​ 从图中可以看出在 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "key_led.h"

void key_led(uint2 key,uint2* led)
{
#pragma HLS INTERFACE ap_none port=led
#pragma HLS INTERFACE ap_none port=key
#pragma HLS INTERFACE ap_ctrl_none port=return

uint2 key_value = key;

switch(key_value)
{
case 3:
*led = 3;
break;
case 2:
*led = 2;
break;
case 1:
*led = 1;
break;
default:
*led = 0;
break;
}
}

点击并拖拽以移动

​ 在代码的第 1 行,包含了一个名为“key_led.h”的头文件,这个是我们自己创建的头文件。“key_led.h” 的创建方式和“key_led.c”的创建方式相同。创建此头文件的目的是为了函数声明,以便在测试文件中引入 此头文件,从而在测试文件中可以调用我们所编写的函数。

​ 头文件“key_led.h”的代码如下:

1
2
3
4
5
6
7
8
#include <ap_cint.h>

#ifndef _KEY_LED_H_
#define _KEY_LED_H_

void key_led(uint2 key,uint2* led);

#endif

点击并拖拽以移动

​ 在工程面板中的“Test Bench”目录上点击右键,然后在打开的列表中选择“New File”新建测试文件, 在弹出的对话框中输入测试文件的名称“key_led_tb.c”,如图 1.3.6 所示。测试文件的保存路径为 src 目录。

img

点击并拖拽以移动编辑

​ 在测试文件中编写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "key_led.h"

int main()
{
uint2 key,led;

key_led(0,&led);
key_led(1,&led);
key_led(2,&led);
key_led(3,&led);

return 0;
}

点击并拖拽以移动

​ 在测试文件中,我们通过调用按键控制函数,并给按键赋不同的值,来实现按键控制的仿真。 代码输入完成后,按快捷键 Ctrl+S 保存。然后点击工具栏中向右的绿色三角形对 C 代码进行综合,综合完成后,会自动打开综合结果(solution)的报告,如下图所示:

img

点击并拖拽以移动编辑

​ 从接口信息中,我们可以看到按键 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 是默认的。

img

点击并拖拽以移动编辑

​ 本实验中,我们规定接口所使用的协议为 ap_none。图中的“D”表示“default”,表示 Vivado HLS 工 具默认综合出来的接口。“S”表示“support”,表示 Vivado HLS 工具支持综合出来的接口。从图中我们可 以看出,**使用 ap_none 时,当变量作为形参时,接口只能被综合成输入而不能被综合成输出,当指针变量作为形参时,接口可以被综合成输入、输出和双向端口。**本节实验中,我们定义按键 key 的类型为变量,led 的类型指针,从而实现了将按键 key 作为输入接口,led 作为输出接口。

​ 下面我们进行仿真,来测试 HLS 设计是否满足要求。点击工具栏中的仿真按钮,进入仿真,如下图所示:

img

点击并拖拽以移动编辑

​ 弹出仿真配置界面,我们在配置界面中启动 C 调试器,并清除编译残留文件。设置如下图所示:

img

点击并拖拽以移动编辑

​ 点击“OK”按钮,打开 C 调试器,在 C 调试器工具栏中点击单步调试按钮,如下图所示:

img

点击并拖拽以移动编辑

​ 执行单步调试,在右侧变量观察窗口中可以看到当按键值 key 为 1 的时候,*LED 值变为了 1。如下图 所示:

img

点击并拖拽以移动编辑

​ 其他内容同二。

四.呼吸灯实验

​ 学习如何使用 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 接口来控制呼吸灯的开关和呼吸的频率。

波形图:

img点击并拖拽以移动编辑

2.HLS设计

​ 新建一个名为 breath_led 的文件夹,作为本 次实验的工程目录。然后打开 Vivado HLS 工具,创建一个新的工程。设置工程名为“breath_led_hls”……在工程面板中的“source”目录上点击右键,然后在打开的列表中选择“New File” 新建源文件,在弹出的对话框中输入源文件的名称“breath_led.c”。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "breath_led.h"

void breath_led(uint32 sw_ctrl, uint32 freq_step, uint1* led)
{
#pragma HLS INTERFACE ap_none port=led
#pragma HLS INTERFACE s_axilite port=freq_step
#pragma HLS INTERFACE s_axilite port=sw_ctrl
#pragma HLS INTERFACE ap_ctrl_none port=return
uint32 duty_cycle,period_cnt;
if(sw_ctrl == 1){
for(duty_cycle=0;duty_cycle<100000;duty_cycle=duty_cycle+freq_step){
for(period_cnt=0;period_cnt<100000;period_cnt++)
*led = (period_cnt <= duty_cycle) ? 1 : 0;
}

for(duty_cycle=100000;duty_cycle>0;duty_cycle= duty_cycle-freq_step){
for(period_cnt=0;period_cnt<100000;period_cnt++)
*led = (period_cnt <= duty_cycle) ? 1 : 0;
}
}else
*led = 0;
}

点击并拖拽以移动

在source中添加头文件:

1
2
3
4
5
6
7
8
#include "ap_cint.h"

#ifndef _BREATH_LED_H
#define _BREATH_LED_H_

void breath_led(uint32 sw_ctrl, uint32 freq_step, uint1* led);

#endif

点击并拖拽以移动

在test bench中添加测试文件:

1
2
3
4
5
6
7
8
9
10
11
12
#include "breath_led.h"

int main(void)
{
uint1 led;

breath_led(1, 4000, &led); //打开呼吸灯,频率步长设置为4000
breath_led(0, 4000, &led); //关闭呼吸灯

printf("test passed!\n");
return 0;
}

点击并拖拽以移动

​ 在代码的第 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
2
3
4
5
6
7
8
9
10
11
#include <ap_int.h>

#ifndef _BREATH_LED_H_
#define _BREATH_LED_H_

typedef ap_int<32> uint32;
typedef ap_uint<1> uint1;

void breath_led(uint32 sw_ctrl, uint32 freq_step, uint1* led);

#endif

点击并拖拽以移动

​ 主要区别在相关库的变化和任意精度数据类型的定义。参考用户手册UG1399Vitis High-Level Synthesis User Guide

img

点击并拖拽以移动编辑

可知如果要定义任意精度数据类型,需要引用头文件ap_int(vivadohls是ap_cint),且定义形式为ap_[u]int。为了减少代码修改量,可以创建头文件public.h,在里面进行typedef。

另外可以参考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 协同仿真。

img

点击并拖拽以移动编辑

​ 弹出 C/RTL 仿真配置界面,我们在配置界面中选择使用“Vivado Simulator”仿真器,“Dump Trace”选 择“all”表示生成仿真波形

img

点击并拖拽以移动编辑

仿真结束后会自动打开报告,在工具栏或在侧边栏 flow navigator打开仿真波形图 Vivado HLS 会自动打开 Vivado Simulation 仿真工具(加载需要一定时间,不要手动打开防止崩溃)

img

点击并拖拽以移动编辑

img

点击并拖拽以移动编辑

将图中所示信号添加到波形窗口中:

img

点击并拖拽以移动编辑

​ 下面我们 将设计打包成 IP 模块,以供 Vivado 开发套件中的其他工具(如 IP 集成器)使用。将之前注释的优化指令重修添加到 Vivado HLS 工程中,在工具栏中点击黄色的“田”字按钮,导出 RTL。

img

点击并拖拽以移动编辑

img

点击并拖拽以移动编辑

​ 到计算机工程目录所指向的文件夹中同样可以看到以ZIP压缩文件形式存在的IP核。

​ 解压压缩包,在相关路径下可也看到HLS给我们生成的c文件:

img

点击并拖拽以移动编辑

​ 其中xbreath_led.h文件包含VITIS所需的函数,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// ==============================================================
// Vitis HLS - High-Level Synthesis from C, C++ and OpenCL v2023.1 (64-bit)
// Tool Version Limit: 2023.05
// Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
// Copyright 2022-2023 Advanced Micro Devices, Inc. All Rights Reserved.
//
// ==============================================================
#ifndef XBREATH_LED_H
#define XBREATH_LED_H

#ifdef __cplusplus
extern "C" {
#endif

/***************************** Include Files *********************************/
#ifndef __linux__
#include "xil_types.h"
#include "xil_assert.h"
#include "xstatus.h"
#include "xil_io.h"
#else
#include <stdint.h>
#include <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stddef.h>
#endif
#include "xbreath_led_hw.h"

/**************************** Type Definitions ******************************/
#ifdef __linux__
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
#else
typedef struct {
u16 DeviceId;
u64 Control_BaseAddress;
} XBreath_led_Config;
#endif

typedef struct {
u64 Control_BaseAddress;
u32 IsReady;
} XBreath_led;

typedef u32 word_type;

/***************** Macros (Inline Functions) Definitions *********************/
#ifndef __linux__
#define XBreath_led_WriteReg(BaseAddress, RegOffset, Data) \
Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))
#define XBreath_led_ReadReg(BaseAddress, RegOffset) \
Xil_In32((BaseAddress) + (RegOffset))
#else
#define XBreath_led_WriteReg(BaseAddress, RegOffset, Data) \
*(volatile u32*)((BaseAddress) + (RegOffset)) = (u32)(Data)
#define XBreath_led_ReadReg(BaseAddress, RegOffset) \
*(volatile u32*)((BaseAddress) + (RegOffset))

#define Xil_AssertVoid(expr) assert(expr)
#define Xil_AssertNonvoid(expr) assert(expr)

#define XST_SUCCESS 0
#define XST_DEVICE_NOT_FOUND 2
#define XST_OPEN_DEVICE_FAILED 3
#define XIL_COMPONENT_IS_READY 1
#endif

/************************** Function Prototypes *****************************/
#ifndef __linux__
int XBreath_led_Initialize(XBreath_led *InstancePtr, u16 DeviceId);
XBreath_led_Config* XBreath_led_LookupConfig(u16 DeviceId);
int XBreath_led_CfgInitialize(XBreath_led *InstancePtr, XBreath_led_Config *ConfigPtr);
#else
int XBreath_led_Initialize(XBreath_led *InstancePtr, const char* InstanceName);
int XBreath_led_Release(XBreath_led *InstancePtr);
#endif


void XBreath_led_Set_sw_ctrl(XBreath_led *InstancePtr, u32 Data);
u32 XBreath_led_Get_sw_ctrl(XBreath_led *InstancePtr);
void XBreath_led_Set_freq_step(XBreath_led *InstancePtr, u32 Data);
u32 XBreath_led_Get_freq_step(XBreath_led *InstancePtr);

#ifdef __cplusplus
}
#endif

#endif

点击并拖拽以移动

4.IP验证

​ 创建工程、配置硬件平台(参考VITIS设计二——“AXI GPIO 按键控制 LED 实验”第 3 小节硬件设计部分),添加HLS IP,自动布线。

img

点击并拖拽以移动编辑

​ 右击,然后选择“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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "xbreath_led.h"
#include "unistd.h"

int main()
{
XBreath_led led;

XBreath_led_Initialize(&led, XPAR_BREATH_LED_0_DEVICE_ID);

while(1)
{
XBreath_led_Set_freq_step(&led,80);
XBreath_led_Set_sw_ctrl(&led,1); //打开呼吸灯
sleep(5);
XBreath_led_Set_sw_ctrl(&led,0); //关闭呼吸灯
sleep(2);
XBreath_led_Set_freq_step(&led,400);
XBreath_led_Set_sw_ctrl(&led,1); //打开呼吸灯
sleep(5);
}
return 0;
}

点击并拖拽以移动

​ 第 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 同时发生的握手:

img

点击并拖拽以移动编辑

img

点击并拖拽以移动编辑

img

点击并拖拽以移动编辑

“AXI4-Stream to Video Out”IP核模块中的 AXI4-Stream 接口:

img

点击并拖拽以移动编辑

​ 重点关注图中的“s_axis_video_tlast”和“s_axis_video_tuser”信号,其中“s_axis_video_tlast”是 AXI4-Stream 协议中“TLAST”信号,这个信号设置为高表示一行像素传输结束,“s_axis_video_tuser”是 AXI4-Stream 协议中的“TUSER”信号,这个信号设置为高表示一帧图像传输开始

img

点击并拖拽以移动编辑

​ 图中的“EOL”表示“End of line”是行传输结束信号,它在一行图像像素传输结束的时候拉高一个时 钟周期;图中的“SOF”表示“Start of frame”是帧传输开始信号。它在一帧图像像素传输开始的时候拉高 一个时钟周期。

2.HLS设计

​ 创建HLS工程lcd_colorbar_hls,指定顶层函数lcd_colorbar,添加src文件lcd_colorbar.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//引入数据类型头文件
#include "ap_cint.h"

//定义axi4-stream类型的结构体
typedef struct ap_axi_stream{
uint24 data;
uint1 user; //帧传输开始信号
uint1 last; //行传输结束信号
}axi_stream;

//彩条显示顶层函数
void lcd_colorbar(axi_stream* color,int rows,int cols)
{
//Vivado HLS接口定义
#pragma HLS INTERFACE s_axilite port=cols
#pragma HLS INTERFACE s_axilite port=rows
#pragma HLS INTERFACE axis port=color
#pragma HLS INTERFACE ap_ctrl_none port=return

int x_pos,y_pos;
int color_edge = cols/7; //显示7个彩条数据

for(y_pos=0; y_pos<rows; y_pos++){
#pragma HLS PIPELINE
for(x_pos=0; x_pos<cols; x_pos++){
//帧传输开始拉高一个时钟周期
if((x_pos==0) && (y_pos==0)){
(*color).user = 1;
}else{
(*color).user = 0;
}
//行传输结束拉高一个时钟周期
if(x_pos == (cols-1)){
(*color).last = 1;
}else{
(*color).last = 0;
}
//像素点彩条数据赋值
if(x_pos < color_edge){
(*color).data = 0xff0000; //红色
}else if(x_pos >= color_edge && x_pos < color_edge*2){
(*color).data = 0xff7f00; //橙色
}else if(x_pos >= color_edge*2 && x_pos < color_edge*3){
(*color).data = 0xffff00; //黄色
}else if(x_pos >= color_edge*3 && x_pos < color_edge*4){
(*color).data = 0x00ff00; //绿色
}else if(x_pos >= color_edge*4 && x_pos < color_edge*5){
(*color).data = 0x00ffff; //青色
}else if(x_pos >= color_edge*5 && x_pos < color_edge*6){
(*color).data = 0x0000ff; //蓝色
}else if(x_pos >= color_edge*6 && x_pos < cols){
(*color).data = 0x8b00ff; //紫色
}
}
}
}

点击并拖拽以移动

​ 我们这里将 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//引入数据类型头文件
#include "ap_int.h"
typedef ap_uint<24> uint24;
typedef ap_uint<1> uint1;

//定义axi4-stream类型的结构体
typedef struct ap_axi_stream{
uint24 data;
uint1 user; //帧传输开始信号
uint1 last; //行传输结束信号
}axi_stream;

//彩条显示顶层函数
void lcd_colorbar(axi_stream* color,int rows,int cols)
{
//Vivado HLS接口定义
#pragma HLS INTERFACE s_axilite port=cols
#pragma HLS INTERFACE s_axilite port=rows
#pragma HLS INTERFACE axis port=color
#pragma HLS INTERFACE ap_ctrl_none port=return

int x_pos,y_pos;
int color_edge = cols/7; //显示7个彩条数据
axi_stream temp;

for(y_pos=0; y_pos<rows; y_pos++){
#pragma HLS PIPELINE
for(x_pos=0; x_pos<cols; x_pos++){
//帧传输开始拉高一个时钟周期
if((x_pos==0) && (y_pos==0)){
temp.user = 1;
}else{
temp.user = 0;
}
//行传输结束拉高一个时钟周期
if(x_pos == (cols-1)){
temp.last = 1;
}else{
temp.last = 0;
}
//像素点彩条数据赋值
if(x_pos < color_edge){
temp.data = 0xff0000; //红色
}else if(x_pos >= color_edge && x_pos < color_edge*2){
temp.data = 0xff7f00; //橙色
}else if(x_pos >= color_edge*2 && x_pos < color_edge*3){
temp.data = 0xffff00; //黄色
}else if(x_pos >= color_edge*3 && x_pos < color_edge*4){
temp.data = 0x00ff00; //绿色
}else if(x_pos >= color_edge*4 && x_pos < color_edge*5){
temp.data = 0x00ffff; //青色
}else if(x_pos >= color_edge*5 && x_pos < color_edge*6){
temp.data = 0x0000ff; //蓝色
}else if(x_pos >= color_edge*6 && x_pos < cols){
temp.data = 0x8b00ff; //紫色
}

*color = temp;

}
}
}

点击并拖拽以移动

注意axi_stream流数据:

img

点击并拖拽以移动编辑

故对代码做了相关修改,参考:Streaming • Vitis HLS Messaging (UG1448)

​ 点击工具栏进行综合,结果如下:

img

点击并拖拽以移动编辑

​ 图中 Protocol 一栏,“s_axi”和“axis”分别表示 Vivado HLS 生成了一个带有“AXI4-Lite”从接口和 “AXI4-Stream”总线接口的 IP 核。其中“AX4-Lite”总线接口用于控制彩条显示的分辨率,“AXI4-Stream” 总线接口用于传输彩条数据。

img

点击并拖拽以移动编辑

​ 最后导出RTL,HLS设计部分结束。

3.IP验证

> --------------- THE END -------------- <