input_gmio
/output_gmio
ポート属性を使用して、PS プログラム内で AI エンジン と DDR 間のメモリ読み出し/書き込みトランザクションを開始できます。これにより、PS プログラム内に記述された API を介して AI エンジン と DDR コントローラー間でデータを転送できます。次に、GMIO API を使用して AI エンジンにデータを送信して処理し、DDR に返された処理済みデータを受け取る PS プログラムの例を示します。
class mygraph: public adf::graph
{
private:
adf::kernel k_m;
public:
adf::output_gmio gmioOut;
adf::input_gmio gmioIn;
mygraph()
{
k_m = adf::kernel::create(weighted_sum_with_margin);
gmioOut = adf::output_gmio::create("gmioOut", 64, 1000);
gmioIn = adf::input_gmio::create("gmioIn", 64, 1000);
adf::connect(gmioIn.out[0], k_m.in[0]);
adf::connect(k_m.out[0], gmioOut.in[0]);
dimensions(k_m.in[0]) = {256};
dimensions(k_m.out[0]) = {256};
adf::source(k_m) = "weighted_sum.cc";
adf::runtime<adf::ratio>(k_m)= 0.9;
};
};
graph.cpp
myGraph gr;
int main(int argc, char ** argv)
{
const int BLOCK_SIZE=256;
int32 *inputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
int32 *outputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
// provide input data to AI Engine in inputArray
for (int i=0; i<BLOCK_SIZE; i++) {
inputArray[i] = i;
}
gr.init();
gr.gmioIn.gm2aie_nb(inputArray, BLOCK_SIZE*sizeof(int32));
gr.gmioOut.aie2gm_nb(outputArray, BLOCK_SIZE*sizeof(int32));
gr.run(8);
gr.gmioOut.wait();
// can start to access output data from AI Engine in outputArray
...
GMIO::free(inputArray);
GMIO::free(outputArray);
gr.end();
}
次の例では、2 つの I/O オブジェクトを宣言しています。gmioIn
は AI エンジンによって読み出される DDR メモリ空間を表し、gmioOut
は AI エンジンによって書き込まれる DDR メモリ空間を表します。コンストラクターに、GMIO の論理名、メモリ マップド AXI4 トランザクションのバースト長 (64、128、256 バイトのいずれか)、必要な帯域幅 (MB/s) が指定されています。
gmioOut = adf::output_gmio::create("gmioOut", 64, 1000);
gmioIn = adf::input_gmio::create("gmioIn", 64, 1000);
アプリケーション グラフ (myGraph
) には、処理カーネルに接続する入力ポート (myGraph::gmioIn
) があります。カーネルは、カーネルから処理されたデータを生成する出力ポート (myGraph::gmioOut
) にデータを生成します。次のコードは、グラフの入力ポートに接続し、グラフの出力ポートに接続します。
adf::connect(gmioIn.out[0], k_m.in[0]);
adf::connect(k_m.out[0], gmioOut.in[0]);
dimensions(k_m.in[0]) = {256};
dimensions(k_m.out[0]) = {256};
main 関数内で、GMIO::malloc
によって要素数が 256 の int32 配列が 2 つ割り当てられます。inputArray
は AI エンジンで読み出されるメモリ空間をポイントし、outputArray
は AI エンジンで書き込まれるメモリ空間をポイントします。Linux では、GMIO::gm2aie_nb
、GMIO::aie2gm_nb
、GMIO::gm2aie
、および GMIO::aie2gm
に渡される仮想アドレスは、GMIO::malloc
で割り当てる必要があります。入力データは割り当て後に初期化できます。
const int BLOCK_SIZE=256;
int32 *inputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
int32 *outputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
gr.gmioIn.gm2aie_nb()
を使用して、AI エンジンが DDR メモリ空間から読み出すメモリ マップド AXI4 トランザクションを初期化します。gr.gmioIn.gm2aie_nb()
の最初の引数は、トランザクションのメモリ空間の開始アドレスへのポインターです。2 番目の引数はトランザクション サイズ (バイト) です。トランザクションのメモリ空間は、GMIO::malloc
によって割り当てられたメモリ空間内になければなりません。同様に、gr.gmioOut.aie2gm_nb()
を使用して、AI エンジンが DDR メモリ空間に書き込むメモリ マップド AXI4 トランザクションを初期化します。gr.gmioOut.gm2aie_nb()
または gr.gmioOut.aie2gm_nb()
は、トランザクションの発行後すぐに返され、トランザクションの完了を待たないので、ノンブロッキング関数であると言えます。一方、gr.gmioIn.gm2aie()
または gr.gmioOut.aie2gm()
は、ブロッキング関数のように動作します。gr.gmioIn.gm2aie_nb()
を 1 回呼び出すと、8 回の繰り返しに相当するデータの読み出しトランザクションが実行され、gr.gmioOut.aie2gm_nb()
を 1 回呼び出すと、8 回の繰り返しに相当するデータの書き込みトランザクションが実行されます。gr.gmioIn.gm2aie_nb(inputArray, BLOCK_SIZE*sizeof(int32));
gr.gmioOut.aie2gm_nb(outputArray, BLOCK_SIZE*sizeof(int32));
gr.run(8)
もグラフを 8 回繰り返して実行するノンブロッキング呼び出しです。PS と AI エンジンの間で DDR メモリ読み出し/書き込みアクセスを同期するには、gr.gmioOut.wait()
を使用して、GMIO トランザクションが完了するまで PS 実行をブロックします。この例では、gr.gmioOut.wait()
を呼び出して、出力データが outputArray
DDR メモリ空間に書き込まれるまで待機しています。
この後、PS プログラムからデータにアクセスできます。PS の処理が完了したら、GMIO::free
を呼び出して、GMIO::malloc
によって割り当てられたメモリ空間を解放します。
GMIO::free(inputArray);
GMIO::free(outputArray);
input_gmio
/output_gmio
API をさまざまな方法で使用して、異なるレベルの読み出し/書き込みアクセス制御や AI エンジン、PS、および DDR メモリ間の同期を実行できます。グラフ実行のさまざまなフェーズで input_gmio::gm2aie
、output_gmio::aie2gm
、input_gmio::gm2aie_nb
または output_gmio::aie2gm_nb
を複数回呼び出すことにより、異なるメモリ空間を同じ input_gmio
/output_gmio
オブジェクトに関連付けることができます。インプレースの AI エンジン - DDR 読み出し/書き込みアクセスのために、異なる input_gmio
/output_gmio
オブジェクトを同じメモリ空間に関連付けることもできます。ブロッキング バージョンの input_gmio::gm2aie
および output_gmio::aie2gm
API は、それ自体がデータ転送とカーネル実行の同期ポイントになっています。input_gmio::gm2aie
(または output_gmio::aie2gm
) の呼び出しは、input_gmio::gm2aie_nb
(または output_gmio::aie2gm_nb
) を呼び出した後で output_gmio::wait
を呼び出すのと同じです。次に、上で説明したユース ケースを組み合わせた例を示します。
myGraph gr;
int main(int argc, char ** argv)
{
const int BLOCK_SIZE=256;
// dynamically allocate memory spaces for in-place AI Engine read/write access
int32* inoutArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
gr.init();
for (int k=0; k<4; k++)
{
// provide input data to AI Engine in inoutArray
for(int i=0;i<BLOCK_SIZE;i++){
inoutArray[i]=i;
}
gr.run(8);
for (int i=0; i<8; i++)
{
gr.gmioIn.gm2aie(inoutArray+i*32, 32*sizeof(int32)); //blocking call to ensure transaction data is read from DDR to AI Engine
gr.gmioOut.aie2gm_nb(inoutArray+i*32, 32*sizeof(int32));
}
gr.gmioOut.wait();
// can start to access output data from AI Engine in inoutArray
// ...
}
GMIO::free(inoutArray);
gr.end();
return 0;
}
上記の例では、inoutArray
によって割り当てられた同じメモリ空間を、2 つの GMIO オブジェクト (gmioIn
、gmioOut
) がインプレース読み出し/書き込みアクセスのために使用しています。
グラフ内でのカーネル間のデータフロー依存関係が不明な場合や、inoutArray
メモリ空間で読み出し後の書き込みを確実にするために、ブロッキング バージョンの gr.gmioIn.gm2aie()
を呼び出して、トランザクション データが DDR メモリから AI エンジンのローカル メモリにコピーされたことを確認してから、gr.gmioOut.aie2gm_nb()
で同じメモリ空間への書き込みトランザクションを実行します。
gr.gmioIn.gm2aie(inoutArray+i*32, 32*sizeof(int32)); //blocking call to ensure transaction data is read from DDR to AI Engine
gr.gmioOut.aie2gm_nb(inoutArray+i*32, 32*sizeof(int32));
gr.gmioOut.wait()
の目的は、データが DDR メモリに移行されたことを確認することであり、その完了後に、PS から出力データにアクセスしてポストプロセスを実行できます。
グラフの実行は、for ループ (for (int k=0;
k<4; k++)
) 内で 4 つのフェーズに分かれています。for
ループ内で inoutArray
を再初期化すると、フェーズごとに異なるデータを処理できます。