描述
允许将内层嵌套循环折叠(平铺)为单个循环,以便在循环的所有迭代上应用流水打拍,从而实现更低的时延。
只能对最内层的循环(完成可能的内部展开后)进行流水打拍。外层循环只能是数据流循环或顺序循环。当流水打拍循环上层的循环为顺序循环时,其迭代会依次执行,而且每次迭代,就会完全执行一次内层循环。在 RTL 实现中,从外层循环移动到内层循环需要一个时钟周期,从内层循环移回外层循环需要一个时钟周期,并且中间要加上完成内层循环的所有迭代的完整时延。与此相反,平铺嵌套循环可以将其作为单个循环来进行优化和流水打拍,这样外层循环的迭代与内层循环不同调用的迭代之间就可能发生重叠(流水打拍)。总之,平铺可以提高性能,但是:
- 平铺并非总是可行,需要遵循一定的编码风格。
- 在某些情况下,根据外层循环中的运算和/或依赖关系,时序甚至 II 都可能会劣化。
将 LOOP_FLATTEN 编译指示应用于循环层级的最内层循环的循环主体。只有完美循环和近乎完美的嵌套循环(在完成可能的初步函数内联或循环展开后)才能以这种方式加以平铺:
- 完美循环嵌套
-
- 每个非最内层循环的主体都包含一个且仅包含一个子循环,不包含任何其他指令。
- 近乎完美的循环嵌套
-
- 每个非最内层循环的主体都包含一个且仅包含一个子循环,无任何其他控制流。
- 每个非最内层循环的主体都不得包含任何含循环的函数调用。
对于近乎完美的循环,编译器会将两个循环之间存在的任何指令自动“推入”最内层的循环,从而使这些循环完美嵌套。
此外具备平铺能力的循环还需要满足一些要求:
- 每个循环都应该是 for 循环,而不是 while 循环;并且不含 break 语句。
- 在平铺循环之前,编译器应能计算出每个循环的循环次数(但它不必是一个常数)。典型的编码风格如下:
- 所含循环计数器按常数递增的循环。
- 循环计数器的下限和上限不依赖于要平铺的循环(应满足“循环不可变”条件)。
举例来说,如果循环的内层循环次数取决于外层循环计数器,那么这类循环就不具备平铺能力。
编译器无法平铺不完美的循环嵌套(例如,当循环包含多个子循环或控制流时)。在此情况下,需要手动执行平铺,方法是重构代码,将指令推入最内层的循环,或者展开内层循环以在其上层创建完美循环嵌套。
语法
只要满足平铺能力要求,即可将 C 语言源代码的编译指示置于如下循环内:该循环通常是最内层的循环,并且要与其上层(完美循环或近乎完美的)循环一起平铺。
#pragma HLS loop_flatten
选项:
-
off - 可选关键字。阻止将 loop_flatten 设为 off 的循环与其子循环(如有)一起进行平铺。如果在最内层的循环中指定 loop_flatten off,则不会发生任何平铺行为(即使有自动平铺也不予执行)。
示例 1
在循环层级内找到要与上层的完美循环或近乎完美的循环(此处的 loop_0)一起平铺的循环,将编译指示置于该循环的(此处的 loop_1)主体内,前提是满足平铺能力要求。
void foo (N, M, ...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
loop_1: for (j=0; j<M; j++) {
#pragma HLS loop_flatten
...
}
}
}
示例 2
阻止将 loop_1 循环与 loop_0 一起进行平铺。仅将 loop_1 和 loop_2 一起平铺。
void foo (N, M, P, ...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
#pragma HLS loop_flatten off
loop_1: for (j=0; j<M; j++) {
loop_2: for (k=0; k<P; k++) {
#pragma HLS loop_flatten
...
}
}
}
}
示例 3
如果 loop_flatten 编译指示上层有两个以上嵌套循环,那么以下情况下编译器可以决定不平铺所有循环:如果预计可能有 II 劣化,或者如果它需要明确知晓(但不能证明)内层循环迭代至少一次。在此情况下,可以在要与周围循环一起平铺的循环内执行一次额外的 loop_flatten 编译指示来强制平铺,例如,此处 loop_1 中,强制与 loop_0 一起平铺。
void foo (N, M, P, ...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
... // some instruction with side effect
loop_1: for (j=0; j<M; j++) {
#pragma HLS loop_flatten
loop_2: for (k=0; k<P; k++) {
#pragma HLS loop_flatten
...
}
}
}
}
备注:
- 对于 loop_flatten 编译指示,如果要将某些有副作用的指令推入循环(针对近乎完美的循环),那么编译器假定包含该编译指示的循环至少迭代一次。
- 平铺时,内层循环中的任何依赖编译指示(false 或有距离)都会应用于平铺后获得的单一循环。
示例 4
以下示例显示了可平铺的近乎完美的循环。
void foo (...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
int s = 0;
loop_1: for (j=0; j<M; j++) {
#pragma HLS loop_flatten
s += a[i][j];
b[i][j] = s;
}
}
}
示例 5
以下示例显示了不可平铺的循环。
void foo (...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
loop_1: for (j=i; j<M; j++) {
#pragma HLS loop_flatten
...
}
}
}