目录
1. 简介
2. 控制初始化与复位
2.1 初始化
2.2 复位
2.3 全局复位选项
2.4 复位排除
3. 阵列初始化和复位
3.1 不使用 static 限定符
3.2 使用 static 限定符
3.3 BRAM 和 URAM
4. 总结
1. 简介
本文对比分析两个方面的初始化和复位:阵列和控制(也即块级)
阵列的初始化和复位
- 阵列初始化:初始化是在编译时(或上电时)为变量(包括阵列)赋予初始值的过程。对于 C/C++ ,全局变量和静态变量默认初始化为0,但也可以给它们指定初始值。对于硬件逻辑,阵列会被实现为存储器(例如RAM),并且在上电或编程 FPGA 时,这些存储器会被预加载指定的初始值。
- 阵列复位:在运行时,通过复位信号将变量(包括阵列)恢复到其初始状态的过程。不同于初始化,复位是一个动态过程,可以在程序执行期间任何时刻发生。如果选择将变量复位到其初始状态,这会导致设计中增加额外的逻辑和存储器资源。
控制的初始化与复位
- 控制初始化:初始化行为主要关注变量(包括阵列和寄存器)在编译时或上电时的状态。
- 控制复位:在 kernel 端口添加复位信号,当用复位信号使能,立即将连接到复位端口的寄存器和 BRAM 还原为初始值。通常 RTL 配置中最重要的操作即选择复位行为。
2. 控制初始化与复位
2.1 初始化
默认情况下,C/C++ 中以 static 变量和全局变量都初始化为 0,也可由用户赋予特定初始值。
在 Vitis HLS 工具中,需要注意:
- 在 RTL 仿真期间,为这些变量设置的初始值与 C/C++ 语言代码中相同。
- 在用于对 FPGA 进行编程的比特流中,也会对这些变量进行初始化。当器件上电时,变量将以其初始状态启动。
在 RTL 中,虽然变量启动时使用的初始值与 C/C++ 语言代码相同,但无法强制该变量返回至此初始状态。要复原初始状态,必须通过复位信号来实现。
2.2 复位
复位端口在 FPGA 中用于在应用复位信号时,立即将连接到复位端口的寄存器和块 RAM 还原为初始值。通常 RTL 配置中最重要的操作即选择复位行为。
* TOP LEVEL CONTROL
+-----------+---------------+-----------+
| Interface | Type | Ports |
+-----------+---------------+-----------+
| ap_clk | clock | ap_clk |
| ap_rst_n | reset | ap_rst_n |
| interrupt | interrupt | interrupt |
| ap_ctrl | ap_ctrl_chain | |
+-----------+---------------+-----------+
对于复位行为,重要的是理解初始化与复位之间的差异。
2.3 全局复位选项
复位选项包含:
- 复位极性(reset_level)
- 同步复位/异步复位(reset_async)
- 最重要的,通过“reset”选项来控制应用复位信号时要复位的寄存器。
“reset”选项包含 4 项设置:
- “none”:在设计中不添加复位。
- “control”:这是默认设置,将所有控制寄存器复位。控制寄存器即状态机中使用的寄存器,用于生成 I/O 协议信号。此设置可确保 kernel 可立即启动其操作状态。
- “state”:在“control”设置基础上,添加衍生自 C/C++ 语言代码中的静态变量和全局变量的任意寄存器或存储器添加复位。此设置可确保应用复位后,C/C++ 语言代码中初始化的静态变量和全局变量均复位为其初始值。
- “all”:为 kernel 中的所有寄存器和存储器添加复位。
2.4 复位排除
通过 RESET 编译指示或指令可提供更精细的复位控制。静态变量和全局变量均可通过 RESET 指令来添加复位。还可使用 RESET 指令的 off 选项从复位的变量中移除变量。
示例一:
void foo(int in[3], char a, char b, char c, int out[3]) {
#pragma HLS reset variable=a
此示例会为 foo 函数中的 a 变量添加复位,即使全局复位设置为 none 或 control 也是如此。
示例二:
void foo(int in[3], char a, char b, char c, int out[3]) {
#pragma HLS reset variable=a off
从 foo 函数中的 a 变量移除复位,即使全局复位设置为 state 或 all 也是如此。
3. 阵列初始化和复位
对于阵列,推荐使用含 static 限定符的存储器来实现。这不仅可确保 HLS 工具以存储器来实现阵列,还允许使用“static”(静态)类型的默认初始化行为。
最为对比,我们设定两种情况:
3.1 不使用 static 限定符
int coeff[8] = {-2, 8, -4, 10, 14, 10, -4, 8};
每次执行函数时,都会为 coeff 阵列分配这些值。综合后,每次执行 kernel 时,用于实现 coeff 的 RAM 都会随这些值一起加载。
如果使用单端口 RAM,此操作耗时 8 个时钟周期。对于 1024 阵列,当然也就需要 1024 个时钟周期,在此期间无法执行任何依赖于 coeff 的运算。
3.2 使用 static 限定符
static int coeff[8] = {-2, 8, -4, 10, 14, 10, -4, 8, -2};
该阵列开始执行时会使用指定的值进行初始化。每次执行该函数时,coeff 阵列都会保留上次执行的值。静态阵列在 C/C++ 语言代码中的行为与 RTL 中的存储器行为相同。
如果变量包含 static 限定符,那么 Vitis HLS 会对 RTL 设计和 FPGA 比特流中的变量进行初始化。因此,无需经历多个时钟周期来初始化存储器,并且可确保大型存储器初始化不会产生任何运算开销。
3.3 BRAM 和 URAM
在不同的硬件平台上,比如 UltraScale+ 或 Versal,内存块(BRAM)和超大型RAM(URAM)的启动和重置方式可能会有所不同。简单来说,Vitis HLS 工具支持两种重置方式:
- 上电初始化:就像设备刚通电时自动设置好的状态。这种情况下,所有平台的 BRAM 和 Versal 的 URAM 都会被设置成预先定义的初始值。
- 运行时重置:当设备正在运行中,如果我们发送一个 RESET 信号,BRAM 和 URAM 会被重置到初始状态,就像重新启动一样。
如果读取/写入阵列,则保留“initial value array”(初始值阵列,ROM)和“run time array”(运行时阵列,RAM)。此行为适用于 BRAM 和 URAM,对应于器件执行期间的硬件 RESET 信号。
4. 总结
本文讨论了初始化和复位在硬件设计中的不同方面。阵列的初始化是在编译时或上电时为变量赋予初始值,而复位是在运行时将变量恢复到其初始状态。控制的初始化和复位也是关键,通过复位信号可以在 kernel 端口添加复位行为。对于阵列,使用 static 限定符的存储器可以实现默认初始化行为,避免运算开销。此外,BRAM 和 URAM 的启动和重置方式也需要根据硬件平台进行考虑。