システム デザインでは、アクセラレータとホスト間のデータ転送モードを正しく指定することが重要です。次のセクションでは、その設計に関する詳細を説明します。
グローバル メモリ I/O
VSC では、グローバル メモリとアクセラレータ インターフェイス間で、次の 2 つの転送モードを使用できます (ガイダンス マクロ を参照)。
-
DATA_COPY
: 効率的に設計された RTL IP であるデータ ムーバーをドロップインします。これにより、グローバル メモリへの M_AXI インターフェイスでのバーストやデータ幅の操作などの特定の機能が自動化されます。アクセラレータ側では、このインターフェイスは、Vitis HLSap_fifo
インターフェイス内のデータのシーケンシャル アクセスと、Vitis HLSap_memory
インターフェイス内のデータのランダム アクセスの両方がサポートされます。 -
ZERO_COPY
: グローバル メモリ プラットフォーム ポートからアクセラレータに M_AXI インターフェイスを接続します。
compute()
関数インターフェイスの定義には、次が推奨されます。
- データ サイズ (すべての計算引数) が大きすぎてデバイスの DDR に収まらない場合は、複数の送信イテレーションで大きな
alloc_buf
計算を小さなチャンクに分割します。 - 通常は、
DATA_COPY
およびSEQUENTIAL
アクセス パターン (特にアクセラレータ コードがシーケンシャル入力データ アクセスの場合) をお勧めします。 - アクセラレータ コードがデータにランダムにアクセスしていて、シーケンシャルにアクセスするようにコードを変更できない場合、データがデバイス RAM (BRAM または URAM) に収まる場合はランダム アクセス パターンを使用します。それ以外の場合は、
ZERO_COPY
を使用します。
シーケンシャル アクセス パターン
カーネル コードによってシーケンシャルにアクセスされる PE ポート data
の場合、ACCESS_PATTERN(data,
SEQUENTIAL);
を使用します。
VSC では、実行時にデータのサイズが必要です。これには、DATA_COPY
マクロを使用する必要があります。たとえば、DATA_COPY(data, data[numData]);
のように使用します。
data
と numData
の両方を引数としてカーネルに渡す必要があり、たとえば、カーネルが numData
を使用しない場合でも、引数として指定しておく必要があります。通常、numData
は、カーネル関数の引数に関して実行時に評価できる限り、任意の式にできます。次の例は、あまり実用的ではないかもしれませんが、アクセラレータ クラス宣言で使用できます。
DATA_COPY(data, data[m * log(n) + 5]);
...
static void PE_func(int n, int m, float* data);
入力データと出力データの両方で、DATA_COPY
の正確なサイズが重要であり、カーネルが読み出しているデータ量とまったく同じである必要があります。サイズがデザインと合わない場合、次のような機能上の問題が発生する可能性があります。
- カーネルがアプリケーション コードにより提供されるデータよりも多くのデータを読み出すと、実行時にハングします。
- 前のカーネル呼び出しが前の計算呼び出しからすべてのデータを読み出さなかった場合、カーネルが不要なデータを読み出します。
これを回避してデバッグするには、ZERO_COPY
を使用してカーネル コードが正しく動作することを確認します。
ランダム アクセス パターン
カーネルが compute()
関数引数にアクセスする必要がある場合は、ACCESS_PATTERN(data,
RANDOM);
のように、data
をランダムに指定します。
したがって、データがオンチップ FPGA メモリ内に収まるようにする必要があります。カーネル コードは、次の例のように、データをスタティック配列として宣言する必要があります。
ACCESS_PATTERN(data, RANDOM);
...
static void PE_func(int n, int m, float data[64]);
データ サイズがランダムにアクセスされ、オンチップ FPGA メモリには大きすぎる場合は、ZERO_COPY(data)
を使用する必要があります。
ACCESS_PATTERN
マクロは、ZERO_COPY
と一緒に使用しないでください。ホストとグローバル メモリ間で転送されるデータの単位量は、必ずしも DATA_COPY
のサイズと同じではありません。これは、VPP_ACC::alloc_buf()
呼び出しのサイズ引数によって決定されます。このサイズは、たとえば、複数の compute()
呼び出しのデータをワンショットで送信する場合など、各カーネル計算に必要なデータ サイズよりも大きくなることがあります。したがって、compute()
への (たとえば N 呼び出しの) PCIe データ転送のクラスターは、次のように簡単に実行できます。
send_while ... { ...
clustered_buffer = acc::alloc_buf( N * size );
for (i = 0; i < N; i++) { ...
acc::compute(&clustered_buf[ i * size ] ...
}
- N 計算呼び出しに適切なデータ バッファー サイズを割り当てます。
-
compute()
を N 回を呼び出すと、各呼び出しがインデックス指定されて、クラスターされたバッファーに入れられます。
計算ペイロード データ型
compute()
データ型ではグローバル メモリ上のデータ レイアウトも決まるので、アクセラレータのパフォーマンスに影響します。カーネルができるだけ速くデータにアクセスできるようにするには、compute()
引数に適切なデータ型を選択することが重要です。たとえば、カーネルが整数を処理していて、クロック サイクルごとに 1 つの整数を処理する必要がある場合 (HLS II = 1)、インターフェイスは次のような整数の配列を使用できます。
static void compute ( int* A );
次の 2 つのコーディング スタイルを使用して、計算呼び出しごとに 4 つの整数を追加する別の例を考慮してください。
|
|
カーネルが、入力配列から 4 つの整数を 1 つずつ追加します。左側の簡単なインプリメンテーションでは、メモリ アクセスごとに 1 サイクルとした場合、結果ごとに 4 クロック サイクルが必要になります。ただし、右に示すように、4 つの整数すべてを 1 つのグローバル メモリ アクセスにパックすることをお勧めします。したがって、この場合は次をお勧めします。
- C 構造体を使ってすべてのデータをパックし、カーネルに渡します。
-
DATA_COPY(data, data[numData]);
を使用して、正しいデータ コピー サイズが提供されていることを確認します。
それ以外にも、次の点に注意してください。
-
int* data[4]
を使用すると、整数はパックされず、int* data;
と同じハードウェアになります。 - 通常、64 バイト (512 ビットの M_AXI バス幅の場合) を超える容量をパッキングしてもパフォーマンスは向上しませんが、パフォーマンスが低下することはありません。