对早期代码可执行的首要改进措施之一是使用 HLS 串流构造(通常称为 hls::stream
)。hls::stream
对象可采用与阵列相同的方式来存储数据采样。hls::stream
中的数据只能按顺序访问。在 C/C++ 语言代码中,hls::stream
的行为与无限深度的 FIFO 相似。
使用 hls::stream
编写的代码通常用于在 FPGA 中创建设计,此类设计性能高且占用资源极少,因为 hls::stream
会强制使用最适合 FPGA 中实现的编码样式。
无法从 hls::stream
多次读取相同数据。从 hls::stream
中读取数据后,该数据在串流中就不再存在。这有助于消除此编码实践。
如果需要再次使用 hls::stream
中的数据,则需将其置于高速缓存中。对于编写将在 FPGA 上进行综合的代码,这是另一项非常实用的实践。
hls::stream
会强制以适合 FPGA 实现的方式来开发 C/C++ 语言代码。
对 hls::stream
进行综合后,它会作为 FIFO 通道来自动实现,深度为 1 个元素。这是适合连接已流水打拍的任务的理想硬件。
无需使用 hls::stream
,并且可使用 C/C++ 语言代码中的阵列来执行相同的实现。hls::stream
构造有助于强制实施正确的编码实践。
对于 hls::stream
构造,经过最优化的新代码大纲如下所示:
template<typename T, int K>
static void convolution_strm(
int width,
int height,
hls::stream<T> &src,
hls::stream<T> &dst,
const T *hcoeff,
const T *vcoeff)
{
hls::stream<T> hconv("hconv");
hls::stream<T> vconv("vconv");
// These assertions let HLS know the upper bounds of loops
assert(height < MAX_IMG_ROWS);
assert(width < MAX_IMG_COLS);
assert(vconv_xlim < MAX_IMG_COLS - (K - 1));
// Horizontal convolution
HConvH:for(int col = 0; col < height; col++) {
HConvW:for(int row = 0; row < width; row++) {
HConv:for(int i = 0; i < K; i++) {
}
}
}
// Vertical convolution
VConvH:for(int col = 0; col < height; col++) {
VConvW:for(int row = 0; row < vconv_xlim; row++) {
VConv:for(int i = 0; i < K; i++) {
}
}
Border:for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
}
}
相比于早期代码,值得注意的明显差异包括:
- 输入和输出数据现已采用
hls::stream
来建模。 - 其中并不采用大小为 HEIGHT*WDITH 的单一局部阵列,而是改为使用 2 个内部
hls::stream
来保存水平和垂直卷积的输出。
此外,部分 assert
语句用于指定循环边界的最大值。这是非常实用的编码样式,允许 HLS 自动报告变量边界循环的时延并最优化循环边界。