HLS プラグマ

Vivado HLS での最適化

Vitis ソフトウェア プラットフォームでは、C/C++ 言語で定義されるカーネルまたは OpenCL™ C をレジスタ トランスファー レベル (RTL) にコンパイルし、ザイリンクス デバイスのプログラマブル ロジックにインプリメントできるようにする必要があります。v++ コンパイラは Vivado® 高位合成 (HLS) ツールを呼び出し、カーネル ソース コードを RTL に合成します。

HLS ツールはユーザーの操作なしで Vitis IDE プロジェクトを処理できるよう設計されていますが、HLS ツールでは、生成される RTL コードのレイテンシの削減、スループット パフォーマンスの向上、エリアおよびデバイス リソース使用率の削減など、デザインを最適化するためのプラグマも提供されています。これらのプラグマは、カーネルのソース コードに直接追加できます。

HLS プラグマには、次の表で指定されるような最適化タイプがあります。Vivado HLS ツールおよびこれらのプラグマの詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) を参照してください。

表 1. Vivado HLS プラグマ (タイプ別)
タイプ 属性
カーネル最適化
関数のインライン展開
インターフェイス合成
タスク レベルのパイプライン処理
パイプライン処理
ループ展開
ループ最適化
配列最適化
構造体のパック

pragma HLS allocation

説明

インプリメントされたカーネルでのリソースの割り当てを制限するインスタンス制限を指定します。特定の関数、ループ、演算、またはコアをインプリメントするのに使用される RTL インスタンスおよびハードウェア リソースの数を定義して制限できます。ALLOCATION プラグマは関数の本体、ループ、またはコード領域内に指定します。

たとえば、C ソース コードに foo_sub という関数のインスタンスが 4 つある場合、ALLOCATION プラグマを使用して最終 RTL で foo_sub のインスタンスを 1 つだけにできます。C 関数の 4 つのインスタンスすべてが、同じ RTL ブロックを使用してインプリメントされます。これにより関数で使用されるリソースは削減されますが、パフォーマンスが低下することがあります。

加算、乗算、配列読み出し、書き込みなどの C コードでの演算は、ALLOCATION プラグマを使用して制限できます。合成中に演算子がマップされるコアは、それらの演算子と同じ方法で制限できます。乗算演算子の総数を制限する代わりに、組み合わせ乗算器コアの数を制限して、残りの乗算がパイプライン乗算器を使用して実行されるようにすることもできます (逆も可能)。

ALLOCATION プラグマは、指定された関数、ループ、またはコード領域内に適用されますが、config_bind コマンドの -min_op 引数を使用して、デザイン全体で演算子の数を最小限に抑えることもできます。

ヒント: 詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) の「ハードウェア リソースの制御」および「config_bind」を参照してください。

構文

このプラグラマは、適用する関数の本体、ループ、または領域内に配置します。

#pragma HLS allocation instances=<list> 
limit=<value> <type>

説明:

instances=<list>
関数、演算子、またはコアの名前を指定します。
limit=<value>
カーネルで使用されるインスタンスの制限を指定します (オプション)。
<type>
ALLOCATION プラグマが、関数、演算、またはコア (デザインを作成するのに使用される加算器、乗算器、パイプライン乗算器、ブロック RAM などのハードウェア コンポーネント) に適用されることを指定します。次のいずれかを指定します。
function
instances= にリストされた関数に適用されることを指定します。関数には、次の条件を満たす元の C または C++ コードの関数を指定できます。
  • pragma HLS inline または set_directive_inline コマンドによりインライン展開されていない。
  • Vivado HLS ツールで自動的にインライン展開されていない。
operation
instances= にリストされた演算に適用されることを指定します。ALLOCATION プラグマを使用して制限可能な演算のリストは、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) を参照してください。
core
ALLOCATION がコア (加算器、乗算器、パイプライン乗算器、ブロック RAM などのデザイン作成のために使用される特定ハードウェア コンポーネント) に適用されるよう指定します。使用するコアは、instances= オプションで指定します。コアを指定する場合、ツールで使用される必要のあるコアを指定するか、または指定したコアの制限を定義できます。

例 1

デザインに関数 foo のインスタンスが複数含まれているとします。この例では、ハードウェア カーネルの RTL における foo のインスタンス数を 2 に制限しています。

#pragma HLS allocation instances=foo limit=2 function

例 2

次の例では、関数 my_func のインプリメンテーションに使用される乗算演算の数を 1 に制限しています。この制限は、my_func 外にある乗算器、または my_func のサブ関数に含まれる乗算器には適用されません。

ヒント: サブ関数のインプリメンテーションに使用される乗算器の数を制限するには、そのサブ関数に ALLOCATION プラグマを指定するか、サブ関数を my_func にインライン展開します。
void my_func(data_t angle) {
#pragma HLS allocation instances=mul limit=1 operation
...
}

関連項目

pragma HLS array_map

説明

複数の小型の配列を 1 つの大型の配列にまとめ、ブロック RAM リソースを削減します。

通常は、pragma HLS array_map コマンド (同じ instance= ターゲット) を使用して、複数の小型の配列を 1 つの大型の配列に組み合わせします。この大型の配列は、1 つの大型メモリ (RAM または FIFO) リソースに配置できます。

各配列は、ブロック RAM または UltraRAM (デバイスでサポートされる場合) にマップされます。FPGA で提供される基本ブロック RAM の単位は 18K です。小型の配列の多くで 18K がフルに使用されない場合、小型の配列を 1 つの大型の配列にマップすることでブロック RAM リソースを効率的に使用できます。

ヒント: ブロック RAM が 18K を超える場合は、それらが複数の 18K 単位に自動的にマップされます。

ARRAY_MAP プラグマでは、複数の小型の配列を 1 つの大型の配列にマップするのに次の 2 つの方法がサポートされています。

水平マップ
元の配列を連結して新しい配列を作成します。物理的には、要素数の多い 1 つの配列にインプリメントされます。
垂直マップ
元の配列を連結して新しい配列を作成します。物理的には、要素数の多い 1 つの配列にインプリメントされます。

配列はプラグマが指定された順に、次のものから連結されます。

  • 水平マップの場合はターゲット要素 0。
  • 垂直マップの場合はビット 0。

構文

C ソースの配列変数が定義されている関数内に配置します。

#pragma HLS array_map variable=<name> instance=<instance> \
<mode> offset=<int>

説明:

variable=<name>
必須の引数で、新しいターゲット配列 <instance> にマップする配列変数を指定します。
instance=<instance>
配列の結合先である新しい配列の名前を指定します。
<mode>
配列マップを horizontal または vertical に指定します (オプション)。
  • デフォルトの <mode> は水平マップ (holizontal) で、配列は要素の多い新しい配列に連結されます。元の N 配列のリマップには、1 ポートのブロック RAM で N サイクルまたは 2 ポートのブロック RAM でシーリング (N/2) サイクル必要です。
  • 垂直マップ (vertical) では、ワード数が多い新しい配列に連結されます。元の N 配列のリマップは、上記の水平マップに似ていますが、同じインデックスが使用された場合は 1 サイクルのみ必要です。
offset=<int>
水平マップにのみ適用されます。配列を新しい配列 <instance> にマップする前に適用するオフセット値を整数で指定します。次に例を示します。
  • 配列変数の要素 0 は、新しいターゲットの要素 <int> にマップされます。
  • ほかの要素は新しいターゲットの <int+1>、<int+2> などにマップされます。
    重要: オフセット値が指定されていない場合は、配列要素の重複を避けるため、Vivado HLS ツールで必要なオフセットが自動的に計算されます。

例 1

次の例では、関数 foo の配列 array1 および array2array3 として指定した 1 つの配列にマップされます。

void foo (...) {
int8 array1[M];
int12 array2[N];
#pragma HLS ARRAY_MAP variable=array1 instance=array3 horizontal
#pragma HLS ARRAY_MAP variable=array2 instance=array3 horizontal
...
loop_1: for(i=0;i<M;i++) {
array1[i] = ...;
array2[i] = ...;
...
}
...
}

例 2

次の例では、関数 foo の配列 A[10] および B[15] を、新しい 1 つの配列 AB[25] にマップしています。

  • 要素 AB[0] は A[0] と同じになります。
  • 要素 AB[10] は B[0] と同じになります (offset= オプションが使用されていないため)。
  • 配列 AB[25] のビット幅は A[10] または B[15] の最大ビット幅になります。
#pragma HLS array_map variable=A instance=AB horizontal
#pragma HLS array_map variable=B instance=AB horizontal

例 3

次の例では、配列 C と D を垂直マップで連結し、C と B を結合したビット幅の新しい配列 CD にまとめています。CD の要素数は、元の配列 C または D の最大数になります。

#pragma HLS array_map variable=C instance=CD vertical
#pragma HLS array_map variable=D instance=CD vertical

関連項目

pragma HLS array_partition

説明

配列をより小型の配列または個々の要素に分割します。この結果、次のようになります。

  • 1 つの大型メモリではなく、複数の小型メモリまたは複数のレジスタを含む RTL が生成されます。
  • ストレージの読み出しおよび書き込みポートの数が増加します。
  • デザインのスループットが向上する可能性があります。
  • より多くのメモリ インスタンスまたはレジスタが必要となります。

構文

C ソースの配列変数が定義されている関数内に配置します。

#pragma HLS array_partition variable=<name> \
<type>  factor=<int>  dim=<int>

説明:

variable=<name>
必須の引数で、パーティションする配列変数を指定します。
<type>
分割タイプを指定します (オプション)。デフォルトは complete です。次のタイプがサポートされます。
cyclic
元の配列の要素がインターリーブされて小型配列に分割されます。新しい各配列に要素が 1 つずつ配置され、すべての配列に配置されたら最初の配列に戻って、配列が完全に分割されるまでそれが繰り返されます。たとえば factor=3 の場合、要素は次のように割り当てられます。
  • 要素 0 は 1 番目の新しい配列。
  • 要素 1 は 2 番目の新しい配列。
  • 要素 2 は 3 番目の新しい配列。
  • 要素 3 は再び 1 番目の新しい配列。
block
元の配列の連続したブロックから小型配列が作成されます。N が factor= 引数で定義される整数だとすると、1 つの配列が N 個のブロックに分割されます。
complete
配列が個々の要素に分割されます。1 次元配列の場合は、メモリが個々のレジスタに分割されます。これがデフォルトの <type> です。
factor=<int>
作成する小型配列の数を指定します。
重要: 完全分割では指定しません。ブロック分割およびサイクリック分割では、factor= は必須です。
dim=<int>
多次元配列のどの次元を分割するかを指定します。<N> 次元の配列の場合、0 ~ <N> の整数を指定します。
  • 0 を指定すると、多次元配列のすべての次元が指定したタイプおよび係数オプションで分割されます。
  • 0 以外の値を指定すると、指定した次元のみが分割されます。たとえば、1 を指定した場合、最初の次元のみが分割されます。

例 1

次の例では、13 要素の配列 AB[13] をブロック分割を使用して 4 つの配列に分割しています。

#pragma HLS array_partition variable=AB block factor=4
ヒント: 4 は 13 の因数ではないので、次のように分割されます。
  • 3 つの配列に要素が 3 個ずつ含まれます。
  • 1 つの配列に 4 つの要素 (AB[9:12]) が含まれます。

例 2

次の例では、2 次元配列 AB[6][4] の 2 番目の次元を次元 [6][2] の配列 2 つに分割しています。

#pragma HLS array_partition variable=AB block factor=2 dim=2

例 3

次の例では、2 次元配列 in_local の 2 番目の次元を個々の要素に分割しています。

int in_local[MAX_SIZE][MAX_DIM];
#pragma HLS ARRAY_PARTITION variable=in_local complete dim=2

関連項目

pragma HLS array_reshape

説明

配列分割と垂直配列マップを組み合わせます。

ARRAY_RESHAPE プラグマは、配列を複数の小型の配列に分割する ARRAY_PARTITION と、ビット幅を増やして配列の要素を連結する ARRAY_MAP の垂直タイプを組み合わせたものです。これにより、使用されるブロック RAM の数を削減すると共に、分割の主な利点であるデータへの並列アクセスを実現できます。このプラグマでは元の配列よりも要素数が少なくビット幅の広い配列が作成され、1 クロック サイクルでアクセスできるデータ量が増加します。

次のようなコードがあるとします。

void foo (...) {
int array1[N];
int array2[N];
int array3[N];
#pragma HLS ARRAY_RESHAPE variable=array1 block factor=2 dim=1
#pragma HLS ARRAY_RESHAPE variable=array2 cycle factor=2 dim=1
#pragma HLS ARRAY_RESHAPE variable=array3 complete dim=1
...
}

ARRAY_RESHAPE プラグマを使用すると、配列が次の図に示すように変換されます。

1: ARRAY_RESHAPE プラグマ


構文

C ソースの配列変数が定義されている関数の領域内に配置します。

#pragma HLS array_reshape variable=<name> \
<type>  factor=<int>  dim=<int>

説明:

<name>
必須の引数で、再形成する配列変数を指定します。
<type>
分割タイプを指定します (オプション)。デフォルトは complete です。次のタイプがサポートされます。
cyclic
元の配列の要素をインターリーブすることにより小型配列が作成されます。たとえば、factor=3 の場合、要素 0 は新しく作成される 1 番目の配列に割り当てられ、要素 1 は 2 番目、要素 2 は 3 番目、要素 3 は 1 番目の配列に割り当てられます。最終的な配列は、新しい配列を 1 つの配列に垂直連結 (ワードを連結してワード数が大きいものを作成) したものになります。
block
ブロック タイプの再形成では、元の配列の連続したブロックから小型配列が作成されます。これにより、配列が <N> 個 (<N>factor= オプションで定義されている数) のブロックに分割され、その <N> 個のブロックが word-width*N で 1 つの配列にまとめられます。
complete
配列を一時的に個々の要素に分割してから、ワード数の大きい 1 つの配列にまとめます。1 次元配列の場合、これはワード数が非常に大きいレジスタを 1 つ作成するのと同じです (元の配列が N 個の M ビット要素を含む場合、N*M ビットのレジスタとなる)。これが配列再形成のデフォルト タイプです。
factor=<int>
現在の配列を分割する量 (一時的に作成する配列数) を指定します。2 に設定すると、配列が 2 分割され、ビット幅は倍になります。3 に設定すると、配列が 3 分割され、ビット幅は 3 倍になります。
重要: 完全分割では指定しません。ブロック分割およびサイクリック分割では、factor= は必須です。
dim=<int>
多次元配列のどの次元を分割するかを指定します。配列が <N> 次元の場合、0 ~ <N> の整数を指定します。
  • 0 を指定すると、多次元配列のすべての次元が指定したタイプおよび係数オプションで分割されます。
  • 0 以外の値を指定すると、指定した次元のみが分割されます。たとえば、1 を指定した場合、最初の次元のみが分割されます。
object
コンテナー配列のみに関連します。このキーワードを指定すると、ARRAY_RESHAPE プラグマがコンテナー内のオブジェクトに適用され、コンテナー内のオブジェクトのすべての次元が再形成されますが、コンテナー自体のすべての次元は保持されます。このキーワードを指定しない場合、プラグマはコンテナー配列に適用され、オブジェクトには適用されません。

例 1

次の例では、17 個の要素を含む 8 ビット配列 AB[17] を、ブロック マップを使用して 5 つの要素を含む 32 ビット配列に再形成 (分割およびマップ) しています。

#pragma HLS array_reshape variable=AB block factor=4
ヒント: factor=4 に指定すると、配列は 4 つに分割されます。つまり、17 個の要素がビット幅が 4 倍の 5 つの要素を含む配列に再形成されます。この場合、最後の要素 AB[17] は、5 番目の要素の下位 8 ビットにマップされ、5 番目の要素の残りは空になります。

例 2

次の例では、2 次元配列 AB[6][4] を次元 [6][2] の配列 1 つに再形成しています。この次元 2 のビット幅は 2 倍です。

#pragma HLS array_reshape variable=AB block factor=2 dim=2

例 3

次の例では、関数 foo の 3 次元の 8 ビット配列 AB[4][2][2] を 128 ビット幅は (4×2×2×8) の 1 要素配列 (1 つのレジスタ) に再形成しています。

#pragma HLS array_reshape variable=AB complete dim=0
ヒント: dim=0 は、配列のすべての次元を再形成することを指定します。

関連項目

pragma HLS data_pack

説明

構造体 (struct) のデータ フィールドをワード幅が広い 1 つのスカラーにパックします。

DATA_PACK プラグマは、構造体のすべての要素を 1 つの幅の広いベクターにパックし、変数に必要なメモリを削減すると同時に、構造体のすべてのメンバーを同時に読み出しおよび書き込みできるようにします。ワード数のビット アライメントは、構造体フィールドの宣言から自動推論されます。最初のフィールドはベクターの LSB で、構造体の最後の要素がベクターの MSB に揃えられます。

構造体に配列が含まれる場合、DATA_PACK プラグマにより ARRAY_RESHAPE プラグマと同様の処理が実行され、再形成された配列が構造体内のほかの要素とまとめられます。構造体内で宣言されている配列はすべて完全に分割されて幅の広いスカラーにまとめられ、ほかのスカラー フィールドと共にパックされます。ただし、DATA_PACKARRAY_PARTITION または ARRAY_RESHAPE は同時に使用して構造体を最適化することはできません。

重要: 大きな配列を含む構造体に DATA_PACK 最適化を使用する際には注意が必要です。配列に int 型の要素が 4096 個含まれる場合、ベクター (およびポート) の幅は 4096x32=131072 ビットになります。Vivado HLS ツールでこの RTL デザインは作成できますが、FPGA インプリメンテーション中に論理合成でこれが配線できることはほとんどありません。

ザイリンクスでは、通常は任意精度 (またはビット精度) データ型を使用することをお勧めします。標準 C 型は 8 ビット境界 (8、16、32、および 64 ビット) に基づいていますが、デザインで任意精度型を使用すると、合成前に C コードでビット サイズを指定できます。ビット精度幅の方が、小型で高速なハードウェア演算子が得られます。これにより、より多くのロジックが FPGA に配置できるようになり、ロジックがより高速のクロック周波数で実行できるようになります。DATA_PACK プラグマでは、必要に応じて、パック型構造体のデータを 8 ビット境界に揃えることもできます。

struct ポートを AXI4 インターフェイスを使用してインプリメントする場合は、DATA_PACK <byte_pad> オプションを使用して struct のメンバー要素が 8 ビット境界に自動的に揃えられるようにすることを考慮してください。AXI4-Stream プロトコルでは、IP の TDATA ポートの幅を 8 の倍数にする必要があります。TDATA ポートの幅が 8 の倍数でない AXI4-Stream IP を定義することは仕様違反となるので、TDATA の幅をバイトの倍数に繰り上げる必要があります。詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) の「インターフェイス合成および構造体」を参照してください。

構文

パックする構造体変数の定義の近くに配置します。

#pragma HLS data_pack variable=<variable> \
instance=<name> <byte_pad>

説明:

variable=<variable>
パックする変数を指定します。
instance=<name>
パック後の変数の名前を指定します。<name> を指定しない場合は <variable> 入力が使用されます。
<byte_pad>
データを 8 ビット境界 (8 ビット、16 ビット、24 ビットなど) 上にパックするかを指定します (オプション)。次の 2 つの値がサポートされます。
struct_level
まず struct 全体をパックし、次の 8 ビット境界までパディングします。
field_level
struct の各要素 (フィールド) をそれぞれ 8 ビット境界までパディングしてから、struct をパックします。
ヒント: データの複数のフィールドをバイト境界に揃える前 (field_level) または後 (struct_level) に連結するかは、通常データがどれだけアトミックであるかによって決定されます。アトミック情報はそれのみで解釈可能なデータであり、非アトミック情報はデータの解釈には不完全です。たとえば、アトミック データには浮動小数点値のすべてのビットが含まれます。浮動小数点値の指数ビットのみでは、アトミックではありません。TDATA に情報をパックする際は通常、ビット幅にかかわらず、データの非アトミック ビットはアトミック ユニットを形成するまで連結されます。その後、アトミック ユニットが必要に応じてパディング ビットを使用してバイト境界にそろえられます。

例 1

次の例では、3 つの 8 ビット フィールド (R、G、B) を含む AB[17] 構造体配列を、17 個の 24 ビット要素の配列 1 つにパックしています。

typedef struct{
unsigned char R, G, B;
} pixel;

pixel AB[17];
#pragma HLS data_pack variable=AB

例 2

次の例では、関数 foo にある 3 つの 8 ビットフィールドを含む構造体ポインター AB (typedef struct {unsigned char R, G, B;} pixel) を 1 つの 24 ビット ポインターにパックしています。

typedef struct{
unsigned char R, G, B;
} pixel;

pixel AB;
#pragma HLS data_pack variable=AB

例 3

次の例では、rgb_to_hsv 関数の in および out 引数に DATA_PACK プラグマを指定して構造体を 8 ビット境界にパックし、メモリ アクセスを向上しています。

void rgb_to_hsv(RGBcolor* in,  // Access global memory as RGBcolor struct-wise
                HSVcolor* out, // Access Global Memory as HSVcolor struct-wise
                int size) {
#pragma HLS data_pack variable=in struct_level
#pragma HLS data_pack variable=out struct_level
...
}

関連項目

pragma HLS dataflow

説明

DATAFLOW プラグマは、タスク レベルのパイプライン処理をイネーブルにして関数およびループを重複できるようにし、RTL インプリメンテーションでの同時実行性を増加してデザイン全体のスループットを向上します。

C 記述では、すべての演算が順次に実行されます。pragma HLS allocation などのリソースを制限する指示子を指定しない場合、Vivado HLS ツールではレイテンシを最小限に抑え、同時実行性を向上するように処理されます。ただし、データ依存性のためにこれが制限されることがあります。たとえば、配列にアクセスする関数またはループは、完了する前に配列への読み出し/書き込みアクセスをすべて終了する必要があります。そのため、そのデータを消費する次の関数またはループの演算を開始できません。DATAFLOW データ最適化を使用すると、前の関数またはループがすべての演算を完了する前に、次の関数またはループの演算を開始できるようになります。

2: DATAFLOW プラグマ


DATAFLOW プラグマを指定した場合、HLS ツールで順次関数またはループ間のデータフローが解析され、プロデューサー関数またはループが完了する前にコンシューマー関数またはループの演算を開始できるように、ピンポン RAM または FIFO に基づいてチャネルが作成されます。これにより関数またはループを並列実行でき、レイテンシが削減されて RTL のスループットが向上します。

開始間隔 (II) (関数またはループの開始から次の関数またはループの開始までのサイクル数) が指定されていない場合は、HLS ツールで開始間隔が最小になるようにし、データが使用可能になったらすぐに演算を開始できるようにすることが試みられます。

ヒント: config_dataflow コマンドは、データフロー最適化で使用されるデフォルトのメモリ チャネルと FIFO の深さを指定します。詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902)config_dataflow コマンドを参照してください。

DATAFLOW 最適化が機能するようにするには、デザイン内でデータが 1 つのタスクから次のタスクに流れる必要があります。次のコーディング スタイルを使用すると、HLS ツールで DATAFLOW 最適化が実行されなくなります。

  • シングル プロデューサー コンシューマー違反
  • タスクのバイパス
  • タスク間のフィードバック
  • タスクの条件付き実行
  • 複数の exit 条件を持つループ
重要: これらのコーディング スタイルのいずれかが使用されている場合、HLS ツールでメッセージが表示され、DATAFLOW 最適化は実行されません。

最後に、DATAFLOW 最適化には階層インプリメンテーションはありません。サブ関数またはループに DATAFLOW 最適化が有益な可能性のあるタスクが含まれる場合、DATAFLOW 最適化をそのループまたはサブ関数に適用するか、サブ関数をインライン展開する必要があります。

構文

C ソースの領域、関数、またはループ内に配置します。

#pragma HLS dataflow

例 1

次の例では、ループ wr_loop_j 内で DATAFLOW 最適化を指定しています。

        wr_loop_j: for (int j = 0; j < TILE_PER_ROW; ++j) {
#pragma HLS DATAFLOW
            wr_buf_loop_m: for (int m = 0; m < TILE_HEIGHT; ++m) {
                wr_buf_loop_n: for (int n = 0; n < TILE_WIDTH; ++n) {
#pragma HLS PIPELINE
                    // should burst TILE_WIDTH in WORD beat
                    outFifo >> tile[m][n];
                }
            }
            wr_loop_m: for (int m = 0; m < TILE_HEIGHT; ++m) {
                wr_loop_n: for (int n = 0; n < TILE_WIDTH; ++n) {
#pragma HLS PIPELINE
                    outx[TILE_HEIGHT*TILE_PER_ROW*TILE_WIDTH*i+TILE_PER_ROW*TILE_WIDTH*m+TILE_WIDTH*j+n] = tile[m][n];
                }
            }

関連項目

pragma HLS dependence

説明

DEPENDENCE プラグマは、ループ運搬依存を克服し、ループをパイプライン処理できるようにする (またはより短い間隔でパイプラインできるようにする) ための追加情報を提供します。

Vivado HLS ツールでは、自動的に次の依存が検出されます。

  • ループ内 (ループ独立依存)。
  • 同じループの反復間 (ループ運搬依存)。

こうした依存は、演算をスケジューリングするタイミング、特に関数およびループのパイプライン処理に影響します。

ループ独立依存
同じ要素が同じループ反復でアクセスされます。
for (i=0;i<N;i++) {
 A[i]=x;
 y=A[i];
}
ループキャリー依存
同じ要素が異なるループ反復でアクセスされます。
for (i=0;i<N;i++) {
 A[i]=A[i-1]*2;
}

複雑な条件下では、自動依存性解析が保守的すぎ、偽依存性が除去されないことがあります。変数依存の配列インデックスや、外部要件を満たす必要があるような状況 (2 つの入力が同じインデックスにならない場合など) では、依存解析が保守的すぎることがあります。DEPENDENCE プラグマを使用すると、依存を明示的に指定し、偽依存を解決できます。

重要: 偽依存でないものを偽依存と指定すると、ハードウェアが正しく機能しなくなる可能性があります。依存性 (true または false) が正しいことを確認してから指定してください。

構文

依存が定義されている関数内に配置します。

#pragma HLS dependence variable=<variable> <class> \
<type> <direction> distance=<int> <dependent>

説明:

variable=<variable>
依存を考慮する変数を指定します (オプション)。
<class>
依存を明確にする必要がある変数のクラスを指定します (オプション)。有効な値は array または pointer です。
ヒント: 関数内で変数または変数のクラスのいずれかを指定していれば、<class> および variable= の両方を指定する必要はありません。
<type>
有効な値は intra または inter です。依存のタイプを指定します。
intra
同じループ反復内の依存。<type>intra<dependent> を false に設定すると、HLS ツールによりループ内で演算を自由に移動でき、パフォーマンスまたはエリアを向上できる可能性が高くなります。<dependent> を true に設定すると、演算を指定の順序で実行する必要があります。
inter
異なるループ反復間の依存。これがデフォルトの <type> です。<type> を inter<dependent> を false に設定すると、関数またはループがパイプライン処理されているか、ループが展開されていない場合または部分的に展開されている場合に、並列実行が可能になり、<dependent> を true に設定すると並列実行が不可能になります。
<direction>
有効な値は RAWWAR または WAW です。これはループ運搬依存にのみ関連し、依存の方向を指定します。
RAW
(Read-After-Write - 真の依存) 書き込み命令により書き込まれた値が読み出し命令で使用されます。
WAR
(Write-After-Read - 非依存) 読み出し命令により値が取得され、その値が書き込み命令で上書きされます。
WAW
(Write-After-Write - 出力依存): 2 つの書き込み命令により、特定の順序で同じロケーションに書き込みが実行されます。
distance=<int>
配列アクセスの反復間隔を指定します。これは、依存が true に設定されているループ運搬依存でのみ使用されます。
<dependent>
依存を使用する必要があるか (true)、削除するか (false) を指定します。デフォルトは true です。

例 1

次の例では、HLS ツールでは cols の値を認識する機能がないので、buff_A[1][col] への書き込みと buff_A[1][col] の読み出し間に常に依存性があると想定されます。このようなアルゴリズムでは、cols が 0 になることはほとんどありませんが、HLS ツールではデータ依存を想定できません。この依存を解決するため、DEPENDENCE プラグマを使用して、ループ反復間 (この例の場合は buff_Abuff_B の両方) に依存がないことを指定できます。

void foo(int rows, int cols, ...)
  for (row = 0; row < rows + 1; row++) {
    for (col = 0; col < cols + 1; col++) {
      #pragma HLS PIPELINE II=1
      #pragma HLS dependence variable=buff_A inter false
      #pragma HLS dependence variable=buff_B inter false
      if (col < cols) {
      buff_A[2][col] = buff_A[1][col]; // read from buff_A[1][col]
      buff_A[1][col] = buff_A[0][col]; // write to buff_A[1][col]
      buff_B[1][col] = buff_B[0][col];
      temp = buff_A[0][col];
}

例 2

次の例では、関数 fooloop_1 の同じ反復での Var1 の依存を削除しています。

#pragma HLS dependence variable=Var1 intra false

例 3

次の例では、関数 fooloop_2 にあるすべての配列の依存を定義し、同じループ反復ではすべての読み出しが書き込みの後に実行される (RAW) ように設定しています。

#pragma HLS dependence array intra RAW true

関連項目

pragma HLS expression_balance

説明

C ベースの仕様が演算のシーケンスで記述され、RTL で長い演算チェーンとなることがあります。クロック周期が短い場合、これによりデザイン レイテンシが増加する可能性あります。デフォルトでは、Vivado HLS ツールで演算の関連性および接続性を考慮して、演算が並べ替えられます。これにより、ツリーのバランスが取られてチェーンが短くなるので、ハードウェア リソースは増加しますが、デザインのレイテンシを削減できます。

EXPRESSION_BALANCE プラグマを使用すると、指定のスコープ内でこの演算調整をディスエーブルまたは明示的にイネーブルにできます。

構文

C ソースの必要なロケーションの境界内に配置します。

#pragma HLS expression_balance off

説明:

off
演算調整を指定の場所でオフにします。
ヒント: プラグマでこのオプションを使用しないと、デフォルトで演算調整がオンになります。

例 1

次の例では、関数 my_Func 内で演算調整を明示的にオンにしています。

void my_func(char inval, char incr) {  
  #pragma HLS expression_balance

例 2

関数 my_Func 内で演算調整をオフにします。

void my_func(char inval, char incr) {  
  #pragma HLS expression_balance off

pragma HLS function_instantiate

説明

FUNCTION_INSTANTIATE プラグマは、関数の階層を保持するエリアの利点を維持しながら、関数の特定のインスタンスに対してローカル最適化を実行する優れたオプションを提供する最適化手法です。これにより関数呼び出し周辺の制御ロジックが単純になり、レイテンシおよびスループットが向上することがあります。

デフォルトでは次のようになります。

  • 関数は RTL で個別の階層ブロックのままになります。
  • 同じ階層レベルにある関数のすべてのインスタンスは、同じ RTL インプリメンテーション (ブロック) を使用します。

FUNCTION_INSTANTIATE プラグマは、関数の各インスタンスに固有の RTL インプリメンテーションを作成し、関数呼び出しに応じて各インスタンスをローカルで最適化できるようにします。このプラグマを使用すると、関数が呼び出されたときに関数の一部の入力が定数であることがあるという事実を利用して、周辺の制御構造が単純化され、小型で最適化された関数ブロックが作成されます。

FUNCTION_INSTANTIATE プラグマを使用しない場合、次のコードから、foo 内の関数の 3 つのインスタンスすべてに対して関数 foo_sub の RTL インプリメンテーションが 1 つ作成されます。関数 foo_sub の各インスタンスはまったく同じようにインプリメントされます。これは、関数を再利用して、関数の各インスタンス呼び出しに必要なエリアを削減するには良いですが、foo_sub の各呼び出しのバリエーションを考慮するため、関数内の制御ロジックがより複雑なものとなります。

char foo_sub(char inval, char incr) {
#pragma HLS function_instantiate variable=incr
 return inval + incr;
}
void foo(char inval1, char inval2, char inval3,
 char *outval1, char *outval2, char * outval3)
{
 *outval1 = foo_sub(inval1, 1);
 *outval2 = foo_sub(inval2, 2);
 *outval3 = foo_sub(inval3, 3);
}

上記のコード例では、FUNCTION_INSTANTIATE プラグマにより関数 foo_sub の 3 つのインプリメンテーションが生成され、それぞれを個別に incr 引数で最適化されるので、エリアが削減されて関数のパフォーマンスが向上します。FUNCTION_INSTANTIATE 最適化の後、foo_sub は 3 つの個別の関数に変換され、それぞれ incr に指定された値によって最適化されます。

構文

C ソースの必要なロケーションの境界内に配置します。

#pragma HLS function_instantiate variable=<variable>

説明:

variable=<variable>
必須の引数で、定数として使用する関数引数を定義します。

例 1

次の例では、関数 swIntFUNCTION_INSTANTIATE プラグマを配置し、関数 swInt の各インスタンスが maxv 関数引数に対して個別に最適化されるようにしています。

void swInt(unsigned int *readRefPacked, short *maxr, short *maxc, short *maxv){
#pragma HLS function_instantiate variable=maxv
    uint2_t d2bit[MAXCOL];
    uint2_t q2bit[MAXROW];
#pragma HLS array partition variable=d2bit,q2bit cyclic factor=FACTOR

    intTo2bit<MAXCOL/16>((readRefPacked + MAXROW/16), d2bit);
    intTo2bit<MAXROW/16>(readRefPacked, q2bit);
    sw(d2bit, q2bit, maxr, maxc, maxv);
}

関連項目

pragma HLS inline

説明

関数を階層の別エンティティとして削除します。インライン展開された関数は呼び出し関数に分解され、RTL で別の階層として表示されなくなります。関数をインライン展開すると、関数内の演算が共有され、周辺の演算と効率よく最適化されるようになることがあります。ただし、インライン展開された関数は共有できないので、RTL をインプリメントするのに必要なエリアが増加する可能性があります。

INLINE プラグマは、その指定方法によって、定義されているスコープに異なる方法で適用されます。

INLINE
引数を指定しない場合、関数は呼び出し関数または領域に上向きにインライン展開されます。
INLINE OFF
プラグマを指定した関数を呼び出し関数または領域にインライン展開しないよう指定します。これにより、自動的にインライン展開、あるいは領域または再帰の一部としてインライン展開されるはずであった関数のインライン展開がディスエーブルになります。
INLINE REGION
プラグマを領域または関数の本体に適用します。領域または関数の内容が下向きにインライン展開されますが、階層全体に再帰的にインライン展開されることはありません。
INLINE RECURSIVE
プラグマを領域または関数の本体に適用します。領域または関数の内容が下向きに再帰的にインライン展開されます。

デフォルトでは、インライン展開は関数階層のすぐ下の階層でのみ実行され、サブ関数では実行されません。recursive オプションを使用すると、階層全体でインライン展開が指定されます。

構文

C ソースの関数本体内またはコードの領域内に配置します。

#pragma HLS inline <region | recursive | off>

説明:

region
指定した領域内の (または関数の本体に含まれる) すべての関数をインライン展開するよう指定し、領域スコープに適用します。
recursive
デフォルトでは、関数のインライン展開は 1 つの階層レベルでのみ実行され、指定の関数内の関数はインライン展開されません。recursive オプションを使用すると、指定の関数または領域内のすべての関数が再帰的にインライン展開されます。
off
関数のインライン展開をオフにし、指定の関数がインライン展開されないようにします。たとえば、関数に recursive オプションが指定されている場合、ほかの関数はインライン展開されても、特定の呼び出された関数がインライン展開されないようにできます。
ヒント: Vivado HLS ツールでは小型の関数が自動的にインライン展開されるので、INLINE プラグマに off オプションを使用すると、この自動インライン展開が実行されないようにできます。

例 1

次の例では、指定した領域内 (foo_top の本体) のすべての関数をインライン展開しています。この関数内の下位関数はインライン展開されません。

void foo_top { a, b, c, d} {
  #pragma HLS inline region
  ...

例 2

次の例では、foo_top の本体内のすべての関数がインライン展開され、この関数に含まれる階層すべてで再帰的にインライン展開が実行されます。ただし、関数 foo_sub はインライン展開されません。recursive プラグマは関数 foo_top に配置されています。関数 foo_sub にはインライン展開をディスエーブルにするプラグマが配置されています。

foo_sub (p, q) {
#pragma HLS inline off
int q1 = q + 10;
foo(p1,q);// foo_3
...
}
void foo_top { a, b, c, d} {
  #pragma HLS inline region recursive 
  ...
  foo(a,b);//foo_1
  foo(a,c);//foo_2
  foo_sub(a,d);
  ...
}
注記: この例では、INLINE は関数 foo_top の内容に下向きに適用されますが、foo_sub を呼び出すコードには上向きに適用されます。

例 3

次の例では、copy_output 関数が copy_output を呼び出す関数または領域にインライン展開されます。

void copy_output(int *out, int out_lcl[OSize * OSize], int output) {
#pragma HLS INLINE
    // Calculate each work_item's result update location
    int stride = output * OSize * OSize;
    
    // Work_item updates output filter/image in DDR
    writeOut: for(int itr = 0; itr < OSize * OSize; itr++) {
    #pragma HLS PIPELINE
        out[stride + itr] = out_lcl[itr];
    }

関連項目

pragma HLS interface

説明

C ベース デザインでは、すべての入力および出力操作がフォーマル関数引数を使用して 0 時間で実行されます。RTL デザインでは、同じ入力および出力操作をデザイン インターフェイスのポートを介して実行する必要があり、通常は特定の入力/出力 (I/O) プロトコルを使用して実行されます。詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) の「インターフェイスの管理」を参照してください。

INTERFACE プラグマは、インターフェイス合成で関数定義からどのように RTL ポートが作成されるかを指定します。

RTL インプリメンテーションのポートは、次のものから導出されます。

  • 指定されている任意の関数レベルのプロトコル: 関数レベルのプロトコルは、ブロック レベルの I/O プロトコルとも呼ばれ、関数の演算が開始したときに制御する信号と、関数の演算が終了したこと、アイドル状態であること、新しい入力を受信できる状態であることを通知する信号を提供します。関数レベルのプロトコルのインプリメンテーションは、次のとおりです。
    • <mode> 値により ap_ctrl_noneap_ctrl_hs、または ap_ctrl_chain のいずれかに指定されます。デフォルトは ap_ctrl_hs ブロック レベル I/O プロトコルです。
    • 関数名に関連付けられています。
  • 関数引数: 各関数引数は、有効ハンドシェイク (ap_vld) や肯定応答ハンドシェイク (ap_ack) などの独自のポート レベル (I/O) インターフェイス プロトコルを持つように指定できます。ポート レベル インターフェイス プロトコルは、最上位関数の各引数および関数の戻り値 (関数が値を返す場合) に対して作成されます。作成されるデフォルトの I/O プロトコルは、C 引数のタイプによって異なります。ブロック レベルのプロトコルが使用されてブロックの演算が開始したら、ポート レベルの I/O プロトコルを使用して、データをブロック内外に順に送信できます。
  • 最上位関数によりアクセスされ、スコープ外部で定義されるグローバル変数:
    • グローバル変数がアクセスされても、すべての読み出しおよび書き込みが関数のローカルである場合は、リソースは RTL 内に作成されます。RTL に I/O ポートは必要ありません。グローバル変数が外部ソースまたはデスティネーションである場合は、インターフェイスは標準関数引数と同様の方法で指定します。次にを示します。

INTERFACE プラグマがサブ関数に使用されている場合は、register オプションのみを使用できます。サブ関数では、<mode> オプションはサポートされません。

ヒント: サブ関数で使用される I/O プロトコルは Vivado HLS ツールにより自動的に決定されます。ポートにレジスタを付けるかどうかを指定することを除き、これらのポートを制御することはできません。

バースト モードの指定

構文セクションに説明されているように max_read_burst_length または max_write_burst_length オプションを使用してインターフェイスのバースト モードを指定する場合は、AXI 規格に関連する制限と考慮事項があります。

  1. ARLEN および AWLEN は 8 ビットで、実際のバースト長は AxLEN+1 なので、トランザクションごとのバースト長は 256 ワード以下にする必要があります。
  2. バースト トランザクションごとに転送される量は合計 4 KB 未満です。
  3. 4 KB アドレス境界を超えないようにしてください。
  4. バス幅は、32 ~ 512 ビットの間の 2 のべき乗数 (32、64、128、512 ビット) または 4、8、16、32、64 バイトに指定します。

4 KB の制限があるので、各バス幅での最大バースト長は次のようになります。

  • 32 ビット幅では、1 つのバースト トランザクションで 256 ワード転送されます。この場合、トランザクションごとに転送される合計バイト数は 1024 です。
  • 64 ビット幅では、1 つのバースト トランザクションで 256 ワード転送されます。トランザクションごとに転送される合計バイト数は 2048 です。
  • 128 ビット幅では、1 つのバースト トランザクションで 256 ワード転送されます。トランザクションごとに転送される合計バイト数は 4096 です。
  • 256 ビット幅では、1 つのバースト トランザクションで 128 ワード転送されます。トランザクションごとに転送される合計バイト数は 4096 です。
  • 512 ビット幅では、1 つのバースト トランザクションで 64 ワード転送されます。トランザクションごとに転送される合計バイト数は 4096 です。
注記: これはデザインによるので、HLS ツールで生成された IP では実際には最大バースト長が実行されないことがあります。たとえば、max_read_burst_length または max_write_burst_length を 128 に設定している場合、100 回反復される for ループからのパイプライン アクセスでは最大バースト長は達成されません。

ただし、指定されたバースト長よりも長いアクセスがソース コード内で実行される場合は、アクセスは小さいバーストに分割されます。たとえば、100 回アクセスされるパイプライン for ループで max_read_burst_length または max_write_burst_length を 64 に設定すると、最大バースト長 (64) のトランザクションと残りのデータのトランザクション (36 ワードのバースト) に分割されます。

構文

関数内に配置します。

#pragma HLS interface <mode> port=<name> bundle=<string> \
register register_mode=<mode> depth=<int> offset=<string> \
clock=<string> name=<string> \
num_read_outstanding=<int> num_write_outstanding=<int> \
max_read_burst_length=<int> max_write_burst_length=<int>

説明:

<mode>
関数引数、関数で使用されるグローバル変数、またはブロック レベルの制御プロトコルのインターフェイス プロトコル モードを指定します。これらのモードの詳細な説明は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) の「インターフェイス合成リファレンス」を参照してください。モードは次のいずれかに指定できます。
ap_none
プロトコルなし。インターフェイスはデータ ポートです。
ap_stable
プロトコルなし。インターフェイスはデータ ポートです。HLS ツールでは、リセット後はデータ ポートが常に安定していると想定され、内部最適化により不要なレジスタが削除されます。
ap_vld
データ ポートと、データが読み出しまたは書き込みに対して有効になったことを示す valid ポートがインプリメントされます。
ap_ack
データ ポートと、データが読み出された/書き込まれたことを肯定応答する acknowledge ポートがインプリメントされます。
ap_hs
データ ポートと、データが読み出しおよび書き込みに対して有効になったことを示す valid ポートおよびデータが読み出された/書き込まれたことを肯定応答する acknowledge ポートがインプリメントされます。
ap_ovld
出力データ ポートと、データが読み出しまたは書き込みに対して有効になったことを示す valid ポートがインプリメントされます。
重要: HLS ツールでは、入力引数または読み出し/書き込み引数の入力部分は ap_none モードを使用してインプリメントされます。
ap_fifo
標準 FIFO インターフェイスのポートが、アクティブ Low FIFO の empty および full ポートが関連付けられたデータ入力および出力ポートを使用してインプリメントされます。
注記: このインターフェイスは、読み出し引数または書き込み引数のみに使用できます。ap_fifo モードでは双方向の読み出し/書き込み引数はサポートされません。
ap_bus
ポインターおよび参照渡しポートがバス インターフェイスとしてインプリメントされます。
ap_memory
配列引数が標準 RAM インターフェイスとしてインプリメントされます。Vivado IP インテグレーターで RTL デザインを使用する場合は、メモリ インターフェイスは個別のポートととして表示されます。
bram
配列引数が標準 RAM インターフェイスとしてインプリメントされます。IP インテグレーターで RTL デザインを使用する場合は、メモリ インターフェイスはシングル ポートとして表示されます。
axis
すべてのポートが AXI4-Stream インターフェイスとしてインプリメントされます。
s_axilite
すべてのポートが AXI4-Lite インターフェイスとしてインプリメントされます。HLS ツールでは、RTL のエクスポート中に C ドライバー ファイルの関連セットが生成されます。
m_axi
すべてのポートが AXI4 インターフェイスとしてインプリメントされます。32 ビット (デフォルト) または 64 ビットのアドレス ポートを指定し、アドレス オフセットを制御するには、config_interface コマンドを使用できます。
ap_ctrl_none
ブロック レベル I/O プロトコルなし。
注記: ap_ctrl_none を使用すると、C/RTL の協調シミュレーション機能を使用してデザインを検証できなくなることがあります。
ap_ctrl_hs
デザインの演算を start し、デザインが idledone、および新しい入力データに対して ready になっていることを示すブロック レベルの制御ポート セットをインプリメントします。
注記: ap_ctrl_hs モードはデフォルトのブロック レベル I/O プロトコルです。
ap_ctrl_chain
デザインの演算を start および continue し、デザインが idledone、および新しい入力データに対して ready になっていることを示すブロック レベルの制御ポート セットをインプリメントします。
注記: ap_ctrl_chain インターフェイス モードは ap_ctrl_hs と似ていますが、バック プレッシャーを適用するために入力信号 ap_continue が追加されている点が異なります。ザイリンクスでは、HLS ツール ブロックをチェーン接続する場合は ap_ctrl_chain ブロックレベル I/O プロトコルを使用することをお勧めします。
port=<name>
INTERFACE プラグマを適用する関数引数、関数戻り値、またはグローバル変数の名前を指定します。
ヒント: 関数の return 値のポートには、ブロックレベル I/O プロトコル (ap_ctrl_noneap_ctrl_hs、または ap_ctrl_chain) を割り当てることができます。
bundle=<string>
  • 関数引数を AXI ポートにまとめます。デフォルトでは、HLS ツールで AXI4-Lite (s_axilite) インターフェイスとして指定されたすべての関数引数は 1 つの AXI4-Lite ポートにまとめられます。同様に、AXI4 (m_axi) インターフェイスとして指定されたすべての関数引数は 1 つの AXI4 ポートにまとめられます。このオプションでは、同じ bundle=<string> が指定されたすべてのインターフェイス ポートが 1 つの AXI インターフェイス ポートにまとめられ、RTL ポートの名前が <string> になります。
    重要: bundle= で名前を指定する場合は、すべて小文字にする必要があります。
  • register: 信号および関連プロトコル信号にレジスタを付けるオプションのキーワードで、信号が少なくとも関数実行の最終サイクルまで保持されます。このオプションは、次のインターフェイス モードに適用されます。
    • ap_none
    • ap_ack
    • ap_vld
    • ap_ovld
    • ap_hs
    • ap_stable
    • axis
    • s_axilite
    ヒント: config_interface コマンドの -register_io オプションは、最上位関数のすべての入力/出力にレジスタを付けるかどうかをグローバルに制御します。詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) を参照してください。
register_mode= <forward|reverse|both|off>
register キーワードと共に使用し、forward パス (TDATA および TVALID)、reverse パス (TREADY)、またはその両方 (both) のパス (TDATA、TVALID、および TREADY) にレジスタを配置するか、どのポート信号にもレジスタを配置しないか (off) を指定します。デフォルトの register_modeboth です。AXI4-Stream (axis) サイドチャネル信号はデータ信号と考慮され、TDATA にレジスタが付けられるとレジスタが付けられます。
depth=<int>
テストベンチで処理されるサンプルの最大数を指定します。この設定は、HLS ツールで RTL 協調シミュレーション用に作成される検証アダプターに必要な FIFO の最大サイズを示します。
ヒント: depth は通常はオプションですが、m_axi インターフェイスでは必須です。
offset=<string>
AXI4-Lite (s_axilite) および AXI4 (m_axi) インターフェイスのアドレス オフセットを指定します。
  • s_axilite インターフェイスでは、<string> はレジスタ マップのアドレスを指定します。
  • m_axi インターフェイスでは、<string> は次の値を指定します。
    • direct: スカラー入力ポートを生成します。
    • slave: オフセット ポートを生成し、AXI4-Lite スレーブ インターフェイスに自動的にマップします。
    • off: オフセット ポートは生成しません。
    ヒント: config_interface コマンドの -m_axi_offset オプションは、デザインのすべての M_AXI インターフェイスのオフセット ポートをグローバルに制御します。
clock=<name>
s_axilite インターフェイス モードのみでオプションで指定します。インターフェイスで使用するクロック信号を定義します。デフォルトでは、AXI4-Lite インターフェイス クロックはシステム クロックと同じです。このオプションを使用すると、AXI4-Lite インターフェイス (s_axilite) に別のクロックを指定できます。
ヒント: bundle オプションを使用して複数の最上位関数引数を 1 つの AXI4-Lite インターフェイスにている場合は、clock オプションはバンドル メンバーの 1 つにのみ指定します。
latency=<value>
<mode> が m_axi の場合、AXI4 インターフェイスのレイテンシを指定し、読み出しまたは書き込みの指定サイクル (レイテンシ) 前にバス要求を開始できるようにします。このレイテンシ値が小さすぎると、デザインが準備完了になるのが早すぎ、バスを待つために停止する可能性があります。レイテンシ値が大きすぎると、バス アクセスは承認されても、デザインがアクセスを開始するのを待つためにバスが停止する可能性があります。
num_read_outstanding=<int>
AXI4 (m_axi) インターフェイスで、デザインが停止するまでに、AXI4 バスに対して応答なしで読み込み要求を送信できる回数を指定します。これにより、デザインの内部ストレージである FIFO のサイズ (num_read_outstanding*max_read_burst_length*word_size) が決まります。
num_write_outstanding=<int>
AXI4 (m_axi) インターフェイスで、デザインが停止するまでに、AXI4 バスに対して応答なしで書き込み要求を送信できる回数を指定します。これにより、デザインの内部ストレージである FIFO のサイズ (num_write_outstanding*max_write_burst_length*word_size) が決まります。
max_read_burst_length=<int>
  • AXI4 (m_axi) インターフェイスでのバースト転送で読み出されるデータの最大数を指定します。
  • max_write_burst_length=<int>: AXI4 (m_axi) インターフェイスでのバースト転送で書き込まれるデータの最大数を指定します。
    ヒント: ポートが読み出し専用ポートの場合は、メモリ リソースを節約するため num_write_outstanding=1 および max_write_burst_length=2 を設定してください。ポートが書き込み専用の場合は、num_read_outstanding=1 および max_read_burst_length=2 を設定します。
name=<string>
ポートの名前を変更します。生成された RTL ポートでこの名前が使用されます。

例 1

次の例では、両方の関数引数が AXI4-Stream インターフェイスを使用してインプリメントされます。

void example(int A[50], int B[50]) {
  //Set the HLS native interface types
  #pragma HLS INTERFACE axis port=A
  #pragma HLS INTERFACE axis port=B
  int i;
  for(i = 0; i < 50; i++){
    B[i] = A[i] + 5;
  }
}

例 2

次の例では、ブロック レベル I/O プロトコルをオフにし、関数戻り値に割り当てています。

#pragma HLS interface ap_ctrl_none port=return

関数引数 InDataap_vld インターフェイスを使用し、入力にレジスタが付けられます。

#pragma HLS interface ap_vld register port=InData

これにより、グローバル変数 lookup_table が RTL デザインでポートとして処理され、インターフェイスは ap_memory に指定されます。

pragma HLS interface ap_memory port=lookup_table

例 3

次の例では、最上位 transpose 関数のポートに INTERFACE 規格を定義しています。bundle= オプションを使用して信号をグループ化しています。

// TOP LEVEL - TRANSPOSE
void transpose(int* input, int* output) {
	#pragma HLS INTERFACE m_axi port=input offset=slave bundle=gmem0
	#pragma HLS INTERFACE m_axi port=output offset=slave bundle=gmem1

	#pragma HLS INTERFACE s_axilite port=input bundle=control
	#pragma HLS INTERFACE s_axilite port=output bundle=control
	#pragma HLS INTERFACE s_axilite port=return bundle=control

	#pragma HLS dataflow

pragma HLS latency

説明

関数、ループ、および領域の完了するまでの最小レイテンシまたは最大レイテンシ、あるいはその両方を指定します。

レイテンシ
出力を生成するのに必要なクロック サイクル数。
関数レイテンシ
関数がすべての出力値を計算して戻るまでに必要なクロック サイクル数。
ループ レイテンシ
ループのすべての反復を実行するのにかかるサイクル数。

『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) の「パフォーマンス メトリクスの例」を参照してください。

Vivado HLS ツールでは常に、デザインのレイテンシを最短にするよう試みられます。LATENCY プラグマを指定すると、ツールで次のように処理されます。

  • レイテンシが最小値より大きく、最大値未満: 制約は満たされています。これ以上の最適化は実行されません。
  • レイテンシが最小値未満: HLS ツールで指定の最小レイテンシ未満を達成できる場合は、レイテンシが指定値まで拡張されます。リソース共有が増加する可能性があります。
  • レイテンシが最大値を超える: HLS ツールで最大値以下でスケジューリングできない場合は、指定された制約を満たすことができるようエフォート レベルが上げられます。それでも最大レイテンシを満たすことができない場合は、警告が表示され、達成可能な最短のレイテンシでデザインが作成されます。
ヒント: LATENCY プラグマを使用すると、ツールで最良のソリューションを探すエフォートを制限することもできます。コード内のループ、関数、または領域にレイテンシ制約を指定すると、そのスコープ内で可能なソリューションが削減され、ツールの実行時間が短縮されます。詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) の「ランタイムおよび容量の改善」を参照してください。

構文

レイテンシを制御する関数、ループ、または領域内に配置します。

#pragma HLS latency min=<int> max=<int>

説明:

min=<int>
関数、ループ、またはコードの領域の最小レイテンシを指定します (オプション)。
max=<int>
関数、ループ、またはコードの領域の最大レイテンシを指定します (オプション)。
注記: min および max はオプションですが、いずれかを指定する必要があります。

例 1

関数 foo のレイテンシの最小値を 4、最大値を 8 に指定します。

int foo(char x, char a, char b, char c) {
  #pragma HLS latency min=4 max=8
  char y;
  y = x*a+b+c;
  return y
}

例 2

次の例では、loop_1 の最大レイテンシを 12 に指定しています。プラグマはループ本体内に記述します。

void foo (num_samples, ...) {
  int i;
  ...
  loop_1: for(i=0;i< num_samples;i++) { 
  #pragma HLS latency max=12
    ...
    result = a + b;
  }
}

例 3

次の例では、コード領域を作成し、同じクロック サイクルで変化する必要のある信号を 0 レイテンシを指定してグループ化しています。

// create a region { } with a latency = 0
{
  #pragma HLS LATENCY max=0 min=0
  *data = 0xFF;
  *data_vld = 1;
}

pragma HLS loop_flatten

説明

入れ子のループを 1 つのループ階層にフラット化し、レイテンシを改善します。

RTL インプリメンテーションでは、外側のループから内側のループに、内側のループから外側のループに移動するのに 1 クロック サイクルかかります。入れ子のループをフラットにすると、それらを 1 つのループとして最適化できるので、クロック サイクル数を削減でき、ループ本文のロジックをさらに最適化することが可能です。

ループ階層内の一番内側にあるループの本体に LOOP_FLATTEN プラグマを適用します。完全または半完全ループのみをこの方法でフラットにできます。

完全ループの入れ子
  • 最内ループのみにループ本体の内容が含まれます。
  • ループ文の間に指定されるロジックはありません。
  • すべてのループ範囲は定数です。
半完全ループの入れ子
  • 最内ループのみにループ本体の内容が含まれます。
  • ループ文の間に指定されるロジックはありません。
  • 最外ループの範囲は変数にできます。
不完全ループの入れ子
内側のループの範囲が変数であったり、ループ本体が内側のループにのみ含まれているとは限らない場合、コードの構造を変更するか、ループ本体内のループを展開して、完全ループの入れ子を作成してみてください。

構文

C ソースの入れ子のループ内に配置します。

#pragma HLS loop_flatten off

説明:

off
オプションのキーワードで、フラット化がされないようにできるほか、指定ロケーション内の一部のループはフラット化せず、それ以外のループをフラット化したりできます。
注記: LOOP_FLATTEN プラグマを使用すると、最適化がイネーブルになります。

例 1

関数 foo 内の loop_1 と、ループ階層でこれより上にあるすべてのループ (完全または半完全) を 1 つのループにフラット化します。プラグマを loop_1 の本体に記述します。

void foo (num_samples, ...) {
  int i;
  ...
  loop_1: for(i=0;i< num_samples;i++) {
   #pragma HLS loop_flatten
   ...
    result = a + b;
  }
}

例 2

loop_1 でループがフラット化されないようにします。

loop_1: for(i=0;i< num_samples;i++) {
   #pragma HLS loop_flatten off
   ...

関連項目

pragma HLS loop_merge

説明

連続するループを 1 つのループに結合して、全体的なレイテンシを削減し、共有を増やして最適化を向上します。ループを結合すると、次が可能になります。

  • RTL でループ本文のインプリメンテーション間の移行に必要なクロック サイクル数を削減できます。
  • ループを並列にインプリメントできます (可能な場合)。

LOOP_MERGE プラグマでは、適用されたスコープ内のループすべてを結合するよう試みられます。たとえば、LOOP_MERGE プラグマをループの本体に適用した場合、Vivado HLS ツールではプラグマがそのループ自体ではなく、ループ内の下位ループに適用されます。

ループ結合の規則は、次のとおりです。

  • ループの境界が変数の場合、同じ値 (反復回数) である必要があります。
  • ループの境界が定数の場合、最大定数値が結合されたループの境界として使用されます。
  • 境界が変数のループと定数のループを結合することはできません。
  • 結合するループ間のコードが、結合により悪影響を受けないようにします。コードを複数回実行しても常に同じ結果になるようにする必要があります。たとえば、a=b は許容されますが a=a+1 は許容されません。
  • ループに FIFO 読み出しが含まれる場合は、ループは結合できません。結合により読み出しの順序が変更されてしまうためです。FIFO または FIFO インターフェイスからの読み出しは、常に順序どおりに実行される必要があります。

構文

C ソースの必要なスコープまたは領域内に配置します。

#pragma HLS loop_merge force

説明:

force
HLS ツールで警告が出力されてもループが結合されるようオプションのキーワードです。
重要: この場合、結合したループが問題なく動作するかどうかをユーザーが確認する必要があります。

次の例では、関数 foo の連続するすべてのループを 1 つのループに結合しています。

void foo (num_samples, ...) {
  #pragma HLS loop_merge
  int i;
  ...
  loop_1: for(i=0;i< num_samples;i++) {
   ...

次の例では、loop_2 内の loop_2 を除くすべてのループを force オプションを使用して結合しています。プラグマを loop_2 の本体に記述します。

loop_2: for(i=0;i< num_samples;i++) {
#pragma HLS loop_merge force
...

関連項目

pragma HLS loop_tripcount

説明

ループに適用すると、ループで実行される反復回数の合計を手動で指定できます。

重要: LOOP_TRIPCOUNT プラグマは解析専用で、合成結果には影響しません。

Vivado HLS ツールにより、各ループの合計レイテンシ、つまりループのすべての反復を実行するためのクロック サイクル数がツールレポートされます。このため、ループ レイテンシは、ループ反復数 (トリップカウント) に依存します。

トリップカウントは、定数値であることもあり、ループ式 (x < y など) で使用される変数の値やループ内の制御文によって異なる場合もあります。HLS ツールでトリップカウントを決定できないこともあり、その場合はレイテンシは不明になります。これは、トリップカウントの決定に使用される変数が次のいずれかの場合です。

  • 入力引数。
  • ダイナミック演算により算出される変数。

ループのレイテンシが不明または算出できない場合、LOOP_TRIPCOUNT プラグマを使用してループの反復回数の最小値および最大値を指定できます。これにより、ループのレイテンシがデザインの総レイテンシのどの程度を占めているのかがツールで解析されてレポートされるので、デザインに適切な最適化を判断するのに役立ちます。

構文

C ソースのループ本体内に配置します。

#pragma HLS loop_tripcount min=<int> max=<int> avg=<int>

説明:

max= <int>
ループの反復回数の最大値を指定します。
min=<int>
ループの反復回数の最小値を指定します。
avg=<int>
ループの反復回数の平均値を指定します。

次の例では、関数 fooloop_1 の最小トリップカウントを 12、最大トリップカウントを 16 に指定しています。

void foo (num_samples, ...) {
  int i;
  ...
  loop_1: for(i=0;i< num_samples;i++) {
   #pragma HLS loop_tripcount min=12 max=16
   ...
    result = a + b;
  }
}

pragma HLS occurrence

説明

関数またはループをパイプライン処理する際に使用して、ある領域のコードがそれを含む関数またはループのコードよりも低頻度で実行されるように指定します。これにより、実行速度が遅いコード部分が低頻度でパイプライン処理されるようにでき、最上位パイプライン内で共有できる可能性があります。OCCURRENCE プラグマは次を基に判断します。

  • ループは <N> 回反復する。
  • ループ本体の一部が条件文で有効になっており、<M> 回しか実行されない (<N><M> の整数倍)。
  • 条件コードの実行は、ループ本体の残りの部分の N/M 倍低速。

たとえば、10 回実行されるループで、ループ内の条件文が 2 回だけ実行される場合、実行頻度は 5 (10/2) となります。

領域に OCCURRENCE プラグマを使用すると、その領域内の関数およびループがそれを含む関数またはループよりも遅い開始間隔 (II) でパイプライン処理されます。

構文

C ソースのコードの領域内に配置します。

#pragma HLS occurrence cycle=<int>

説明:

cycle=<int>
N/M の実行頻度を指定します。
<N>
領域を含む関数/ループの実行回数です。
<M>
条件領域の実行回数です。
重要: <N><M> の整数倍数である必要があります。

次の例では、領域 Cond_Region の実行頻度を 4 に指定しています。この領域は、それを含むコードの 1/4 の速度で実行されます。

Cond_Region: {
#pragma HLS occurrence cycle=4
...
}

関連項目

pragma HLS pipeline

説明

演算を同時実行できるようにすることにより関数またはループの開始間隔 (II) を短縮します。

パイプライン処理された関数またはループは、<N> クロック サイクル (<N> は関数またはループの II) ごとに新しい入力を処理できます。PIPELINE プラグマのデフォルトの II は 1 で、クロック サイクルごとに 1 つの入力を処理します。開始間隔は、プラグマの II オプションを使用して指定できます。

ループをパイプライン処理すると、次の図に示すように、ループ内の演算が同時実行されるようインプリメントできます。次の図の (A) はデフォルトの順次演算を示しています。各入力は 次の クロック サイクルごとに処理され (II=3)、最後の出力が書き出されるまでに 8 クロック サイクルかかっています。

3: ループのパイプライン


重要: ループのパイプラインは、ループ運搬依存を使用することにより回避できます。DEPENDENCE プラグマでは、ループ運搬依存を克服し、ループをパイプライン処理できるようにする (またはより短い間隔でパイプラインできるようにする) ための追加情報を提供します。

Vivado HLS ツールで指定した II でデザインを作成できない場合は、警告メッセージが表示されて、可能な最短の開始間隔でデザインが作成されます。

警告メッセージを参考にデザインを解析し、必要な開始間隔を満たしてデザインを作成するためにどの手順が必要なのかを判断します。

構文

C ソースの関数の本体またはループ内に配置します。

#pragma HLS pipeline II=<int> enable_flush rewind

説明:

II=<int>
パイプラインの開始間隔を指定します。HLS ツールでは、この指定を満たすことが試みられます。データの依存性によって、実際の開始間隔はこの指定より大きくなることがあります。デフォルトは 1 です。
enable_flush
オプションのキーワードで、パイプラインの入力で有効であったデータが非アクティブになった場合に、データをフラッシュして空にするパイプラインをインプリメントします。
ヒント: この機能は、パイプライン処理された関数でのみサポートされ、パイプライン処理されたループではサポートされません。
rewind
オプションのキーワードで、1 つのループ反復の終了と次の反復の開始の間に一時停止のない連続ループ パイプライン処理 (巻き戻し) をイネーブルにします。巻き戻しは、最上位関数内に 1 つのループしかない (完全なループ ネスト) 場合にのみ効果的です。ループ前のコード部分は、次のようになります。
  • 初期化と認識されます。
  • パイプラインで一度だけ実行されます。
  • 条件演算 (if-else) を含むことはできません。
    ヒント: この機能は、パイプライン処理されたループでのみサポートされ、パイプライン処理された関数ではサポートされません。

例 1

この例では、関数 foo が開始間隔 1 でパイプライン処理されます。

void foo { a, b, c, d} {
  #pragma HLS pipeline II=1
  ...
}
注記: II のデフォルト値は 1 なので、II=1 を指定する必要はありません。

関連項目

pragma HLS reset

説明

特定のステート変数 (グローバルまたはスタティック) のリセットを追加または削除します。

リセット ポートは、リセット信号が適用されたときにリセット ポートに接続されているレジスタおよびブロック RAM を初期値に戻すために FPGA で使用されます。RTL リセット ポートの存在と動作は、config_rtl コンフィギュレーション ファイルで制御されます。リセット設定ではリセットの極性および同期か非同期かを設定できますが、重要なのは、[reset] オプションを使用してリセット信号を適用したときにリセットするレジスタを指定できるということです。詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) の「クロック、リセット、および RTL 出力」を参照してください。

RESET プラグマを使用すると、リセットを詳細に制御できます。変数がスタティックまたはグローバルの場合、RESET プラグマを使用してリセットを明示的に追加したり、off に設定して変数からリセットを削除したりできます。これはスタティック配列またはグローバル配列がデザインに含まれる場合に特に便利なことがあります。

構文

C ソース コードの変数のライフ サイクルの境界内に配置します。

#pragma HLS reset variable=<a> off

説明:

variable=<a>
プラグマを適用する変数を指定します。
off
指定した変数にリセットが生成されないようにします。

例 1

次の例では、グローバル リセット設定が none または control の場合でも、関数 foo の変数 a にリセットが追加されます。

void foo(int in[3], char a, char b, char c, int out[3]) {
#pragma HLS reset variable=a 

例 2

次の例では、グローバル リセット設定が state または all の場合でも、関数 foo の変数 a からリセットが削除されます。

void foo(int in[3], char a, char b, char c, int out[3]) {
#pragma HLS reset variable=a off

pragma HLS resource

説明

RESET プラグマでは、変数 (配列、算術演算、関数引数) を RTL にインプリメントするのに使用するライブラリ リソース (コア) を指定します。RESOURCE プラグマを指定しない場合、Vivado HLS ツールにより使用するリソースが自動的に判断されます。HLS ツールでは、ハードウェア コアを使用して演算がツールインプリメントされます。演算をインプリメントできるコアがライブラリに複数ある場合、RESOURCE プラグマを使用して使用するコアを指定できます。使用可能なコアのリストを生成するには、list_core コマンドを使用します。

ヒント: list_core コマンドを使用すると、ライブラリで使用可能なコアの詳細を表示できます。list_core は HLS ツールの Tcl コマンド インターフェイスでのみ使用でき、set_part コマンドを使用してザイリンクス デバイスを指定する必要があります。デバイスを指定しない場合、list_coreコマンドは実行されません。

たとえば、配列をインプリメントするのに使用するライブラリのメモリ エレメントを RESOURCE プラグマを使用して指定し、配列をシングル ポート RAM とデュアル ポート RAM のどちらとしてインプリメントするかを制御できます。配列に関連付けられているメモリ タイプによって RTL で必要なポートが決まるので、これは最上位関数インターフェイスの配列には重要な方法です。

latency= オプションを使用すると、コアのレイテンシを指定できます。インターフェイスのブロック RAM の場合、latency= オプションを指定すると、たとえばレイテンシ 2 または 3 の SRAM をサポートするなど、インターフェイスにオフチップの標準でない SRAM を記述できます。内部演算の場合、latency= オプションを使用すると、演算をより多くのパイプライン段を使用してインプリメントできます。これらの追加のパイプライン段により、RTL 合成中にタイミング問題を解決しやすくなります。

詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) の「インターフェイスの配列」を参照してください。

重要: latency= オプションを使用するには、使用可能な複数段のコアを演算に含める必要があります。HLS ツールには、基本的な算術演算 (加算、減算、乗算、除算)、すべての浮動小数点演算、およびすべてのブロック RAM 用に複数段コアが含まれています。

より良い結果を得るため、ザイリンクスでは C の場合は -std=c99 を、C および C++ の場合は -fno-builtin を使用することをお勧めします。-std=c99 などの C コンパイル オプションを指定するには、Tcl コマンドの add_files-cflags オプションを使用します。または、[Project Settings] ダイアログ ボックスの Edit CFLAGs ボタンをクリックします。詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) の「新規合成プロジェクトの作成」を参照してください。

構文

C ソースの配列変数が定義されている関数の本体内に配置します。

#pragma HLS resource variable=<variable> core=<core>\
latency=<int>

説明:

variable=<variable>
必須の引数。RESOURCE プラグマを設定する配列、算術演算、または関数引数を指定します。
core=<core>
必須の引数。テクノロジ ライブラリで定義されているのと同じように、コアの名前を指定します。
latency=<int>
コアのレイテンシを指定します。

例 1

次の例では、関数 foo の変数 <c> の乗算をインプリメントするのに 2 段のパイプライン乗算器を指定しています。変数 <d> に使用するコアは、HLS ツールにより決定されます。

int foo (int a, int b) {
int c, d;
#pragma HLS RESOURCE variable=c latency=2
c = a*b;
d = a*c;
return d;
}

例 2

次の例では、変数 <coeffs[128]> が最上位関数の foo_top に対する引数で、coeffs がライブラリの RAM_1P コアを使用してインプリメントされるように指定しています。

#pragma HLS resource variable=coeffs core=RAM_1P 
ヒント: coeffs の値にアクセスするために RTL で作成されるポートは、RAM_1P コアで定義されます。

pragma HLS stream

説明

デフォルトでは、配列変数は RAM としてインプリメントされます。

  • 最上位関数の配列パラメーターは、RAM インターフェイスのポートとしてインプリメントされます。
  • 一般配列は、読み出しおよび書き込みアクセス用に RAM としてインプリメントされます。
  • DATAFLOW 最適化に関連するサブ関数では、配列引数は RAM のピンポン バッファー チャネルを使用してインプリメントされます。
  • ループ ベースの DATAFLOW 最適化に関連する配列は、RAM のピンポン バッファー チャネルを使用してインプリメントされます。

配列に格納されているデータが順次に消費または生成される場合は、STREAM プラグマを指定して RAM ではなく FIFO を使用し、ストリーミング データを使用する方が効率的です。

重要: 最上位関数の引数の INTERFACE タイプが ap_fifo に指定されている場合は、配列は自動的にストリーミングとしてインプリメントされます。

構文

C ソースの必要なロケーションの境界内に配置します。

#pragma HLS stream variable=<variable> depth=<int> dim=<int> off 

説明:

variable=<variable>
ストリーミング インターフェイスとしてインプリメントする配列の名前を指定します。
depth=<int>
DATAFLOW チャネルの配列ストリーミングにのみ適用されます。RTL にインプリメントされる FIFO の深さは、デフォルトでは C コードで指定した配列と同じサイズになります。このオプションを使用すると、FIFO に異なる深さを指定できます。

配列が DATAFLOW 領域にインプリメントされる場合は、depth= オプションで FIFO のサイズを削減する方法がよく使用されます。たとえば、DATAFLOW 領域ですべてのループおよび関数がデータを II=2 のレートで処理する場合、データはクロック サイクルごとに生成および消費されるので、大型 FIFO は必要ありません。この場合、depth= オプションを使用して FIFO サイズを 1 に削減すると、RTL デザインのエリアを大幅に削減できます。

ヒント: config_dataflow -depth コマンドを使用すると、DATAFLOW 領域のすべての配列をストリーミングできます。depth= オプションを指定すると、その <variable> に対して config_dataflow コマンドを無効にできます。
dim=<int>
ストリーミングする配列の次元を指定します。デフォルトは次元 1 です。配列が <N> 次元の場合、0 ~ <N> の整数を指定します。
off
ストリーミング データをディスエーブルにします。DATAFLOW チャネルの配列ストリーミングにのみ適用されます。
ヒント: config_dataflow -default_channel fifo コマンドを使用すると、STREAM をデザインのすべての配列に適用するのと同じになります。off オプションを指定すると、その variable に対して config_dataflow コマンドを無効し、RAM ピンポン バッファー ベースのチャネルを使用したデフォルトでインプリメントできます。

例 1

次の例では、配列 A[10] をストリーミングにし、FIFO としてインプリメントするよう指定しています。

#pragma HLS STREAM variable=A

例 2

次の例では、配列 B が深さ 12 の FIFO でストリーミングされるように設定しています。

#pragma HLS STREAM variable=B depth=12

例 3

次の例では、配列 C のストリーミングをディスエーブルにし、config_dataflow でイネーブルになります。

#pragma HLS STREAM variable=C off

pragma HLS top

説明

関数に名前を付けます。この名前を set_top コマンドで使用し、名前を付けた関数および指定した最上位から呼び出される関数を合成できます。これは通常 C/C++ のクラスのメンバー関数を合成するために使用されます。

このプラグマはアクティブ ソリューションで指定し、その新しい名前を set_top コマンドで使用します。

構文

C ソースの必要なロケーションの境界内に配置します。

#pragma HLS top name=<string>

説明:

name=<string>
set_top コマンドで使用する名前を指定します。

次の例では、関数 foo_long_name を最上位関数に指定し、名前を DESIGN_TOP に変更しています。コードにプラグマを含めた後、Tcl コマンド ラインから、または IDE プロジェクト設定で指定した最上位で set_top コマンドを実行する必要があります。

void foo_long_name () {
  #pragma HLS top name=DESIGN_TOP
  ...
}

set_top DESIGN_TOP

pragma HLS unroll

説明

ループを展開し、複数の演算を 1 つにまとめたものではなく、複数の個別の演算を作成します。UNROLL プラグマを使用すると、RTL デザインにループ本体のコピーを複数作成することにより、一部またはすべてのループ反復を並列実行できるようになります。

C/C++ 関数のループは、デフォルトでは展開されません。ループが展開されていない場合、合成ではそのループの 1 反復に対してロジックが作成され、RTL デザインでこのロジックがループの反復ごとに順に実行されます。ループは、ループ帰納変数で指定されている反復回数実行されます。反復の回数は、break 条件やループ exit 変数の変更など、ループ本体内のロジックにも影響されます。UNROLL プラグマを使用すると、データのアクセスおよびスループットを向上するためにループを展開できます。

UNROLL プラグマでは、ループを完全にまたは部分的に展開できます。ループを完全に展開すると、RTL に各ループ反復対してループ本体のコピーが作成され、ループ全体を同時に実行できるようになります。ループの部分展開では、係数 <N> を指定してループのコピーを <N> 個作成し、ループ反復数を削減します。ループを完全に展開するには、ループの境界がコンパイル時に認識される必要があります。これは部分展開には必要ありません。

ループを部分展開する場合、<N> は最大反復回数の整数因数である必要はありません。Vivado HLS ツールでは、部分展開されたループが元のループと同じように動作することを確認する終了チェックが追加されます。たとえば、次のようなコードがあるとします。

for(int i = 0; i < X; i++) {
  pragma HLS unroll factor=2
  a[i] = b[i] + c[i];
}
ループを係数 2 で展開すると、コードが次のように変換されます。このコードでは、break コンストラクトにより機能が同じになり、ループが適切なポイントで終了します。
for(int i = 0; i < X; i += 2) {
  a[i] = b[i] + c[i];
  if (i+1 >= X) break;
  a[i+1] = b[i+1] + c[i+1];
}

最大反復回数 <X> は変数であり、HLS ツールでその値を判断できないので、部分展開されるループに終了チェックと制御ロジックが追加されます。ただし、展開係数 (この例では 2) が最大反復回数 <X> の整数因数であるとわかっている場合は、skip_exit_check オプションを使用して終了チェックとその関連ロジックを削除できます。これによりエリアが最小限に抑えられ、制御ロジックが単純になります。

ヒント: DATA_PACKARRAY_PARTITIONARRAY_RESHAPE などのプラグマの使用により 1 クロック サイクルでより多くのデータにアクセスできるようになっている場合、このデータを消費するループを展開することによりスループットが向上するのであれば、HLS ツールでこれらのループが自動的に展開されます。ループを完全または部分展開すると、1 クロック サイクルでこれらの追加データを消費するのに十分なハードウェアを作成できます。この機能は、config_unroll コマンドを使用して制御します。詳細は、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902)config_unroll を参照してください。

構文

C/C++ ソースの展開するループの本体内に配置します。

#pragma HLS unroll factor=<N> region skip_exit_check

説明:

factor=<N>
以外の整数値を指定して、部分展開が実行されるようにします。ループの本体が指定した回数繰り返され、反復情報がそれに合わせて調整されます。factor= を指定しない場合、ループは完全に展開されます。
region
オプションのキーワードで、指定したループ自体は展開せずに、そのループの本体 (領域) 内に含まれるすべてのループを展開します。
skip_exit_check
factor= を使用して部分展開を指定している場合にのみ適用されるオプションのキーワードです。ループの反復回数が既知であるかどうかによって、終了チェックが削除されます。
固定 (既知の) 境界
反復回数が係数の倍数である場合は、終了条件はチェックされません。反復回数が係数の整数倍でない場合は、次のようになります。
  1. 展開は実行されません。
  2. 処理を続行するには終了チェックを実行する必要があることを示す警告メッセージが表示されます。
可変 (不明な) 境界
必要に応じて終了条件チェックが削除されます。次を確認してください。
  1. 可変境界が指定した展開係数の整数倍数である。
  2. 終了チェックが不要である。

例 1

次の例では、関数 foo 内の loop_1 を完全に展開しています。プラグマを loop_1 の本体に記述します。

loop_1: for(int i = 0; i < N; i++) {
  #pragma HLS unroll
  a[i] = b[i] + c[i];
}

例 2

次の例では、関数 fooloop_2 を係数 4 で部分展開し、終了チェックを削除しています。

void foo (...) {
  int8 array1[M];
  int12 array2[N];
  ...
  loop_2: for(i=0;i<M;i++) {
    #pragma HLS unroll skip_exit_check factor=4
    array1[i] = ...;  
    array2[i] = ...;
    ...
  }
  ...
}

例 3

次の例では、関数 fooloop_1 内に含まれるすべてのループを完全に展開しますが、region キーワードを使用して loop_1 自体は展開しないようにしています。

void foo(int data_in[N], int scale, int data_out1[N], int data_out2[N]) {
  int temp1[N];
  loop_1: for(int i = 0; i < N; i++) {  
    #pragma HLS unroll region
    temp1[i] = data_in[i] * scale;
      loop_2: for(int j = 0; j < N; j++) {
        data_out1[j] = temp1[j] * 123;
      }
      loop_3: for(int k = 0; k < N; k++) {
        data_out2[k] = temp1[k] * 456;
      }
  }
}

関連項目