- Define your application graph class in a separate header file (for
example
project.h
). First, add the Adaptive Data Flow (ADF) header (adf.h) and include the kernel function prototypes. The ADF library includes all the required constructs for defining and executing the graphs on AI Engines.#include <adf.h> #include "kernels.h"
- Define your graph class by using the objects which are defined in the
adf
namespace. All user graphs are derived from the classgraph
.include <adf.h> #include "kernels.h" using namespace adf; class simpleGraph : public graph { private: kernel first; kernel second; };
This is the beginning of a graph class definition that declares two kernels (
first
andsecond
). - Add some top-level input/output objects,
input_plio
andoutput_plio
, to the graph.#include <adf.h> #include "kernels.h" using namespace adf; class simpleGraph : public graph { private: kernel first; kernel second; public: input_plio in; output_plio out; };
- Use the
kernel::create
function to instantiate thefirst
andsecond
C++ kernel objects using the functionality of the C functionsimple
.#include <adf.h> #include "kernels.h" using namespace adf; class simpleGraph : public graph { private: kernel first; kernel second; public: input_plio in; output_plio out; simpleGraph() { first = kernel::create(simple); second = kernel::create(simple); } };
- Configure input/output objects with specified PLIO width and
input/output files, and add the connectivity information, which is equivalent to the nets
in a data flow graph. In this description, input/output objects are referenced by indices.
The first input buffer or stream argument in the
simple
function is assigned index 0 in an array of input ports (in
). Subsequent input arguments take ascending consecutive indices. The first output buffer or stream argument in thesimple
function is assigned index 0 in an array of output ports (out
). Subsequent output arguments take ascending consecutive indices.#include <adf.h> #include "kernels.h" using namespace adf; class simpleGraph : public graph { private: kernel first; kernel second; public: input_plio in; output_plio out; simpleGraph() { first = kernel::create(simple); second = kernel::create(simple); in = input_plio::create(plio_32_bits, "data/input.txt"); out = output_plio::create(plio_32_bits, "data/output.txt"); connect(in.out[0], first.in[0]); connect(first.out[0], second.in[0]); connect(second.out[0], out.in[0]); dimensions(first.in[0]) = {128}; dimensions(first.out[0]) = {128}; dimensions(second.in[0]) = {128}; dimensions(second.out[0]) = {128}; } };
This figure represents the graph connectivity specified in the previous graph code. Graph connectivity can be viewed when you open the compilation results in the Vitis Analyzer. For more information, see Viewing Compilation Results in the Vitis Analyzer in the AI Engine Tools and Flows User Guide (UG1076). As shown in the previous figure, the input port from the top level is connected into the input port of the first kernel, the output port of the first kernel is connected to the input port of the second kernel, and the output port of the second kernel is connected to the output exposed to the top level. The first kernel executes when 128 bytes of data (32 complex samples) are collected in a buffer from an external source. This is specified using a
dimensions(first.in[0])=128
construct. Likewise, the second kernel executes when its input buffer has valid data being produced as the output of the first kernel. Finally, the output of the second kernel is connected to the top-level output port and thedimensions(second.out[0])=128
specifies the number of bytes of data kernel will produce upon termination.buf0
andbuf0d
are ping pong buffers allocated for thefirst
kernel input buffer. Similarly,buf2
andbuf2d
are ping pong buffers allocated for thesecond
kernel output buffer. Notice thatbuf1
which is the buffer output from thefirst
kernel to thesecond
kernel is not a ping pong buffer. This is because both thefirst
andsecond
kernel buffers are placed in a single AI Engine tile where they will execute sequentially. See Run-Time Ratio for more information. - Set the source file and tile usage for each of the kernels. The source
file kernel.cc contains kernel first and kernel
second source code. Then the ratio of the function run time compared to the cycle budget,
known as the run-time ratio, and must be between
0
and1
. The cycle budget is the number of instruction cycles a function can take to either consume data from its input (when dealing with a rate limited input data stream), or to produce a block of data on its output (when dealing with a rate limited output data stream). This cycle budget can be affected by changing the block sizes.#include <adf.h> #include "kernels.h" using namespace adf; class simpleGraph : public graph { private: kernel first; kernel second; public: input_plio in; output_plio out; simpleGraph(){ first = kernel::create(simple); second = kernel::create(simple); in = input_plio::create(plio_32_bits, "data/input.txt"); out = output_plio::create(plio_32_bits, "data/output.txt"); connect(in.out[0], first.in[0]); connect(first.out[0], second.in[0]); connect(second.out[0], out.in[0]); dimensions(first.in[0]) = {128}; dimensions(first.out[0]) = {128}; dimensions(second.in[0]) = {128}; dimensions(second.out[0]) = {128}; source(first) = "kernels.cc"; source(second) = "kernels.cc"; runtime<ratio>(first) = 0.1; runtime<ratio>(second) = 0.1; } };
Note: See Run-Time Ratio for more information. - Define a top-level application file (for example project.cpp) that contains an instance of your graph
class.
#include "project.h" simpleGraph mygraph; int main(void) { adf::return_code ret; mygraph.init(); ret=mygraph.run(<number_of_iterations>); if(ret!=adf::ok){ printf("Run failed\n"); return ret; } ret=mygraph.end(); if(ret!=adf::ok){ printf("End failed\n"); return ret; } return 0; }
mygraph.run()
option specifies a graph that runs forever.
The AI Engine compiler generates code to execute the
data flow graph in a perpetual while
loop. To limit the
execution of the graph for debugging and test, specify the mygraph.run(<number_of_iterations>)
in the graph code. The specified
number of iterations can be one or more.ADF APIs have return enumerate type return_code
to show the API running status.
The main
program is the driver for the
graph. It is used to load, execute, and terminate the graph.