1. 简介
初始化行为
在 C/C++ 中,默认情况下,以静态限定符定义的变量和全局作用域中定义的变量都初始化为 0。(可选)对于这些变量,可赋予特定初始值。对于这些已初始化的变量,C/C++ 语言代码中的值在编译时(时序为 0 时)进行赋值,并且不再进行赋值。上述 2 种情况下,在 RTL 中实现初始值。
• 在 RTL 仿真期间,为这些变量设置的初始值与 C/C++ 语言代码中相同。
• 在用于对 FPGA 进行编程的比特流中,也会对这些变量进行初始化。当器件上电时,变量将以其初始状态启动。
在 RTL 中,虽然变量启动时使用的初始值与 C/C++ 语言代码相同,但无法强制该变量返回至此初始状态。要复原初始状态,必须通过复位信号来实现这些信号变量。
2. 阵列初始化和复位
对于阵列,推荐使用含 static 限定符的存储器来实现。这不仅可确保 HLS 工具以存储器来实现阵列,还允许使用"static"(静态)类型的默认初始化行为。
最为对比,我们看一下两种情况:
不使用 static 限定符。
cpp
int coeff[8] = {-2, 8, -4, 10, 14, 10, -4, 8, -2};
每次执行函数时,都会为 coeff 阵列分配这些值。综合后,每次执行 kernel 时,用于实现 coeff 的 RAM 都会随这些值一起加载。
如果使用单端口 RAM,此操作耗时 9 个时钟周期。对于 1024 阵列,当然也就需要 1024 个时钟周期,在此期间无法执行任何依赖于 coeff 的运算。
使用 static 限定符。
cpp
static int coeff[8] = {-2, 8, -4, 10, 14, 10, -4, 8, -2};
该阵列开始执行时会使用指定的值进行初始化。每次执行该函数时,coeff 阵列都会保留上次执行的值。静态阵列在 C/C++ 语言代码中的行为与 RTL 中的存储器行为相同。
如果变量包含 static 限定符,那么 Vitis HLS 会对 RTL 设计和 FPGA 比特流中的变量进行初始化。因此,无需经历多个时钟周期来初始化存储器,并且可确保大型存储器初始化不会产生任何运算开销。
RTL 配置命令 config_rtl -reset 可指定在应用复位后,静态变量是否返回其初始状态。这并非默认设置。使用reset state 或 all 时,会强制作为块 RAM 实现的所有阵列都在复位后返回初始化状态。这可能导致 RTL 设计中出现 2 个非常不利的条件:
- 不同于上电初始化(或上电复位),显式复位要求 RTL 设计对块 RAM 中的每个地址进行迭代以设置该值:如果 N较大,这可能导致时钟周期数量显著增加,并增加实现复位所需的面积资源。
- 在设计中每个阵列中都添加复位。
为防止在每个此类 BRAM 上添加复位逻辑,并因复位 RAM 内所有元素而产生周期开销,请指定默认 control 复位模式,并使用 RESET 编译指示或指令来识别要复位的个别静态变量或全局变量。
或者,可使用 state 复位模式,并使用 RESET 指令 off 选项选择不执行复位的个别静态变量或全局变量。
最后,根据所选的硬件器件或平台(UltraScale+ 或 Versal 等),BRAM 和 URAM 的初始化和/或复位方式可能不尽相同。总体上,Vitis HLS 支持两类复位:一种类型是器件上电(也称为上电初始化或上电复位),第二种类型是在器件执行期间,硬件 RESET 信号断言有效。以下显示了不同存储器资源的行为差异:
- 初始化行为:适用于所有平台上的所有 BRAM,仅适用于 Versal URAM。这是上电初始化(或上电复位)期间的行为。
- 如果读取/写入阵列,则保留"initial value array"(初始值阵列)和"run time array"(运行时阵列)。此行为适用于 BRAM 和 URAM,对应于器件执行期间的硬件 RESET 信号。
3. 控制初始化与复位行为
3.1 复位行为
复位端口在 FPGA 中用于在应用复位信号时,立即将连接到复位端口的寄存器和块 RAM 还原为初始值。通常 RTL 配置中最重要的操作即选择复位行为。
对于复位行为,重要的是理解初始化与复位之间的差异。
复位设置包含设置复位极性以及使用同步复位还是异步复位的功能,但更重要的是,它可通过"reset"选项来控制应用复位信号时要复位的寄存器。
复位设置包含设置复位极性以及使用同步复位还是异步复位的功能,但更重要的是,它可通过"reset"选项来控制应用复位信号时要复位的寄存器。
"reset"选项包含 4 项设置:
- "none":在设计中不添加复位。
- "control":这是默认设置,用于确保将所有控制寄存器复位。控制寄存器即状态机中使用的寄存器,用于生成I/O 协议信号。此设置可确保设计可立即启动其操作状态。
- "state":该选项可为控制寄存器添加复位(与"control"设置相同),并且还可为衍生自 C/C++ 语言代码中的静态变量和全局变量的任意寄存器或存储器添加复位。此设置可确保应用复位后,C/C++ 语言代码中初始化的静态变量和全局变量均复位为其初始值。
- "all":此设置用于为设计中的所有寄存器和存储器添加复位。
通过 RESET 编译指示或指令可提供更精细的复位控制。静态变量和全局变量均可通过 RESET 指令来添加复位。还可使用 RESET 指令的 off 选项从复位的变量中移除变量。
3.2 初始化行为
在 C/C++ 中,默认情况下,以静态限定符定义的变量和全局作用域中定义的变量都初始化为 0。(可选)对于这些变量,可赋予特定初始值。对于这些已初始化的变量,C/C++ 语言代码中的值在编译时(时序为 0 时)进行赋值,并且不再进行赋值。上述 2 种情况下,在 RTL 中实现初始值。
- 在 RTL 仿真期间,为这些变量设置的初始值与 C/C++ 语言代码中相同。
- 在用于对 FPGA 进行编程的比特流中,也会对这些变量进行初始化。当器件上电时,变量将以其初始状态启动。
在 RTL 中,虽然变量启动时使用的初始值与 C/C++ 语言代码相同,但无法强制该变量返回至此初始状态。要复原初始状态,必须通过复位信号来实现这些信号变量。
4. 总结
对于阵列,推荐使用带有 static 限定符的存储器来实现。这样不仅可以确保 HLS 工具以存储器形式实现阵列,还允许使用默认初始化行为。静态阵列在 C/C++ 代码中的行为与 RTL 中的存储器行为相同。
在复位方面,你可以选择不添加复位、控制复位、状态复位或全部复位。通过 RESET 指令,你可以更精细地控制哪些变量需要复位。
了解初始化和复位之间的差异对于有效设计和编程至关重要。