While Vitis Model Composer lets you import C or C++ functions to create a library of blocks, it does have specific requirements for the code to be properly recognized and processed. The function source can be defined in either a header file (.h), or in a C or C++ source file (.c, .cpp), but the header file must include the function signature.
You can import functions with function arguments that are real or complex types of scalar, vectors, or matrices, as well as using all the data types supported by Model Composer HLS Library, including fixed-point data types. Model Composer also lets you define functions as templates, with template variables defined by input signals, or as customization parameters to be specified when the block is added into the model or prior to simulating the model. Using function templates in your code lets you create a Model Composer block that supports different applications, and can increase the re-usability of your block library. Refer to Defining Blocks Using Function Templates for more information.
The xmcImportFunction
command supports C++
functions using std::complex<T>
or hls::x_complex<T>
types. For more details, see the
explanation in Using Complex Types.
If the input of an imported function is a 1-D array, the tool can perform
some automatic mappings between the input signal and the function argument. For example, if
the function argument is a[10]
, then the connected signal in
Model Composer can be either a vector of size 10, or a row or column matrix of size 1x10 or
10x1.
However, if all of the inputs and outputs of an imported function are scalar arguments, you can connect a vector signal, or a matrix signal to the input. In this case, the imported function processes each value of the vector, or matrix on the input signal as a separate value, and will combine those values into the vector, or matrix on the output signal. For example, a vector of size 10 connected to a scalar input, will have each element of the vector processed, and then returned to a vector of size 10 on the output signal.
y
is the
output:#include <stdint.h>
#include <ap_fixed.h>
#pragma XMC OUTPORT y
#pragma XMC PARAMETER Limit
template <typename T>
void counter(T &y, int16_t Limit)
{
static T count = 0;
count++;
if (count > Limit)
count =0;
y = count;
}
SampleTime
parameter is automatically added when
the block is created with xmcImportFunction
command, as shown
in the Function declaration in the following image. The default value is -1 which means the
sample time is inherited from the model. You can also explicitly specify the sample time by
customizing the block when it is added to a model, as shown below.The direction of ports for the function arguments can be determined
automatically by the xmcImportFunction
command, or manually
specified by pragma with the function signature in the header file.
- Automatically determining input and output ports:
- The
return
value of the function is always defined as an output, unless the return value isvoid
. - A formal function argument declared with the
const
qualifier is defined as an input. - An argument declared with a reference, a pointer type, or an array
type without a
const
qualifier is defined as an output. - Other arguments are defined as inputs by default (for example, scalar read-by-value).
- The
- Manually defining input and output ports:
- You can specify which function arguments are defined as inputs and outputs by adding the INPORT and OUTPORT pragmas into the header file immediately before the function declaration.
-
#pragma XMC INPORT <parameter_name> [, <parameter_name>...]
-
#pragma XMC OUTPORT <parameter_name> [, <parameter_name>...]
In the following example in
is automatically
defined as an input due to the presence of the const
qualifier, and out
is defined as an output. The imported
block will also have a second output due to the integer return
value of the function.
int func(const int in, int &out);
In the following function in
is
automatically defined as an input, and out
as an output,
however, there is no return
value.
void func(const in[512], int out[512]);
In the following example the ports are manually identified using pragmas that have been added to the source code right before the function declaration. This is the only modification to the original C++ code needed to import the function into Model Composer. In this example the pragmas specify which parameter is the input to the block and which parameter is the output of the block.
#pragma XMC INPORT din
#pragma XMC OUTPORT dout
void fir_sym (ap_fixed<17,3,AP_TRN,AP_WRAP> din[100],
ap_fixed<17,3,AP_TRN,AP_WRAP> dout[100]);
ap_fixed
specifies a fixed-point number compatible with Vitis
HLS.Manually adding pragmas to the function signature in the header file to
define the input and output parameters of the function is useful when your code does not use
the const
qualifier, and adding the const
qualifier can require extensive editing of the source code when there is a
hierarchy of functions. It also makes the designation of the inputs and outputs explicit in
the code, which can make the relationship to the imported block more clear.
Some final things to consider when writing C or C++ code for importing into Model Composer:
- You should develop your source code to be portable between 32 bit and 64 bit architectures.
- Your source code can use Vitis HLS pragmas for resource and performance optimization, and Model Composer uses those pragmas but does not modify or add to them.
- If your code has static variables, the static variable will be shared
across all instances of the blocks that import that function. If you do not want to share
that variable across all instances you should copy and rename the function with the static
variable and import a new library block using the
xmcImportFunction
command. -
If you use C (.c) source files to model the library function (as opposed to C++ (.cpp) source files), the .h header file must include an
extern "C"
declaration for the downstream tools (such as Vitis HLS) to work properly. An example of how to declare theextern "C"
in the header files is as follows:// c_function.h: #ifdef __cplusplus extern 'C' { #endif void c_function(int in, int &out); #ifdef __cplusplus } #endif