ランタイム グラフ制御 API

この章では、グラフの初期化、実行、アップデート、および外部コントローラーからのグラフ実行の制御に使用できる制御 API について説明します。この章では、カーネル内のデータ処理に影響を与え、グラフ全体の制御フローを同期または非同期で変更するランタイム パラメーターを入力グラフ仕様内で指定する方法についても説明します。

グラフの実行制御

AI エンジンを搭載した Versal™ ACAP では、プロセッシング システム (PS) を使用して、AI エンジン アレイ上で実行されるグラフを動的にロード、モニター、および制御できます。AI エンジン グラフが単一のビットストリーム イメージとして一度ロードされている場合でも、PS のプログラムを使用して実行の状態をモニターし、グラフのランタイム パラメーターを変更できます。

graph ベース クラスは、main プログラム内で使用できるグラフの初期化と実行を制御するための多数の API メソッドを提供します。詳細は、適応型データフロー グラフ仕様の参考資料 を参照してください。

グラフの基本的な反復実行

次のグラフ制御 API は、グラフ API を使用して、グラフの初期化、実行、待機、および終了を特定の回数だけ反復実行する方法の例を示しています。グラフ オブジェクト mygraph は、simpleGraph という名前の事前に定義されたグラフ クラスを使用して宣言されます。次に、main アプリケーション内で、このグラフ オブジェクトが初期化および実行されます。init() メソッドは、事前に指定された AI エンジン タイルにある AI エンジン アレイにグラフをロードします。この動作には、各 AI エンジン用 ELF バイナリのロード、配線用ストリーム スイッチのコンフィギュレーション、I/O 用 DMA のコンフィギュレーションが含まれます。プロセッサはディスエーブル状態のままになります。run() メソッドは、プロセッサをイネーブルにしてグラフの実行を開始します。run API は、実行時に正の整数引数を指定することにより、グラフを特定の回数だけ反復実行できます。この形式はグラフ実行のデバッグに便利です。

#include "project.h" 
simpleGraph mygraph; 

int main(void) { 
  mygraph.init(); 
  mygraph.run(3); // run 3 iterations 
  mygraph.wait(); // wait for 3 iterations to finish 
  mygraph.run(10); // run 10 iterations 
  mygraph.end(); // wait for 10 iterations to finish 
  return 0; 
}
注記: API wait() を使用すると、1 回目の実行の終了を待機した後に 2 回目の実行を開始します。wait には end と同じブロッキング効果がありますが、グラフを再初期化せずに再実行できる点が異なります。run API はグラフのアクティブ プロセッサのループ境界を変更するため、wait を間に挟まずに run を連続して呼び出してグラフの実行を終了すると、予測不可能な影響をもたらすことがあります。

グラフの有限実行

グラフの有限実行では、グラフのステートは graph.run(n) の前後で維持されます。graph.run(n) の実行後、AI エンジンは再初期化されず、メモリの内容はクリアされません。次のコード例では、最初の 3 回の呼び出しの実行後、core-main ラッパー コードのステートは、カーネルが次の (10 回の反復の) 実行で pong バッファーを使用して開始される状態のままになります。ping-pong バッファー セレクターのステートは現状のままになります。graph.end() は、グラフのステートをクリア (グローバル変数を再初期化) せず、ストリーム スイッチの設定もクリアしません。この API は、core-main を終了するだけです。グラフを再実行するには、PDI/XCLBIN を再ロードする必要があります。

#include "project.h" 
simpleGraph mygraph; 

int main(void) { 
  mygraph.init(); 
  mygraph.run(3); // run 3 iterations 
  mygraph.wait(); // wait for 3 iterations to finish 
  mygraph.run(10); // run 10 iterations 
  mygraph.end(); // wait for 10 iterations to finish 
  return 0; 
}

グラフの無期限実行

次のグラフ制御 API は、グラフを無期限に実行する方法の例を示しています。

#include "project.h"
simpleGraph mygraph;

int main(void) {
  mygraph.init();  // load the graph
  mygraph.run();   // start the graph
  return 0;
}

グラフ オブジェクト mygraph は、simpleGraph という名前の事前に定義されたグラフ クラスを使用して宣言されます。次に、main アプリケーション内で、このグラフ オブジェクトが初期化および実行されます。init() メソッドは、事前に指定された AI エンジン タイルにある AI エンジン アレイにグラフをロードします。この動作には、各 AI エンジン用 ELF バイナリのロード、配線用ストリーム スイッチのコンフィギュレーション、I/O 用 DMA のコンフィギュレーションが含まれます。プロセッサはディスエーブル状態のままになります。run() メソッドは、プロセッサをイネーブルにしてグラフの実行を開始します。run() メソッドの反復実行の回数が指定されていないため、このグラフは無期限に実行を続けます。

注記: 引数を指定せずに graph::run() を使用すると、前回に指定した反復回数だけ (デフォルトでは、引数を指定せずにグラフを実行する場合は無制限に) AI エンジン カーネルを実行します。たとえば mygraph.run(3);mygraph.run() のように、有限の反復回数を指定してグラフを実行した場合、2 番目の呼び出しも 3 回反復されます。

グラフの並列実行

上記の API メソッドのうち、main アプリケーションを無期限にブロックできるブロッキング動作は、wait()end() だけです。したがって、最上位で複数のグラフを宣言した場合、次に示すように、API を適切にインターリーブしてグラフを並列で実行する必要があります。

#include "project.h"
simpleGraph g1, g2, g3;

int main(void) {
  g1.init(); g2.init(); g3.init();
  g1.run(<num-iter>); g2.run(<num-iter>); g3.run(<num-iter>);
  g1.end(); g2.end(); g3.end();
  return 0;
}
注記: 各グラフは、初期化 (init) してから開始 (run) する必要があります。また、並列実行では、すべてのグラフが開始 (run) されてから、いずれかのグラフの終了 (end) を待機する必要があります。

時間制限付き実行

マルチレート グラフでは、すべてのカーネルを同じ回数反復実行する必要はありません。このような状況では、テストには時間制限付き実行モデルの方が適しています。wait および end API には、サイクルのタイムアウトを指定する正の整数を持つバリアントがあります。この AI エンジン サイクル数の間 API 読み出しがブロックされると、プロセッサをディスエーブルにして制御を返します。ブロッキング条件は、グラフ終了イベントに依存しません。タイムアウト終了時のグラフのステートは任意です。

#include "project.h"
simpleGraph mygraph;

int main(void) {
  mygraph.init();
  mygraph.run();
  mygraph.wait(10000);  // wait for 10000 AI Engine cycles
  mygraph.resume();     // continue executing 
  mygraph.end(15000);   // wait for another 15000 cycles and terminate
}
注記: API resume() を使用して、最初のタイムアウト後にグラフが停止したポイントから実行を再開できます。resume は、タイマーをリセットして AI エンジン をイネーブルにするだけです。AI エンジンの実行が既に終了した後に resume を呼び出しても、影響はありません。

ランタイム パラメーター仕様

これまでに示したデータフロー グラフは、完全に静的に定義されています。ただし、実際の状況では、何らかの動的な条件またはイベントに基づいて動作を変更しなければならない場合があります。必要な変更には、処理されているデータに関連するものと (変更された動作モードや、新しい係数テーブルなど)、グラフの制御フローに関連するもの (条件付き実行や、ほかのグラフによるグラフの動的なリコンフィギュレーションなど) があります。ランタイム パラメーターは、このような状況で役に立ちます。カーネルまたはグラフのいずれかを、パラメーターに基づいて実行されるように定義できます。グラフの実行中にこれらのパラメーター値をアップデートまたは読み出すための追加のグラフ API も用意されています。

2 つのタイプのランタイム パラメーターをサポートしています。最初のタイプは非同期パラメーター (スティッキー パラメーター) であり、プロセッシング システム (PS) などの制御プロセッサまたはほかの AI エンジン カーネルによっていつでも変更できます。これらのパラメーターは、カーネルが起動されるたびに、特に同期されずに読み出されます。これらのタイプのパラメーターは、たとえば変更頻度の低いフィルター係数として使用できます。

サポートされているもう 1 つのタイプのランタイム パラメーターは、同期パラメーター (トリガー パラメーター) です。トリガー パラメーターを必要とするカーネルは、制御プロセッサがこれらのパラメーターに値を書き込むまで実行されません。値が書き込まれると、カーネルが 1 回実行され、アップデートされた新しい値を読み出します。動作の完了後、パラメーターが再びアップデートされるまで、カーネルの実行はブロックされます。これにより、通常のストリーミング モデルとは異なるタイプの実行モデルを利用でき、アップデート動作でブロッキングの同期が重要な場合に便利です。

ランタイム パラメーターは、スカラー値でも配列値でもかまいません。制御プロセッサ (PS など) がアップデートを担当する場合は、graph.update() API を使用する必要があります。

ランタイム データ パラメーターの指定

パラメーターの推論

カーネル関数の仮引数に整数スカラー値が表示される場合、パラメーターはランタイム パラメーターになります。次の例では、引数 select はランタイム パラメーターです。

#ifndef FUNCTION_KERNELS_H
#define FUNCTION_KERNELS_H
 
  void simple_param(input_window_cint16 * in, output_window_cint16 * out, int select);

#endif
ランタイム パラメーターは、ストリームおよびウィンドウで生成されたパラメーターと共にポートとして処理されます。スカラーおよびスカラー配列のデータ型は、いずれもランタイム パラメーターとして渡すことができます。サポートされる有効なスカラー データ型は、int8, int16, int32, int64, uint8, uint16, uint32, uint64, cint16, cint32, float, cfloat です。
注記: Structs とポインターは、ランタイム パラメーターとして渡すことはできません。各 AI エンジンにはそれぞれメモリ空間があるので、ポインターが AI エンジン間で渡されると、一貫性が失われます。また、PS と AI エンジンではポインターの表現が異なります。structs にはメンバーとしてポインターが含まれることがあるため、structs をランタイム パラメーターとして渡すことはできません。
次の例では、32 個の整数の配列がパラメーターとして filter_with_array_param 関数に渡されます。
#ifndef FUNCTION_KERNELS_H
#define FUNCTION_KERNELS_H

  void filter_with_array_param(input_window_cint16 * in, output_window_cint16 * out, const int32 (&coefficients)[32]);

#endif

関数引数 (配列パラメーターを含む) の各パラメーターにはポートが推論されます。次の表に、各関数引数に推論されるポートのタイプを示します。

表 1. パラメーター別のポートのタイプ
仮引数 ポート クラス
T 入力
const T 入力
T & 入出力
const T & 入力
const T (&)[ …] 入力
T(&)[…] 入出力

この表から、AI エンジンで関数パラメーターに対して外部から見える変更を加えられない場合、入力ポートが推論されることがわかります。仮引数が値渡しの場合、コピーが作成されるため、そのコピーに対する変更は外部から見えません。パラメーターが const 修飾子付きで渡される場合、パラメーターに書き込めないため、これらも入力ポートとして扱われます。

AI エンジン カーネルにパラメーター参照が渡され、カーネルがそれを変更できる場合、入出力ポートが推論されるので、これを AI エンジン カーネル間でのパラメーター渡し、または制御プロセッサからの結果の読み出しに使用できます。

注記: 入出力ポートは、カーネル自体が読み出しまたは書き込みを実行できるポートです。ただし、グラフから見ると、入出力ポートは出力ポートとしてのみ動作します。そのため、入出力ポートは、入力 RTP ポートに接続するか、または graph::read() で読み出すことのみが可能です。入出力ポートは、graph::update() でアップデートすることはできません
注記: カーネルが入力ランタイム パラメーターを受け入れ、その値を変更し、変更した値を出力ランタイム パラメーターとして返す場合、変数は arg リストに 2 回 (1 回は入力として、もう 1 回は入出力として) 現れる必要があります (例: kernel_function(int32 foo_in, int32 &foo_out))。

パラメーターの接続

入力および入出力ランタイム パラメーター ポートは、それらを含むグラフ内の対応する階層的ポートに接続できます。このメカニズムにより、パラメーターはランタイムに変更するためアクセスできるようになります。次のグラフでは、事前に定義された simple_param カーネルのインスタンスが作成されます。このカーネルには、2 つの入力ポートと 1 つの出力ポートがあります。引数リストに現れる最初の引数 in[0] は入力ウィンドウです。2 番目の引数は出力ウィンドウです。3 番目の引数はランタイム パラメーター (ウィンドウ型でもストリーム型でもない) であり、値渡しであるため、入力パラメーター in[1] として推論されます。

次のグラフ定義では、simple_param カーネルがインスタンシエートされ、ウィンドウは in[0] および out[0] (カーネルの入力ウィンドウと出力ウィンドウ) に接続されます。ランタイム パラメーターは、グラフの入力ポート select_value に接続されます。

class parameterGraph : public graph {
private:
  kernel first;
  
public:
  input_port select_value;
  input_port in;
  output_port out;
  parameterGraph() {
    first = kernel::create(simple_param);

    connect< window <32> >(in, first.in[0]);
    connect< window <32> >(first.out[0], out);
    connect<parameter>(select_value, first.in[1]);
  }
};

配列パラメーターも同じ方法で接続できます。配列データには、このカーネルがマップされるプロセッサからアクセスできるように、空間が自動的に割り当てられます。

class arrayParameterGraph : public graph {
private:
  kernel first;
  
public:
  input_port coeffs;
  input_port in;
  output_port out;
  arrayParameterGraph() {
    first = kernel::create(filter_with_array_param);

    connect< window <32> >(in, first.in[0]);
    connect< window <32> >(first.out[0], out);
    connect<parameter>(coeffs, first.in[1]);
  }
};

入力パラメーターの同期

入力ランタイム パラメーター ポートのデフォルト動作は、動作をトリガーすることです。つまり、このパラメーターは、カーネルがいつ起動するかを決定する規則の一部として使用されます。このグラフの例では、カーネルは次の 3 つの条件が満たされたときにのみ起動します。

  • 32 バイトの入力データの有効なウィンドウが使用可能
  • 出力データ用に 32 バイトの空のウィンドウが使用可能
  • 入力パラメーターへの書き込みが発生

トリガー モードでは、入力パラメーターへの 1 回の書き込みでカーネルを 1 回起動でき、カーネルが呼び出されるたびに入力パラメーターの値が設定されます。

入力カーネル パラメーターを非同期で設定できるモードもあります。パラメーターを非同期でアップデートするよう指定するには、ポートの接続時に async 修飾子を使用します。

connect<parameter>(param_port, async(first.in[1]));

カーネル ポートが非同期と指定されている場合、カーネルの起動規則の一部としては使用されません。パラメーターに一度値が書き込まれると、それ以降のカーネル起動にその値が使用されます。PS は、いつでもランタイム パラメーターに新しい値を書き込むことができます。その値は、それ以降のカーネル起動に使用されます。

入出力パラメーターの同期

入出力ランタイム パラメーター ポートのデフォルト動作は、非同期動作です。つまり、パラメーターは制御プロセッサまたはほかのカーネルで読み出すことができますが、プロデューサー カーネルの実行には影響しません。入出力パラメーターからの同期動作で、カーネルの各呼び出しでパラメーター値が読み出されるまでカーネルがブロックされる場合は、次のように、それらを含むグラフで入出力ポートを接続する際に sync 修飾子を使用できます。

connect<parameter>(sync(first.out[1]), param_port);

ランタイム パラメーターのアップデート/読み出しメカニズム

このセクションでは、ランタイム パラメーターのアップデートと読み出しのメカニズムについて説明します。これらのタイプのアプリケーションでは、通常はコンパイル時の反復回数の上限を指定せずにコアを無制限に実行し、パラメーターの変更の影響をモニターすることを推奨します。

グラフ API を使用したパラメーターのアップデート/読み出し

デフォルトのコンパイル モードでは、main アプリケーションは個別の制御スレッドとしてコンパイルされます。この制御スレッドは、AI エンジン アレイ上で実行されるグラフと並行して PS 上で実行する必要があります。main アプリケーションは、アップデート API および読み出し API を使用して、グラフ内で宣言されたランタイム パラメーターに任意のレベルでアクセスできます。このセクションでは、これらの API を例を使用して説明します。

同期アップデート/読み出し

次のコードは、ランタイム データ パラメーターの指定 に説明されている simple_param グラフの main アプリケーションを示します。

#include "param.h"
parameterGraph mygraph;

int main(void) {
  mygraph.init();
  mygraph.run(2);
  
  mygraph.update(mygraph.select_value, 23);
  mygraph.update(mygraph.select_value, 45);

  mygraph.end();
  return 0;
}

この例では、グラフ mygraph が初期化された後に反復が 2 回実行されます。これにより、受信カーネルの実行ごとに新しい値で更新する必要がある入力パラメーター ポート select_value がトリガーされます。API の最初の引数 update はアップデートするポートを示し、2 番目の引数はその値を示します。ポートの方向、そのデータ型、およびスカラー パラメーターか配列パラメーターかに基づいて、ほかにもいくつかのアップデート API がサポートされています。適応型データフロー グラフ仕様の参考資料 を参照してください。

プログラムが固定された回数のテスト反復を使用してコンパイルされている場合、トリガーされたパラメーターに対して、main プログラム内のアップデート API 呼び出しの数がテスト反復回数と一致している必要があります。一致していない場合、シミュレーションが追加のアップデートを待機し続けることになる可能性があります。非同期パラメーターの場合、アップデートはグラフの実行とは非同期に実行されれ、アップデートされなかった場合はカーネルは以前の値を使用します。

さらに、前のグラフが同期入出力パラメーターを使用してコンパイルされている場合は、次の例に示すように、アップデート呼び出しと読み出し呼び出しを交互に実行する必要があります。

#include "param.h"
parameterGraph mygraph;

int main(void) {
  int result0, result1;
  mygraph.init();
  mygraph.run(2);

  mygraph.update(mygraph.select_value, 23);
  mygraph.read(mygraph.result_out, result0);
  mygraph.update(mygraph.select_value, 45);
  mygraph.read(mygraph.result_out, result1);

  mygraph.end();
  return 0;
}

この例では、グラフは反復ごとに入力ポート result_out を介してスカラー結果を生成すると想定されています。read API は、各反復後にこのポートの値を同期的に読み出すために使用されます。read API の最初の引数はリードバックするグラフの入出力ポートを示し、2 番目の引数はその値を保存する場所を示します (参照により渡す)。

同期プロトコルにより、グラフで値が生成されてから読み出し操作用に値がサンプリングされ、グラフの値が読み出されてから次の反復に進むようになります。このため、updateread を交互に実行することが重要です。

非同期アップデート/読み出し

入力パラメーターが非同期プロトコルで指定されている場合、最初のアップデートが実行されてパラメーターが初期化されてからカーネルが実行されます。ただし、次のアップデートの前に、カーネルが任意の回数実行される可能性があります。これは通常、アプリケーション運用時に非同期にアップデートされることを意図しています。デバッグでは、次の例に示すように、次のアップデートの前に、反復を決まった回数実行するために wait API を使用できます。

#include "param.h"
asyncGraph mygraph;

int main(void) {
  int result0, result1;
  mygraph.init();

  mygraph.update(mygraph.select_value, 23);
  mygraph.run(5);
  mygraph.wait();
  mygraph.update(mygraph.select_value, 45);
  mygraph.run(15);
  mygraph.end();
  return 0;
}

上記の例では、最初のアップデート後に反復が 5 回実行され、その後に別のアップデートが実行されてから、反復が 15 回実行されます。グラフに非同期入出力ポートがある場合は、待機 (または終了) の直後にデータをリードバックすることもできます。

非同期アップデートの別のテンプレートでは、次の例に示すように、wait API でタイムアウトを使用します。

#include "param.h"
asyncGraph mygraph;

int main(void) {
  int result0, result1;
  mygraph.init();
  mygraph.run();
  mygraph.update(mygraph.select_value, 23);
  mygraph.wait(10000);
  mygraph.update(mygraph.select_value, 45);
  mygraph.resume();
  mygraph.end(15000);
  return 0;
}

この例では、グラフは無限に実行されるよう設定されています。ただし、run API が呼び出された後、最初のアップデートでパラメーターが初期化されるまで実行がブロックされます。その後約 10,000 サイクル実行されてから、制御スレッドで別のアップデートが実行できるようになります。新しいアップデートは、次のカーネル呼び出し境界で有効になります。その後、グラフがさらに 15,000 サイクル実行されてから終了します。

AI エンジン カーネル間でのチェーン アップデート

上記のランタイム パラメーターの例は、制御プロセッサからランタイム パラメーターをアップデートする機能を示しています。パラメーターのアップデートを AI エンジン 間で伝搬することも可能です。カーネルの入出力ポートがほかのカーネルの入力ポートに接続されている場合、複数の AI エンジンを介してアップデートのチェーンをトリガーできます。次のコードで定義される 2 つのカーネルがあるとします。プロデューサー カーネルには、スカラー整数を読み出す入力ポートと、32 個の整数からなる配列の読み出しと書き込みを実行する入出力ポートがあります。コンシューマー カーネルには、係数の配列を読み出す入力ポートと、データのウィンドウを書き込む出力ポートがあります。

#ifndef FUNCTION_KERNELS_H
#define FUNCTION_KERNELS_H
 
  void producer(const int32 &, int32 (&)[32] );
  void consumer(const int32 (&)[32], output_window_cint16 *);

#endif

次のグラフに示すように、PS はプロデューサー カーネルのスカラー入力をアップデートします。プロデューサー カーネルが実行されると、コンシューマーの実行が自動的にトリガーされます (出力データ用にバッファーが使用可能な場合)。

#include <adf.h>
#include "kernels.h"

using namespace adf;

class chainedGraph : public graph {
private:
  kernel first;
  kernel second;
  
public:
  input_port select_value; 
  output_port out;

  chainedGraph() {
      first =  kernel::create(producer);
      second = kernel::create(consumer);

      connect< window <32> >(second.out[0], out);
      connect<parameter>(select_value, first.in[0]);
      connect<parameter>(first.inout[0], second.in[0]);
  }
};

ストリームの連続処理に使用される値を一度だけアップデートすることが目的である場合は、継続的に実行されることを確実にするため、コンシューマー カーネルのパラメーター入力ポートに async 修飾子を使用できます (パラメーターが指定されている場合)。

制御パラメーターによるランタイム グラフ リコンフィギュレーション

ランタイム パラメーターを使用して、グラフ内でデータのフローを切り替え、代替処理ルートを動的に提供できます。カーネル バイパスは、このタイプの処理で最も基本的であり、ランタイム パラメーターに基づいてデータの処理またはパススルーを許可します (カーネル バイパス 参照)。この機能は、たとえば、マルチモード アプリケーションでモードの切り替え時にカーネルをバイパスする必要がある場合に便利です。

ランタイム パラメーターによるバイパス制御

次の図に、2つのチャネルの信号データをサポートするアプリケーションを示します。1 つのチャネルは 2 つの低帯域幅チャネルに分割され、もう 1 つのチャネルは影響を受けずに動作を続けます。このタイプの動的リコンフィギュレーションは、ワイヤレス アプリケーションによく見られます。

1: 2 つの LTE20 チャネルを 1 つの LTE20 チャネルと 2 つの LTE10 チャネルに分割する動的リコンフィギュレーション

この図で、最初のチャネルは LTE20 データを変更せずに処理しますが、中間のチャネルは 2 つの LTE10 チャネルに動的に分割されます。carrier configuration RTP という制御パラメーターを使用して、ブロック境界上でデータ処理を分割します。中間のチャネルが 1 つの LTE20 チャネルとして動作しているときは、11 タップのハーフバンド カーネルはバイパスされます。ただし、中間のチャネルの帯域幅がそれ自体と 3 番目のチャネルの間で分割されて 2 つの LTE10 チャネルを形成するときは、両方のデータをミキシングする前に、両チャネルに 3 段フィルター チェーンが必要です。これを実現するには、11 タップのハーフバンド フィルターを切り替えてフローに戻し、2 つのデータ ストリームの代わりに 3 つのデータ ストリームを処理するようにミキサーを再設定します。

ヒント: LTE20 信号と LTE10 信号をミキシングするときにサンプル遅延のバランスがとれるように、遅延アライメント カーネルが必要です。2 つの信号は動的にスイッチングされるため、これらのカーネルを制御フローの一部にする必要があります。

次のコードに、上記のアプリケーションの最上位の入力グラフ仕様を示します。

class lte_reconfig : public graph {
  private:
    kernel demux;
    kernel cf[3];
    kernel interp0[3];
    kernel interp1[2];
    bypass bphb11;
    kernel delay ;
    kernel delay_byp ;
    bypass bpdelay ;
    kernel mixer ;
  public:
    input_port in;
    input_port  fromPS;
    output_port out ;

    lte_reconfig() {
      // demux also handles the control
      demux = kernel::create(demultiplexor);
      connect< window<1536> >(in, demux.in[0]);
      connect< parameter >(fromPS, demux.in[1]);

      runtime<ratio>(demux) = 0.1;
      source(demux) = "kernels/demux.cc";

      // instantiate all channel kernels
      for (int i=0;i<3;i++) {
        cf[i] = kernel::create(fir_89t_sym);
        source(cf[i]) = "kernels/fir_89t_sym.cc";
        runtime<ratio>(cf[i]) = 0.12;
      }
      for (int i=0;i<3;i++) {
        interp0[i] = kernel::create(fir_23t_sym_hb_2i);
        source(interp0[i]) = "kernels/hb23_2i.cc";
        runtime<ratio>(interp0[i]) = 0.1;
      }
      for (int i=0;i<2;i++) {
        interp1[i] = kernel::create(fir_11t_sym_hb_2i);
        source(interp1[i]) = "kernels/hb11_2i.cc";
        runtime<ratio>(interp1[i]) = 0.1;
      }
      bphb11 = bypass::create(interp1[0]);
      mixer =  kernel::create(mixer_dynamic);
      source(mixer) = "kernels/mixer_dynamic.cc";
      runtime<ratio>(mixer) = 0.4;
      delay =  kernel::create(sample_delay);
      source(delay) = "kernels/delay.cc";
      runtime<ratio>(delay) = 0.1;
      delay_byp =  kernel::create(sample_delay);
      source(delay_byp) = "kernels/delay.cc";
      runtime<ratio>(delay_byp) = 0.1;
      bpdelay = bypass::create(delay_byp) ;

      // Graph connections
      for (int i=0; i<3; i++) {
        connect< window<512, 352> >(demux.out[i], cf[i].in[0]);
        connect< parameter >(demux.inout[i], cf[i].in[1]);
      }
      connect< parameter >(demux.inout[3], bphb11.bp);
      connect< parameter >(demux.inout[3], negate(bpdelay.bp)) ;
      for (int i=0;i<3;i++) {
        connect< window<512, 64> >(cf[i].out[0], interp0[i].in[0]);
        connect< parameter >(cf[i].inout[0], interp0[i].in[1]);
      }
       // chan0 is LTE20 and is output right away
      connect< window<1024, 416> >(interp0[0].out[0], delay.in[0]);
      connect< window<1024> >(delay.out[0], mixer.in[0]);
      // chan1 is LTE20/10 and uses bypass
      connect< window<1024, 32> >(interp0[1].out[0], bphb11.in[0]);
      connect< parameter >(interp0[1].inout[0], bphb11.in[1]);
      connect< window<1024, 416> >(bphb11.out[0], bpdelay.in[0]);
      connect< window<1024> >(bpdelay.out[0], mixer.in[1]);
      // chan2 is LTE10 always
      connect< window<512, 32> >(interp0[2].out[0], interp1[1].in[0]);
      connect< parameter >(interp0[2].inout[0], interp1[1].in[1]);
      connect< window<1024> >(interp1[1].out[0], mixer.in[2]);
      //Mixer
      connect< parameter >(demux.inout[3], mixer.in[3]);
      connect< window<1024> >(mixer.out[0], out);
    };
};

バイパス仕様は、バイパスされるカーネル上の特殊なエンキャプスレーターとしてコード化されます。バイパスのポート シグネチャは、そのバイパスがカプセル化するカーネルのポート シグネチャと一致します。このカーネルは、バイパスを制御するランタイム パラメーターも受け取ります (0 はバイパスなし、1 はバイパス)。否定関数を使用して、制御を反転することもできます。

このグラフのバイパス パラメーター ポートは、通常のスカラー ランタイム パラメーターであり、ランタイム パラメーターのアップデート/読み出しメカニズム で説明するインタラクティブまたはスクリプトによるメカニズムを使用して、ほかのカーネルまたは Arm® プロセッサによって駆動できます。このポートは、囲い込むグラフに組み込むことにより、階層的に接続することもできます。

複数のカーネルによるランタイム パラメーターの共有

前述のグラフに含まれるチャネルを切り替えるランタイム パラメーターは、バイパス エンキャプスレーターとミキサー カーネルにより共有されています。両方のエンティティが同じデータ境界で切り替えられた同じ値を検出する必要があります。ランタイム パラメーターを共有している複数のノードが同じ AI エンジンにマップされる場合、同じプロセッサにマップされているすべてのノードが現在のデータ セットを処理してから次のデータ セットを処理するので、パラメーター値の切り替えは同期されます。ただし、共有しているカーネルが異なるプロセッサにマップされる場合は、次の図に示すように、これらのカーネルは異なるデータ セットをパイプライン方式で処理できます。この場合、ランタイム パラメーターはデータと共にパイプライン処理されます。

2: パイプライン処理されるランタイム パラメーター


現在のリリースでは、制御パラメーターを、コンシューマー カーネルの入力パラメーターに接続されているプロデューサー カーネルの入出力パラメーターにして、カーネルを介して制御パラメーターをパイプライン処理する必要があります。複数のプロセッサのパイプライン処理と、1 つのプロセッサ内の 1 対多のブロードキャスト接続を組み合わせて、任意の制御トポロジを作成できます。

ランタイム パラメーターのサポートのまとめ

このセクションでは、AI エンジン ランタイム パラメーター (RTP) のサポート ステータスについてまとめます。グラフ内の PL カーネルの RTP サポートについては、PL カーネルのランタイム パラメーター サポートのまとめ を参照してください。

表 2. AI エンジン RTP のデフォルトおよびサポート ステータス
AI エンジン RTP

(PS から/PS へ)

入力 出力
同期 非同期 同期 非同期
スカラー デフォルト サポート サポート デフォルト
アレイ デフォルト サポート サポート デフォルト

PS から/PS への RTP 接続用のコードの一部

connect<parameter>(fromPS, first.in[0]); //Synchronous RTP, default for input
connect<parameter>(fromPS, sync(first.in[0])); //Synchronous RTP
connect<parameter>(fromPS, async(first.in[0])); //Asynchronous RTP
connect<parameter>(second.inout[0], toPS); //Asynchronous RTP, default for output
connect<parameter>(async(second.inout[0]), toPS); //Asynchronous RTP
connect<parameter>(sync(second.inout[0]), toPS); //Synchronous RTP
表 3. AI エンジン RTP から AI エンジン RTP への接続のサポート ステータスのデフォルト
AI エンジン RTP から AI エンジン RTP へ 接続先
同期 非同期 未指定
接続元 同期 同期 サポートされない 同期
非同期 サポートされない 非同期 非同期
未指定 同期 非同期 同期

AI エンジン 間の RTP 接続用のコードの一部

connect<parameter>(first.inout[0], second.in[0]); //Not specified for output and input. Synchronous RTP from first.inout to second.in
connect<parameter>(sync(first.inout[0]), second.in[0]); //Specify “sync” for output. Synchronous RTP from first.inout to second.in
connect<parameter>(first.inout[0], sync(second.in[0])); //Specify “sync” for input. Synchronous RTP from first.inout to second.in
connect<parameter>(sync(first.inout[0]), sync(second.in[0])); //Specify “sync” for both. Synchronous RTP from first.inout to second.in
connect<parameter>(async(first.inout[0]), async(second.in[0])); //Specify “async” for both. Asynchronous RTP from first.inout to second.in
connect<parameter>(first.inout[0], async(second.in[0])); //Specify “async” for input. Asynchronous RTP from first.inout to second.in
connect<parameter>(async(first.inout[0]), second.in[0]); //Specify “async” for output. Asynchronous RTP from first.inout to second.in
connect<parameter>(async(first.inout[0]), sync(second.in[0])); //Not supported
connect<parameter>(sync(first.inout[0]), async(second.in[0])); //Not supported
注記: AI エンジン RTP から AI エンジン RTP への接続を使用する場合、ソース カーネルとデスティネーション カーネルは、データ通信用に共有メモリを使用します。DMA はデータ通信に含まれません。したがって、RTP 接続がある AI エンジン カーネルは、同じ AI エンジン タイルまたは隣接するタイルに配置する必要があります。