Behavior of HLS Functions on Blocking Calls - 2021.1 English

Vitis Model Composer User Guide (UG1483)

Document ID
UG1483
Release Date
2021-06-16
Version
2021.1 English

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).

Figure 1. Data Producer/Consumer

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.

Note: In its current form, xmcImportFunction cannot import a function with a signature that includes hls::stream. For that, a wrapper is required.
Note: You cannot connect a block created using 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.