hls::task
also supports the
definition of tasks inside of dataflow regions. The dataflow region allows the
definition of processes that access external arrays mapped to M_AXI, scalar, or PIPO
arguments from upper levels of the design hierarchy. This requires dataflow
processes identified by the #pragma HLS dataflow
statement, synchronized via ap_ctrl_chain
, that
read data from any non-streamed C++ data structure and output it as hls::stream
or hls::stream_of_blocks
channels for connection to hls::tasks
. Tasks can then output streams that are
read by other dataflow processes and written to M_AXI, scalar, or PIPO arguments.
Tasks must be declared after the processes or regions that produce their input
streams, and before the processes or regions that consume their output streams.
hls::task
objects cannot read or write M_AXI, scalar,
or PIPO arguments dataflow processes must read or write these interfaces and write
or read stream channels to hls::tasks
as shown in
the example below. The following example illustrates tasks and dataflow processes
together. The top-level function (top-func
) is a
dataflow region that defines sequential functions write_out()
and read_in()
, as well
as hls::task
objects and hls::stream
channels.
#include "hls_task.h"
// This is an I/O dataflow process
void write_out(int* out, int n, hls::stream<int> &s2) {
for (int i=0; i<n; i++)
out[i] = s2.read();
}
// This is an I/O dataflow process
void read_in(int* in, int n, hls::stream<int> &s1) {
for (int i=0; i<n; i++)
s1.write(in[i]);
}
// This is an hls::task body
void func1(hls::stream<int> &s1, hls::stream<int> &s3) {
// No while(1) needed! This will be a task
s3.write(... + s1.read());
}
// This is an hls::task body
void func2(hls::stream<int> &s3, hls::stream<int> &s2) {
// No while(1) needed! This will be a task
s2.write(... * s3.read());
}
// This could legally be at the top of the design hierarchy
void top-func(int *in, int *out, int n) {
#pragma HLS dataflow
hls_thread_local hls::stream<int> sk3;
hls_thread_local hls::stream<int> sk1;
hls_thread_local hls::stream<int> sk2;
read_in(in, n, sk1); // can access stream, scalar or array; calling order matters
hls_thread_local hls::task t2(func2, sk3, sk2); // can access only stream; instance order does not matter
hls_thread_local hls::task t1(func1, sk1, sk3); // can access only stream; instance order does not matter
write_out(out, n, sk2); // can access stream, scalar or array; calling order matters
}
#pragma HLS DATAFLOW
is required for the two
sequential functions, but the hls::task
objects do
not require it. Internally, Vitis HLS will
automatically split top-func
, including both
regular dataflow processes and KPN processes into two dataflow regions:
- One dataflow region using
ap_ctrl_chain
that contains regular dataflow processes, likeread_in()
andwrite_out()
, in the order in which they appear in the C++ code, and a call to theap_ctrl_none
region below - A second dataflow region using
ap_ctrl_none
, containing the task and channels.
As a result of this, you can expect to see two levels of hierarchy in the Dataflow viewer in the Vitis HLS GUI.