阵列是任意 C++ 软件程序中的基础数据结构。软件程序员将阵列视作为简单的容器,并对阵列进行按需(通常动态)分配/解除分配。如需综合程序以供硬件使用,则不支持对阵列进行此类型的动态存储器分配。要将阵列综合为硬件,需准确知晓存储器量(静态),算法才可用。此外,FPGA 上的存储器架构(也称为“本地存储器”)相比于全局存储器(通常为 DDR 存储器或 HBM 存储体)有着截然不同的侧重点。访问全局存储器会产生很高的时延成本,可耗时大量周期,而访问本地存储器通常十分快速,只需一个或多个周期即可。
当 HLS 设计已适当完成流水打拍和/或展开后,即可确立存储器访问模式。HLS 编译器允许用户将阵列映射到各种类型的资源,其中无论有无握手信号,阵列元素均可并行使用。内部阵列和顶层函数接口内的阵列均可映射到寄存器或存储器。如果阵列位于顶层接口内,那么该工具会自动创建与外部存储器对接所需的地址信号、数据信号和控制信号。如果阵列位于设计内部,那么该工具不仅会创建必要的地址信号、数据信号和控制信号以访问存储器,还会例化存储器模型(随后下游 RTL 综合工具会将该模型推断为存储器)。
综合后,阵列通常作为存储器(RAM、ROM 或移位寄存器)来实现。阵列还可按各寄存器进行完全分区,以创建完全并行的实现,前提是平台具有足够寄存器来支持该步骤。GitHub 上提供的 initialization_and_reset 示例演示了存储器的不同实现。
顶层函数接口上的阵列作为访问外部存储器的 RTL 端口来进行综合。而在设计内部,大小小于 1024 的阵列将作为移位寄存器来综合。根据最优化设置,大小大于 1024 的阵列将综合到块 RAM (BRAM)、LUTRAM 或 UltraRAM (URAM) 中(请参阅 BIND_STORAGE directive/pragma)。
请参阅以下示例,其中,HLS 编译器在遇到以下代码时推断移位寄存器:
int A[N]; // This will be replaced by a shift register
for(...) {
// The loop below is the shift operation
for (int i = 0; i < N-1; ++i)
A[i] = A[i+1];
A[N] = ...;
// This is an access to the shift register
... A[x] ...
}
移位寄存器每个周期都能执行一次移位操作,还支持在移位寄存器内任意位置每个周期执行一次随机读取访问,因此,比 FIFO 更灵活。
在下列情况下,阵列在 RTL 中可能引发问题:
- 作为存储器 (BRAM/LUTRAM/URAM) 来实现时,存储器端口的数量可能限制对数据的访问,从而导致流水打拍循环内出现 II 违例
- HLS 编译器可能无法正确推断互斥访问
- 请确保在 RTL 中将只需读访问权的阵列作为 ROM 来实现
HLS 编译器支持指针阵列。每个指针都只能指向 1 个标量或 1 个标量阵列。
Array[10];
。但不支持非固定大小的阵列,如:Array[];
。