In default compilation mode, the main
application is compiled as a separate control thread which needs to be executed on
the PS in parallel with the graph executing on the AI Engine-ML array. The main
application can use update and read APIs to access runtime parameters declared
within the graphs at any level. This section describes these APIs using
examples.
Synchronous Update/Read
The following code shows the main
application of the simple_param
graph described in
Specifying Runtime Data Parameters.
#include "param.h"
parameterGraph mygraph;
int main(void) {
mygraph.init();
mygraph.run(2);
mygraph.update(mygraph.select_value, 23);
mygraph.update(mygraph.select_value, 45);
mygraph.end();
return 0;
}
In this example, the graph mygraph
is initialized first and then run for two iterations. It has a triggered input
parameter port select_value
that must be updated
with a new value for each invocation of the receiving kernel. The first argument of
the update
API identifies the port to be updated
and the second argument provides the value. Several other forms of update APIs are
supported based on the direction of the port, its data type, and whether it is a
scalar or array parameter.
If the program is compiled with a fixed number of
test iterations, then for triggered parameters the number of update API calls in the
main
program must match the number of test
iterations, otherwise the simulation could be waiting for additional updates. For
asynchronous parameters, the updates are done asynchronously with the graph
execution and the kernel uses the old value if the update was not made.
If additionally, the previous graph was compiled with a synchronous inout parameter, then the update and read calls must be interleaved as shown in the following example.
#include "param.h"
parameterGraph mygraph;
int main(void) {
int result0, result1;
mygraph.init();
mygraph.run(2);
mygraph.update(mygraph.select_value, 23);
mygraph.read(mygraph.result_out, result0);
mygraph.update(mygraph.select_value, 45);
mygraph.read(mygraph.result_out, result1);
mygraph.end();
return 0;
}
In this example, it is assumed that the graph produces a scalar
result every iteration through the inout port result_out
. The read
API is used to
read out the value of this port synchronously after each iteration. The first
argument of the read
API is the graph inout port to
be read back and the second argument is the location where the value will be stored
(passed by reference).
The synchronous protocol ensures that the read operation will wait
for the value to be produced by the graph before sampling it and the graph will wait
for the value to be read before proceeding to the next iteration. This is why it is
important to interleave the update
and read
operations.
Asynchronous Update/Read
When an input parameter is specified with asynchronous protocol, the kernel
execution waits for the first update to happen for parameter initialization.
However, an arbitrary number of kernel invocations can take place before the next
update. This is usually the intent of the asynchronous update during application
deployment. However, for debugging, wait
API can be
used to finish a predetermined set of iterations before the next update as shown in
the following example.
#include "param.h"
asyncGraph mygraph;
int main(void) {
int result0, result1;
mygraph.init();
mygraph.update(mygraph.select_value, 23);
mygraph.run(5);
mygraph.wait();
mygraph.update(mygraph.select_value, 45);
mygraph.run(15);
mygraph.end();
return 0;
}
In the previous example, after the initial update, five iterations are run to completion followed by another update, then followed by another set of 15 iterations. If the graph has asynchronous inout ports, that data can also be read back immediately after the wait (or end).
Another template for asynchronous updates is to use timeouts in wait
API as shown in the following example.
#include "param.h"
asyncGraph mygraph;
int main(void) {
int result0, result1;
mygraph.init();
mygraph.run();
mygraph.update(mygraph.select_value, 23);
mygraph.wait(10000);
mygraph.update(mygraph.select_value, 45);
mygraph.resume();
mygraph.end(15000);
return 0;
}
In this example, the graph is set up to run forever. However, after the
run
API is called, it still blocks for the
first update to happen for parameter initialization. Then, it runs for 10,000 cycles
(approximately) before allowing the control thread to make another update. The new
update takes effect at the next kernel invocation boundary. Then the graph is
allowed to run for another 15,000 cycles before terminating.