Hierarchical Graphs - 2022.2 English

AI Engine Kernel and Graph Programming Guide (UG1079)

Document ID
UG1079
Release Date
2022-10-19
Version
2022.2 English
A graph can contain a mix of kernels and sub-graphs. If you want to instantiate multiple kernels they can be declared as an array or with specific names. In the following example a template parameter specifies the number of kernels that will be declared as an array:
template <int NK>
class MultiKernelGraph: public adf::graph {
private:
  adf::kernel k[NK];

public:
  adf::port<input> din;
  adf::port<output> dout;

  MultiKernelGraph() {
    for(int i=0;i<NK;i++)
    {
      k[i] = adf::kernel::create(passthrough);
      adf::source(k[i]) = "kernels.cpp";

      adf::runtime<ratio>(k[i]) = 0.9;
    }
    adf::connect<window<FRAME_LENGTH*4>>(din, k[0].in[0]);
    for(int i=0;i<NK-1;i++)
      adf::connect<window<FRAME_LENGTH*4>>(k[i].out[0], k[i+1].in[0]);
    adf::connect<window<FRAME_LENGTH*4>>(k[NK-1].out[0], dout);
  };
};
class TestGraphMulti: public adf::graph {
public:
  adf::input_plio plin;
  adf::output_plio plout;

  MultiKernelGraph<3> Multi;

  TestGraphMulti()
  {
    plin = adf::input_plio::create("input2",adf::plio_64_bits,"data/Input_64.txt",500);
    adf::connect(plin.out[0],Multi.din);

    plout = adf::output_plio::create("output2",adf::plio_64_bits,"data/Output2.txt",500);
    adf::connect(Multi.dout,plout.in[0]);
  };
};
Figure 1. Graph View

As seen earlier, a graph can contain kernels and sub-graphs. The testbench .cpp code can instantiate multiple graphs and run them independently.

TestGraphSimple Simple_UnitTest;
TestGraphMulti Multi_UnitTest;

int main(int argc, char ** argv) {

  Simple_UnitTest.init();
  Multi_UnitTest.init();

  Simple_UnitTest.run(NFRAMES*NITERATIONS);
  Multi_UnitTest.run(NFRAMES*NITERATIONS);

  Simple_UnitTest.end();
  Multi_UnitTest.end();
  return 0;
}

Vitis Analyzer displays two independent graphs on the graph view.

Figure 2. Multiple Graphs in Vitis Analyzer

These two graphs can also be instantiated in a third graph that is instantiated in the testbench:

class TestGraph: public adf::graph {
public:
  adf::input_plio plin1,plin2;
  adf::output_plio plout1,plout2;

  SimplestGraph Simple;
  MultiKernelGraph Multi;

  TestGraph()
  {
    plin1 = adf::input_plio::create("input1",adf::plio_64_bits,"data/Input_64.txt",500);
    adf::connect(plin1.out[0],Simple.din);

    plout1 = adf::output_plio::create("output1",adf::plio_64_bits,"data/Output1.txt",500);
    adf::connect(Simple.dout,plout1.in[0]);

    plin2 = adf::input_plio::create("input2",adf::plio_64_bits,"data/Input_64.txt",500);
    adf::connect(plin2.out[0],Multi.din);

    plout2 = adf::output_plio::create("output2",adf::plio_64_bits,"data/Output2.txt",500);
    adf::connect(Multi.dout,plout2.in[0]);

  };
};
TestGraph UnitTest;

int main(int argc, char ** argv) {

  UnitTest.init();
  UnitTest.run(NFRAMES*NITERATIONS);
  UnitTest.end();
  return 0;
}
Figure 3. Composite Graph View

Much more complex graphs can be created including sequential and parallel kernels, and graphs where the data flow is split and merged multiple times throughout the graph, using windows and streams to transfer data through the AI Engine array.

VeryComplexGraph() {
  // Declare Kernels
  for(int i=0;i<2;i++)
  {
    simple[i] = adf::kernel::create(passthrough);
    adf::source(simple[i]) = "kernels.cpp";
    adf::runtime<ratio>(simple[i]) = 0.9;
  }

  // Connections
  // Inputs
  connect<>(din[0],split[0].din);
  connect<>(din[1],split[1].din);

  // Outputs
  connect<>(merge[2].dout,dout[0]);
  connect<>(split[3].dout[0],dout[1]);
  connect<>(split[3].dout[1],dout[2]);

  // Internal connections
  connect<>(split[0].dout[0],multi3.din);
  connect<>(multi3.dout,merge[2].din[0]);
  connect<>(split[0].dout[1],merge[0].din[0]);
  connect<>(split[1].dout[0],merge[0].din[1]);
  connect<window<FRAME_LENGTH*4>>(merge[0].dout,simple[0].in[0]);
  connect<window<FRAME_LENGTH*4>>(simple[0].out[0],split[2].din);
  connect<>(split[2].dout[0],multi2[1].din);
  connect<>(multi2[1].dout,merge[2].din[1]);
  connect<window<FRAME_LENGTH*4>>(split[2].dout[1],simple[1].in[0]);
  connect<window<FRAME_LENGTH*4>>(simple[1].out[0],merge[1].din[0]);
  connect<>(split[1].dout[1],multi2[0].din);
  connect<>(multi2[0].dout,merge[1].din[1]);
  connect<>(merge[1].dout,split[3].din);
};
Figure 4. Complex Graph View

In this graph view, a complex dataflow diverges and converges in multiple points. If a part of the dataflow needs to go in and out of the PL, you will just see the corresponding output ports and input ports that you connect to your PL kernels during the link phase.