Conditional Ports - 2023.1 English

AI Engine Kernel and Graph Programming Guide (UG1079)

Document ID
UG1079
Release Date
2023-06-23
Version
2023.1 English

Certain graphs design may require the instantiation of a port based on a template parameter. For example if you want to use the same templated graph with or without a port that will be connected to a cascade in or a cascade out kernel port, then you will need to use this conditional port feature.

The syntax to conditionally instantiate a port or a sub-graph is as follows:
typename std::conditional<SEL,T1,T2>::type Object

Where,

  • T1 is an input_port, output_port, kernel, or user-defined graph.
  • T2 is a dummy type as std::tuple<> or int.
  • Object is the name of the object that will be instantiated.

The syntax to conditionally instantiate an array of ports or sub-graphs is as follows:

typename std::array<T3,N>::type Object

Where,

  • T3 is an input_port, output_port, kernel, or user-defined graph.
  • N is the number of items in the array.
  • Object is the name of the object that will be instantiated.
Here is an example of conditional port instantiation:

#include "adf.h"

using namespace adf;

void k0(input_stream<int32> *, output_stream<int32> *);
void k0_cascin(input_stream<int32> *, output_stream<int32> *, input_stream<acc48>*);
void k0_cascout(input_stream<int32> *, output_stream<int32> *, output_stream<acc48>*);
void k0_cascin_cascout(input_stream<int32> *, output_stream<int32> *, input_stream<acc48>*, output_stream<acc48>*);

template<bool HAS_CASCADE_IN, bool HAS_CASCADE_OUT>
struct SubGraph: public graph
{
  input_port strmIn;
  input_port strmOut;
  kernel k1;
  typename std::conditional<HAS_CASCADE_IN, input_port, int>::type cascIn;
  typename std::conditional<HAS_CASCADE_OUT, output_port, int>::type cascOut;

  SubGraph()
  {
    if constexpr (HAS_CASCADE_IN && HAS_CASCADE_OUT)
    {
      k1 = kernel::create(k0_cascin_cascout);
    }
    else if constexpr (HAS_CASCADE_IN)
    {
      k1 = kernel::create(k0_cascin);
    }
    else if constexpr (HAS_CASCADE_OUT)
    {
      k1 = kernel::create(k0_cascout);
    }
    else
    {
      k1 = kernel::create(k0);
    }
    connect(strmIn, k1.in[0]);
    connect(k1.out[0], strmOut);
    if constexpr (HAS_CASCADE_IN)
    {
      connect(cascIn, k1.in[1]);
    }
    if constexpr (HAS_CASCADE_OUT)
    {
      connect(k1.out[1], cascOut);
    }
    source(k1) = "kernels.cc";
    runtime<ratio>(k1) = 0.6;
  }
};
In this example, the four functions have different ports. One of these functions is instantiated in the graph depending on the template parameters. All functions have one input stream and one output stream, however, the cascade input and output ports are optional. The two template parameters are used in the std::conditional to create, or not, the cascade ports at the sub-graph level. They are also used to specify which function is instantiated in the sub-graph, and to connect the kernel ports to the right sub-graph port.
Here is another example where a complete sub-graph is instantiated and not depending on some template parameter:

#include "adf.h"

using namespace adf;

template<int ID>
void f0(input_stream<int32> *, output_stream<int32> *);


struct Sub0: public graph
{
  input_port _in0;
  input_port _out0;
  kernel _k0;
  Sub0()
  {
    _k0 = kernel::create(f0<0>);
    connect(_in0, _k0.in[0]);
    connect(_k0.out[0], _out0);
    runtime<ratio>(_k0) = 0.9;
    source(_k0) = "k0.cpp";
  }
};


struct Sub1: public graph
{
  input_port _in0;
  input_port _out0;
  kernel _k0;
  Sub1()
  {
    _k0 = kernel::create(f0<1>);
    connect(_in0, _k0.in[0]);
    connect(_k0.out[0], _out0);
    runtime<ratio>(_k0) = 0.8;
    source(_k0) = "k0.cpp";
  }
};


template<int ID>
struct MyGraph: public graph
{
  input_plio _plioI;
  output_plio _plioO;

  constexpr static bool hasSub0() {return ID & 0x1;}
  constexpr static bool hasSub1() {return ID & 0x2;}

  typename std::conditional<hasSub0(), Sub0, int >::type _sub0;
  typename std::conditional<hasSub1(), Sub1, int >::type _sub1;

  MyGraph()
  {
    _plioI = input_plio::create("plio_I"+std::to_string(ID), plio_32_bits,   "input"+std::to_string(ID)+".txt");
    _plioO = output_plio::create("plio_O"+std::to_string(ID), plio_64_bits, "output"+std::to_string(ID)+".txt");
    if constexpr (hasSub0() && hasSub1())
    {
      connect(_plioI.out[0], _sub0._in0);
      connect(_sub0._out0, _sub1._in0);
      connect(_sub1._out0, _plioO.in[0]);
    }
    else if constexpr (hasSub0())
    {
      connect(_plioI.out[0], _sub0._in0);
      connect(_sub0._out0, _plioO.in[0]);
    }
    else if constexpr (hasSub1())
    {
      connect(_plioI.out[0], _sub1._in0);
      connect(_sub1._out0, _plioO.in[0]);
    }
  }
};
Depending on the template parameter ID, the graph MyGraph will contain either one or both of the sub-graphs Sub0 and Sub1. If ID=0, no sub-graph is instantiated, and the compiler will error out. The two lines containing the std::conditional are used to conditionally instantiate the two sub-graphs. The connection of the kernels to the I/Os is also driven by the template parameter ID.