HLS设计步骤
1) HLS介绍
1. 什么是HLS
High level synthesis, 能够将高级语言(C/C++)综合为HDL。
2. 为什么使用HLS
在算法层面,高级语言通常比HDL开发效率更高, 因此使用HLS可以大大加速原型验证。高级语言进行程序设计可以脱离硬件, HLS可以将项目的软件, 硬件开发分离, 更加灵活, 利于项目的管理。
2) 了解Vivado HLS的设计流程
开发流程:
C/C++ source code + Testbench-> C功能仿真 -> C综合 -> C/RTL联合仿真 -> 导出RTL,生成IP核 -> Vivado工程中调用这个IP核
如图, Vivado HLS界面的设计也体现了这样的设计流程。
1. 实例演示
我们将用HLS实现一个detection模块。
Vivado HLS只能对一个顶层函数进行综合, 而且这个函数名不能是main。 所以我们应该将我们想要综合的功能封装成一个函数 detection。
我们一个图像识别模块为例。我们将实现一个快速圆心识别模块,输入一帧包含一个圆的黑白二值图像,输出圆心的估计坐标。
下面是一个示例:我们用MATLAB创建了一个测试图像,模块输出的坐标应该接近(128, 73)。
我们简略地提供一种实现算法。算法过程如下:
将图像分成一系列正方形网格
对于每个网格Block,计算网格中白点的个数
如果白点的个数大于某个阈值Threshold, 并且该Block比周围所有的Block计数值都大(处在中心),那么就认为圆心可能位于该Block
在该Block周围的邻域内,求白点的重心。即,以周围的Block的白点个数为权重,求Block位置的加权平均(分别在x, y方向)。
返回求得的平均值
2. 工程建立步骤
首先准备好源文件之后, 我们打开HLS创建工程
创建完之后,首先执行C仿真:
跳出如下界面,并显示没有错误
第二步执行综合:
综合之后会生成综合报告, 其中可以看到生成HDL的latency参数, 和硬件资源用量。
然后执行cosimulation
3. 在Vivado中调用HLS生成的IP核。
上面我们完成了从工程的创建一直到C/RTL联合仿真, 那么我们该如何将生成的代码应用到Vivado里面去呢?
Solution > Export RTL,或者点击工具栏快捷按钮
然后默认ok
然后在以下目录下发现ip
4. 接口综合
首先我们要了解以下, HLS是怎么综合函数的输入输出变量的。
在综合报告中, 我们会看到一栏描述接口的表格。
可以看到, 除了模块自动生成的控制信号, 我们的三个输入变量和一个返回变量分别被综合成了四个ap_none协议, 32bits的变量。 这些在生成IP核之后, 就会成为四组32根的数据线。 ap_none是最简单的接口协议, 也是最常用的协议之一。
除了采用默认的接口协议, 我们也可以在directive窗口手动指定想要综合的接口协议。
以上的例子中, 函数被综合成了一个组合逻辑电路。 我们再来看一个时序逻辑电路的例子。
可以看到, 输入输出数组都被综合成了ap_memory协议的接口。
从仿真结果也可以看到各个信号的作用:
ap_memory是另一个非常常用的协议, 所有的数组都会综合成这样的协议。 而在生成IP核之后, 我们需要为这些接口手动分配memory, 这是尤其需要注意的一点。 我们在下面实例中将演示方法。
此外,为了提高读取效率,数组还可以进行分组partition,这样可以多路并行读写,提高数据吞吐量,这点在后文也有体现。
5. 数据类型
值得注意的一点是数据类型。 Int数据类型占用4个Byte, 因此自然会生成32根信号线, 但是实际应用中, 我们的变量往往不用这么大的位宽。 比如某个变量mode的取值范围是0~3, 显然我们只需要2根信号线就够了。怎么实现呢?
HLS内置了任意精度的数据类型。
#include
ap_uint<2> mode;
这样, 变量mode就会被综合成2 bits
不仅是接口, 对中间变量合理使用数据类型也可以大大节约资源使用, 这是非常必要的。
6. 综合并导出RTL
上面的算法能够识别一个圆心,我们这里提供一个更复杂的例子,它使用同样的算法识别四个圆心。
顶层函数有四个变量。
输入:第一个是img数组,是图像输入,数组长度是图像的长宽乘积。
输出:第二个和第三个变量是x, y坐标,用于输出识别到的物体坐标,这里最多可以识别四个物体,因此数组长度是4。
第四个变量输出识别到物体的个数。
注意到右侧directive窗口,我们对这些接口的综合方式做了一些指定。
对img, 我们使用了RESOURCE方式,它能够自动生成支持各类memory的接口。一般来说,Vivado中最简单的RAM就是单口BRAM了,因此我们core一栏选择RAM_1P_BRAM。
对于第二个和第三个变量,如果我们采用默认设置,HLS会自动为这些数组也综合成memory接口。而在这里,数组的长度仅为4,而且每个坐标变量位宽仅为9。所以,我们希望HLS把这个数组所有元素直接引出到模块IO口。我们采用的方法是partition。
方式设为complete, 维度设置为1,这样数组就会被完全展开。
我们执行综合,在综合报告interface一栏看一看生成的接口。
Img被综合成了ap_memory类型,支持单口BRAM。 X, y所有元素都被展开,引出了8组9bits位宽的数据线, 跟我们希望的一样。
7. 调用IP
通过block design添加ip
首先create block design
在添加IP核之前,我们要将生成的IP核目录加入项目目录。打开Project Settings
我们生成的IP核会出现在HLS工程对应solution下的impl/ip目录。将该目录添加到IP Repository。
完成之后,在Block design中就可以顺利找到我们的IP核了。把它添加。
选中它,按下CTRL+T,就可以一键引出所有端口。
回到source窗口,右键我们刚刚创建的block design, 点击Create HDL Wrapper, 我们就可以生成Vivado可以调用的.v文件了
那么,我们的img数组是从RAM读取的,怎么创建这个RAM呢?添加Block Memory Generator。
双击生成的模块。
这里选择简单双口RAM(一路只读,一路只写), A口可以作为数据的输入,B口可以给我们的detection模块读取,就可以成为缓冲了。
设置好位宽和大小,并确定。
将port B对应数据线 地址线和控制线与img的接口连好。
引出Port A供外部调用。再执行一次上述的create HDL Wrapper, 这样模块。v文件就创建完成了,可以在你的Vivado顶层文件中调用。