hls::split<>
或 hls::merge<>
对象,请按如下示例所示方式包含头文件 hls_np_channel.h。拆分/合并通道可供在数据流进程内使用,支持您创建一对多或多对一类型的通道,用于将数据分发给多项任务,或者聚集来自多项任务的数据。这些通道具有内置作业调度器,使用循环方法按顺序跨通道分发或收集数据,或者采用基于通道可用性来判定的负载均衡方法。
如下图所示,数据读取自输入串流,通过循环调度器机制进行拆分,并分发至关联的工作程序任务。当工作程序完成任务后,同样会采用循环调度器将合并的输出写入单一串流。
拆分通道具有单一生产者和众多使用者,通常可用于将任务分发给一系列工作程序,在 RTL 中将分发逻辑加以抽象化并实现,从而提升性能并减少耗用的资源。从 N 个输出中的任一输出分发输入的操作可采用:
- 循环法,其中使用者按固定轮换顺序读取输入数据,这样即可确保确定性行为,但不允许负载共享,且工作程序间存在动态可变的计算负载。
- 负载均衡,其中尝试执行读取的首个使用者将读取首个输入数据,这样可确保良好的负载均衡,但存在非确定性结果。
每条合并通道都具有多个生产者和单一使用者,基于相反逻辑来工作:
- 循环法,其中按固定轮换顺序合并生产者输出数据,这样即可确保确定性行为,但不允许负载共享,且工作程序间存在动态可变的计算负载。
- 负载均衡合并通道,其中完成工作的首个生产者将首先写入通道,这存在非确定性结果。
拆分与合并的一般构想是借助 round_robin(循环)调度器,以确定性方式将数据分发给周围工作程序以供拆分,从工作程序读取数据以供合并。因此,如果所有工作程序都计算相同函数,那么结果与使用单一工作程序的计算结果相同,但性能更好。
如果工作程序执行不同的函数,那么设计必须确保按工作程序循环方式(分别从 out[0] 或 in[0] 开始),将正确的数据项发送到正确的函数。
规范
hls::split::load_balancing<DATATYPE, NUM_PORTS[, DEPTH, N_PORT_DEPTH]> name;
hls::split::round_robin<DATATYPE, NUM_PORTS[, DEPTH]> name
hls::merge::load_balancing<DATATYPE, NUM_PORTS[, DEPTH]> name
hls::merge::round_robin<DATATYPE, NUM_PORTS[, DEPTH]> name
-
round_robin
/load_balancing
:指定用于该通道的调度器机制类型。 -
DATATYPE:指定通道上的数据类型。这其中存在与标准
hls::stream
相同的限制。DATATYPE 可设为:- 任何 C++ 原生数据类型
-
Vitis HLS 任意精度类型(例如,
ap_int<>
和ap_ufixed<>
) - 用户定义的结构体(包含以上任意类型)
-
NUM_PORTS:表示拆分操作所需的写入端口数量 (
1:num
) 或者合并操作所需的读取端口数量 (num:1
)。 - DEPTH:可选实参,表示主缓冲器的深度,位于拆分之前或合并之后。此项可选,如不指定,则默认深度为 2。
-
N_PORT_DEPTH:可选字段,供循环法用于指定在拆分之后或合并之前应用的输出缓冲器的深度。此项可选,如不指定,则默认深度为 0。 提示: 要指定可选
N_PORT_DEPTH
值,还必须指定DEPTH
。 - name:指定创建的通道对象的名称
#include "hls_np_channel.h"
const int N = 16;
const int NP = 4;
void dut(int in[N], int out[N], int n) {
#pragma HLS dataflow
hls::split::round_robin<int, NP> split1;
hls::merge::round_robin<int, NP> merge1;
read_in(in, n, split1.in);
// Task-Channels
hls_thread_local hls::task t[NP];
for (int i=0; i<NP; i++) {
#pragma HLS unroll
t[i](worker, split1.out[i], merge1.in[i]);
}
write_out(merge1.out, out, n);
}
hls::task
对象实现的工作程序。但这只是示例的一项特征,而非 split
/merge
通道的要求。拆分/合并的应用
拆分与合并的主要用途是支持多次计算引擎例化,以便充分利用 DDR 或 HBM 端口的带宽。在此情况下,生产者是负载进程,从 MAXI 读取数据突发,然后通过拆分通道将各个待处理的数据包发送到多个工作程序。如果工作程序需耗费大量时间,请使用循环协议,或者如果输入可变,则根据输入对执行时间进行负载均衡。使用者则执行相反操作,将数据写回 DRAM。
在拆分通道或合并通道两端,通过实现 hls::stream
对象的方式对这些通道进行建模。这意味着可将拆分或合并通道端连接到取 hls::stream
作为输入或输出的任何进程。此进程无需知晓通道连接的类型。因此,这两种类型的连接均可用于标准数据流或 hls::task
对象。
以下示例显示了单一生产者和多个使用者使用拆分的方式:
#include "hls_np_channel.h"
void producer(hls::stream<int> &s) {
s.write(xxx);
}
void consumer1(hls::stream<int> &s) {
... = s.read();
}
void consumer2(hls::stream<int> &s) {
... = s.read();
}
void top-func() {
#pragma HLS dataflow
hls::split::load_balancing<int, 4, 6> s; // NUM_PORTS=4, DEPTH=6
producer(s.in, ...);
consumer1(s.out[0], ...);
consumer2(s.out[1], ...);
consumer3(s.out[2], ...);
consumer4(s.out[3], ...);
}