C++ カーネル クラス サポート - 2023.2 日本語

AI エンジン カーネルおよびグラフ プログラミング ガイド (UG1079)

Document ID
UG1079
Release Date
2023-12-04
Version
2023.2 日本語

aiecompiler では、C++ カーネル クラスがサポートされます。次の例で、コンストラクターで FIR フィルター クラスのフィルター係数とサンプル数を設定する方法を示します。C++ カーネル クラスでは、各カーネル インスタンスの内部ステートを対応するクラス オブジェクト内にカプセル化できます。次のコードから、コンストラクターでフィルター係数 (coeffs) を指定する例を確認できます。これにより、C 関数カーネルの内部ステートを保存するためにファイル スコープ変数、グローバル変数、またはスコープ関数のスタティック変数を使用する際の問題が解決されます。このようなカーネルの複数のインスタンスが同じコアにマップされると、内部ステートの変数が複数のインスタンスで共有され、競合が生じます。

//fir.h
#pragma once
#include "adf.h"
#define NUM_COEFFS 12
using namespace adf;
class FIR
{
private:
    int32 coeffs[NUM_COEFFS];
    int32 tapDelayLine[NUM_COEFFS];
    uint32 numSamples;

public:
    FIR(const int32(&coefficients)[NUM_COEFFS], uint32 samples);
    void filter(input_buffer<int32> &in, output_buffer<int32> &out);
    static void registerKernelClass()
    {
        REGISTER_FUNCTION(FIR::filter);
    }
};

ヘッダー ファイル内に static void registerKernelClass() メソッドを記述する必要があります。registerKernelClass() メソッド内で、REGISTER_FUNCTION マクロを呼び出す必要があります。このマクロを使用して AI エンジン コア上で実行されるクラス run メソッドを登録し、カーネル機能を実行します。前述の例では、このマクロを使用して FIR::filter を登録しています。カーネル クラスのコンストラクターと run メソッドは、別のソース ファイル内にインプリメントする必要があります。カーネル クラスの run メソッドのインプリメンテーションは、前章で説明したカーネル関数の記述と同様です。

//fir.cpp
//implementation in this example is not optimized and is for illustration purpose
#include "fir.h"
#include <aie_api/aie.hpp>
#include <aie_api/aie_adf.hpp>

FIR::FIR(const int32(&coefficients)[NUM_COEFFS], uint32 samples)
{
    for (int i = 0; i < NUM_COEFFS; i++)
        coeffs[i] = coefficients[i];

    for (int i = 0; i < NUM_COEFFS; i++)
        tapDelayLine[i] = 0;

    numSamples = samples;
}

void FIR::filter(input_buffer<int32> &in, output_buffer<int32> &out){
  auto inIter=aie::begin(in);
  auto outIter=aie::begin(out);
  for (int i = 0; i < numSamples; i++){
    for (int j = NUM_COEFFS-1; j > 0; j--){
      tapDelayLine[j] = tapDelayLine[j - 1];
    }
    tapDelayLine[0] = *inIter++;
    int32 y = 0;
    for (int j = 0; j < NUM_COEFFS; j++){
      y += coeffs[j] * tapDelayLine[j];
    }
    *outIter++=y;
  }
}
//graph.h
#pragma once
#include "adf.h"
#include "fir.h"
using namespace adf;
class mygraph : public graph
{
  public:
    input_plio in1, in2;
    output_plio out1, out2;
    kernel k1, k2;
    mygraph(){
      in1=input_plio::create("Datain1",plio_32_bits,"data/input1.txt");
      in2=input_plio::create("Datain2",plio_32_bits,"data/input2.txt");
      out1=output_plio::create("Dataout1",plio_32_bits,"data/output1.txt");
      out2=output_plio::create("Dataout2",plio_32_bits,"data/output2.txt");
      k1 = kernel::create_object<FIR>(std::vector<int>({ 180, 89, -80,
-391, -720, -834, -478, 505, 2063, 3896, 5535, 6504 }), 8);
      runtime<ratio>(k1) = 0.9;
      source(k1) = "aie/fir.cpp";
      k2 = kernel::create_object<FIR>(std::vector<int>({ -21, -249, 319,
-78, -511, 977, -610, -844, 2574, -2754, -1066, 18539 }), 8);
      runtime<ratio>(k2) = 0.9;
      source(k2) = "aie/fir.cpp";

      connect(in1.out[0], k1.in[0]);
      connect(in2.out[0], k2.in[0]);
      connect(k1.out[0], out1.in[0]);
      connect(k2.out[0], out2.in[0]);

      dimensions(k1.in[0])={8};
      dimensions(k2.in[0])={8};
      dimensions(k1.out[0])={8};
      dimensions(k2.out[0])={8};
    }
};

デフォルトではないコンストラクターを使用するカーネル クラスでは、カーネル インスタンスの表現を作成する際に、kernel::create_object の引数でコンストラクター パラメーター値を指定できます。上記の例では、kernel::create_object<FIR>.を使用して 2 つの FIR フィルター カーネル (k1 および k2) が作成されています。k1 はフィルター係数 {180, 89, -80, -391, -720, -834, -478, 505, 2063, 3896, 5535, 6504} を使用し、k2 はフィルター係数 {-21, -249, 319, -78, -511, 977, -610, -844, 2574, -2754, -1066, 18539} を使用します。どちらも 1 回の呼び出しで 8 個のサンプルを消費します。

次のコードは、aiecompiler コンパイラ生成プログラムを示しています。2 つの FIR カーネル オブジェクトは、適切なコンストラクター パラメーターでインスタンシエートされます。

//Work/aie/<COL_ROW>/src/<COL_ROW>.cc
...
FIR i4({180, 89, -80, -391, -720, -834, -478, 505, 2063, 3896, 5535, 6504}, 8);
FIR i5({-21, -249, 319, -78, -511, 977, -610, -844, 2574, -2754, -1066, 18539}, 8);

int main(void) {
    ...
    // Kernel call : i4:filter
      i4.filter(window_buf0_buf0d_i[0],window_buf2_buf2d_o[0]);
    ...
    // Kernel call : i5:filter
      i5.filter(window_buf1_buf1d_i[0],window_buf3_buf3d_o[0]);
    ...
}

カーネル クラスには、データ メモリに収まらないほどのメモリ空間を占有するメンバー変数が存在する場合があります。カーネル クラスのメンバー変数の位置は制御できます。aiecompilerarray reference のメンバー変数をサポートしており、オブジェクトへの参照を渡しながら、メモリ空間を割り当てまたは制約できるようになっています。

//fir.h
#pragma once
#include "adf.h"
#define NUM_COEFFS 12
using namespace adf;
class FIR
{
private:
    int32 (&coeffs)[NUM_COEFFS];
    int32 tapDelayLine[NUM_COEFFS];
    uint32 numSamples;

public:
    FIR(int32(&coefficients)[NUM_COEFFS], uint32 samples);
    void filter(input_buffer<int32> &in, output_buffer<int32> &out);
    static void registerKernelClass()
    {
        REGISTER_FUNCTION(FIR::filter);
        REGISTER_PARAMETER(coeffs);
    }
};
//fir.cpp
#include "fir.h"
FIR::FIR(int32(&coefficients)[NUM_COEFFS], uint32 samples)
    : coeffs(coefficients)
{
    for (int i = 0; i < NUM_COEFFS; i++)
        tapDelayLine[i] = 0;

    numSamples = samples;
}

void FIR::filter(input_buffer<int32> &in, output_buffer<int32> &out)
{
...
}

上記の例では、FIR カーネル クラスに少し修正を加えたものを示しています。coeffs メンバー変数のデータ型は int32 (&)[NUM_COEFFS] です。コンストラクター初期化子 coeffs(coefficients) は、coeffs をクラス オブジェクトに外部で割り当てられているアレイへの参照に初期化します。coeffs メンバー変数がコンパイルのマップ段階で再配置されることを aiecompiler に通知するため、REGISTER_PARAMETER を使用して registerKernelClass 内にアレイ参照メンバー変数を登録する必要があります。

kernel::create_object を使用し、FIR カーネル インスタンスの表現を作成してコンストラクター パラメーターの初期値を指定することは、前述の例と同じです。次のコードを参照してください。

//graph.h
...
class mygraph : public graph
{
...
    mygraph()
    {
        k1 = kernel::create_object<FIR>(std::vector<int>({ 180, 89, -80, -391, -720, -834, -478, 505, 2063, 3896, 5535, 6504 }), 8);
        ...
        k2 = kernel::create_object<FIR>(std::vector<int>({ -21, -249, 319, -78, -511, 977, -610, -844, 2574, -2754, -1066, 18539 }), 8);
        ...
    }
};

次のコードは、対応する aiecompiler で生成されるプログラムを示しています。int32 i4_coeffs[12] および int32 i5_coeffs[15] のメモリ空間はカーネル オブジェクト インスタンスの外にあり、FIR オブジェクトに参照で渡されます。

//Work/aie/<COL_ROW>/src/<COL_ROW>.cc
int32 i4_coeffs[12] = {180, 89, -80, -391, -720, -834, -478, 505, 2063, 3896, 5535, 6504};
FIR i4(i4_coeffs, 8);
int32 i5_coeffs[12] = {-21, -249, 319, -78, -511, 977, -610, -844, 2574, -2754, -1066, 18539};
FIR i5(i5_coeffs, 8);

int main(void) {
    ...
    // Kernel call : i4:filter
    i4.filter(window_buf0_buf0d_i[0],window_buf2_buf2d_o[0]);
    ...
    // Kernel call : i5:filter
    i5.filter(window_buf1_buf1d_i[0],window_buf3_buf3d_o[0]);
    ...
}

配列参照メンバー変数のメモリ空間は aiecompiler によって割り当てられるため、次のコード例に示すように、ロケーション制約を適用してこれらの配列のメモリ位置を制約できます。REGISTER_PARAMETER マクロにより、kernel::create_objectk1.param[0]k2.param[0] といった配列参照メンバー変数のパラメーター ハンドルを作成でき、location<parameter> 制約が適用可能となります。

//graph.h
...
class mygraph : public graph
{
...
    mygraph()
    {
        k1 = kernel::create_object<FIR>(std::vector<int>({ 180, 89, -80, -391, -720, -834, -478, 505, 2063, 3896, 5535, 6504 }), 8);
        ...
        k2 = kernel::create_object<FIR>(std::vector<int>({ -21, -249, 319, -78, -511, 977, -610, -844, 2574, -2754, -1066, 18539 }), 8);
        ...

        location<parameter>(k1.param[0]) = address(…);
        location<parameter>(k2.param[0]) = bank(…);
    }
};

C++ カーネル クラスのヘッダー ファイルおよび C++ カーネル関数テンプレート (C++ テンプレート サポート 参照) には、シングル コア固有の組み込み API やプラグマを含めることはできません。これは、通常の C 言語の関数カーネルを記述する際のプログラミング ガイドラインと同様です。これらのヘッダー ファイルはグラフ ヘッダー ファイルに含まれ、PS プログラムの一部としてクロス コンパイルされるためです。 Arm® クロス コンパイラでは、シングル コアの組み込み API やプラグマは理解されません。シングル コア固有のプログラミング内容は、ソース ファイルに含める必要があります。