将软件阵列映射到硬件存储器 - 2023.2 简体中文

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

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

阵列是任意 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 个标量阵列。

提示: 阵列大小必须固定。对于如下函数实参,这是必需的:C++ 编译器忽略实参大小,但该工具会使用此大小,例如:Array[10];。但不支持非固定大小的阵列,如:Array[];