循环流水打拍 - 2023.2 简体中文

Vitis 高层次综合用户指南 (UG1399)

Document ID
UG1399
Release Date
2023-12-18
Version
2023.2 简体中文

流水打拍循环允许在前一次循环迭代完成前就启动后一次循环迭代,从而支持部分循环在执行时重叠。默认情况下,循环的每次迭代仅在前一次迭代完成后才会开始。在以下循环示例中,单次循环迭代会添加 2 个变量,并将结果存储在第 3 个变量中。假定在硬件中,此循环需要 3 个周期才能完成 1 次迭代。同时假定循环变量 len 为 20,即,vadd 循环在内核内运行 20 次迭代。因此,需要总计 60 个时钟周期(20 次迭代 * 3 个周期),才能完成此循环的所有操作。

vadd: for(int i = 0; i < len; i++) { 
   c[i] = a[i] + b[i];
}
提示: 最好始终按以上示例所示方式来标记循环 (vadd:…)。此实践有助于在 Vitis HLS 中进行设计调试。有时,未使用的标记在编译期间会生成警告,用户可放心忽略此警告。

循环流水打拍支持该循环的后续迭代发生重叠且以并发方式运行。您可通过在循环主体内添加 PIPELINE 编译指示或指令来启用循环流水打拍,如下所示:

vadd: for(int i = 0; i < len; i++) { 
#pragma HLS PIPELINE 
c[i] = a[i] + b[i];
}
提示: Vitis HLS 会对含超过 64 次迭代的循环自动进行流水打拍。此功能特性可使用 syn.compile.pipeline_loops 配置命令加以更改或者禁用,如 编译选项 中所述。

启动下一次循环迭代所需的周期数称为流水打拍循环的启动时间间隔 (Initiation Interval, II)。因此 II = 2 表示循环的下一次迭代会在当前迭代的 2 个周期后启动。II = 1 是理想情况,即每个周期内都会启动一次循环迭代。使用 pragma HLS pipeline 时,您可以指定编译器要实现的 II。如果不指定目标 II,那么默认情况下编译器可尝试实现 II=1。

下图显示了流水打拍循环与非流水打拍循环的执行差异。在下图中,(A) 显示了默认顺序操作,每次输入读操作间存在 3 个时钟周期 (II = 3),并且需要经过 8 个时钟周期后才会执行最后一次输出写操作。

图 1. 循环流水打拍

在 (B) 所示的循环的流水打拍版本中,每个周期都会读取一次新输入样本 (II = 1),仅需 4 个时钟周期后即可写入最终输出,在使用相同硬件资源的前提下显著改善 II 和时延。

重要: 循环流水打拍会导致自动展开流水打拍循环内部嵌套的任意循环。

如果循环内部存在任何数据依赖关系,则可能无法实现 II = 1,并且可能导致启动时间间隔增大。循环依赖关系属于数据依赖关系,可能对循环优化(通常为流水打拍)施加约束。此类依赖关系可出现在任一循环的单一迭代内或者也可出现在任一循环的不同迭代内。了解循环依赖关系的最简单的方法是检验极端示例。在以下示例中,循环结果用作为循环持续条件或循环退出条件。循环的每次迭代必须完成后才能开始下一次迭代。

Minim_Loop: while (a != b) {
if (a > b)a -= b;
else b -= a;
}

以上示例中的 Minim_Loop 循环无法流水打拍,因为前一次迭代结束后,后一次循环迭代才能开始。并非所有循环依赖关系都如此极端,但此示例着重展示了部分操作在某些其他操作完成后才能开始的情况。解决方案是尝试确保尽早执行初始运算。

循环依赖关系可能发生于所有类型的数据。使用阵列时尤其常见。