引入了任意精度的数据类型
跟数据类型相关的一个函数–sizeof
对任意长度的数据类型使用sizeof
需要对visual studio进行一定的修改,就可在visual studio编写vivado程序
可拷贝初始化,也可以直接初始化
但是不支持初始化列表初始化
还可以通过声明所初始化数值的数据格式(例如二进制、八进制、十进制),来直接初始化变量
要避免每一行初始化多个变量,每一行只初始化一个变量
分别解释WIQO四个字母的含义
W:整个定点数数据位数
I:整数部分的位数
Q:量化模式,针对低位部分
AP_RND:向正无穷舍入,其实就是舍去最低位,但是如果最低位是一个1,我们就要舍去最低位的值,并给高一位进位1
O:溢出模式,针对高位模式(默认为只整数部分的低位,AP_SAT是根据定点数的位宽决定,如果超出的位宽决定得最大值或者最小值,就会以位宽的最大值,最小值代替,下边两个都是有符号数,所以最大值为正数,最高位为0。)
单精度浮点数的定义必须要添加f,否则会定义为双精度
HLS还提供了数学库
可以使用括号进行类型的转换,类似与c/c++中的使用方法
常用的算数运算 +和-统一为+
下面表格指定了对于运算结果所需要的最小保存位数
总之就是做到大数据不溢出,小数据不损失。
可以使用typeid函数来获取变量的类型,需要包含头文件typeinfo。
总结:创建变量的时候需要确保数据类型所用的正确性
c++中常用的复合数据类型就两类,一类是结构体,另一类是类
我们在我们定义的顶层函数中使用结构体时,
结构体中的标量成员会被映射为一个标量的端口,
结构体中的数组,会被映射为一个memory端口
结构体预先在头文件中进行声明
结构体中的元素,HLS提供了两种优化方式,好像叫做数据打包?
分为两种层次filed_level和struct_level
下边是执行程序,A是一个位宽为4位的数,B是一个位宽位4,深度为4的数组。
修改优化的方法需要使用HLS工具中的右侧的directive来指定对应的元素的优化方法
什么是field_level呢?
每个以元素都以8bit为边界,所以例子当中的每个元素都被从4位扩展到8位
struct_level呢?
每个元素的位宽还是保持不变,但是整个结构体的位宽是以8位边界
我们可以看到,不管是哪种优化方法,相对与原来的延时和间隔都有了很大提升,但是两种优化方法的效果是一样的,这是因为我们对for循环进行了展开。
枚举类型就是将数组定义为一个符号常量
需要使用enum关键字来定义
每个枚举类型中的定义元素都会被分到一个数值,就是c++中的枚举
枚举在HLS内的例子
vivado既支持结构体,也支持枚举
两种类型都可以在顶层中作为接口出现
如果是结构体出现在顶层函数中,它会通过data_pack的filed_level和struct_level来封装结构体。
如果枚举类型出现在顶层函数,它会被顶层函数自动的推断
例子说明:
使用复数运算,首先我们需要包含复数的头文件,也可以说是模板类!真的有这个类!
头文件:< complex >
comple实在std的命名空间内
操作的函数是std的命名空间下的求实部和虚部函数real()和image()
总共进行了四次乘法和两次加法
下面的方法使用了三次乘法和五次加法
资源使用情况对比,可以看出不同算法,使用的资源也不同
注意:递归函数 vivodo HLS是不支持的
testbetch使用来验证我们设计的算法或模型的正确性的环境
下图中的Driver/Stimulus是指的是输入激励,Reference是一个标准的参考值,DUT指的是我们写的算法,Monitor是指我们的激励通过算法产生的结果,scoreboard是对比板,用来对比我们算法激励得到的结果和参考值的一致性,我们通常认为Reference是一个正确的值
可以直接定义变量、数组、或者外部文件,三者都可以用来产生激励。
使用外部文件可以更方便的获取更多的数据,灵活性也更大
得分板的主要功能就是比较DUT的在激励的输出和黄金数据,以及给错误数据一些信息
一般来说,两种得分板的比较方法是:1. 使用if判断 2. 使用system这个函数
我们可以使用system(“diff -w file1 file2”)函数来进行比较
通过控制输出格式,我们可以获取更多有用的信息
一般来说,推荐使用to_string()函数方式相比于cout<
在综合后,会生成一些接口,在下例中,共有三类接口
将顶层函数中的接口协议设置为ap_fifo协议,可以看出生成的端口包括fifo读/写控制信号、fifo的空/满标记信号、fifo的数据输入/输出的端口,数据必须要有一定的顺序才可以选择ap_fifo协议
HLS提供了
最常用的使前两种
Loop1的资源将被loop2和loop3使用,且没有冲突,那么我们有什么降低延迟的方法呢?
data-flow可以看作一种特殊的流水线方式,只要前一级循环有输出数据,那么后一级循环就可以使用这组数据进行运算,也就降低了for循环的执行时间
对于不完美的嵌套循环,我们通过使用代码优化的方式,或者展开循环的方式来改造成一个完美的嵌套循环或者半完美的嵌套循环
在我们开启了for循环的pipeline的大前提之下,在我们不对这个for循环开启rewind这个选项的时候,两次for循环之间会有停止的时钟间隔,但是如果开启了rewind这个选项,两次for循环之间会持续的进行流水线的执行
当函数中有多个for循环的时候,开启pipeline中的rewind就会报错,所以可以看出,rewind并不是适合所有的for循环,它是有条件的。
自动添加流水 ,使用set solution中的configuration setting中添加config_compile,设置pipeline_loop,当循环的次数低于设置的pipeline_loop时,就会自动添加pipeline流水,在编辑for循环的directive,选择INTERFACE,可以设置不使用pipeline流水,这样for循环自动添加流水就不会影响到此for循环
什么时候添加pipeline会失效呢?当函数和for循环被添加pipeline时,函数和for循环里面的运算就会被展开,但是如果循环的边界是一个变量的时候,HLS就会阻止这样的pipeline流水,原因是因为HLS并不知道这样的循环何时会停止
当循环边界是变量的时候,HLS会无法确定整个循环的执行时间,进一步也就无法确定整个函数的执行时间,这时候相应的执行时间会以❓的形式来代替
对于这种情况,我们有如下三种处理方式
使用C语言的assert宏,需要包含
对于数组,可以resource这个directive可以明确告知HLS当前这个数组是使用什么样的memory来实现的,比如通过分布式的还是block ram,是采用单端口,还是双端口,如果没有使用这个resource这个directive,HLS会自动推断出是单端口还是双端口,取决于哪种方式可以有效地加快执行速度
在综合后,数组最终会以memory这种形式出现,包含了RAM,ROM,或者FIFO这三种形式
下边图片描述有错误应该是在sum处,添加directive,选择SOURCE选项,在core选择RAM_2P_BRAM,同时for循环也进行pipeline展开流水,这样就通过设置数组加快了循环的执行速度。
HLS提供了三种对数组分割的方法
都是在array_partition的directive下进行类型(type)选择,同时需要设置数量(完全分割除外)
*三种数组分割方式对数组的操作执行均有加速的效果
是不是意味着越多的block越多,效果越好呢?
实际上,block的数量取决于真是的数据流,因为上面例子我们一次运算只需要3个数据,我们使用两个block,并且一个block有两个端口,对于我们来说,数据也是够用的