Specifying Run-Time Data Parameters - 2022.1 English

Versal ACAP AI Engine Programming Environment User Guide (UG1076)

Document ID
UG1076
Release Date
2022-05-25
Version
2022.1 English

Parameter Inference

If an integer scalar value appears in the formal arguments of a kernel function, then that parameter becomes a run-time parameter. In the following example, the arguments select and result_out are run-time parameters.

#ifndef FUNCTION_KERNELS_H
#define FUNCTION_KERNELS_H
  void simple_param(input_window<int32> *in, output_window<int32> *out, int32 select, int32 &result_out);
#endif
Run-time parameters are processed as ports alongside those created by streams and windows. Both scalar and array of scalar data types can be passed as run-time parameters. The valid scalar data types supported are int8, int16, int32, int64, uint8, uint16, uint32, uint64, cint16, cint32, float, cfloat.
Note: Structs and pointers cannot be passed as run-time parameters. Every AI Engine has a different memory space and pointers have an inconsistent meaning when passed between AI Engines. Furthermore, the PS and AI Engines have different pointer representations. Because structs can contain pointers as members, the passing of structs as run-time parameters is not supported.
In the following example, an array of 32 integers is passed as a parameter into the filter_with_array_param function.
#ifndef FUNCTION_KERNELS_H
#define FUNCTION_KERNELS_H

  void filter_with_array_param(input_window_cint16 * in, output_window_cint16 * out, const int32 (&coefficients)[32]);

#endif

Implicit ports are inferred for each parameter in the function argument, including the array parameters. The following table describes the type of port inferred for each function argument.

Table 1. Port Type per Parameter
Formal Parameter Port Class
T Input
const T Input
T & Inout
const T & Input
const T (&)[ …] Input
T(&)[…] Inout

From the table, you can see that when the AI Engine cannot make externally visible changes to the function parameter, an input port is inferred. When the formal parameter is passed by value, a copy is made, so changes to that copy are not externally visible. When a parameter is passed with a const qualifier, the parameter cannot be written, so these are also treated as input ports.

When the AI Engine kernel is passed a parameter reference and it is able to modify it, an inout port is inferred and can be used to pass parameters between AI Engine kernels or to allow reading back of results from the control processor.

Note: The inout port is a port that a kernel itself can read or write. But, from the graph, the inout port can only behave as an output port. Therefore, the inout port can only be connected to the input RTP port, or be read by graph::read(). The inout port cannot be updated by graph::update().
Note: If a kernel wants to accept an input run-time parameter, modify its value, and pass back this modified value via an output run-time parameter, then the variable has to appear twice in the arg list, once as an input and once as an inout, for example, kernel_function(int32 foo_in, int32 &foo_out).

Parameter Hookup

Both input and inout run-time parameter ports can be connected to corresponding hierarchical ports in their enclosing graph. This is the mechanism that parameters are exposed for run-time modification. In the following graph, an instance is created of the previously defined simple_param kernel. This kernel has two input ports, one output port and one inout port. The first argument to appear in the argument list, in[0], is an input window. The second argument is an output window. The third argument is a run-time parameter (it is not a window or stream type) and is inferred as an input parameter, in[1], because it is passed by value. The fourth argument is a run-time parameter and is inferred as an inout parameter, inout[0], because it is passed by reference.

Note: The different port types in, out, and inout for a kernel belong to their own categories, and all start from index 0 in the graph.

In the following graph definition, a simple_param kernel is instantiated and windows are connected to in[0] and out[0] (the input and output windows of the kernel). The input run-time parameter is connected to the graph input port, select_value, and the inout run-time parameter is connected to the graph inout port, result_out.

class parameterGraph : public graph {
private:
  kernel first;
  
public:
  input_port select_value;
  input_port in;
  output_port out;
  inout_port result_out;
  parameterGraph() {
    first = kernel::create(simple_param);

    connect< window <32> >(in, first.in[0]);
    connect< window <32> >(first.out[0], out);
    connect<parameter>(select_value, first.in[1]);//default sync rtp input
    connect<parameter>(first.inout[0], result_out);//default async rtp output
  }
};

An array parameter can be hooked up in the same way. The compiler automatically allocates space for the array data so that it is accessible from the processor where this kernel gets mapped.

class arrayParameterGraph : public graph {
private:
  kernel first;
  
public:
  input_port coeffs;
  input_port in;
  output_port out;
  arrayParameterGraph() {
    first = kernel::create(filter_with_array_param);

    connect< window <32> >(in, first.in[0]);
    connect< window <32> >(first.out[0], out);
    connect<parameter>(coeffs, first.in[1]);
  }
};

Input Parameter Synchronization

The default behavior for input run-time parameters ports is triggering behavior. This means that the parameter plays a part in the rules that determine when a kernel could execute. In this graph example, the kernel only executes when three conditions are met:

  • A valid window of 32 bytes of input data is available
  • An empty window of 32 bytes is available for the output data
  • A write to the input parameter takes place

In triggering mode, a single write to the input parameter allows the kernel to execute once, setting the input parameter value on every individual kernel call.

There is an alternative mode to allow input kernels parameters to be set asynchronously. To specify that parameters update asynchronously, use the async modifier when connecting a port.

connect<parameter>(param_port, async(first.in[1]));

When a kernel port is designated as asynchronous, it no longer plays a role in the firing rules for the kernel. When the parameter is written once, the value is observed in subsequent firings. At any time, the PS can write a new value for the run-time parameter. That value is observed on the next and any subsequent kernel firing.

Inout Parameter Synchronization

The default behavior for inout run-time parameters ports is asynchronous behavior. This means that the parameter can be read back by the controlling processor or another kernel, but the producer kernel execution is not affected. For synchronous behavior from the inout parameter where the kernel blocks until the parameter value is read out on each invocation of the kernel, you can use a sync modifier when connecting the inout port to the enclosing graph as follows.

connect<parameter>(sync(first.inout[1]), param_port);