インテル高位合成 (HLS) コンパイラー プロ・エディション: ベスト・プラクティス・ガイド
バージョン情報
更新対象: |
---|
インテル® Quartus® Prime デザインスイート 19.4 |
この翻訳版は参照用であり、翻訳版と英語版の内容に相違がある場合は、英語版が優先されるものとします。翻訳版は、資料によっては英語版の更新に対応していない場合があります。最新情報につきまし ては、必ず英語版の最新資料をご確認ください。 |
1. インテルHLSコンパイラー プロ・エディション ベスト・プラクティス・ガイド
本文書では、 <quartus_installdir> の指すロケーションは、 インテル® Quartus® Primeデザインスイートをインストールした場所です。
- Windows
- C:\intelFPGA_pro\19.4
- Linux
- /home/<username>/intelFPGA_pro/19.4
インテル®HLSコンパイラードキュメント・ライブラリーについて
タイトルと説明 | ||
---|---|---|
Release
Notes
インテル®HLSコンパイラーに関する最新情報を提供しています。 |
リンク | リンク |
スタートガイド
インテル®HLSコンパイラーコンパイラーを作動させるために、コンパイラー環境を初期化する方法を身に付け、また、 インテル®HLSコンパイラーで用意されているさまざまなデザイン例とチュートリアルを確認します。 |
リンク | リンク |
ユーザーガイド
インテルFPGA製品向けにデザインする知的財産 (IP) の合成、検証、およびシミュレーションに関する手順を説明しています。コンポーネントの開発フロー全体の確認ができます。コンポーネントやテストベンチの作成から、コンポーネントのIPをインテルQuartus Prime開発ソフトウェアを使用してより大きなシステムに統合するまでのフローについてカバーしています。 |
リンク | リンク |
リファレンス・マニュアル
インテルHLSコンパイラーでサポートしている機能に関する参照情報を提供しています。 インテル®HLSコンパイラーのコマンドオプション、ヘッダーファイル、プラグマ、属性、マクロ、宣言、引数、テンプレート・ライブラリーに関する詳細が確認できます。 |
リンク | リンク |
ベスト・プラクティス・ガイド
ここで紹介している手法と実践方法を使用することによって、HLSコンポーネントのFPGA領域の使用とパフォーマンスを向上させます。通常、このベスト・プラクティスは、コンポーネントの機能の正確性の確認後に適用します。 |
リンク | リンク |
Quick
Reference
インテルHLSコンパイラーの宣言と属性の概要を2ページ (両面印刷で1枚) で提供しています。 |
リンク | リンク |
2. コンポーネントのコーディングおよびコンパイルのベスト・プラクティス
-
インターフェイスのベスト・プラクティス
インテル®HLS (高位合成) コンパイラーを使用すると、コンポーネントでは、基本的なワイヤーからAvalon StreamingおよびAvalon Memory-Mapped Masterインターフェイスまでのさまざまなインターフェイスが使用できます。インターフェイスのベスト・プラクティスは、コンポーネントに適切なインターフェイスの選択やコンフィグレーションに役立ちます。
-
ループのベスト・プラクティス
インテル®HLS (高位合成) コンパイラーでは、ループをパイプライン処理してスループットを向上させます。このループのベスト・プラクティスでは、ループを最適化してコンポーネントのパフォーマンスを向上させる手法を身に付けることができます。
-
メモリー・アーキテクチャーのベスト・プラクティス
インテル®HLS (高位合成) コンパイラーでは、コンポーネント内の効率的なメモリー・アーキテクチャー (メモリー幅、バンク数、ポート数など) を推測します。そのために インテル®HLS (高位合成) コンパイラーでは、アーキテクチャーをコンポーネントのメモリー・アクセス・パターンに適応させます。メモリー・アーキテクチャーのベスト・プラクティスでは、コンポーネントの最適なメモリー・アーキテクチャーをコンパイラーから得る方法を身に付けます。
-
タスクのシステムのベスト・プラクティス
HLSタスクのシステムをコンポーネントで使用すると、実装可能なさまざまなデザイン構造が可能になります。これには、複数のループの並列実行や負荷のかかる計算ブロックの共有などがあります。
-
データ型のベスト・プラクティス
コンポーネントのデータ型や、データ型が受ける可能性のある変換またはキャストは、コンポーネントのパフォーマンスやFPGA領域の使用に大きく影響する可能性があります。データ型のベスト・プラクティスでは、コンポーネントのサイズや変換を制御するための最適な方法についてのヒントとガイダンスが確認できます。
- 代替アルゴリズム
インテル®HLS (高位合成) コンパイラーを使用すると、コンポーネントのコンパイルを迅速に行うことができ、コンポーネントのパフォーマンスと領域使用率に関する初期の見通しが得られます。この迅速さを利用して、より大規模なアルゴリズム変更を試し、その変更がコンポーネントのパフォーマンスに与える影響について確認します。
3. インターフェイスのベスト・プラクティス
インテル®HLS (高位合成) コンパイラーを使用すると、コンポーネントでは、基本的なワイヤーからAvalon StreamingおよびAvalon Memory-Mapped Masterインターフェイスまでのさまざまなインターフェイスが使用できます。インターフェイスのベスト・プラクティスは、コンポーネントに適切なインターフェイスの選択やコンフィグレーションに役立ちます。
インテル®HLSコンパイラー プロ・エディションでサポートする各インターフェイスには、そのタイプによって異なる利点がああります。しかしながら、コンポーネントを囲むシステムによって、インターフェイスの選択が制限される場合があります。コンポーネントにとって最適なインターフェイスを決定する際には、ご自身の必要条件を念頭に置いてください。
インターフェイスのベスト・プラクティスの説明
インテル®HLSコンパイラー プロ・エディションに付属している多数のチュートリアルでは、 インテル®HLSコンパイラーの重要な概念や優れたコーディング・プラクティスについて説明しています。
チュートリアル | 内容 |
---|---|
この表のチュートリアルは、
インテル®
Quartus® Primeシステム内の次の場所にあります。<quartus_installdir>/hls/examples/tutorials |
|
interfaces/ overview | コンポーネント・アルゴリズムが変化しない状態でも、異なるコンポーネント・インターフェイスの選択が結果の品質 (QoR) に与える影響を示します。 |
best_practices/ parameter_aliasing |
コンポーネント引数に __restrict キーワードを使用する方法を示します。 |
best_practices/ lsu_control | 可変遅延 Avalon® Memory Mapped Masterインターフェイス向けにインスタンス化されたLSUのタイプの制御効果を示します。 |
interfaces/ explicit_streams_buffer |
明示的 stream_in および stream_out インターフェイスをコンポーネントおよびテストベンチで使用する方法を示します。 |
interfaces/ explicit_streams_packets_empty | usesPackets 、usesEmpty 、および firstSymbolInHighOrderBits ストリーム・テンプレート・パラメーターの使用方法を示します。 |
interfaces/explicit_streams_packets_ready_valid | usesPackets、 usesValid および usesReady ストリーム・テンプレート・パラメーターの使用方法を示します。 |
interfaces/ explicit_streams_ready_latency | readyLatency ストリーム・テンプレート・パラメーターを使用したストリーム書き込みで、より優れたループ開始間隔 (II) を達成する方法を示します。 |
interfaces/ mm_master_testbench_operators | Avalon Memory Mapped (MM) Master (mm_master class) インターフェイスのさまざまなインデックスでコンポーネントを呼び出す方法を示します。 |
interfaces/ mm_slaves | Avalon-MM Slaveインターフェイス (スレーブレジスターとスレーブメモリー) の作成方法を示します。 |
interfaces/ multiple_stream_call_sites | 複数のストリーム呼び出しサイトを使用する場合のトレードオフを示します。 |
interfaces/ pointer_mm_master | Avalon-MM Masterインターフェイスの作成方法とそのパラメーターの制御方法を示します。 |
interfaces/ stable_arguments | 不変引数のstable属性を使用してリソース使用率を向上させる方法を示します。 |
3.1. コンポーネントに適したインターフェイスの選択
コンポーネント・インターフェイスが異なると、コンポーネントの結果の品質 (QoR) に影響します。このとき、コンポーネント・アルゴリズムは変更されません。インターフェイスの違いによる影響について考慮した上で、コンポーネントのインターフェイスを選択してください。
コンポーネントに最適なインターフェイスが、すぐに明らかにならない場合もあるでしょう。その場合は、さまざまなインターフェイスをコンポーネントに対して試してみて、最適なQoRを実現することが必要になることがあります。 インテル®HLSコンパイラー プロ・エディションで提供されるコンポーネントの高速コンパイル時間や、その結果得られるHigh Level Designレポートを活用して、コンポーネントに最適なQoRを提供するインターフェイスを決定します。
この項では、ベクトルの追加例を使用して、コンポーネント・アルゴリズムを同じ状態に保った場合のコンポーネント・インターフェイスの変更による影響について説明します。この例では、ベクトル a とベクトル b の2つの入力ベクトルがあり、結果をベクトル c に格納します。ベクトルの長さは N です (これは非常に大きくなる可能性があります)。
#pragma unroll 8 for (int i = 0; i < N; ++i) { c[i] = a[i] + b[i]; }
インテル®HLSコンパイラー プロ・エディションでは、ループに依存関係が存在しない場合、このアルゴリズムの並列性を抽出するために、ループをパイプライン処理します。また、ループを (係数8で) 展開することで、より多くの並列性が抽出できます。
生成されたコンポーネントの理想的なレイテンシーは、N/8サイクルです。次の項で挙げる例では、1024の値は N に使用されるため、理想的なレイテンシーは128サイクル (1024/8) です。
次のいくつかの項で示すとおり、この例のバリエーションでは、異なるインターフェイスを使用しています。それぞれのインターフェイスがこのコンポーネントのQoRにどのような影響を及ぼすかを確認してください。
このような例のそれぞれのバリエーションについては、 <quartus_installdir>/hls/examples/tutorials/interfaces/overview にあるチュートリアルを確認してください。
3.1.1. ポインター・インターフェイス
コンポーネント内のポインターは、 Avalon® Memory Mapped (Avalon-MM) として、デフォルト設定で実装されます。ポインター・パラメーター・インターフェイスについて詳しくは、 Intel High Level Synthesis Compiler Pro Edition Reference Manual内の Intel HLS Compiler Default Interfacesを参照してください。
component void vector_add(int* a, int* b, int* c, int N) { #pragma unroll 8 for (int i = 0; i < N; ++i) { c[i] = a[i] + b[i]; } }

次のLoop Analysisレポートで示しているとおり、コンポーネントのループ開始間隔 (II) が大きくなってしまっています。IIが大きいのは、ベクトル a 、 b 、および c からのアクセスがすべて、同じAvalon-MM Masterインターフェイスを介して行われるためです。 インテル®HLSコンパイラー プロ・エディションでは、ストール可能なアービトレーション・ロジックを使用してこのようなアクセスをスケジュールします。これにより、パフォーマンスが低下し、FPGA領域の使用率が高くなります。
さらに、コンパイラーでは、ループ・イタレーション間にデータ依存関係がないと仮定することはできません。これは、ポインターのエイリアシングが存在する可能性があるためです。コンパイラーでは、ベクトル a 、 b 、および c が重複していないことは、判定できません。データの依存関係が存在する場合、 インテル®HLSコンパイラーでは、ループ・イタレーションの効果的なパイプライン処理はできません。

QoRメトリック | 値 |
---|---|
ALM | 15593.5 |
DSP | 0 |
RAM | 30 |
fmax (MHz)2 | 298.6 |
レイテンシー (サイクル) | 24071 |
開始間隔 (II) (サイクル) | 約508 |
1QoRメトリックの計算に使用されたコンパイルフローでは、インテル Quartus Prime プロ・エディションのバージョン17.1を使用しています。 |
2fmax の測定値は1シードから計算しています。 |
3.1.2. Avalon Memory Mappedマスター・インターフェイス
デフォルトでは、コンポーネント内のポインターの実装は、 Avalon® -MM (Avalon Memory Mapped) マスター・インターフェイスのデフォルト設定になっています。このデフォルト設定によるパフォーマンスの低下を緩和するには、Avalon-MMマスター・インターフェイスのコンフィグレーションを実行します。
Yベクトル加算コンポーネントの例の Avalon® MMマスター・インターフェイスのコンフィグレーションには、 ihc::mm_master クラスを次のとおり使用します。
component void vector_add( ihc::mm_master<int, ihc::aspace<1>, ihc::dwidth<8*8*sizeof(int)>, ihc::align<8*sizeof(int)> >& a, ihc::mm_master<int, ihc::aspace<2>, ihc::dwidth<8*8*sizeof(int)>, ihc::align<8*sizeof(int)> >& b, ihc::mm_master<int, ihc::aspace<3>, ihc::dwidth<8*8*sizeof(int)>, ihc::align<8*sizeof(int)> >& c, int N) { #pragma unroll 8 for (int i = 0; i < N; ++i) { c[i] = a[i] + b[i]; } }
- ベクトルは、異なるアドレス空間にそれぞれ割り当てられ、
ihc::aspace
属性が指定されます。また、各ベクトルでは、別々の
Avalon®
MMマスター・インターフェイスを受け取ります。
ベクトルは、異なる物理インターフェイスに割り当てられると、相互干渉することなく同時アクセスが可能になるため、メモリー・アービトレーションは不要です。
- ベクトルでのインターフェイス幅は、ihc::dwidth 属性で調整されます。
- ベクトルでのインターフェイスのアライメントは、ihc::align 属性で調整されます。

この図で示しているとおり、 vector_add.B2 には2ロードと1ストアがあります。 ポインター・インターフェイス のコード例で使用されるデフォルトの Avalon® MMマスター設定には、16ロードと8ストアがあります。
ベクトル・インターフェイスの幅とアライメントを拡張することにより、元のポインター・インターフェイスのロードおよびストアは結合されて、ベクトル a とベクトル b ではそれぞれ 1 ワイドロードに、ベクトルcでは 1 ワイドストア になっています。
また、メモリーはストールフリーです。これは、この例のロードおよびストアから個別のメモリーにアクセスするためです。
QoR メトリック | ポインター | Avalon MMマスター |
---|---|---|
ALM | 15593.5 | 643 |
DSP | 0 | 0 |
RAM | 30 | 0 |
fMAX (MHz)2 | 298.6 | 472.37 |
レイテンシー (サイクル) | 24071 | 142 |
開始間隔 (II) (サイクル) | ~508 | 1 |
1QoRメトリックの計算に使用されたコンパイルフローでは、インテル Quartus Prime プロ・エディションのバージョン17.1を使用しています。 |
2fmax の測定値は1シードから計算しています。 |
3.1.3. Avalon Memory Mappedスレーブ・インターフェイス
スレーブメモリーの割り当ての際には、そのサイズを定義してください。サイズを定義すると、コンポーネントによって処理可能な N の値に制限が課されます。この例では、RAMサイズは1024ワードです。このRAMサイズの意味は、N が持つことができる最大サイズは1024ワードであるということです。
component void vector_add( hls_avalon_slave_memory_argument(1024*sizeof(int)) int* a, hls_avalon_slave_memory_argument(1024*sizeof(int)) int* b, hls_avalon_slave_memory_argument(1024*sizeof(int)) int* c, int N) { #pragma unroll 8 for (int i = 0; i < N; ++i) { c[i] = a[i] + b[i]; } }

QoR メトリック | ポインター | Avalon® MMマスター | Avalon® MMスレーブ |
---|---|---|---|
ALM | 15593.5 | 643 | 490.5 |
DSP | 0 | 0 | 0 |
RAM | 30 | 0 | 48 |
fMAX (MHz)2 | 298.6 | 472.37 | 498.26 |
レイテンシー (サイクル) | 24071 | 142 | 139 |
開始間隔 (II) (サイクル) | ~508 | 1 | 1 |
1QoRメトリックの計算に使用されたコンパイルフローでは、インテル Quartus Prime プロ・エディションのバージョン17.1を使用しています。 |
2fmax の測定値は1シードから計算しています。 |
3.1.4. Avalon Streaming インターフェイス
Avalon® Streaming (Avalon ST) インターフェイスでは、データの単方向フローをサポートします。また、通常使用されるのは、高帯域幅および低レイテンシー・データを駆動するコンポーネントです。
struct int_v8 { int data[8]; }; component void vector_add( ihc::stream_in<int_v8>& a, ihc::stream_in<int_v8>& b, ihc::stream_out<int_v8>& c, int N) { for (int j = 0; j < (N/8); ++j) { int_v8 av = a.read(); int_v8 bv = b.read(); int_v8 cv; #pragma unroll 8 for (int i = 0; i < 8; ++i) { cv.data[i] = av.data[i] + bv.data[i]; } c.write(cv); } }
Avalon® STインターフェイスには、データバス、およびハンドシェイクのためのready信号とbusy信号があります。struct は、8つの整数をパックするように生成され、一度に8演算を並列処理して、他のインターフェイスの例と比較できます。同様に、ループカウントは8で除算されます。

ストリーミング・インターフェイスは、アップストリーム・ソースおよびダウンストリーム出力からストール可能です。インターフェイスがストール可能なため、ループの開始間隔 (II) の値は約1 (正確に1ではない ) です。コンポーネントで、アップストリームからのバブル (データフロー内のギャップ)またはダウンストリームからのstall信号の受け取りがない場合 、コンポーネントでは望ましいII = 1を達成します。
ストリーム・インターフェイスがストールしないことが分かっている場合は、このコンポーネントをさらに最適化することができます。このためには、 usesReady および usesValid ストリーム・パラメーターを活用します。
QoR メトリック | ポインター | Avalon® MMマスター | Avalon® MMスレーブ | Avalon® ST |
---|---|---|---|---|
ALM | 15593.5 | 643 | 490.5 | 314.5 |
DSP | 0 | 0 | 0 | 0 |
RAM | 30 | 0 | 48 | 0 |
fMAX (MHz)2 | 298.6 | 472.37 | 498.26 | 389.71 |
レイテンシー (サイクル) | 24071 | 142 | 139 | 134 |
開始間隔 (II) (サイクル) | ~508 | 1 | 1 | 1 |
1QoRメトリックの計算に使用されたコンパイルフローでは、インテル Quartus Prime プロ・エディションのバージョン17.1を使用しています。 |
2fmax の測定値は1シードから計算しています。 |
3.1.5. 値渡しインターフェイス
CPUをターゲットとするコードの記述に慣れているソフトウェア開発者にとっては、配列内の各要素を値で渡すことは、直感的ではないかもしれません。これは、一般的に、多くの関数呼び出しや大きなパラメーターをもたらすからです。しかし、FPGAをターゲットとするコードでは、配列要素を値渡しすることでFPGA上のハードウェアをより小さくよりシンプルなものにすることができます。
struct int_v8 { int data[8]; }; component int_v8 vector_add( int_v8 a, int_v8 b) { int_v8 c; #pragma unroll 8 for (int i = 0; i < 8; ++i) { c.data[i] = a.data[i] + b.data[i]; } return c; }
このコンポーネントでは、ベクトル a とベクトル b の8要素のみを受け取って処理し、ベクトル c の8要素を返します。例の1024要素を計算するには、コンポーネントのコールが128回 (1024/8) 必要です。前述の例ではコンポーネントのループがパイプライン処理されましたが、この例ではコンポーネント・コールがパイプライン処理されます。

QoR メトリック | ポインター | Avalon® MMマスター | Avalon® MMスレーブ | Avalon® ST | 値渡し |
---|---|---|---|---|---|
ALM | 15593.5 | 643 | 490.5 | 314.5 | 130 |
DSP | 0 | 0 | 0 | 0 | 0 |
RAM | 30 | 0 | 48 | 0 | 0 |
fMAX (MHz)2 | 298.6 | 472.37 | 498.26 | 389.71 | 581.06 |
レイテンシー (サイクル) | 24071 | 142 | 139 | 134 | 128 |
開始間隔 (II) (サイクル) | ~508 | 1 | 1 | 1 | 1 |
1QoRメトリックの計算に使用されたコンパイルフローでは、インテル Quartus Prime プロ・エディションのバージョン17.1を使用しています。 |
2fmax の測定値は1シードから計算しています。 |
3.2. 可変遅延MMマスター・インターフェイスのLSUの制御
ロード・ストア・ユニット (LSU) は、 インテル®HLSコンパイラー プロ・エディションによって可変レイテンシーMemory Mapped (MM) Masterインターフェイスとの対話に使用されます。LSUのタイプを制御すると、デザインの面積が節約できます。また、ロード/ストアと他のロード/ストア動作の静的結合をディスエーブルすると、デザインのパフォーマンスが向上する場合があります。
次のチュートリアルで、LSUの制御について確認してください。 <quartus_installdir>/hls/examples/tutorials/best_practices/lsu_control
LSU制御を使用する必要性を確認するには、コンポーネント、特にFunction Memory ViewerのHigh-Level Design Reportを確認します。その上で、メモリー・アクセス・パターン (およびその関連LSU) が、 インテル®HLSコンパイラー プロ・エディションで予想するメモリー・アクセス・パターンと一致するかどうかを確認します。一致しない場合は、LSUタイプ、LSU結合、またはその両方の制御を検討してください。
作成されたLSUのタイプを制御する
インテル®HLSコンパイラー プロ・エディションでは、バースト結合LSUまたはパイプラインLSUを作成します。
通常、バースト結合LSUを使用するのは、LSUによる連続するメモリーワードへのロード/ストア要求の処理が多数予想される場合です。バースト結合したLSUでは、メモリー帯域幅をより効率的に利用するために、要求を「動的結合」して大きなバーストにしようとします。
パイプライン処理されたLSUでは、消費するFPGA領域は大幅に少なくなりますが、ロード/ストアリクエストの処理は、結合はしないで個別に行います。この処理が役立つのは、デザインが面積の点で厳しい場合や、可変遅延MM Masterインターフェイスへのアクセスが必ずしも連続していない場合です。
component void dut(mm_master<int, dwidth<128>, awidth<32>, aspace<4>, latency<0>> &Buff1, mm_master<int, dwidth<32>, awidth<32>, aspace<5>, latency<0>> &Buff2) { int Temp[SIZE]; using pipelined = lsu<style<PIPELINED>>; using burst_coalesced = lsu<style<BURST_COALESCED>>; for (int i = 0; i<SIZE; i++) { Temp[i] = burst_coalesced::load(&Buff1[i]); // Burst-Coalesced LSU } for (int i = 0; i<SIZE; i++) { pipelined::store(&Buff2[i], 2*Temp[i]); // Pipelined LSU } }
静的結合のディスエーブル
静的結合が一般的に有益である理由は、デザイン内のLSUの総数を削減するため、複数のロード/ストア動作を組み合わせて、広い静的ロード/ストア動作にするからです。
しかし、場合によっては、静的結合により、非整列アクセスが発生します。また、一部のロード/ストアのみが同時に動作することを意図していた場合でも、複数のロード/ストアが結合する場合があります。このような場合、結合させたくないロード/ストア動作に対して静的結合をディスエーブルすることを検討してください。
component int dut(mm_master<int, dwidth<256>, awidth<32>, aspace<1>, latency<0>> &Buff1, int i, bool Cond1, bool Cond2) { using no_coalescing = lsu<style<PIPELINED>, static_coalescing<false>>; int Val = 0; if (Cond1) { Val = no_coalescing::load(&Buff1[i]); } if (Cond2) { Val = no_coalescing::load(&Buff1[i + 1]); } return Val; }
3.3. ポインター・エイリアシングの回避
可能な場合は常に、制限型修飾子をポインター型に追加します。制限型修飾子ポインターを使用することにより、不要なメモリー依存関係が、非競合読み出し動作と書き込み動作との間に インテル®HLSコンパイラー プロ・エディションによって作成されるのを防ぎます。
制限型修飾子は __restrict です。
ループでの各イタレーションによるデータの読み出しが、1つの配列から行われる場合について検討します。この場合、各イタレーションよるデータの書き込みは、同じ物理メモリー内の別の配列に対して行われます。制限型修飾子をこのポインター引数に追加しない場合、コンパイラーでは、2つの配列が重複していると仮定します。そうすると、コンパイラーでは、メモリーアクセスの元の順序を両方の配列に対して維持する必要があります。その結果、ループの最適化が不十分になったり、メモリーアクセスを含むループのパイプライン処理が失敗したりします。
<quartus_installdir>/hls/examples/tutorials/best_practices/parameter_aliasing
4. ループのベスト・プラクティス
インテル®HLSコンパイラー プロ・エディションでは、ループの最適化を妨げる依存関係があるかどうかを知らせます。コード内にそのような依存関係がある場合は、それを排除して、コンポーネントのパフォーマンスを最適化してください。さらに、追加のガイダンスをコンパイラーに対して提供することもできます。このためには、入手可能なループプラグマを使用します。
- 隣接するループ本体の命令を並列実行できる場合は、そのループ本体を手動で融合します。この融合ループは、順次実行せずにパイプライン処理します。パイプライン処理によって、コンポーネントのレイテンシーが短縮され、コンポーネントで使用するFPGA領域の削減ができます。
- #pragma loop_coalesce ディレクティブを使用して、ネストされたループの折りたたみをコンパイラーに試行させます。ループを結合すると、コンポーネントのレイテンシーが短縮され、ネストされたループに必要なFPGA領域のオーバーヘッドを削減することができます。
- 2つのループの並列実行が可能な場合は、タスクのシステムの使用を検討してください。詳しくは、タスクのシステム を参照してください。
ループのベスト・プラクティスに関するチュートリアル
インテル®HLSコンパイラー プロ・エディションに付属している多数のチュートリアルでは、 インテル®HLSコンパイラーの重要な概念や優れたコーディング・プラクティスを示しています。
チュートリアル | 説明 |
---|---|
この表のチュートリアルは、
インテル®
Quartus® Primeシステム内の次の場所にあります。<quartus_installdir>/hls/examples/tutorials |
|
best_practices/ loop_coalesce | ネストされたループで loop_coalesce プラグマを使用した場合のパフォーマンスとリソース使用率の向上について示します。 |
best_practices/ loop_memory_dependency | ループ伝送された依存関係の解除をivdepプラグマを使用して行う方法について示します。 |
best_practices/ resource_sharing_filter | 次のバージョンの32タップ有限インパルス応答 (FIR) フィルターデザインについて示します。
|
best_practices/ divergent_loops | 発散ループを備えたデザインのソースレベルを示します。 |
best_practices/ triangular_loop | 依存関係を持つ三角ループパターンの記述方法を示します。 |
loop_controls/ max_interleaving |
次の条件を満たすループの領域使用率の削減方法を示します。
|
best_practices/ relax_reduction_dependency |
浮動小数点アキュムレーターを含むループのIIの削減方法、またはその他の単一クロックサイクルでは高速計算ができない削減動作について示します。 |
4.1. ループでの呼び出しによるハードウェアの再利用
ループは、ハードウェアを再利用する便利な方法です。コンポーネント関数で別の関数をコールすると、コールされた関数はトップレベルのコンポーネントになります。関数を複数回コールすると、ハードウェアが重複します。
int foo(int a) { return 4 + sqrt(a) / } component void myComponent() { ... int x = x += foo(0); x += foo(1); x += foo(2); ... }
component void myComponent() { ... int x = 0; #pragma unroll 1 for (int i = 0; i < 3; i++) { x += foo(i); } ... }
component void myComponent() { ... int x = 0; #pragma unroll 1 for (int i = 0; i < 3; i++) { int val = 0; switch(i) { case 0: val = 3; break; case 1: val = 6; break; case 2: val = 1; break; } x += foo(val); } ... } H
ハードウェアの再利用とインラインの最小化ついては、リソース共有チュートリアルを参照してください。これは、次のウェブサイトで入手可能です。 <quartus_installdir>/hls/examples/tutorials/best_practices/resource_sharing_filter
4.2. ループの並列化
空間計算構造を利用して、ループの高速化ができます。このためには、ループの複数のイタレーションを同時に実行します。ループの複数のイタレーションを同時に実行するには、可能であればループを展開し、また、ループの構成は、ループ・イタレーション間の依存関係が最小化されて、1クロックサイクル内で解決できるようにします。
このようなプラクティスで示しているのは、同じループの異なるイタレーションを並列化する方法です。異なる2つのループを並列化する場合は、タスクのシステムの使用を検討してください。詳細については、タスクのシステム を参照してください。
4.2.1. ループのパイプライン処理


このループのパイプライン処理では、ループの開始間隔 (II) の値は1です。II の値が1ということは、1クロックサイクルの遅延が、連続した各ループのイタレーションが開始する間に存在するという意味です。
The インテル®HLSコンパイラー プロ・エディション では、デフォルトでループのパイプライン処理を試みます。ループのパイプライン処理は、ループの展開と同じ一定のイタレーション回数の制約は受けません。
すべてのループが、図 7 で示されるループと同じ様にパイプライン処理できるわけではありません。特に、各イタレーションの依存する値が、前のイタレーションで計算された値である場合はそうです。
例えば、Stage 1のループが、前のループ・イタレーションのStage 3の処理中に計算された値に依存するとします。このような場合、2回目 (オレンジ色) のイタレーションによる実行は、1回目 (青色) のイタレーションがステージ Stage 3に到達するまでは開始できません。このような依存関係は、ループ搬送依存関係 と呼ばれます。
この例では、ループのパイプライン処理は、II = 3で行われます。IIはループ・イタレーションのレイテンシーと同じであるため、ループは、実際にはパイプライン処理されません。ループの合計レイテンシーの見積もりは、次の数式ですることができます。
ここでは、 は、ループの実行にかかるサイクル数です。また、 iは、1回のループ・イタレーションの実行にかかるサイクル数です。
インテル®HLSコンパイラー プロ・エディション では、ネストされたループのパイプライン処理をサポートします。このとき内部ループは展開しません。ネストされたループのレイテンシーを計算するとき、この式を再帰的に適用します。この再帰が意味するのは、II>1を持つことによる問題は、外側のループに対してよりも内側のループに対することです。したがって、アルゴリズムは、ほとんどの作業をII=1の内側のループで行う場合は、外側のループがII>1であっても、引き続き良好に機能します。
4.2.2. ループの展開


コンパイラーによるループの展開方法の制御には、#pragma unroll ディレクティブを使用できますが、このディレクティブが機能するのは、コンパイラーでループのトリップ数を把握している場合、または展開係数を指定した場合のみです。コンパイラーでは、ハードウェアの複製のほかにも、回路の再スケジュールを行います。これにより、各演算は、演算の入力準備ができ次第実行されます。
#pragma unroll ディレクティブの使用例については、best_practices/resource_sharing_filter チュートリアルを参照してください。
4.2.3. 例 : ループのパイプライン処理および展開
1. #define ROWS 4 2. #define COLS 4 3. 4. component void dut(...) { 5. float a_matrix[COLS][ROWS]; // store in column-major format 6. float r_matrix[ROWS][COLS]; // store in row-major format 7. 8. // setup... 9. 10. for (int i = 0; i < COLS; i++) { 11. for (int j = i + 1; j < COLS; j++) { 12. 13. float dotProduct = 0; 14. for (int mRow = 0; mRow < ROWS; mRow++) { 15. dotProduct += a_matrix[i][mRow] * a_matrix[j][mRow]; 16. } 17. r_matrix[i][j] = dotProduct; 18. } 19. } 20. 21. // continue... 22. 23. }
このコンポーネントのパフォーマンスを向上させるには、特定のカラムの各エントリーで繰り返されるループを展開します。ループ動作が独立している場合、コンパイラーでは、それを並列実行します。
浮動小数点演算を実行する順序は、通常、ソースコードで表現されているのと同じにして、数値精度を維持してください。ただし、--ffp-contract=fast コンパイラー・フラグを使用して、浮動小数点演算の順序を緩和することができます。浮動小数点演算の順序を緩和すると、このループ内のすべての乗算が並行して発生する場合があります。詳細については、次のチュートリアルを確認してください。 <quartus_installdir>/hls/examples/ tutorials/best_practices/ floating_point_ops
コンパイラーでは、展開することでパフォーマンスが向上すると考えられる場合、ループを単独で展開しようとします。例えば、14行目のループは自動展開されます。これは、ループのイタレーション回数は一定であり、多くのハードウェアを消費しないためです。(ROWS は、コンパイル時に定義された定数です。これにより、このループのイタレーション回数が一定になります。)
01: #define ROWS 4 02: #define COLS 4 03: 04: component void dut(...) { 05: float a_matrix[COLS][ROWS]; // store in column-major format 06: float r_matrix[ROWS][COLS]; // store in row-major format 07: 08: // setup... 09: 10: for (int i = 0; i < COLS; i++) { 11: 12: #pragma unroll 13: for (int j = 0; j < COLS; j++) { 14: float dotProduct = 0; 15: 16: #pragma unroll 17: for (int mRow = 0; mRow < ROWS; mRow++) { 18: dotProduct += a_matrix[i][mRow] * a_matrix[j][mRow]; 19: } 20: 21: r_matrix[i][j] = (j > i) ? dotProduct : 0; // predication 22: } 23: } 24: } 25: 26: // continue... 27: 28: }
これで j ループが完全に展開されました。依存関係がないため、4イタレーションすべてが同時に実行されます。
詳しくは、resource_sharing_filter チュートリアルを参照してください。このチュートリアルの場所は次のとおりです。 <quartus_installdir>/hls/examples/tutorials/best_practices/resource_sharing_filter
続行して、ループの展開を10行目ですることもできますが、このループを展開すると、面積が再び増加します。コンパイラーで、このループを展開するのではなく、パイプライン処理できるようにすると、面積の増加が回避でき、さらに使うのは約4クロックサイクルのみになります ( i ループのIIが1のみの場合)。ハイレベルのデザインレポートのLoops Analysis (report.html ) の詳細ペインには、この改善方法のヒントが表示されます。
- ループ搬送依存関係
<quartus_installdir>/hls/examples/tutorials/best_practices/loop_memory_dependency にあるチュートリアルを参照してください。
- 長いクリティカル・ループ・パス
- ループII > 1の内側のループ
4.3. 整形式ループの構造
整形式ループの終了条件では、整数境界との比較ができます。また整形式ループでは、各イタレーションごとにシンプルな誘導インクリメントを1つ備えています。 インテル®HLSコンパイラー プロ・エディションでは、整形式ループの解析を効率的に行うことができます。これにより、コンポーネントのパフォーマンスが向上します。
for(i=0; i < N; i++) { //statements }
また、整形式のネスト化ループは、コンポーネントのパフォーマンスを最大にするのに役立ちます。
for(i=0; i < N; i++) { //statements for(j=0; j < M; j++) { //statements } }
4.4. ループ搬送依存関係の最小化
次のループ構造にはループ搬送依存関係があります。これは、各ループ・イタレーションでは、前のイタレーションによって書き込まれたデータを読み出すためです。その結果、各読み出し動作の続行は、前のイタレーションからの書き込み動作が終了するまではできません。ループ搬送依存関係が存在すると、 インテル®HLSコンパイラー プロ・エディションによって達成できるパイプライン並列性を低下させます。これにより、カーネルのパフォーマンスが低下します。
for(int i = 1; i < N; i++) { A[i] = A[i - 1] + i; }
インテル®HLSコンパイラー プロ・エディションでは、スタティック・メモリー依存関係の解析をループで実行し、達成できる並列性の程度を判断します。 インテル®HLSコンパイラー プロ・エディションでは、ループ搬送依存関係がないと判断できない場合、ループ搬送依存関係は存在するとみなします。コンパイル時に未知の変数がある場合や、コード内の配列アクセスに複雑なアドレッシングが含まれる場合は、コンパイラーのループ搬送依存関係のテスト機能が妨げられます。
不要なループ搬送依存関係を回避し、コンパイラーによるループの解析が適切にできるようにするには、次のガイドラインに従います。
ポインター演算の回避
コンパイラーの出力が最適にならないのは、コンポーネントによる配列へのアクセスするために、算術演算から導出されたポインター値を逆参照する場合です。例えば、次のように配列へのアクセスを回避します。
for(int i = 0; i < N; i++) { int t = *(A++); *A = t; }
シンプルな配列インデックスの導入
複雑な配列インデックスの中には、効率的な解析ができないものがあります。これは、最適ではないコンパイラーの出力に繋がる可能性があります。次のような構造は、できるだけ避けてください。- 配列インデックスの非定数
例えば、A[K + i] では、i はループ・インデックス変数、K は未知の変数です。
- 同じサブスクリプト位置の複数のインデックス変数
例えば、A[i + 2 × j] では、i および j は、二重ネスト化ループのループ・インデックス変数です。
配列インデックス A[i][j] は、インデックス変数が異なるサブスクリプトであるため、効率的な解析ができます。
- 非線形インデックス
例えば、A[i & C] では、i はループ・インデックス変数で、C は定数または非定数の変数です。
定数の境界でのループの使用 (可能な場合)
コンパイラーによる範囲解析が効果的に実行できるのは、ループに定数の境界がある場合です。
if ステートメントをループ内に配置して、どのイタレーションでループ本体を実行するかを制御します。
ループ搬送依存関係の無視
ループ・イタレーション間で暗黙的なメモリー依存関係がない場合、 ivdep プラグマを使用して、可能性のあるメモリー依存関係を無視するように インテル®HLSコンパイラー プロ・エディションに指示します。
ivdep プラグマの使用方法の詳細については、 インテル®HLS (高位合成) コンパイラー: リファレンス・マニュアル内の ivdep プラグマを使用したループ搬送依存関係の削除を参照してください。
4.5. 複雑なループ終了条件の回避
コンポーネント内のループに複雑な終了条件がある場合、メモリーアクセスまたは複雑な演算が、条件を評価するために必要な場合があります。ループの後続イタレーションのループのパイプラインでの起動は、評価が完了するまではできません。このため、ループ全体のパフォーマンスが低下することがあります。
speculated_iterations プラグマを使用して、ループ終了条件の計算に必要なサイクル数を指定します。
4.6. ネスト化ループから単一ループへの変換
パフォーマンスを最大限に高めるには、可能な限りネスト化ループを結合して単一ループにします。ループの制御フローでは、ロジックで必要なオーバーヘッドとFPGAのハードウェア・フットプリントのオーバーヘッドの両方が追加されます。ネスト化ループを単一ループに結合すると、これらの側面が緩和し、コンポーネントのパフォーマンスが向上します。
次のコード例は、ネスト化ループから単一ループへの変換を示しています。
ネスト化ループ | 変換された単一ループ |
---|---|
for (i = 0; i < N; i++) { //statements for (j = 0; j < M; j++) { //statements } //statements } |
for (i = 0; i < N*M; i++) { //statements } |
また、loop_coalesce プラグマを指定して、ネスト化ループを単一ループに結合することができます。このとき、ループの機能には影響を与えません。次に示すシンプルな例では、loop_coalesce プラグマを指定した場合に、コンパイラーによって2つのループを単一ループに結合します。
#pragma loop_coalesce for (int i = 0; i < N; i++) for (int j = 0; j < M; j++) sum[i][j] += i+j;
int i = 0; int j = 0; while(i < N){ sum[i][j] += i+j; j++; if (j == M){ j = 0; i++; } }
loop_coalesce プラグマについて詳しくは、 インテル®HLS (高位合成) コンパイラー: リファレンス・マニュアル内のループ結合 ( loop_coalesce プラグマ) を参照してください。
次のチュートリアルでも確認できます。 <quartus_installdir>/hls/examples/tutorials/best_practices/loop_coalesce
4.7. 可能な限り最も深いスコープでの変数宣言
変数の実装に必要なFPGAハードウェア・リソースを低減するためには、変数の宣言は、ループでの使用直前に行います。変数の宣言を可能な限り最も深いスコープで行うと、データ依存関係とFPGAハードウェア使用率が最小限に抑えられます。これは、 インテル®HLSコンパイラー プロ・エディションによる変数データの保持は、変数を使用しないループでは必要がないためです。
次の例を検討します。
int a[N]; for (int i = 0; i < m; ++i) { int b[N]; for (int j = 0; j < n; ++j) { // statements } }
配列 a 実装では、配列 b よりも多くのリソースを必要とします。ハードウェア使用率を削減するには、配列 a を内側のループの外側に宣言します。ただしこれは、外側のループのイタレーションによってデータを維持する必要がない場合のみです。
5. メモリー・アーキテクチャーのベスト・プラクティス
ほとんどの場合、メモリー・アーキテクチャーを最適化するには、アクセスパターンを変更しますが、 インテル®HLSコンパイラー プロ・エディションを使用すると、メモリー・アーキテクチャーを制御することができます。
メモリー・アーキテクチャーのベスト・プラクティスに関するチュートリアル
インテル®HLSコンパイラー プロ・エディションに付属している多数のチュートリアルでは、 インテル®HLSコンパイラーの重要な概念や優れたコーディング・プラクティスを示しています。
チュートリアル | 説明 |
---|---|
この表のチュートリアルは、
インテル®
Quartus® Primeシステム内の次の場所にあります。<quartus_installdir>/hls/examples/tutorials/component_memories |
|
memory_bank_configuration | 次のメモリー属性を1つ以上使用して、各メモリーバンクのロード/ストアポートの数を制御し、コンポーネント領域の使用率、スループットを最適化する方法を示します。
|
memory_geometry | 各メモリーバンクのロード/ストアポートの数を制御し、コンポーネント領域の使用率、スループットを最適化する方法を示します。このためには、次のメモリー属性を1つ以上使用します。
|
memory_implementation | 変数または配列をレジスター、MLAB、またはRAMに実装する方法を示します。このためには、次のメモリー属性を使用します。
|
memory_merging | リソース使用率の改善方法を示します。このためには、2つのロジックメモリーを単一の物理メモリーとして実装します。そのためには、hls_merge メモリー属性を深さ方向または幅方向にマージさせます。 |
static_var_init | コンポーネント内のスタティックの初期化動作を制御する方法を示します。このためには、hls_init_on_reset または hls_init_on_powerup メモリー属性を使用します。 |
attributes_on_mm_slave_arg | メモリー属性を Avalon® Memory Mapped (MM) スレーブ引数に適用する方法を示します。 |
exceptions | メモリー属性を定数および構造体メンバーで使用する方法を示します。 |
5.1. 例 : 結合メモリー・アーキテクチャーのオーバーライド
メモリー属性をさまざまな組み合わせでコードに使用すると、メモリー・アーキテクチャーのオーバーライドができます。 インテル®HLSコンパイラー プロ・エディションでは、コンポーネントのメモリー・アーキテクチャーを推測します。
次のコード例で示しているのは、次のメモリー属性を使用して結合メモリーをオーバーライドし、FPGAのメモリーブロックを節約する方法です。
- hls_bankwidth(N)
- hls_numbanks(N)
- hls_singlepump
- hls_max_replicates(N)
元のコードによって2つのメモリーアクセスが結合し、結果として、深さ256の位置、64ビット幅 (256x64ビット) のメモリーシステム (2つのオンチップ・メモリー・ブロック) になります。
component unsigned int mem_coalesce_default(unsigned int raddr, unsigned int waddr, unsigned int wdata){ unsigned int data[512]; data[2*waddr] = wdata; data[2*waddr + 1] = wdata + 1; unsigned int rdata = data[2*raddr] + data[2*raddr + 1]; return rdata; }
次の画像で示すのは、このコードサンプルの256x64ビットメモリーの構造と、ハイレベルのデザインレポート ( report.html ) でのコンポーネント・メモリー構造の表示方法です。
![]() |
変更されたコードによって実装されるのは、512のワード深さ、32ビット幅で、ストール可能なアービトレーションを備えたオンチップ・メモリー・ブロックです。
component unsigned int mem_coalesce_override(unsigned int raddr, unsigned int waddr, unsigned int wdata){ //Attributes that stop memory coalescing hls_bankwidth(4) hls_numbanks(1) //Attributes that specify a single-pumped single-replicate memory hls_singlepump hls_max_replicates(1) unsigned int data[512]; data[2*waddr] = wdata; data[2*waddr + 1] = wdata + 1; unsigned int rdata = data[2*raddr] + data[2*raddr + 1]; return rdata; }
次の画像で示すのは、このコードサンプルのストール可能なアービトレーションを備えた512x32ビットメモリーの構造と、ハイレベルのデザインレポート ( report.html ) でのコンポーネント・メモリー構造の表示方法です。
![]() |
ハードウェア領域の節約は、コンポーネントに必要なRAMブロック数の削減によってできるように見えますが、ストール可能なアービトレーションの導入により、コンポーネントの実装に必要なハードウェアの量が増えます。次の表では、コンポーネントに必要なALMとFFの数の比較ができます。

5.2. 例 : バンク・メモリー・アーキテクチャーのオーバーライド
メモリー属性をさまざまな組み合わせでコードに使用すると、メモリー・アーキテクチャーのオーバーライドができます。 インテル®HLSコンパイラー プロ・エディションでは、コンポーネントのメモリー・アーキテクチャーを推測します。
次のコード例で示しているのは、次のメモリー属性を使用してバンクメモリーをオーバーライドし、FPGA上のメモリーブロックを節約する方法です。
- hls_bankwidth(N)
- hls_numbanks(N)
- hls_singlepump
- hls_doublepump
元のコードでは、16ビット幅のシングル・ポンプ・オンチップ・メモリー・ブロックのバンクを2つ作成します。
component unsigned short mem_banked(unsigned short raddr, unsigned short waddr, unsigned short wdata){ unsigned short data[1024]; data[2*waddr] = wdata; data[2*waddr + 9] = wdata +1; unsigned short rdata = data[2*raddr] + data[2*raddr + 9]; return rdata; }
バンクメモリーの節約のために、ダブルパンプ32ビット幅オンチップ・メモリー・ブロックのバンクを1つ実装します。このためには、次の属性の追加を data[1024] の宣言前に行います。この属性によって、半分使用したメモリーバンク2つを折りたたんで、完全に使用したメモリーバンク (ダブルパンプ済み) 1つにします。これにより、半分使用したメモリーバンク2つと同じ速さでのアクセスが可能になります。
hls_bankwidth(2) hls_numbanks(1)hls_doublepump unsigned short data[1024];
あるいは、ダブルパンプされたメモリーのダブルクロック要件を回避するために、シングルパンプ・オンチップ・メモリー・ブロックのバンクを1つ実装します。このためには、次の属性の追加を data[1024] の宣言前に行います。ただし、この例では、この属性によってストール可能なアービトレーションがコンポーネント・メモリーに追加され、コンポーネントのパフォーマンスが低下します。
hls_bankwidth(2) hls_numbanks(1)hls_singlepump unsigned short data[1024];
5.3. 領域削減のためのメモリーのマージ
場合によっては、FPGAメモリーブロックを節約するために、コンポーネント・メモリーをマージします。これにより、消費するメモリー・ブロックが少なくなり、コンポーネントで使用するFPGA領域が減ります。hls_merge 属性を使用し、 インテル®HLSコンパイラー プロ・エディションに強制して、異なる変数を同じメモリーシステム内に実装させます。
メモリーをマージすると、複数のコンポーネント変数では、同じメモリーブロックを共有します。メモリーのマージは、幅 (幅方向のマージ) または深さ (深さ方向のマージ) ごとに行います。メモリーのマージは、メモリー内のデータが異なるデータ型の場合に可能です。
次の図で示すのは、4つのメモリーを幅方向および深さ方向にマージする方法です。
5.3.1. 例 : 深さ方向へのメモリーのマージ
hls_merge("<mem_name>","depth") 属性を使用し、 インテル®HLSコンパイラー プロ・エディションに強制して、同じメモリーシステム内に変数を実装させ、そのメモリーを深さごとにマージします。
変数は、hls_merge 属性の <mem_name> ラベルセットが同じ場合はすべてマージされます。
次のコンポーネント・コードについて検討します。
component int depth_manual(bool use_a, int raddr, int waddr, int wdata) { int a[128]; int b[128]; int rdata; // mutually exclusive write if (use_a) { a[waddr] = wdata; } else { b[waddr] = wdata; } // mutually exclusive read if (use_a) { rdata = a[raddr]; } else { rdata = b[raddr]; } return rdata; }
このコードによる インテル®HLSコンパイラー プロ・エディションへの指示によって、ローカルメモリー a および b の実装をそれぞれ独自のロード/ストア命令を備えた 2つのオンチップ・メモリーブロックとして行います。
ローカルメモリー a および b に対するロード/ストア命令は相互排他的であるため、アクセスのマージが可能です。サンプルコードは次のとおりです。メモリーアクセスのマージによって、ロード/ストア命令の数が減り、オンチップ・メモリー・ブロックの数も半減します。
component int depth_manual(bool use_a, int raddr, int waddr, int wdata) { int a[128] hls_merge("mem","depth"); int b[128] hls_merge("mem","depth"); int rdata; // mutually exclusive write if (use_a) { a[waddr] = wdata; } else { b[waddr] = wdata; } // mutually exclusive read if (use_a) { rdata = a[raddr]; } else { rdata = b[raddr]; } return rdata; }
ローカルメモリーを深さに対してマージすると、メモリーアクセスの効率が低下する場合があります。ローカルメモリーのマージを深さに対して行うかどうかを決定する前に、HLDレポート ( <result>.prj/reports/report.html) を使用して、予想されるメモリー・コンフィグレーションの生成が、予想される数のロードおよびストア命令で行われたことを確認します。次の例では、 インテル®HLSコンパイラー プロ・エディションによるアクセスのマージは、ローカルメモリー a と b に対しては行うべきではありません。これは、各メモリーへのロード命令とストア命令は相互排他的ではないためです。
component int depth_manual(bool use_a, int raddr, int waddr, int wdata) { int a[128] hls_merge("mem","depth"); int b[128] hls_merge("mem","depth"); int rdata; // NOT mutually exclusive write a[waddr] = wdata; b[waddr] = wdata; // NOT mutually exclusive read rdata = a[raddr]; rdata += b[raddr]; return rdata; }
この場合、 インテル®HLSコンパイラー プロ・エディションでは、メモリーシステムをダブルパンプし、すべてのアクセスに十分なポートを提供する可能性があります。それ以外の場合、アクセスにはポートを共有してください。これによって、ストールフリーのアクセスを防止します。
5.3.2. 例 : メモリーの幅方向のマージ
hls_merge("<mem_name>","width") 属性を使用し、 インテル®HLSコンパイラー プロ・エディションに強制して、同じメモリーシステム内に変数を実装させ、そのメモリーを幅ごとにマージします。
変数は、hls_merge 属性の <mem_name> ラベルセットが同じ場合はすべてマージされます。
次のコンポーネント・コードを検討します。
component short width_manual (int raddr, int waddr, short wdata) { short a[256]; short b[256]; short rdata = 0; // Lock step write a[waddr] = wdata; b[waddr] = wdata; // Lock step read rdata += a[raddr]; rdata += b[raddr]; return rdata; }
この場合、 インテル®HLSコンパイラー プロ・エディションでは、ロード/ストア命令を結合して、ローカルメモリー a と b にマージします。これは、次に示すとおり、ロード/ストア命令のアクセス先は、同じアドレスだからです。
component short width_manual (int raddr, int waddr, short wdata) { short a[256] hls_merge("mem","width"); short b[256] hls_merge("mem","width"); short rdata = 0; // Lock step write a[waddr] = wdata; b[waddr] = wdata; // Lock step read rdata += a[raddr]; rdata += b[raddr]; return rdata; }
5.4. 例 : ローカル・メモリー・アドレスのバンク選択ビットの指定
(b 0 , b 1 , ... ,b n ) 引数では、ローカル・メモリー・アドレスのビット位置を参照します。 インテル®HLSコンパイラー プロ・エディションでは、このビット位置をバンク選択ビットに使用します。hls_bankbits(b 0, b 1 , ..., b n) 属性の指定は、バンクの数が 2 number of bank bits に等しいことを意味します。
Bank 0 | Bank 1 | Bank 2 | Bank 3 | |
Word 0 | 00 000 | 01 000 | 10 000 | 11 000 |
Word 1 | 00 001 | 01 001 | 10 001 | 11 001 |
Word 2 | 00 010 | 01 010 | 10 010 | 11 010 |
Word 3 | 00 011 | 01 011 | 10 011 | 11 011 |
Word 4 | 00 100 | 01 100 | 10 100 | 11 100 |
Word 5 | 00 101 | 01 101 | 10 101 | 11 101 |
Word 6 | 00 110 | 01 110 | 10 110 | 11 110 |
Word 7 | 00 111 | 01 111 | 10 111 | 11 111 |
hls_bankbits 属性の実装例
component int bank_arbitration (int raddr, int waddr, int wdata) { #define DIM_SIZE 4 // Adjust memory geometry by preventing coalescing hls_numbanks(1) hls_bankwidth(sizeof(int)*DIM_SIZE) // Force each memory bank to have 2 ports for read/write hls_singlepump hls_max_replicates(1) int a[DIM_SIZE][DIM_SIZE][DIM_SIZE]; // initialize array a… int result = 0; #pragma unroll for (int dim1 = 0; dim1 < DIM_SIZE; dim1++) #pragma unroll for (int dim3 = 0; dim3 < DIM_SIZE; dim3++) a[dim1][waddr&(DIM_SIZE-1)][dim3] = wdata; #pragma unroll for (int dim1 = 0; dim1 < DIM_SIZE; dim1++) #pragma unroll for (int dim3 = 0; dim3 < DIM_SIZE; dim3++) result += a[dim1][raddr&(DIM_SIZE-1)][dim3]; return result; }
次の図で示すとおり、このコード例によって複数のロード/ストアユニット (LSU) が生成され、それによって、複数のロードおよびストア命令がハードウェアに生成されます。メモリーシステムが複数のバンクに分割されていない場合、メモリーアクセス命令よりもポート数が少なくなり、アービトレーションされたアクセスにつながります。このアービトレーションにより、ループ開始間隔 (II) の値が高くなります。アービトレーションは、可能な限り避けてください。これは、アービトレーションによってコンポーネントのFPGAエリアの使用率が増加し、コンポーネントのパフォーマンスが低下するためです。

デフォルトでは、 インテル®HLSコンパイラー プロ・エディションでは、分割がコンポーネントのパフォーマンスに有益であると判断した場合、メモリーをバンクに分割します。コンパイラーでは、アクセス間で一定のビットが残っているかどうかを確認し、バンク選択ビットを自動的に推測します。
component int bank_no_arbitration (int raddr, int waddr, int wdata) { #define DIM_SIZE 4 // Adjust memory geometry by preventing coalescing and splitting memory hls_bankbits(4, 5) hls_bankwidth(sizeof(int)*DIM_SIZE) // Force each memory bank to have 2 ports for read/write hls_singlepump hls_max_replicates(1) int a[DIM_SIZE][DIM_SIZE][DIM_SIZE]; // initialize array a… int result = 0; #pragma unroll for (int dim1 = 0; dim1 < DIM_SIZE; dim1++) #pragma unroll for (int dim3 = 0; dim3 < DIM_SIZE; dim3++) a[dim1][waddr&(DIM_SIZE-1)][dim3] = wdata; #pragma unroll for (int dim1 = 0; dim1 < DIM_SIZE; dim1++) #pragma unroll for (int dim3 = 0; dim3 < DIM_SIZE; dim3++) result += a[dim1][raddr&(DIM_SIZE-1)][dim3]; return result; }
次の図で示しているとおり、このサンプルコードによって作成されるメモリー・コンフィグレーションには4つのバンクが備わっています。ビット4および5をバンク選択ビットとして使用すると、各ロード/ストアアクセスは、確実にそれ自体のメモリーバンクに送られます。

このコード例では、hls_bankbits(4,5) ではなく hls_numbanks(4) を設定すると、同じメモリー・コンフィグレーション結果になります。これは、 インテル®HLSコンパイラー プロ・エディションでは、最適なバンク選択ビットを自動的に推測するためです。
Function Memory Viewer (High-Level Design Report内) では、 Address bit information によって表示されるバンク選択ビットは、b6 および b7 です。b4 および b5 ではありません。
この違いが発生する理由は、Function Memory Viewerで報告されるアドレスビットが、要素アドレスではなくバイトアドレスに基づいているためです。配列 a のすべての要素のサイズは4バイトであるため、要素アドレスビットのビット b4 および b5 の対応先は、バイトアドレス指定のビット b6 および b7 になります。
6. タスクのシステム
6.1. 複数ループの並列実行
component void foo() { // first loop for (int i = 0; i < n; i++) { // Do something } // second loop for (int i = 0; i < m; i++) { // Do something else } }
void offloaded_work() { // first loop for (int i = 0; i < n; i++) { // Do something } } component void foo() { ihc::launch(offloaded_work); // second loop for (int i = 0; i < m; i++) { // Do something else } ihc::collect(offloaded_work); }
チュートリアル <quartus_installdir>/hls/examples/tutorials/system_of_tasks/parallel_loop で 、複数ループの並列実行方法についての詳細を参照してください。
6.2. 負荷のかかる計算ブロックの共有
複数の場所からタスクへのコールを許可するには、 インテル®HLSコンパイラー プロ・エディションでは、アービトレーション・ロジックの生成をコールされたタスク関数に対して行います。このアービトレーション・ロジックによって、コンポーネントの領域使用率を高めることができます。共有ロジックが大きい場合は、トレードオフによりFPGAリソースの節約ができます。この節約が特に顕著になるのは、コンポーネントの大きな計算ブロックが、常にアクティブではない場合です。
チュートリアル <quartus_installdir>/hls/examples/tutorials/system_of_tasks/resource_sharing で、計算ブロックをコンポーネントで共有する方法の簡単な例を参照してください。
6.3. 階層デザインの実装
タスクのシステムを使用しない場合、HLSコンポーネントの関数コールはインライン化され、呼び出しコードとともに最適化されます。これは状況によっては有害な場合があります。タスクのシステムを使用して、デザインの小さなブロックがシステムの他の部分の影響を受けないようにします。
- ハードウェア記述言語 (HDL) によって提供される可能性のあるモジュール性への類似
- パイプライン化できない、またはパイプライン化が不十分なループを分離して、ループネスト全体に影響を与えないようにできる
6.4. 潜在的なパフォーマンスの落とし穴の回避
通常、このパフォーマンスの問題の原因は、ihc::launch および ihc::collect 呼び出しを使用してタスク関数を呼び出す関数のデータパスの容量不足です。このような場合、システムのスループットを改善するには、明示的なストリームにバッファーを追加して、タスク関数のレイテンシーを考慮します。
- <quartus_installdir>/hls/examples/tutorials/system_of_tasks/balancing_pipeline_latency
- <quartus_installdir>/hls/examples/tutorials/system_of_tasks/balancing_loop_dealy
インテル®HLSコンパイラー プロ・エディションのエミュレーターでは、ストリームに接続されたバッファーのサイズをモデル化します。ただし、エミュレーターでは、ハードウェアのレイテンシーを完全には考慮しておらず、こういったケースではシミュレーションとエミュレーションの間で異なる動作を示す場合があります。
7. データ型のベスト・プラクティス
デザインのアルゴリズムのボトルネックを最適化した後、コンポーネント内の一部のデータ型を微調整することができます。このためには、任意の精度のデータ型を使用して、データ幅を縮小します。 インテル®HLSコンパイラー プロ・エディションのデバッグ機能では、任意の精度データ型のオーバーフローを簡単に検出することができます。
C++では、short や char などの小さいデータ型を32ビットに自動昇格させ、加算やビットシフトなどの操作に使用します。このため、コンポーネント内に狭いデータパスを作成する場合は、任意の精度のデータ型を使用してください。
データ型のベスト・プラクティスに関するチュートリアル
インテル®HLSコンパイラー プロ・エディションに付属している多数のチュートリアルでは、 インテル®HLSコンパイラーの重要な概念や優れたコーディング・プラクティスを示しています。
チュートリアル | 内容 |
---|---|
この表のチュートリアルは、
インテル®
Quartus® Primeシステム内の次の場所にあります。<quartus_installdir>/hls/examples/tutorials |
|
best_practices/ac_datatypes | int データ型の代わりに ac_int データ型を使用した場合の効果について示します。 |
ac_datatypes/ac_fixed_constructor | ac_fixed コンストラクターの使用例を示します。ここでは、より優れたQoRを得るために、コーディング・スタイルのわずかな相違を使用します。 |
ac_datatypes/ac_int_basic_ops | ac_int クラスで使用可能な演算子を示します。 |
ac_datatypes/ac_int_overflow | DEBUG_AC_INT_WARNING および DEBUG_AC_INT_ERROR キーワードの使用方法を示します。これは、エミュレーション・ランタイム中のオーバーフローの検出に役立ちます。 |
best_practices/single_vs_double_precision_math | 倍精度リテラルと関数の代わりに、単精度リテラルと関数を使用した場合の効果を示します。 |
7.1. 暗黙的データ型変換の回避
このオプションを使用すると、倍精度変数が不要な場合に、不注意で倍精度と単精度の値の間での変換が起こるのを回避するのに役立ちます。FPGAでは、倍精度の変数を使用すると、コンポーネントのデータ転送レート、レイテンシー、およびFPGAリソース使用率に悪影響を与える可能性があります。
Algorithmic C (AC) 任意精度データ型を使用する場合は、型の伝播規則に注意してください。
7.2. ac_int データ型使用時の負のビットシフトの回避
ac_int データ型が、CおよびVerilogなどの他の言語と異なる点は、ビットシフトです。デフォルトでは、シフト量が符号付きデータ型 ac_int の場合、負のシフトを許容します。
ハードウェアでは、この負のシフトにより左シフターと右シフターの両方が実装されます。次のコード例で示しているシフト量は、符号付きデータ型です。
int14 shift_left(int14 a, int14 b) { return (a << b); }
シフトが常に一方向であるとわかっている場合、効率的なシフト演算子を実装するには、次のようにシフト量を符号なしのデータ型として宣言します。
int14 efficient_left_only_shift(int14 a, uint14 b) { return (a << b); }
8. 高度なトラブルシューティング
- コンポーネントの動作がコ・シミュレーションとエミュレーションで異なる
- コンポーネントのパフォーマンス、リソース使用率、またはその両方が突然低下する
8.1. コンポーネントがコ・シミュレーションでのみ失敗する場合
浮動小数点の結果を比較する
イプシロンを使用して、テストベンチでの浮動小数点値の結果を比較します。RTLハードウェアからの浮動小数点の結果は、x86エミュレーション・フローとは異なります。
#pragma ivdep を使用してメモリーの依存関係を無視する
#pragma ivdep コンパイラー・プラグマを使用すると、コンポーネントの機能が不正確になる可能性があります。これは、コンポーネントのメモリー依存関係をプラグマで無視しようとした場合です。safelen 修飾子を使用して、メモリー依存関係の発生前に許可できるメモリーアクセス数の制御ができます。
このプラグマの説明については、 インテルHLS (高位合成) コンパイラー: リファレンス・マニュアル内のivdep プラグマを使用したループ伝搬依存性の削除 を参照してください 。
ivdep プラグマの使用例は、 <quartus_installdir>/hls/examples/tutorials/best_practices/loop_memory_dependency のチュートリアルを確認してください。
初期化されていない変数の確認
コーディングを実行すると、C++仕様で定義されていない動作が生じることがよくあります。この未定義の動作は、エミュレーションでは期待どおりに機能することがあります。コ・シミュレーションでは機能しません。
よくある例としてこの状況が発生するのは、デザインからの読み出しが、初期化されていない変数、特に初期化されていない struct 変数から行われる場合です。
初期化されていない値に対するコードの確認を -Wuninitialized コンパイラー・フラグを使用して行います。または、エミュレーション・テストベンチのデバッグを valgrind デバッグツールを使用して行います。-Wuninitialized コンパイラー・フラグでは、初期化されていない struct 変数は表示しません。
変数の誤動作をチェックするためには、1つ以上のストリーム・インターフェイスをデバッグストリームとして使用することもできます。1つ以上の ihc::stream_out インターフェイスをコンポーネントに追加して、コンポーネントによる内部状態変数の書き出しを実行時させることができます。エミュレーション・フローとコ・シミュレーション・フローの出力を比較すると、RTLの動作がエミュレータの動作と異なっている場所がわかります。
ノン・ブロッキング・ストリーム・アクセス
tryRead() のエミュレーション・モデルはサイクル精度ではないため、 サイクル精度 の動作は、エミュレーションとコ・シミュレーションとで異なる場合があります。
ノン・ブロッキング・ストリーム・アクセス (例えば tryRead()) が、FIFOを備えたストリーム (つまり ihc::depth<> テンプレート・パラメーター) からである場合、tryRead() の最初の数回のイタレーションによって返される可能性があるのは、コ・シミュレーションでは false ですが、エミュレーションでは true です。
この場合、コンポーネントをテストベンチからさらに数回呼び出して、ストリーム内のすべてのデータを確実に消費するようにします。この追加の呼び出しによって機能的な問題は起きないはずです。これは、tryRead() によって返されるのは false だからです。
8.2. コンポーネントの結果の品質が悪い
この項内の情報で説明しているのは、ストール可能なアービトレーション・ノードまたは過剰なRAM使用率の一般的なソースについてです。
コンポーネントで予想以上のFPGAリソースを使用する
デフォルトで インテル®HLSコンパイラー プロ・エディションでは、コンポーネントを最適化して最大限のスループットを得るために、最大動作周波数 (fMAX) を最大化しようとします。
領域の消費を削減する方法の1つは、fMAX 要件の緩和です。そのためには、ターゲットfMAX 値の設定を -clock i++コマンドオプションまたは hls_scheduler_target_fmax_mhz コンポーネント属性を使用して行います。HLSコンパイラーでは、多くの場合、指定値よりも高いfMAX を達成します。そのため、ターゲットfMAX rの設定を必要な値よりも低くしても、デザインでは、許容可能なfMAX 値を達成し、消費面積を小さくすることができる場合があります。
fMAX ターゲット値制御の動作について詳しくは、次のチュートリアルを参照してください。 <quartus_installdir>/hls/examples/tutorials/best_practices/set_component_target_fmax
誤ったバンクビット
配列の一部に並列アクセスする場合 (一次元または多次元配列)、メモリーバンクの選択ビットのコンフィグレーションが必要になることがあります。
効率的なメモリーシステムのコンフィグレーション方法について詳しくは、メモリー・アーキテクチャーのベスト・プラクティス を参照してください。
struct 変数の異なる2つの配列にアクセスする条件演算子
場合によっては、struct 変数の異なる配列にアクセスを条件演算子を使用してしようとすると、 インテル®HLSコンパイラー プロ・エディションでは、配列を同じRAMブロックにマージします。Function Memory Viewerには、ストール可能なアービトレーションが表示される場合があります。これは、メモリーシステムに十分なロード/ストアサイトがないためです。
struct MyStruct { float a; float b; } MyStruct array1[64]; MyStruct array2[64];
MyStruct value = (shouldChooseArray1) ? array1[idx] : array2[idx];
MyStruct value; if (shouldChooseArray1) { value = array1[idx]; } else { value = array2[idx]; }
クラスターロジック
デザインで消費するRAMブロックは、予想より多くなることがあります。これは、多くの配列変数を大きなレジスターに格納する場合は特にそうです。ハイレベルのデザインレポート ( report.html ) のArea Analysis of Systemレポートは、この問題の発見に役立ちます。

3つのマトリックスは、意図的にRAMブロックに格納されます。しかしながら、マトリックスのRAMブロックが占めるRAMブロックは、コンポーネントで消費するRAMブロックの半分未満です。
レポートのさらに下を見ると、多くのRAMブロックが、Cluster logicまたはState変数によって消費されていることがわかります。また、レジスターに格納する予定の配列値の一部が、代わりに多数のRAMブロックに格納されていることもわかります。

Cluster LogicおよびStateによって消費されるRAMブロックの数に注意してください。
- ループをパイプライン処理する (ループは展開しない)
- ローカル変数をローカルRAMブロック (hls_memory メモリー属性) に格納 (大きなレジスター (hls_register メモリー属性) には格納しない)
A. インテルHLSコンパイラー プロ・エディション ベスト・プラクティス・ガイド
インテル®HLSコンパイラーのバージョン | タイトル |
---|---|
19.4 | インテル®HLSコンパイラー プロ・エディション ベスト・プラクティス・ガイド |
19.3 | インテル®HLSコンパイラー ベストプラクティス・ガイド |
19.2 | インテル®HLSコンパイラー ベストプラクティス・ガイド |
19.1 | インテル®HLSコンパイラー ベストプラクティス・ガイド |
18.1.1 | インテル®HLSコンパイラー ベストプラクティス・ガイド |
18.1 | インテル®HLSコンパイラー ベストプラクティス・ガイド |
18.0 | インテル®HLSコンパイラー ベストプラクティス・ガイド |
17.1.1 | インテル®HLSコンパイラー ベストプラクティス・ガイド |
17.1 | インテル®HLSコンパイラー ベストプラクティス・ガイド |
B. インテルHLSコンパイラー プロ・エディション ベスト・プラクティス・ガイドの改訂履歴
ドキュメント・バージョン | インテル®HLSコンパイラー プロ・エディション バージョン | 変更内容 |
---|---|---|
2019.12.16 | 19.4 |
|
インテル®HLSコンパイラー ベスト・プラクティス・ガイドの改訂履歴
以前のバージョンの インテル®HLSコンパイラー ベスト・プラクティス・ガイドには、 インテル®HLSコンパイラー スタンダード・エディションと インテル®HLSコンパイラー プロ・エディションの両方の情報が含まれています。
ドキュメント・バージョン | インテル® Quartus® Prime バージョン | 変更内容 |
---|---|---|
2019.09.30 | 19.3 |
|
2019.07.01 | 19.2 |
|
2019.04.01 | 19.1 |
|
2018.12.24 | 18.1 |
|
2018.09.24 | 18.1 |
|
2018.07.02 | 18.0 |
|
2018.05.07 | 18.0 |
|
2017.12.22 | 17.1.1 |
|
2017.11.06 | 17.1 | 初回リリース。 本文書の一部は、以前の インテル®HLS (高位合成) コンパイラーインテルHLSコンパイラー ユーザーガイドおよび インテル®HLS (高位合成) コンパイラーインテルHLSコンパイラー: リファレンス・マニュアルに記載された内容で構成されています。 |