An HLS function first and foremost is a function. It has a predetermined number of inputs
and outputs and every time the function is invoked, it consumes the inputs and produces
the predetermined number of outputs. If an HLS function imported using
xmcImportFunction
hangs, (for example, if it has an infinite loop),
Simulink will also hang, waiting indefinitely for the output from the imported block.
This is because an imported HLS function using xmcImportFunction
runs
on the same thread as Simulink. If the imported functions hangs, Simulink also
hangs.
While a function with an infinite loop is a rather trivial example, as a more practical example, assume you have an AI Engine kernel producing data and an HLS function consuming the data (see the following figure).
The HLS function may have a blocking call reading the input. Following is a snippet of pseudo code outlining the HLS function
Pseudo code - Blocking call
void hls_func(hls::stream<ap_axis<64, 0, 0, 0> > & in_sample
hls::stream<ap_axis<64, 0, 0, 0> > & out_sample) {
...
ap_axis<64, 0, 0, 0> in_sample_x = in_sample.read();
...
}
In the previous code, read()
is a blocking
call. If there is no data on the stream, this call will block. The producing
AI Engine kernel may or may not produce any
output when invoked. As such, if Simulink calls the HLS Function
with no data available from the producing block, the HLS function will block, and as
a result Simulink will hang.
xmcImportFunction
cannot import
a function with a signature that includes hls::stream
. For that, a
wrapper is required.xmcImportFucntion
with an
AI Engine block as it does not accept the
variable sized signals produced by AI Engine blocks.Unlike an imported HLS function, an HLS Kernel block runs on a separate thread. As such, even if the HLS Kernel blocks (for example when waiting for input from the producing AI Engine block), Simulink will continue to function. In such cases, the output of the HLS kernel block will be a variable size signal containing no data.
HLS Kernels are IPs
When you import an HLS function into a design by itself, the HLS function will not operate as an IP with streaming ports. In Model Composer, you need to use the interface specification block to designate streaming ports for the design, and then generate the HLS IP. Unlike an HLS function, an HLS Kernel is a proper HLS IP that can be used in the Vitis™ software platform HLS and be synthesized directly. The following code snippet highlights the HLS kernel code with streaming interface.
hls_kernel.cc
void hls_kernel_blk(
hls::stream<ap_axis<64, 0, 0, 0> > & in_sample1,
hls::stream<ap_axis<64, 0, 0, 0> > & in_sample2,
hls::stream<ap_axis<64, 0, 0, 0> > & out0_itr1,
hls::stream<ap_axis<64, 0, 0, 0> > & out1_itr1
)
{
#pragma HLS PIPELINE II=1
#pragma HLS INTERFACE ap_ctrl_none port=return
#pragma HLS INTERFACE axis register both port=out1_itr1
#pragma HLS INTERFACE axis register both port=out0_itr1
#pragma HLS INTERFACE axis register both port=in_sample1
#pragma HLS INTERFACE axis register both port=in_sample2
ap_int64 in_samp0 ; // Iteration-1: 2 complex samples concatenated to 64-bit
ap_int64 in_samp1 ; // Iteration-2: 2 complex samples concatenated to 64-bit
...
In this example, notice the function signature and also the HLS pragmas specifying the interface on the ports. This function has all the constructs required by the HLS IP.
It is necessary to declare the corresponding kernel function in a specified format in the header file as follows.
void hls_kernel_blk(
adf::dir::in hls::stream<ap_axis<64, 0, 0, 0> > &in_sample1,
adf::dir::in hls::stream<ap_axis<64, 0, 0, 0> > &in_sample2,
adf::dir::out hls::stream<ap_axis<64, 0, 0, 0> > &out0_itr1,
adf::dir::out hls::stream<ap_axis<64, 0, 0, 0> > & out1_itr1
);
As shown, it is required to prepend each parameter in the function definition with
either adf::dir::in
or adf::dir::out
based on the
port direction.