在祥瑞Coding的博客中提到了下面的一个例子:
// Array Order : 0 1 2 3 4 5 6 7 8 9 10 etc. 16 etc...
// Sample Order: A0 B0 C0 D0 E0 F0 G0 H0 A1 B1 C2 etc. A2 etc...
// Output Order: A0 B0 C0 D0 E0 F0 G0 H0 A0+A1 B0+B1 C0+C2 etc. A0+A1+A2 etc...
#define CHANNELS 8
#define SAMPLES 400
#define N CHANNELS * SAMPLES
void foo (dout_t d_o[N], din_t d_i[N]) {
int i, rem;
// Store accumulated data
static dacc_t acc[CHANNELS];
// Accumulate each channel
For_Loop: for (i=0;i<N;i++) {
rem=i%CHANNELS;
acc[rem] = acc[rem] + d_i[i];
d_o[i] = acc[rem];
}
}
优化前
void foo (dout_t d_o[N], din_t d_i[N]) {
#pragma HLS ARRAY_PARTITION variable=d_i cyclic factor=8 dim=1 partition
#pragma HLS ARRAY_PARTITION variable=d_o cyclic factor=8 dim=1 partition
int i, rem;
// Store accumulated data
static dacc_t acc[CHANNELS];
// Accumulate each channel
For_Loop: for (i=0;i<N;i++) {
#pragma HLS PIPELINE rewind
#pragma HLS UNROLL factor=8
rem=i%CHANNELS;
acc[rem] = acc[rem] + d_i[i];
d_o[i] = acc[rem];
}
}
优化后
看了半天终于对pipeline、unroll和partition都有了一个全面的理解。
先简单说一下概念,在这个例子中pipeline就是将循环流水化;unroll就是将循环展开;partiton就是对数组进行划分,使数组中的数据存在不同的BRAM中。
图中的循环主要是实现了读取d_i[i]中的数据,累加一个数,存取到相应的d_o[i]位置中,累加的这个数据呢,是根据i对CHANNELS取模的数组位置,加上d_i[i]不断增加的数。CHANNELS为8,也就是d_i[i]累加的数一直都在acc中0~7的索引中。
优化前的循环是这样进行的:
使用了unroll且factor为8时,即将循环展开8次8次的进行计算,循环流程变为:
但是由于d_i和d_o数组都存放在一个BRAM中,最多只有两个读写口,一次性只能读或写两个数据,因此要将d_i[0]—d_o[7]存放在不同的BRAM中,就可以一次性都读取出来,就需要使用partition,有三种划分模式:
cyclic是满足我们的要求的,故使用cyclic对d_i和d_o数组进行划分,上图是factor设为2的效果,我们需要将factor设为8,将d_i[0]—d_i[7]存放在不同的BRAM中。
最后再对循环进行pipeline,将循环流水线,循环流程则变为:
d_i[0]—d_i[7]同时进行计算,而d_i[8]和d_i[0]存放在同一个BRAM中,使用流水线进行计算。不需要等到d_i[0]计算完了存放在d_o[0]之后再来读取d_i[8]。