阵列访问和性能 - 2023.2 简体中文

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

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

在上一章节中介绍了多个最优化概念,例如循环展开和流水打拍,这些均为探索并行化的方法。但这其中均未考量当阵列映射到存储器而不是寄存器时,阵列访问模式可能对此类最优化产生的阻碍。映射到存储器的阵列可能会成为设计性能中的瓶颈。Vitis HLS 提供了多种最优化措施来消除这些存储器瓶颈,例如,阵列重塑和阵列分区。应尽可能利用这些自动存储器最优化措施来最大程度减少代码修改量。但在某些情况下,可能需要对存储器架构进行显式编码来满足性能需求或者允许设计人员实现更好的结果质量。在此类情况下,必须按不会限制性能的方式来对阵列访问进行编码。这意味着在设计中通过分析阵列访问模式并组织存储器来实现所期望的吞吐量和面积。以下代码示例显示了访问阵列导致最终 RTL 设计性能受限的案例。在此示例中,对 mem[N] 阵列执行了 3 次访问以创建求和结果。请参阅 Vitis-HLS-Introductory-Examples/Interface/Memory/memory_bottleneck 以获取此示例的完整版本。

#include "array_mem_bottleneck.h"
  
dout_t array_mem_bottleneck(din_t mem[N]) { 
 
 dout_t sum=0;
 int i;
 
 SUM_LOOP:for(i=2;i<N;++i)
   sum += mem[i] + mem[i-1] + mem[i-2];
     
 return sum;
}

在综合期间,该阵列作为 RAM 来实现。如果将此 RAM 指定为单端口 RAM,那么将无法通过对 SUM_LOOP 循环进行流水打拍来实现在每个时钟周期内处理新的循环迭代的目标。

尝试以值为 1 的启动时间间隔对 SUM_LOOP 进行流水打拍将生成如下消息(无法达成吞吐量 1 后,Vitis HLS 会放宽约束):


INFO: [SCHED 61] Pipelining loop 'SUM_LOOP'.
WARNING: [SCHED 69] Unable to schedule 'load' operation ('mem_load_2', 
bottleneck.c:62) on array 'mem' due to limited memory ports.
INFO: [SCHED 61] Pipelining result: Target II: 1, Final II: 2, Depth: 3.

此处的问题是单端口 RAM 只有 1 个数据端口:每个时钟周期内只能执行 1 次读取(或 1 次写入)。

  • SUM_LOOP Cycle1:读取 mem[i]
  • SUM_LOOP Cycle2:读取 mem[i-1],对值求和;
  • SUM_LOOP Cycle3:读取 mem[i-2],对值求和;

可使用双端口 RAM,但这样仅支持每个时钟周期 2 次访问。要计算总和值,需读取 3 次,因此要将循环流水打拍,每个时钟周期需访问 3 次,即每个时钟周期内新增 1 次迭代。

以上示例中的代码可按以下代码示例中所示方式重写,以便按吞吐量为 1 来对代码进行流水打拍。在以下代码示例中,通过执行预读取并手动对数据访问进行流水打拍,即可在循环每次迭代中仅指定 1 次阵列读取。这样可确保只需 1 个单端口 RAM 即可实现性能目标。

#include "array_mem_perform.h"
  
dout_t array_mem_perform(din_t mem[N]) { 
 
 din_t tmp0, tmp1, tmp2;
 dout_t sum=0;
 int i;
 
 tmp0 = mem[0];
 tmp1 = mem[1];
 SUM_LOOP:for (i = 2; i < N; i++) {
 tmp2 = mem[i];
 sum += tmp2 + tmp1 + tmp0;
 tmp0 = tmp1;
 tmp1 = tmp2;
 }
     
 return sum;
}

需要按如上所述方式更改源代码的场景并不多见。更为常见的场景是使用最优化指令/编译指示来实现相同的结果。Vitis HLS 包含用于更改阵列实现和访问方式的最优化指令。最优化主要分两类:

  • “Array Partition”(阵列分区)用于将原始阵列拆分为较小的阵列或者独立的寄存器。
  • “Array Reshape”(阵列重塑)用于将阵列重新组织为不同的存储器排列方式,以增加并行度而不拆分原始阵列。