Importing C/C++ into Model Composer - 2025.2 English - UG1483

Vitis Model Composer User Guide (UG1483)

Document ID
UG1483
Release Date
2025-11-20
Version
2025.2 English

Vitis Model Composer lets you import C or C++ functions to create a library of blocks. However it requires the code to be properly recognized and processed. You can define the function source in either a header file (.h), or in a C or C++ source file (.c, .cpp). 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. You can also use all the data types supported by the Model Composer HLS Library, including fixed-point data types. Model Composer lets you define functions as templates. You can use input signals or customization parameters to set template variables. Set these parameters when you add the block to the model or before simulation.

Using function templates in your code lets you create a Model Composer block that supports different applications. This can increase the re-usability of your block library. Refer to Defining Blocks Using Function Templates for more information.

Important: You must define the function signature in the header file. Also, specify any Model Composer (XMC) pragmas as part of the function signature in the header file.

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 automatically map the input signal to the function argument. For example, if the function argument is a[10], then the connected signal in Model Composer can be either of the following:

  • a vector of size 10
  • a row or column matrix of size 1x10 or 10x1

However, if all inputs and outputs of an imported function are scalar arguments, you can connect a vector or 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. It combines those values into the vector, or matrix on the output signal. For example, a vector of size 10 connected to a scalar input processes and returns each element of the vector to a vector of size 10 on the output signal.

You can import functions that do not have any inputs, and instead only generate outputs. This is known as a source block, and can have an output type of scalar, vector, complex, or matrix. You can also import source blocks with multiple outputs. The following example function has no input port, and 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;
}
Tip: Because source blocks have no inputs, the 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 inherits 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.
Figure 1. Setting Sample Time for a Source Block Generated by Your Tool

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 is void.
    • 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).
  • 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]);

The following example uses pragmas that were added to the source code before the function declaration to manually identify ports. 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]);
Tip: 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:

  • Developing 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. Model Composer uses those pragmas but does not modify or add to them.
  • If your code has static variables, the static variable is shared across all instances of the blocks that import that function. If you do not want to share that variable across all instances, 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 the extern "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