制御駆動型のタスク レベル並列処理 - 2023.2 日本語

Vitis 高位合成ユーザー ガイド (UG1399)

Document ID
UG1399
Release Date
2023-12-18
Version
2023.2 日本語

制御駆動型 TLP は、連続して実行するスレッドではなく、C++ のシーケンシャル セマンティクスを使用しつつ、並列処理をモデル化する際に便利です。たとえば、ループ内で同時パイプライン方式で実行可能な関数や、チャネルではなく C++ スカラー変数および配列変数を引数として持つ関数があげられます (両方ともオンチップおよびオフチップ メモリを参照する)。このようなモデルに対して、Vitis HLS は元の C++ 順次実行の動作を維持させつつ、できるだけ並列処理を適用します。制御駆動型の TLP (またはデータフロー) モデルでは次が可能です。

  • 次の関数は、前の関数が終了する前に開始
  • 関数は終了する前に再開
  • 2 つ以上のシーケンシャル関数を同時に開始

データフロー モデルを使用する一方で、Vitis HLS は、タスク間に同期と通信のメカニズムを自動的に挿入し、C++ コードのシーケンシャル セマンティクスをインプリメントします。

データフロー モデルは、このような一連のシーケンシャル関数を取り込み、同時実行プロセスのタスク レベルのパイプライン アーキテクチャを作成します。このツールは、並列タスクおよびチャネルを推論することでこれを実現します。次に示すように DATAFLOW プラグマまたは指示子を指定して、データフロー形式でモデル化する領域 (関数本体またはループ本体など) を指定します。このツールは、ループ/関数本体をスキャンし、並列タスクを並列プロセスとして抽出し、これらのプロセス間の通信チャネルを構築します。さらに、FIFO (hls::stream または #pragma HLS STREAM) あるいは PIPO (hls::stream_of_blocks) など、チャネルのタイプをツールで選択することもできます。データフロー モデルは、デザインのスループットとレイテンシを向上する優れた方法です。

Vitis HLS でどのように C++ コードがデータフロー モデルに変換されるかを理解するには、次に示す simple_fifos の例を参照してください。この例では、DATAFLOW プラグマを使用して、データフロー モデルを最上位関数の diamond に適用しています。

#include "diamond.h"
 
void diamond(data_t vecIn[N], data_t vecOut[N])
{
  data_t c1[N], c2[N], c3[N], c4[N];
#pragma HLS dataflow
  funcA(vecIn, c1, c2);
  funcB(c1, c3);
  funcC(c2, c4);
  funcD(c3, c4, vecOut);
}

上記の例の場合、4 つの関数 (funcAfuncBfuncCfuncD) があります。funcB および funcC は、これらの間にデータの依存関係がないため、並列に実行できます。funcA はローカル メモリ以外のメモリ (vecIn) から読み出すため、最初に実行する必要があります。同様に、funcD はローカル メモリ以外のメモリ (vecOut) に書き込むため、最後に実行する必要があります。

次に示す波形には、データフロー モデルを使用しないこのデザインの実行プロファイルが示されます。テストベンチから diamond 関数への呼び出しは 3 回です。funcAfuncBfuncCfuncD がシーケンシャルで実行されます。つまり、diamond への各呼び出しには、次の図に示すように合計 475 サイクルが必要です。

図 1. データフローを使用しない diamond の例

次の図は、データフロー モデルが適用され、設計者がチャネルに FIFO の使用を選択した場合で、すべての関数がコントローラーによって直ちに開始され、入力待ちで停止している状態を示しています。入力が受信されると、すぐに処理して送信されます。このようなオーバーラップにより、次に示すように、diamond への各呼び出しは合計で 275 サイクルで済むようになりました。この例で実現可能なパラレル処理の詳細は、3 つのパラダイムの組み合わせを参照してください。

図 2. データフローを使用する diamond の例

このタイプの並列処理では、ハードウェアの使用率増加なしには達成できません。関数本体やループ本体などの領域にデータフロー最適化を適用するよう指定されている場合、Vitis HLS で関数本体またはループ本体が解析され、データフロー領域のデータのフローをモデル化する個別のチャネルが C++ 変数 (スカラー、配列、または hls::streamshls::stream_of_blocks などのユーザー指定チャネル) から作成されます。これらのチャネルは、スカラー変数については単純な FIFO に、または配列 (FIFO および PIPO の動作を組み合わせ、ブロックを明示的にロックする必要がある場合はストリーム オブ ブロック) などのスカラー以外の変数についてはピンポン (PIPO) バッファーにできます。

これらの各チャネルには、フルまたは空であることを示す信号を追加できます。個別の FIFO または PIPO バッファーにより、Vitis HLS で各タスクを独立して実行でき、スループットは入力バッファーおよび出力バッファーが使用可能かどうかのみによって制限されます。この手法では、通常のパイプライン インプリメンテーションよりもタスクの実行をより良くオーバーラップできますが、追加の FIFO またはピンポン バッファー用のブロック RAM が使用されます。

ヒント: このオーバーラップ実行の最適化は、デザインの協調シミュレーションを実行した後にのみ確認できます。スタティックに確認できません (C 合成後の [Dataflow] ビュー では確認できます)。

データフロー モデルは、一連のプロセスに制限されず、どの有向非巡回グラフ (DAG) 構造にも、hls::streams を使用した場合の巡回構造にも使用できます。この場合、1 つの反復内でのオーバーラップ (プロセスが FIFO に接続されている場合) が作成されるほか、PIPO および FIFO と接続される場合は、異なる反復間のオーバーラップが作成されます。ここでは、スタティックにパイプライン処理されたソリューションよりもパフォーマンスを向上できる可能性があります。厳密な中央制御型のパイプライン ストールが、FIFO または PIPO を使用して配布されているハンドシェイク アーキテクチャに置き換えられます。中央制御型構造を分散型に置き換えても、制御信号、たとえばレジスタ イネーブルなどが個別プロセスの制御構造間で分散され、ファンアウトが改善されます。これらのコンセプトの例の詳細は、GitHub のタスク レベルの並列処理/制御駆動を参照してください。