写在前面
本文是本系列专题的第十二篇,参考高亚军老师的视频教程以及课程的ppt,主要介绍了vivado HLS对for 循环优化。
循环嵌套的优化
循环的类型
完美循环嵌套:循环次数为常数,循环体只会在最内部的循环中出现。
**半完美循环嵌套:**内部循环体次数为常数,外部循环体次数为变量,循环部分只在最内层循环出现。
不完美循环嵌套:内外层的循环此时为常量,但是循环体存在内外循环中。
或者是这种,循环体只出现在最内层中,但是内层的循环次数不确定,是个变量。
对于不完美循环嵌套,内部循环有变量边界,或者循环体不完全在内部循环中,设计师应该尝试重组代码,或展开循环体中的循环,以创建一个完美的循环嵌套。
完美循环嵌套优化
对大多数应用程序来说,流水操作最内层的循环提供了具有一般可接受吞吐量的最小硬件将层次结构。而对于外层循环进行流水操作,则内部所有的for循环都会展开,并可以创建更多要调度的操作(这可能会影响运行时和内存容量),但通常会在吞吐量和延迟方面提供最高的性能设计。
LOOP FLATTEN
允许嵌套循环被折叠成一个具有改进延迟的单一循环。它只能应用于完美和半完美循环。它消除了重新编码以获得最佳硬件性能的需要。
不完美循环嵌套优化
如果只对最内层做循环流水情况:
如果对中间层做循环流水情况:
如果只对最外层做循环流水情况:
经过比较可以看出,在最外层做流水,资源使用情况是最多的,但是延迟也是最低的。在最内层做流水,资源使用是最少的,同时延时也是最大的。
下图为矩阵乘法的运算机制。
下图代码在执行最开始时,对ab行列的值进行了缓存。以便于提升性能。
for循环嵌套小结
当流水化循环或函数时,在流水化循环或函数下面的层次结构中的任何循环都必须展开。对大多数应用程序来说,流水最内层的循环提供了具有一般可接受吞吐量的最小硬件。
for循环其他优化方法
封装后使用ALLOCATION
从下面的这个示例中可以看出,这两个for循环的变量相互独立,在翻译成硬件电路时候可以进行并行执行。而默认状态下,HLS会进行串行的分析。
分装成函数也是串行执行的。
这里使用的是进行封装后使用ALLOCATION,实现了延时的降低。
pipeline操作中使用rewind
在未使用rewind操作时,每执行一次for循环中间会有一个时钟周期的延时,在使用了rewind操作后,执行完上一个循环后,下一个for循环直接进流水,而不需要等待一个时钟周期的延迟。
在下面这个例子中,勾选rewind的pipeline操作,从综合报告中可得知,使用rewind操作的电路延迟是最小的,使用的电路资源也是最少的
从仿真结果中也可以看出上述差异。
自动循环流水
config_compile配置使循环能够自动进行循环流水操作优化。pipeline_loops选项设置迭代限制。所有迭代计数低于此限制的循环将自动流水线化。默认值是0,即为不执行自动循环流水操作。
如果在设计中存在不希望使用自动流水的循环,则对该循环应用带有off选项的PIPELINE指令。off选项防止自动循环流水操作。
流水操作失效
当一个任务被流水线化时,层次结构中的所有循环都会自动展开。这是流水操作运行的必要条件。如果一个循环有变量边界,它将不能被展开,这将阻止任务被流水线化。因为Vivado HLS无法知道何时循环将完成。
循环边界问题及解决办法
循环边界问题阻止了Vivado HLS决定循环的延迟,因为该设计的性能未知。 Vivado HLS以问号(?)的形式报告延迟,而不是使用确切的值。如下图:
解决办法:
- 使用Tripcount指令。
- 定义循环边界的数据类型为ap_int< W >
- 在代码中设置一个assert宏定义
使用Tripcount指令
使用Tripcount指令,不会影响最后的综合结果,只会作用于报告显示,方便对比。
定义循环边界的数据类型为ap_int< W >
在前面也提到了,ap_int类型会根据设定的w的大小进行自动综合为对应个数的寄存器,例子中的W为5,所以计数的个数为0-15,这里定义为了int的有符号类型,所以最大值为15。
资源对比结果:
设置一个assert宏定义
设置assert宏定义后,(这里设置小于5,所以最后的trip count 是0-4)。
资源对比:
这里的资源对比可能存在问题,因为在上面的循环的最大次数不一致。