存储器架构是实现的关键。由于访问带宽有限,可能会严重影响整体性能,如以下示例所示:
void run (ap_uint<16> in[256][4],
ap_uint<16> out[256]
) {
...
ap_uint<16> inMem[256][4];
ap_uint<16> outMem[256];
... Preprocess input to local memory
for( int j=0; j<256; j++) {
#pragma HLS PIPELINE OFF
ap_uint<16> sum = 0;
for( int i = 0; i<4; i++) {
sum += inMem[j][i];
}
outMem[j] = sum;
}
... Postprocess write local memory to output
}
此代码添加与二维输入阵列内部维度相关联的四个值。如果在不进行任何其它修改的情况下实现,则会产生以下估算:
图 1. 性能估算
4608(循环 2)的总体时延应该为 18 个周期的 256 次迭代(16 个周期消耗在内部循环中,加上总和复位,加上写入的输出)。此结果可在 HLS 工程的调度查看器中查看。当展开内部循环时,该估值结果会有明显改善。
图 2. 性能估算
但此改善主要原因在于使用了双端口存储器的两个端口。此结果可在 HLS 工程的调度查看器中查看:
图 3. 调度查看器
每个周期执行两次读取操作,以访问来自该存储器的所有值并计算总和。这通常并非期望的结果,因为这样完全阻塞了对于存储器的访问。为了进一步改善结果,可将该存储器沿第二个维度拆分为 4 个更小的存储器:
#pragma HLS ARRAY_PARTITION variable=inMem complete dim=2
如需了解更多信息,请参阅 pragma HLS array_partition 。
这样会导致 4 次阵列读取,并且读取操作全部在使用单一端口的不同存储器上执行:
图 4. 执行 4 个阵列的结果
使用的周期总数为 256 * 4 = 1024 个周期(循环 2)。
图 5. 性能估算
或者,存储器可以重构为带有 4 个并行字的单个存储器。这可通过如下编译指示来执行:
#pragma HLS array_reshape variable=inMem complete dim=2
如需了解更多信息,请参阅 pragma HLS array_reshape 。
这样会导致与阵列分区相同的时延,但是只有一个存储器且使用单个端口:
图 6. 时延结果
虽然上述任何一种解决方案在总体时延和利用率上可产生类似的结果,但是重塑阵列可带来更清洁的接口和更少的布线拥塞,因而使其成为首选解决方案。
注释: 这样即可完成阵列最优化,在实际设计中,可通过利用循环并行化来进一步改善时延(请参阅 循环并行化)。
void run (ap_uint<16> in[256][4],
ap_uint<16> out[256]
) {
...
ap_uint<16> inMem[256][4];
ap_uint<16> outMem[256];
#pragma HLS array_reshape variable=inMem complete dim=2
... Preprocess input to local memory
for( int j=0; j<256; j++) {
#pragma HLS PIPELINE OFF
ap_uint<16> sum = 0;
for( int i = 0; i<4; i++) {
#pragma HLS UNROLL
sum += inMem[j][i];
}
outMem[j] = sum;
}
... Postprocess write local memory to output
}