将阵列指定为块串流 - 2023.2 简体中文

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

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

hls::stream_of_blocks 类型可提供用户同步的串流,支持对数据块进行串流以供数据流上下文中的进程级别接口使用,在此类上下文中每个块都是单个阵列或多维阵列。块串流的目标用途是替换数据流区域内的成对进程之间的基于阵列的通信。请参阅 GitHub 上的 using_stream_of_blocks 示例。

当前,Vitis HLS 可在数据流区域内实现由生产者进程写入并由使用者进程读取的阵列,方法是将这些阵列映射到乒乓缓冲器 (PIPO)。在 C++ 中返回生产者函数和调用使用者函数时,PIPO 缓冲器就会发生缓冲器交换。

块串流建模样式

从另一方面来看,对于块串流,生产者与使用者之间的通信以类阵列对象串流方式来建模,这样与通过 PIPO 进行阵列传输相比存在多方面优势。

在代码中使用块串流需要以下 include 文件:

#include "hls_streamofblocks.h"

块串流对象模板为:

hls::stream_of_blocks<block_type, depth> v

其中:

  • <block_type> 用于指定块串流所包含的阵列或多位阵列的数据类型
  • <depth> 是可选实参,可提供与 hls::stream 或 PIPO 相似的深度控制,并指定块的总数,包括在任意时间生产者获取的块和使用者获取的块。默认值为 2
  • v 为块串流对象指定变量名称

使用以下步骤访问块串流中的某一个块:

  1. 如果生产者或使用者进程要首先访问串流,则需要使用 hls::write_lockhls::read_lock 对象获取对该串流的访问权。
  2. 当生产者获取锁定后,它即可开始写入(或读取)获取的块。此块完全初始化后,当 write_lock 对象超出范围时,生产者即可将此块释放。

    注释:具有 write_lock 的生产者进程也可以读取此块,前提是它从已写入的位置读取,因为新获取的块必须假定包含未初始化的数据。仅限生产者进程才能对块进行写入和读取,不支持使用者执行此操作。

  3. 随后,此块将在块串流中以 FIFO 方式排队,当使用者获取 read_lock 对象后,此块即可供使用者进程读取。

前述示例中所示的 hls::stream_of_blocks 与 PIPO 机制之间的主要差异在于一旦 write_lock 超出范围,块就会变为对使用者可用,而不是仅在生产者进程返回时才可用。因此,含块串流时的存储量远小于含 PIPO 时的存储量:即,此时存储量为 2N 而不是 2xMxN。

生产者通过构造名为 bhls::write_lock 对象,并将名为 s 的块串流对象引用传递给该对象,即可获取此块。write_lock 对象可提供重载阵列访问运算符,这样即可像阵列一样访问此对象,以便按随机顺序访问底层存储器,如以下示例所示。

通过构造 write_lock/read_lock 对象即可执行锁定的获取,当对象超出范围时,就会被销毁,从而自动释放锁定。此方法使用常用的“资源获取即初始化”(RAII) 样式的锁定和解锁。

#include "hls_streamofblocks.h"
typedef int buf[N];
void producer (hls::stream_of_blocks<buf> &s, ...) {
  for (int i = 0; i < M; i++) {
    // Allocation of hls::write_lock acquires the block for the producer
    hls::write_lock<buf> b(s);
    for (int j = 0; j < N; j++)
      b[f(j)] = ...;
    // Deallocation of hls::write_lock releases the block for the consumer
  }
}
  
void consumer(hls::stream_of_blocks<buf> &s, ...) {
  for (int i = 0; i < M; i++) {
    // Allocation of hls::read_lock acquires the block for the consumer
    hls::read_lock<buf> b(s);
    for (int j = 0; j < N; j++)
       ... = b[g(j)] ...;
    // Deallocation of hls::write_lock releases the block to be reused by the producer
  }
}
  
void top(...) {
#pragma HLS dataflow
  hls::stream_of_blocks<buf> s;
  
  producer(b, ...);
  consumer(b, ...);
}

此方法的关键特征包括:

  • 以上生产者的外层循环的目标性能是达成总体启动时间间隔 (II) 为 1 的目标
  • 锁定的块虽可使用,但它仅供生产者或使用者进程专用,直至被释放为止。
  • 生产者的阵列对象的初始状态并未定义,但它包含由生产者为使用者写入的值。
  • 块串流的主要优势在于允许重叠执行使用者和生产者的多次迭代,以提升吞吐量。

资源使用

当深度增加超过默认值 2 时,资源成本与 PIPO 的资源成本类似。换言之,每递增 1,都需要足够一个块使用的存储器,例如,在以上示例中,需要 N * 32 位字。

块串流对象可绑定到特定 RAM 类型,方法是在声明块串流的位置放置 BIND_STORAGE 编译指示,例如,在顶层函数内。默认情况下,块串流使用 2 端口块 RAM (type=RAM_2P)。