SDAccel での使用方法

この章では、SDAccel™ 環境での xfOpenCV の使用方法を説明します。カーネル、対応するホスト コード、および SDAccel でサポートされるプラットフォーム用に xfOpenCV カーネルをコンパイルする makefile の作成方法を示し、カーネルをさまざまなエミュレーション モードおよびハードウェアで検証する方法を説明します。

使用要件

  1. SDx™ 2019.1 以降のバージョンの有効なライセンスを入手してインストールします。
  2. SDx で提供されるものと異なるライブラリを使用する場合は xfOpenCV ライブラリをインストールします。
  3. SDx 2019.1 以降のバージョンでサポートされるプラットフォームのカードをインストールします。
  4. ザイリンクス ランタイム (XRT) をインストールします。XRT では、ザイリンクス FPGA へソフトウェア インターフェイスが提供されます。
  5. プラットフォームと共に libOpenCL.so をインストールする必要があります。

SDAccel 設計手法

SDAccel™ を使用してカーネルをプラットフォームで機能させるには、次の 3 つのコンポーネントが必要です。
  1. OpenCL コンストラクトを含むホスト コード
  2. HLS カーネルのラッパー
  3. カーネルをエミュレーションまたはハードウェアでの実行用にコンパイルする makefile

OpenCL を含むホスト コード

ホスト コードは、ホストで稼動するホスト マシン用にコンパイルされ、ホスト マシンに接続された FPGA を含むハードウェアにデータおよび制御信号を供給します。ホスト コードは OpenCL コンストラクトを使用して記述されており、FPGA 上のカーネルを設定および実行する機能があります。次の関数は、ホスト コードを使用して実行されます。
  1. カーネル バイナリの FPGA への読み込み: xcl::import_binary_file() は、ビットストリームを読み込んで FPGA をプログラムし、必要なデータ処理を可能にします。
  2. データ転送用にメモリ バッファーを設定: データをハードウェア上の DDR メモリとデータを送受信する必要があります。cl::Buffers は、ハードウェアとのデータ転送用に必要なメモリを割り当てるために作成されます。
  3. ハードウェアとのデータ転送: enqueueWriteBuffer() および enqueueReadBuffer() は、必要な時にハードウェアとデータを転送するために使用されます。
  4. FPGA デバイスでのカーネルの実行: FPGA でカーネルを実行する関数があります。1 つのカーネル実行または複数のカーネル実行 (カーネルどうしは同期または非同期) があります。よく使用されるコマンドは enqueueTask() です。
  5. カーネル実行のパフォーマンスのプロファイリング: OpenCL で記述されたホスト コードにより、FPGA 上でのカーネルの実行時間を計測できます。ザイリンクスの例でプロファイリングに使用される関数は getProfilingInfo() です。

HLS カーネルのラッパー

すべての xfOpenCV カーネルには、C++ 関数テンプレート (<Github repo>/include に配置) と、xf::Mat クラスのオブジェクトとして画像コンテナーが提供されています。また、これらのカーネルは、ストリーム ベース (画像全体を連続して読み出し) またはメモリ マップド (画像データにブロック単位でアクセス) の両方で機能します。

SDAccel フロー (OpenCL) では、カーネル インターフェイスは幅が 2 のべき数のメモリ ポインターである必要があります。そのため、xfOpenCV カーネルとデータを転送するには、メモリ ポインターを xf::Mat クラスのデータ型に変換 (およびその逆変換) するためのグルー ロジックが必要です。カーネルとこのグルー ロジックを含むラッパーがビルドされます。次の例では、異なるカーネル (<Github repo>/include にある xfOpenCV カーネル) のタイプ (ストリームおよびメモリ マップド) を処理する方法を示します。

ストリーム ベース カーネル

ポインターから xf::Mat およびその逆の変換を実行するため、xfOpenCV の一部として 2 つのアダプター関数 xf::Array2xfMat() および xf::xfMat2Array() が含まれます。これらの xf::Mat オブジェクトは、HLS プラグマを使用して 2 以上の深さでストリームとして呼び出す必要があります。カーネルの最上位 (ラッパー) 関数は、次のようになります。

extern “C” 
{ 
void func_top (ap_uint *gmem_in, ap_uint *gmem_out, ...) { 
xf::Mat<…> in_mat(…), out_mat(…);
#pragma HLS stream variable=in_mat.data depth=2
#pragma HLS stream variable=out_mat.data depth=2
#pragma HLS dataflow 
xf::Array2xfMat<…> (gmem_in, in_mat); 
xf::xfopencv-func<…> (in_mat, out_mat…); 
xf::xfMat2Array<…> (gmem_out, out_mat); 
}
}

上記では、xf::Mat のデータがストリーミング入力/出力されると想定されています。1 つの xfOpenCV 関数の代わりに、複数の関数のパイプラインを作成することもできます。

異なるサイズの異なる入力を使用するストリーム ベース カーネルでは、アダプター関数の複数のインスタンスが必要です。

extern “C” { 
void func_top (ap_uint *gmem_in1, ap_uint *gmem_in2, ap_uint *gmem_in3, ap_uint *gmem_out, ...) { 
xf::Mat<...,HEIGHT,WIDTH,…> in_mat1(…), out_mat(…);
xf::Mat<...,HEIGHT/4,WIDTH,…>  in_mat2(…), in_mat3(…); 
#pragma HLS stream variable=in_mat1.data depth=2
#pragma HLS stream variable=in_mat2.data depth=2
#pragma HLS stream variable=in_mat3.data depth=2
#pragma HLS stream variable=out_mat.data depth=2
#pragma HLS dataflow 
xf::accel_utils obj_a, obj_b;
obj_a.Array2xfMat<…,HEIGHT,WIDTH,…> (gmem_in1, in_mat1);
obj_b.Array2xfMat<…,HEIGHT/4,WIDTH,…> (gmem_in2, in_mat2); 
obj_b.Array2xfMat<…,HEIGHT/4,WIDTH,…> (gmem_in3, in_mat3); 
xf::xfopencv-func(in_mat1, in_mat2, int_mat3, out_mat…); 
xf::xfMat2Array<…> (gmem_out, out_mat); 
}
}

ストリーム ベースのインプリメンテーションでは、特定の構成の xfcv カーネルでの必要に応じて、入力 AXI からデータをフェッチし、xfMat にプッシュする必要があります。同様に、同じ操作を xfcv カーネルの出力に対しても実行する必要があります。このため、xf::Array2xfMat() と xf::xfMat2Array() の 2 つのユーティリティ関数が提供されています。

ストリーム ベース カーネル

ポインターから xf::Mat およびその逆の変換を実行するため、xfOpenCV の一部として 2 つのアダプター関数 xf::Array2xfMat() および xf::xfMat2Array() が含まれます。これらの xf::Mat オブジェクトは、HLS プラグマを使用して 2 以上の深さでストリームとして呼び出す必要があります。カーネルの最上位 (ラッパー) 関数は、次のようになります。

extern “C” 
{ 
void func_top (ap_uint *gmem_in, ap_uint *gmem_out, ...) { 
xf::Mat<…> in_mat(…), out_mat(…);
#pragma HLS stream variable=in_mat.data depth=2
#pragma HLS stream variable=out_mat.data depth=2
#pragma HLS dataflow 
xf::Array2xfMat<…> (gmem_in, in_mat); 
xf::xfopencv-func<…> (in_mat, out_mat…); 
xf::xfMat2Array<…> (gmem_out, out_mat); 
}
}

上記では、xf::Mat のデータがストリーミング入力/出力されると想定されています。1 つの xfOpenCV 関数の代わりに、複数の関数のパイプラインを作成することもできます。

異なるサイズの異なる入力を使用するストリーム ベース カーネルでは、アダプター関数の複数のインスタンスが必要です。

extern “C” { 
void func_top (ap_uint *gmem_in1, ap_uint *gmem_in2, ap_uint *gmem_in3, ap_uint *gmem_out, ...) { 
xf::Mat<...,HEIGHT,WIDTH,…> in_mat1(…), out_mat(…);
xf::Mat<...,HEIGHT/4,WIDTH,…>  in_mat2(…), in_mat3(…); 
#pragma HLS stream variable=in_mat1.data depth=2
#pragma HLS stream variable=in_mat2.data depth=2
#pragma HLS stream variable=in_mat3.data depth=2
#pragma HLS stream variable=out_mat.data depth=2
#pragma HLS dataflow 
xf::accel_utils obj_a, obj_b;
obj_a.Array2xfMat<…,HEIGHT,WIDTH,…> (gmem_in1, in_mat1);
obj_b.Array2xfMat<…,HEIGHT/4,WIDTH,…> (gmem_in2, in_mat2); 
obj_b.Array2xfMat<…,HEIGHT/4,WIDTH,…> (gmem_in3, in_mat3); 
xf::xfopencv-func(in_mat1, in_mat2, int_mat3, out_mat…); 
xf::xfMat2Array<…> (gmem_out, out_mat); 
}
}

ストリーム ベースのインプリメンテーションでは、特定の構成の xfcv カーネルでの必要に応じて、入力 AXI からデータをフェッチし、xfMat にプッシュする必要があります。同様に、同じ操作を xfcv カーネルの出力に対しても実行する必要があります。このため、xf::Array2xfMat() と xf::xfMat2Array() の 2 つのユーティリティ関数が提供されています。

xfMat2Array

入力 xf::Mat を出力配列に変換します。xf::kernel 関数の出力は xf::Mat で、これを出力ポインターに変換する必要があります。

template <int PTR_WIDTH, int MAT_T, int ROWS, int COLS, int NPC>
void xfMat2Array(xf::Mat<MAT_T,ROWS,COLS,NPC>& srcMat, ap_uint< PTR_WIDTH > *dstPtr)
表 1. xfMat2Array 関数のパラメーターの説明
パラメーター 説明
PTR_WIDTH 出力ポインターのデータ幅。8 ~ 512 の 2 のべき数で指定。
MAT_T 入力 Mat 型。XF_8UC1、XF_16UC1、XF_8UC3、XF_8UC4 など。
ROWS 画像の最大高さ
COLS 画像の最大幅
NPC 並列計算されるピクセルの数。XF_NPPC1、XF_NPPC8 など。
dstPtr 出力ポインター。PTR_WIDTH に基づくポインターのタイプ。
srcMat xf::Mat 型の入力画像

インターフェイス ポインターの幅

次の表に、異なる構成でのポインターの最小幅を示します。
表 2. 異なる Mat 型の最小および最大ポインター幅
Mat 型 並列処理 最小 PTR_WIDTH 最大 PTR_WIDTH
XF_8UC1 XF_NPPC1 8 512
XF_16UC1 XF_NPPC1 16 512
XF_ 8UC1 XF_NPPC8 64 512
XF_ 16UC1 XF_NPPC8 128 512
XF_ 8UC3 XF_NPPC1 32 512
XF_ 8UC3 XF_NPPC8 256 512
XF_8UC4 XF_NPPC8 256 512
XF_8UC3 XF_NPPC16 512 512

ストリーム ベース カーネル

ポインターから xf::Mat およびその逆の変換を実行するため、xfOpenCV の一部として 2 つのアダプター関数 xf::Array2xfMat() および xf::xfMat2Array() が含まれます。これらの xf::Mat オブジェクトは、HLS プラグマを使用して 2 以上の深さでストリームとして呼び出す必要があります。カーネルの最上位 (ラッパー) 関数は、次のようになります。

extern “C” 
{ 
void func_top (ap_uint *gmem_in, ap_uint *gmem_out, ...) { 
xf::Mat<…> in_mat(…), out_mat(…);
#pragma HLS stream variable=in_mat.data depth=2
#pragma HLS stream variable=out_mat.data depth=2
#pragma HLS dataflow 
xf::Array2xfMat<…> (gmem_in, in_mat); 
xf::xfopencv-func<…> (in_mat, out_mat…); 
xf::xfMat2Array<…> (gmem_out, out_mat); 
}
}

上記では、xf::Mat のデータがストリーミング入力/出力されると想定されています。1 つの xfOpenCV 関数の代わりに、複数の関数のパイプラインを作成することもできます。

異なるサイズの異なる入力を使用するストリーム ベース カーネルでは、アダプター関数の複数のインスタンスが必要です。

extern “C” { 
void func_top (ap_uint *gmem_in1, ap_uint *gmem_in2, ap_uint *gmem_in3, ap_uint *gmem_out, ...) { 
xf::Mat<...,HEIGHT,WIDTH,…> in_mat1(…), out_mat(…);
xf::Mat<...,HEIGHT/4,WIDTH,…>  in_mat2(…), in_mat3(…); 
#pragma HLS stream variable=in_mat1.data depth=2
#pragma HLS stream variable=in_mat2.data depth=2
#pragma HLS stream variable=in_mat3.data depth=2
#pragma HLS stream variable=out_mat.data depth=2
#pragma HLS dataflow 
xf::accel_utils obj_a, obj_b;
obj_a.Array2xfMat<…,HEIGHT,WIDTH,…> (gmem_in1, in_mat1);
obj_b.Array2xfMat<…,HEIGHT/4,WIDTH,…> (gmem_in2, in_mat2); 
obj_b.Array2xfMat<…,HEIGHT/4,WIDTH,…> (gmem_in3, in_mat3); 
xf::xfopencv-func(in_mat1, in_mat2, int_mat3, out_mat…); 
xf::xfMat2Array<…> (gmem_out, out_mat); 
}
}

ストリーム ベースのインプリメンテーションでは、特定の構成の xfcv カーネルでの必要に応じて、入力 AXI からデータをフェッチし、xfMat にプッシュする必要があります。同様に、同じ操作を xfcv カーネルの出力に対しても実行する必要があります。このため、xf::Array2xfMat() と xf::xfMat2Array() の 2 つのユーティリティ関数が提供されています。

makefile

現在の使用モデルでは、SDAccel の xfOpenCV を使用してアプリケーションをビルドするのに makefile ベースのフローのみが提供されています。makefile の例は、GitHub のサンプル セクションから入手できます。

makefile

現在の使用モデルでは、SDAccel の xfOpenCV を使用してアプリケーションをビルドするのに makefile ベースのフローのみが提供されています。makefile の例は、GitHub のサンプル セクションから入手できます。

機能の評価

カーネルをビルドして機能を評価するには、ソフトウェア エミュレーション、ハードウェア エミュレーション、FPGA を含むサポートされるハードウェア上での直接実行を使用できます。PCIe ベース プラットフォームでは、次のコマンドを実行して環境を設定します。

$ cd <path to the proj folder, where makefile is present>
$ source <path to the SDx installation folder>/SDx/<version number>/settings64.sh
$ source <path to Xilinx_xrt>/packages/setenv.sh
$ export PLATFORM_PATH=<path to the platform folder>
$ export XLNX_SRC_PATH=<path to the xfOpenCV repo>
$ export XILINX_CL_PATH=/usr

ソフトウェア エミュレーション

ソフトウェア エミュレーションは、カーネルの C シミュレーションを実行するのと同等です。コンパイルにかかる時間は最小限なので、カーネルのテストの第一段階としてお勧めします。ソフトウェア エミュレーションを実行する手順は、次のとおりです。

$ make all TARGETS=sw_emu
$ export XCL_EMULATION_MODE=sw_emu
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:<sdx installation path>/SDx/2019.1/lnx64/tools/opencv:/usr/lib64
$ ./<executable> <args>

ハードウェア エミュレーション

ハードウェア エミュレーションでは、C/C++ コードの合成後に生成された RTL に対してテストを実行します。このシミュレーションは RTL 上で実行されるので、ソフトウェア エミュレーションよりも時間がかかります。ハードウェア エミュレーションを実行する手順は、次のとおりです。

$ make all TARGETS=hw_emu
$ export XCL_EMULATION_MODE=hw_emu
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:<sdx installation path>/SDx/2019.1/lnx64/tools/opencv:/usr/lib64
$ ./<executable> <args>

ハードウェア上でのテスト

ハードウェア上でテストするには、カーネルをビットストリームにコンパイル (ハードウェア用にビルド) する必要があります。

$ make all TARGETS=hw

これには、C/C++ コードを RTL に変換し、合成およびインプリメンテーションを実行してビットストリームを生成する必要があるので、時間がかかります。例をビルドした対応する DSA 用のドライバーをインストール必要があります。次に、ハードウェア上でカーネルを実行する手順を示します。

$ source /opt/xilinx/xrt/setup.sh
$ export XILINX_XRT=/opt/xilinx/xrt
$ cd <path to the executable and the corresponding xclbin>
$ ./<executable> <args>