説明
入れ子になっている内側のループを 1 つのループにフラット化して統合することで、ループのすべての反復に対してパイプライン処理を適用できるようにし、より良いレイテンシを達成することができます。
(必要に応じてループ展開を実行した後で) 最も内側のループのみをパイプライン処理できます。外側のループは、データフロー化か逐次実行のいずれかで処理する必要があります。パイプライン処理されたループの外側にあるループが逐次実行である場合、その外側ループの各反復は順に実行され、そのたびに内側ループが完全に 1 回実行されます。RTL インプリメンテーションでは、外側ループから内側ループに移動するのに 1 クロック サイクル、内側のループから外側ループに戻るのに 1 クロック サイクルが必要です。さらに、その間に内側ループのすべての反復を完了するためのレイテンシ全体が加わります。逆に、入れ子のループをフラット化すると、それらを 1 つのループとして最適化およびパイプライン処理できるようになり、外側ループの反復と、内側ループの異なる呼び出しの反復が、パイプラインで重なって実行できるようになります。一般的に、ループのフラット化はパフォーマンスを向上させますが、次の注意点があります。
- フラット化は常に実現できるわけではなく、特定のコーディング スタイルに従う必要があります。
- 場合によっては、外側ループ内の演算や依存関係によって、タイミングやさらには II が低下することがあります。
ループ階層内の一番内側にあるループの本体に LOOP_FLATTEN プラグマを適用します。この方法でフラット化できるのは、(必要に応じて関数のインライン展開やループ展開を実行した後の) 完全な入れ子のループ、またはほぼ完全な入れ子のループのみです。
- 完全な入れ子のループ
-
- 最内ループ以外の各ループの本体には、1 つのサブループのみが含まれており、その他の命令は含まれていません。
- ほぼ完全な入れ子のループ
-
- 最内ループ以外の各ループの本体には、1 つのサブループのみが含まれており、その他の制御フローは含まれていません。
- 最内ループ以外の各ループの本体には、ループを含む関数呼び出しを含めないようにする必要があります。
ほぼ完全な入れ子のループの場合、コンパイラは、2 つのループの間に存在する命令を自動的に最も内側のループに移動し、ループが完全な入れ子構造になるようにします。
さらに、フラット化を可能にするために、次のような要件があります。
- 各ループは、while ループではなく for ループにする必要があり、break 文は含めないようにする必要があります。
- 各ループのトリップカウントは、ループをフラット化する前にコンパイラが計算可能である必要があります (ただし、トリップカウントは定数である必要はありません)。典型的なコーディング スタイルは次のとおりです。
- ループ カウンターを定数でインクリメントするループ。
- ループ カウンターの下限値と上限値は、フラット化するループに依存しないものである必要がある (それらはループ不変でなければならない)。
フラット化できない例として、内側ループのトリップカウントが外側ループのカウンターに依存しているループがあります。
不完全な入れ子のループ (つまり、ループ内に複数のサブループや制御フローが含まれる場合) は、コンパイラによってフラット化することはできません。この場合、コードを手動で再構築することでフラット化を実行する必要があります。具体的には、最も内側のループに命令を移動するか、内側ループを展開して、上位に完全な入れ子のループを作成します。
構文
フラット化の要件が満たされている限り、(完全またはほぼ完全な) ループが上にある状態で、フラット化される (通常は最も内側の) ループにプラグマを C ソース内で配置します。
#pragma HLS loop_flatten
オプション:
-
off - オプションのキーワードで、loop_flatten off を含むループとそのサブループ (存在する場合) がフラット化されないようにします。loop_flatten off が最も内側のループに適用されている場合、フラット化は実行されません (自動フラット化も実行されません)。
例 1
ループ (ここでは loop_1) の本体にプラグマを配置し、その上位のループ階層にある (完全もしくはほぼ完全な) ループ (loop_0) とフラット化できるようにします。ただし、フラット化の要件が満たされていることが条件です。
void foo (N, M, ...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
loop_1: for (j=0; j<M; j++) {
#pragma HLS loop_flatten
...
}
}
}
例 2
loop_1 と loop_0 がフラット化されないようにします。loop_1 と loop_2 のみが一緒にフラット化されます。
void foo (N, M, P, ...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
#pragma HLS loop_flatten off
loop_1: for (j=0; j<M; j++) {
loop_2: for (k=0; k<P; k++) {
#pragma HLS loop_flatten
...
}
}
}
}
例 3
loop_flatten プラグマの上に 2 つを超える入れ子のループがある場合、コンパイラは、すべてのループをフラット化しない選択をすることがあります。具体的には、二次的なパフォーマンス低下が見込まれるとき、または内側ループが少なくとも 1 回は反復する必要があるのにそれを確認できないときです。この場合、対象のループとその外側のループに追加の loop_flatten プラグマを適用して、フラット化を強制できます。ここでは、loop_1 にプラグマを配置して loop_0 と一緒にフラット化を強制します。
void foo (N, M, P, ...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
... // some instruction with side effect
loop_1: for (j=0; j<M; j++) {
#pragma HLS loop_flatten
loop_2: for (k=0; k<P; k++) {
#pragma HLS loop_flatten
...
}
}
}
}
補足説明:
- loop_flatten プラグマを使用する場合、ほぼ完全なループで副作用のある命令をループ内に移動する必要がある場合、コンパイラは、そのプラグマを含むループが少なくとも 1 回は反復するとみなします。
- フラット化を実行する際、内側ループにある依存関係のプラグマ (false の依存関係や距離を伴うもの) は、フラット化後に得られる単一のループに対して適用されるものと理解されます。
例 4
次の例は、フラット化できるほぼ完全なループを示しています。
void foo (...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
int s = 0;
loop_1: for (j=0; j<M; j++) {
#pragma HLS loop_flatten
s += a[i][j];
b[i][j] = s;
}
}
}
例 5
次の例は、フラット化されないループを示しています。
void foo (...) {
int i, j;
loop_0: for (i=0; i<N; i++) {
loop_1: for (j=i; j<M; j++) {
#pragma HLS loop_flatten
...
}
}
}