Vitis HLS中的Array Partition与Array Reshape详解
引言
在高层次综合(HLS)设计中,数组是最常用的数据结构之一,但默认情况下,HLS会将数组映射到单个BRAM块,这会限制并行访问能力,成为性能瓶颈。为了克服这一限制,Vitis HLS提供了两种强大的数组优化指令:ARRAY_PARTITION
和ARRAY_RESHAPE
。这两种优化都能显著提高设计的并行度,但它们的工作原理、适用场景和资源影响各不相同。本文将深入剖析这两种优化技术的细节,帮助您在HLS设计中做出明智的选择。
Array Partition(数组分区)
基本概念
ARRAY_PARTITION
指令将一个大数组分解成多个较小的数组或独立的存储单元,从而增加可用的访问端口数量,实现并行访问。当一个数组被分区后,综合工具可以为每个分区生成独立的存储结构,允许在同一时钟周期内访问多个数组元素。
分区类型
Vitis HLS支持三种分区类型,每种类型适用于不同的访问模式:
1. Complete Partition(完全分区)
完全分区将数组的每个元素分解为完全独立的变量,通常映射到寄存器而非BRAM。
int array[16];
#pragma HLS ARRAY_PARTITION variable=array complete dim=1
工作原理:
- 数组的每个元素都被分配到独立的存储单元
- 允许在同一时钟周期内访问所有元素
- 完全消除了存储结构,每个元素都可能映射到寄存器
适用场景:
- 需要在同一时钟周期内访问多个或所有数组元素
- 数组大小较小(通常小于100个元素)
- 实现高度并行的算法,如矩阵乘法内循环
BRAM影响:
- 通常减少或完全消除BRAM使用
- 转而使用寄存器资源,适合小型数组
- 对于大型数组,可能导致寄存器资源耗尽
2. Block Partition(块分区)
块分区将数组分成连续的块,每个块包含相邻的元素。
int array[100];
#pragma HLS ARRAY_PARTITION variable=array block factor=4 dim=1
// 创建4个分区:[0-24], [25-49], [50-74], [75-99]
工作原理:
- 数组被分成大小相等的连续块
- 每个块映射到单独的存储结构
- 同一时钟周期可以访问不同块中的元素
适用场景:
- 算法以块为单位访问数据,如图像处理中的块操作
- 数据局部性较强的应用
- 需要在同一时钟周期访问不同块的元素
BRAM影响:
- 通常会增加BRAM使用量,因为每个分区需要单独的BRAM块
- 当分区因子增加时,可能导致BRAM利用率降低
- 例如,一个1024元素的数组默认使用1个BRAM,分成4块后可能使用4个BRAM,每个只使用25%的容量
3. Cyclic Partition(循环分区)
循环分区以交替方式将元素分配到不同分区,相邻元素被分配到不同分区。
int array[100];
#pragma HLS ARRAY_PARTITION variable=array cyclic factor=4 dim=1
// 分区0: 0, 4, 8, ...
// 分区1: 1, 5, 9, ...
// 分区2: 2, 6, 10, ...
// 分区3: 3, 7, 11, ...
工作原理:
- 数组元素以循环方式分配到不同分区
- 每隔factor个元素被分配到同一分区
- 允许同时访问间隔固定的多个元素
适用场景:
- 需要以步长访问数组(如stride访问)
- 循环中有规律地访问间隔元素
- 实现向量化操作,如SIMD处理
BRAM影响:
- 与Block分区类似,通常会增加BRAM使用量
- 可能导致BRAM利用率降低
- 适合特定的访问模式,如卷积或FFT算法
多维数组分区
对于多维数组,可以指定要分区的维度:
int matrix[10][10];
#pragma HLS ARRAY_PARTITION variable=matrix complete dim=1 // 分割行
#pragma HLS ARRAY_PARTITION variable=matrix cyclic factor=2 dim=2 // 循环分割列
也可以使用dim=0
表示分割所有维度:
int cube[10][10][10];
#pragma HLS ARRAY_PARTITION variable=cube complete dim=0 // 完全分割所有维度
Array Reshape(数组重塑)
基本概念
ARRAY_RESHAPE 指令通过垂直重映射模式来重构阵列,用于减少所耗用的块 RAM 的数量,同时提供对数据的并行访问。
ARRAY_RESHAPE
是一种结合了分区和合并的优化技术。它首先将数组分区,然后将各个分区的对应元素合并为更宽的数据类型,创建一个新的数组结构。这种方法既增加了并行访问能力,又保持了数组的原始结构。
工作原理
假设有一个8元素的数组,使用factor=4进行reshape:
- 首先将数组分成4个分区(类似于partition)
- 然后将每个分区的对应元素合并为一个更宽的数据类型
- 结果是一个新数组,长度是原来的1/4,但每个元素是原来宽度的4倍
原始数组: [a0, a1, a2, a3, a4, a5, a6, a7]
使用factor=4的cyclic reshape:
结果数组: [{a0,a1,a2,a3}, {a4,a5,a6,a7}]
使用示例
与Array Partition类似,Array Reshape也支持complete、block和cyclic三种类型:
void foo (...) {
int array1[N];
int array2[N];
int array3[N];
#pragma HLS ARRAY_RESHAPE variable=array1 type=block factor=2 dim=1
#pragma HLS ARRAY_RESHAPE variable=array2 type=cycle factor=2 dim=1
#pragma HLS ARRAY_RESHAPE variable=array3 type=complete dim=1
...
}
BRAM消耗影响
ARRAY_RESHAPE
对BRAM消耗的影响与ARRAY_PARTITION
不同:
-
总BRAM数量:
- 通常不会显著增加BRAM数量
- 倾向于保持相似的总体BRAM使用量
-
BRAM配置:
- 改变BRAM的配置,使用更宽但更短的存储结构
- 例如,可能从32位x1024转变为128位x256
-
BRAM利用率:
- 通常比等效的
ARRAY_PARTITION
有更高的BRAM利用率 - 减少了由于最小BRAM大小限制导致的浪费
- 通常比等效的
Array Partition与Array Reshape的比较
相同点
- 目标相同:两者都旨在通过优化数组访问模式来提高并行度
- 提升性能:通过增加并行访问能力,改善设计的吞吐量和延迟
不同点
-
实现方式:
- Array Partition:通过分割数组本身来实现并行访问
- Array Reshape:通过改变数组的存储结构来实现并行访问
-
存储结构:
- Array Partition:创建多个独立的小数组或寄存器
- Array Reshape:创建一个结构上更宽但更短的数组
-
访问方式:
- Array Partition:需要使用不同的索引访问不同分区
- Array Reshape:使用相同的索引访问,但需要额外的位选择逻辑
-
资源影响:
- Array Partition:可能增加总体BRAM使用,或将BRAM转换为寄存器
- Array Reshape:通常保持相似的BRAM数量,但增加数据宽度
-
适用场景:
- Array Partition:更适合需要直接并行访问多个元素的情况
- Array Reshape:适合需要在多维空间中调整访问模式的情况
实际应用案例
案例1:矩阵乘法优化
矩阵乘法是一个经典的需要高度并行访问的算法:
void matrix_multiply(int A[N][N], int B[N][N], int C[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
int sum = 0;
for (int k = 0; k < N; k++) {
#pragma HLS PIPELINE II=1
sum += A[i][k] * B[k][j];
}
C[i][j] = sum;
}
}
}
优化策略:
- 对矩阵B进行complete分区,因为内循环需要随机访问B的行
#pragma HLS ARRAY_PARTITION variable=B complete dim=1
- 对矩阵A进行cyclic分区,因为内循环顺序访问A的列
#pragma HLS ARRAY_PARTITION variable=A cyclic factor=16 dim=2
案例2:图像处理滤波器
考虑一个3x3卷积滤波器,需要同时访问9个像素:
void filter(pixel_t image[HEIGHT][WIDTH], pixel_t result[HEIGHT-2][WIDTH-2]) {
pixel_t window[3][3];
for (int i = 1; i < HEIGHT-1; i++) {
for (int j = 1; j < WIDTH-1; j++) {
#pragma HLS PIPELINE II=1
// 加载3x3窗口
for (int di = -1; di <= 1; di++) {
for (int dj = -1; dj <= 1; dj++) {
window[di+1][dj+1] = image[i+di][j+dj];
}
}
// 应用滤波器
result[i-1][j-1] = apply_filter(window);
}
}
}
优化策略:
- 对窗口数组进行complete分区,因为需要同时访问所有元素
#pragma HLS ARRAY_PARTITION variable=window complete dim=0
- 对图像数组进行cyclic分区,以支持并行访问相邻像素
#pragma HLS ARRAY_PARTITION variable=image cyclic factor=3 dim=2
案例3:FFT算法
FFT算法需要特定模式的数据访问:
void fft(complex_t data[N]) {
// FFT实现
for (int stage = 0; stage < log2(N); stage++) {
int distance = 1 << stage;
for (int i = 0; i < N; i += 2*distance) {
for (int j = 0; j < distance; j++) {
#pragma HLS PIPELINE II=1
butterfly(data[i+j], data[i+j+distance]);
}
}
}
}
优化策略:
- 使用reshape而非partition,因为FFT的访问模式适合更宽的数据类型
#pragma HLS ARRAY_RESHAPE variable=data cyclic factor=8 dim=1
最佳实践与选择指南
何时选择Array Partition
- 随机访问需求:当算法需要在同一时钟周期随机访问多个数组元素时
- 小型数组:对于小型数组(通常<100元素),complete分区效果好
- 高度并行算法:需要最大化并行性的算法,如矩阵运算
- 特定访问模式:
- 块访问模式 → block分区
- 步长访问模式 → cyclic分区
何时选择Array Reshape
- 顺序访问模式:当算法主要以顺序方式访问数组元素时
- BRAM资源受限:当需要提高并行性但BRAM资源有限时
- 中大型数组:对于较大的数组,reshape通常比partition更节省资源
- 数据局部性强:当算法在特定时间只访问数组的一小部分
三种实现二维矩阵的方式。左边采用原始的方式,保存N*M个元素。中间数组使用了array_partition directive进行了变形,结果是使用了M个存储单元,每个保存N个元素。右边的,使用array_shape directive进行了变形,结果保存在一个存储单元,带有N个入口,每个入口保存原始矩阵的M个元素
举例
Xilinx Virtex Ultrascale+器件最小的block RAM(BRAM)是18 Kbits,可以支持不同深度和宽度的组合。当数组分块小于18kbits后,那么BRAM 就没有被充分使用。如果我们使用最原始的矩阵,4-bit数组,维度是 [1024][4] ,这个数组就可放到一个单独的BRAM 中,它配置为4Kbit x 4。对这个数组的第二个维度进行分块,那么将使用4个 1Kbit x 4的存储单元,每个都要比BRAM小很多。通过使用array_reshape 将数组变为1Kbit x 16,可以被一个BRAM 存储。
总结
ARRAY_PARTITION
和ARRAY_RESHAPE
都是Vitis HLS中强大的数组优化工具,能够显著提高设计的并行度和性能。选择哪种优化方式取决于具体的应用场景、访问模式和资源限制:
-
Array Partition:
- 将数组分解为多个独立的小数组或单个元素
- 提供最大的并行访问能力
- 可能增加总体BRAM使用量或将BRAM转换为寄存器
- 提供三种分区类型(complete, block, cyclic),适用于不同的访问模式
-
Array Reshape:
- 重组数组结构,创建更宽但更短的数组
- 在保持类似BRAM数量的同时提高并行性
- 通常有更好的BRAM利用率
- 适合顺序访问模式和资源受限的设计
经验之谈:
array_partition指令更加灵活,但可能会导致存储资源使用不充分。
array_reshape指令更加节约资源,并且在时序较差的情况下有奇效。
大数组reshape直接干,小数组/高度优化 仔细使用partition。
计算好BRAM利用率,片上的cache非常宝贵。