Creating a Data Flow Graph (Including Kernels) - 2025.2 English - UG1603

AI Engine-ML Kernel and Graph Programming Guide (UG1603)

Document ID
UG1603
Release Date
2025-11-26
Version
2025.2 English
This following process describes how to construct data flow graphs in C++.
  1. 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"
  2. Define your graph class using objects from the adf namespace. All user graphs derive from the class graph.
    include <adf.h>
    #include "kernels.h"
    
    class simpleGraph : public adf::graph {
    private:
      adf::kernel first;
      adf::kernel second;
    };

    This is the beginning of a graph class definition that declares two kernels (first and second).

  3. Add some top-level input/output objects, input_plio and output_plio, to the graph.
    #include <adf.h>
    #include "kernels.h"
    
    class simpleGraph : public adf::graph {
    private:
      adf::kernel first;
      adf::kernel second;
    public:
      adf::input_plio in;
      adf::output_plio out;
    
    };
  4. Use the kernel::create function to instantiate the first and second C++ kernel objects using the functionality of the C function simple.
    #include <adf.h>
    #include "kernels.h"
    
    class simpleGraph : public adf::graph {
    private:
      adf::kernel first;
      adf::kernel second;
    public:
      adf::input_plio in;
      adf::output_plio out;
      simpleGraph() {
          first = adf::kernel::create(simple);
          second = adf::kernel::create(simple);
      }
    };
  5. Configure input and output PLIO objects with the specified width and input/output files. Add connectivity information, which is equivalent to 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 has index 0 in an array of input ports (in). Subsequent input arguments take ascending consecutive indices. The first output buffer or stream argument in the simple 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"
    
    class simpleGraph : public adf::graph {
    private:
      adf::kernel first;
      adf::kernel second;
    public:
      adf::input_plio in;
      adf::output_plio out;
    
      simpleGraph() {
        first = adf::kernel::create(simple);
        second = adf::kernel::create(simple);
    
        in = adf::input_plio::create(plio_32_bits, "data/input.txt");
        out = adf::output_plio::create(plio_32_bits, "data/output.txt");
        adf::connect(in.out[0], first.in[0]);
        adf::connect(first.out[0], second.in[0]);
        adf::connect(second.out[0], out.in[0]);
        adf::dimensions(first.in[0]) = {128};
        adf::dimensions(first.out[0]) = {128};
        adf::dimensions(second.in[0]) = {128};
        adf::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 IDE. For more information, see Viewing AI Engine Compilation Summary Results in the Vitis Reference Guide (UG1702). 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 the dimensions(second.out[0])={128} specifies the number of samples of data kernels produced upon termination.

  6. 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 runtime compared to the cycle budget, known as the runtime ratio, and must be between 0 and 1. The cycle budget is the number of instruction cycles a function can take to do either of the following:
    • Consume data from its input (when dealing with a rate limited input data stream), or
    • Produce a block of data on its output (when dealing with a rate limited output data stream).

    Changing block sizes can affect the cycle budget.

    #include <adf.h>
    #include "kernels.h"
    
    class simpleGraph : public adf::graph {
    private:
      adf::kernel first;
      adf::kernel second;
    public:
      adf::input_plio in;
      adf::output_plio out;
    
      simpleGraph(){
    
        first = adf::kernel::create(simple);
        second = adf::kernel::create(simple);
    
        in = adf::input_plio::create(plio_32_bits, "data/input.txt");
        out = adf::output_plio::create(plio_32_bits, "data/output.txt");
            
        adf::connect(in.out[0], first.in[0]);
        adf::connect(first.out[0], second.in[0]);
        adf::connect(second.out[0], out.in[0]);
        adf::dimensions(first.in[0]) = {128};
        adf::dimensions(first.out[0]) = {128};
        adf::dimensions(second.in[0]) = {128};
        adf::dimensions(second.out[0]) = {128};
    
        adf::source(first) = "kernels.cc";
        adf::source(second) = "kernels.cc";
    
        adf::runtime<ratio>(first) = 0.1;
        adf::runtime<ratio>(second) = 0.1;
    
      }
    };
    Note: See Runtime Ratio for more information.
  7. 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; //Must have return statement
    }
Important: By default, the 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.
Important: The main function must have a return statement, otherwise, the AI Engine compiler errors out.

ADF APIs have return enumerate type return_code to show the API running status.

The main program is the driver for the graph. The main program loads, executes, and terminates the graph.

Note: Write kernel code such that no name clashes occur when two kernels are assigned to the same core.