目录
1. 简介
2. 分析与详解
2.1 未优化
2.2 LOOP_TRIPCOUNT 优化指令
2.3 重写变量循环边界
3. 总结
1. 简介
在硬件设计中,循环的迭代次数通常需要是固定的,因为这有助于资源的预分配和时序分析。
循环边界包含变量意味着循环的迭代次数不是固定的,而是由程序运行时的某个变量值决定。这种情况下,编译器无法预先知道循环的确切迭代次数,从而影响代码的优化和性能分析。
当循环包含变量边界时,将禁止执行 Vitis HLS 可应用的某些最优化操作。
int example(int A[32], int n) {
...
for (i = 0; i < n; i++) {
out_accum += A[i];
}
...
}
其中,循环边界由顶层输入驱动的变量 n 来判定。在此情况下,循环被视为包含变量边界,因为 Vitis HLS 无从知晓循环将何时完成。
本文探讨如何解决这种问题。
2. 分析与详解
2.1 未优化
Vitis HLS 工具会尝试循环进行进行最优化,并且最终的报告包含变量循环边界造成的问题。
#include "ap_int.h"
ap_int<13> example(ap_int<8> A[32], ap_uint<5> n) {
ap_int<13> out_accum = 0;
ap_uint<5> i;
LOOP:
for (i = 0; i < n; i++) {
out_accum += A[i];
}
return out_accum;
}
变量循环边界的第一个问题是阻止 Vitis HLS 判定循环时延。
Vitis HLS 可判定完成一次循环迭代所需的时延,但由于它无法静态判定精确的变量宽度值 n,因此无从知晓执行的迭代次数,因而无法报告循环时延(即完全执行循环的所有迭代的周期数)。
存在变量循环边界时,Vitis HLS 会将时延报告为问号 (?) 而不是使用精确值。以下显示了示例的综合后的结果:
* Loop:
+----------+---------+---------+----------+-----------+-----------+------+----------+
| | Latency (cycles) | Iteration| Initiation Interval | Trip | |
| Loop Name| min | max | Latency | achieved | target | Count| Pipelined|
+----------+---------+---------+----------+-----------+-----------+------+----------+
|- LOOP | ?| ?| 2| 1| 1| ?| yes|
+----------+---------+---------+----------+-----------+-----------+------+----------+
2.2 LOOP_TRIPCOUNT 优化指令
loop_tripcount 语法
#pragma HLS loop_tripcount min=<int> max=<int> avg=<int>
克服此问题的方法是使用 LOOP_TRIPCOUNT 指令来为循环指定最小和/或最大迭代计数。
#include "ap_int.h"
ap_int<13> example(ap_int<8> A[32], ap_uint<5> n) {
ap_int<13> out_accum = 0;
ap_uint<5> i;
LOOP:
for (i = 0; i < n; i++) {
#pragma HLS loop_tripcount min=32 max=32 avg=32
out_accum += A[i];
}
return out_accum;
}
循环次数(tripcount) 表示循环的迭代次数。优化后的是示例中,tripcount 最大值 32,那么报告将更新为显示如下内容:
* Loop:
+----------+---------+---------+----------+-----------+-----------+------+----------+
| | Latency (cycles) | Iteration| Initiation Interval | Trip | |
| Loop Name| min | max | Latency | achieved | target | Count| Pipelined|
+----------+---------+---------+----------+-----------+-----------+------+----------+
|- LOOP | 32| 32| 2| 1| 1| 32| yes|
+----------+---------+---------+----------+-----------+-----------+------+----------+
LOOP_TRIPCOUNT 指令提供的值仅用于报告,或者用于支持 PERFORMANCE 编译指示或指令。指定的循环次数值使 Vitis HLS 能够在报告中判定时延值,以便对来自不同解决方案的值进行比较。要将此循环边界信息用于综合,必须使用断言更新 C/C++ 语言代码,这样会影响综合,由于断言条件假定为 true,因此必须谨慎使用。
由于变量边界循环无法完全展开,因此不仅阻止应用展开 (unroll) 指令,而且还会阻止循环上层的层级的流水打拍操作。
2.3 重写变量循环边界
这里的解决方案是将循环的迭代次数设置为一个固定的最大值(此处为32),然后在循环体内部使用条件语句来决定是否执行循环的主体。
#include "ap_int.h"
ap_int<13> example(ap_int<8> A[32], ap_uint<5> n) {
ap_int<13> out_accum = 0;
ap_uint<5> i;
LOOP:
for (i = 0; i < 32; i++) {
if (i < n) {
out_accum += A[i];
}
}
return out_accum;
}
在 LOOP 循环中,i 从0迭代到 32,但只有当 i 小于 n 时,才会将 A[i] 加到累加器 out_accum 上。这样,即使 n 是一个变量,循环的迭代次数也是固定的,满足硬件设计的要求。
示例中的 for 循环 (LOOP) 则可完全展开。由于此循环的上限固定,因此 Vitis HLS 知晓需创建的硬件数量。在 RTL设计中包含 32 份循环主体副本。每份循环主体副本都包含与之关联的条件逻辑,并根据变量 n 值来执行。
3. 总结
在硬件设计中,固定的循环迭代次数对于资源分配和时序分析至关重要。Vitis HLS 在处理包含变量边界的循环时面临挑战,因为这限制了某些优化操作的执行。通过使用 LOOP_TRIPCOUNT 指令指定迭代次数的范围,可以帮助工具更好地进行时延分析。此外,将循环重写为固定次数的迭代,同时在循环体内部使用条件语句来控制执行,可以满足硬件设计的需求,允许循环展开和流水线化,从而提高性能。这些方法为变量边界循环提供了有效的解决方案,使得即使在变量驱动的情况下,也能实现硬件设计的优化。