システム パフォーマンスの向上

この章では、プログラマが全体的なシステム パフォーマンスを向上しやすくするために、SDSoC™ システム コンパイラの基本的な原則と推論規則について説明します。

  • ハードウェア関数での並列処理の増加。
  • システムでの並列処理および同時処理の増加。
  • プログラマブル ロジックから外部メモリへのアクセスを向上。
  • データ モーション ネットワークの理解 (デフォルト動作とユーザー仕様)。

全体的なシステム パフォーマンスに影響する要素は多数あります。適切に設計されたシステムでは、すべてのハードウェア コンポーネントが有益な処理を実行するように、計算と通信のバランスが取られます。

  • 計算負荷の高いアプリケーションでは、ハードウェア アクセラレータのスループットを最大にし、レイテンシを最低限に抑えるようにしてください。
  • メモリ バウンド アプリケーションでは、ハードウェアの一時的なおよび空間的な局所性を増加するようアルゴリズムを再構築することが必要な場合があります。たとえば、外部メモリへのランダム配列アクセスではなく、copy-loop や memcopy を追加してデータ ブロックをハードウェアに戻すなどです。

コード内にプラグマを使用すると、さまざまな最適化の機能を制御できます。使用可能なすべてのプラグマの詳細は、 を参照してください。

ハードウェア関数の並列機能の改善

このセクションでは、プログラマブル ロジックにクロスコンパイル可能な効率的なコードを記述するための概要を示します。

SDSoC 環境では、Vivado® HLS (高位合成) ツールをプログラマブル ロジックのクロス コンパイラとして使用して、C/C++ 関数をハードウェアに変換します。

このセクションで説明される原則に従うと、合成済み関数のパフォーマンスを大幅に改善でき、アプリケーションの全体的なシステム パフォーマンスを大幅に向上できる可能性があります。

最上位ハードウェア関数のガイドライン

このセクションでは、Vivado HLS ツール ハードウェア関数で Arm® コア GNU ツールチェーンで生成されたオブジェクト コードと一貫したインターフェイスが使用されるようにするためのコーディング ガイドラインを示します。

最上位ハードウェア関数引数には標準 C99 データ型を使用

  1. bool の配列は使用しないでください。bool の配列のメモリ レイアウトは、GNU Arm クロス コンパイラと HLS ツールで異なります。
  2. ハードウェア関数の最上位インターフェイスには hls::stream を使用しないでください。このデータ型は、HLS コンパイラがハードウェア関数内に効率的なロジックを合成するのに役立ちますが、アプリケーション ソフトウェアには役立たないからです。

最上位ハードウェア関数の引数の HLS インターフェイス指示子を使用しない

HLS インターフェイス プラグマを含む最上位ハードウェア関数はサポートされていますが、通常は使用しないでください。sdcc/sds++ (sds++ と呼ぶ) システム コンパイラでは、自動的に適切な HLS インターフェイス指示子が生成されます。

最上位ハードウェア関数に次の 2 つの SDSoC 環境プラグマを指定すると、sds++ システム コンパイラで必要な HLS インターフェイス指示子が生成されるようにできます。
#pragma SDS data zero_copy()
ハードウェアに AXI マスター インターフェイスとしてインプリメントされる共有メモリ インターフェイスを生成します。
#pragma SDS data access_pattern(argument:SEQUENTIAL)
ハードウェアに FIFO インターフェイスとしてインプリメントされるストリーミング インターフェイスを生成します。

最上位関数の引数に対して #pragma HLS interface を使用してインターフェイスを指定すると、その引数に対する HLS インターフェイス指示子は SDSoC 環境では生成されないので、生成されたハードウェア インターフェイスがその他すべての関数引数のハードウェア インターフェイスと一貫したものになるようにしてください。

注記: 互換性のない HLS インターフェイス タイプを使用した関数があると、意味不明な sds++ システム コンパイラのエラー メッセージが表示されるので、必須ではありませんが、HLS インターフェイス プラグマを削除することをお勧めします。

Vivado Design Suite HLS ライブラリの使用

このセクションでは、SDSoC 環境で Vivado HLS ツールライブラリを使用する方法について説明します。

HLS ライブラリは、SDSoC 環境の HLS ツール インストールにソース コードとして含まれており、HLS ツールを使用してプログラマブル ロジック向けにクロス コンパイルする予定のほかのソース コード同様に使用できます。ソース コードは、ハードウェア関数の引数型に説明されている規則に従っている必要があります。このとき、関数でソフトウェア インターフェイスがアプリケーションにエクスポートされるようにするため、C/C++ ラッパー関数を作成することが必要な場合があります。

SDSoC IDE では、すべての基本的なプラットフォームの有限インパルス応答 (FIR) サンプル テンプレートに、HLS ライブラリを使用する例が含まれています。samples/hls_lib ディレクトリには、HLS math ライブラリを使用するコード例が数個含まれています。たとえば、samples/hls_lib/hls_math には平方根関数をインプリメントして使用する例が含まれています。

my_sqrt.h ファイルには次が含まれています。

#ifndef _MY_SQRT_H_ 
#define _MY_SQRT_H_ 

#ifdef __SDSVHLS__
#include "hls_math.h" 
#else 
// The hls_math.h file includes hdl_fpo.h which contains actual code and 
// will cause linker error in the ARM compiler, hence we add the function 
// prototypes here 
static float sqrtf(float x); 
#endif 

void my_sqrt(float x, float *ret); 

#endif // _SQRT_H_

my_sqrt.cpp ファイルには次が含まれています。

#include "my_sqrt.h" 

void my_sqrt(float x, float *ret) 
{ 
    *ret = sqrtf(x); 
}

makefile には、これらのファイルをコンパイルするコマンドが含まれています。

sds++ -c -hw my_sqrt –sds-pf zc702 my_sqrt.cpp 
sds++ -c my_sqrt_test.cpp 
sds++ my_sqrt.o my_sqrt_test.o -o my_sqrt_test.elf

システムでの並列処理および同時処理の増加

同時処理のレベルを増加することは、システムの全体的なパフォーマンスを向上するための標準的な方法であり、並列処理のレベルを増加することは同時処理を増加させる標準的な方法です。プログラマブル ロジックは、同時実行されるアプリケーション特定のアクセラレータを含むアーキテクチャをインプリメントするのに適しており、特にデータ プロデューサーとコンシューマー間で同期化されるフロー制御ストリームを介した通信に適しています。

SDSoC 環境では、関数およびデータ ムーバー レベルでのマクロ アーキテクチャの並列処理、ハードウェア アクセラレータ内でのマクロ アーキテクチャの並列処理を制御できます。sds++/sdscc (sds++ と呼ぶ) でシステム接続とデータ ムーバーがどのように推論されるかを理解することにより、必要に応じてアプリケーション コードを構成してプラグマを適用して、アクセラレータとソフトウェア間のハードウェア接続、データ ムーバーの選択、ハードウェア関数のアクセラレータ インスタンス数、タスク レベルのソフトウェア制御を制御できます。

Vivado HLS ツールまたは C 呼び出し可能/リンク可能ライブラリとして組み込む IP 内で、マイクロ アーキテクチャの並列処理、同時処理、およびハードウェア関数のスループットを制御できます。

システム レベルでは、ハードウェア関数間のデータフローでプログラマブル ロジックとシステム メモリの間の引数転送が不要な場合は、sds++ コンパイラによりハードウェア関数がチェーン接続されます。

たとえば、mmult および madd 関数がハードウェアに選択されている次の図に示すコードがあるとします。

図: 直接接続を使用したハードウェア/ソフトウェアの接続

2 つのハードウェア関数間でデータを渡すのには中間配列変数 tmp1 のみが使用されるので、sds++ コンパイラにより 2 つの関数が直接接続を使用してチェーン接続されます。

ハードウェアへの呼び出しのタイムラインを次の図に示すように考慮すると有益です。

図: mmult/madd 関数呼び出しのタイムライン



プログラムでは元のプログラム セマンティクスが保持されますが、標準 Arm コアのプロシージャ呼び出しシーケンスではなく、各ハードウェア関数呼び出しがデータ ムーバー (DM) とアクセラレータの両方に対してセットアップ、実行、およびクリーンアップを含む複数のフェーズに分割されます。CPU は各ハードウェア関数 (基になる IP 制御インターフェイス) と関数呼び出しのデータ転送をノンブロッキング API でセットアップし、すべての呼び出しと転送が完了するのを待ちます。

図に示す例では、mmultmadd 関数の入力が使用可能になると、これらの関数が同時に実行されます。プログラム、データ ムーバー、アクセラレータ構造に基づいて sds++ システム コンパイラで自動的に生成された制御コードにより、コンパイルされたプログラムですべての関数呼び出しが調整されます。

通常、sds++ システム コンパイラでアプリケーション コード内の関数呼び出しの悪影響を判断することはできないので (たとえば sds++ でリンクされたライブラリ内の関数のソース コードにアクセスできないなど)、ハードウェア関数呼び出し間で変数の中間アクセスが発生する場合は、データをメモリに戻す必要があります。

たとえば、次の図に示すデバッグ プリント文のコメントを不注意にはずしてしまうと、大幅に異なるデータ転送グラフとなり、その結果システムおよびアプリケーションのパフォーマンスがまったく異なるものになる可能性があります。

図: 直接接続が切断されたハードウェア/ソフトウェアの接続

プログラムでは、複数の呼び出しサイトから 1 つのハードウェア関数を呼び出すことができます。この場合、sds++ システム コンパイラは次のように動作します。関数呼び出しのどれかが直接接続データフローとなった場合、sds++ システム コンパイラにより同様の直接接続をサービスするハードウェア関数のインスタンスと、メモリ (ソフトウェア) と PL 間の残りの呼び出しをサービスするハードウェア関数のインスタンスが作成されます。

PL で高パフォーマンスを達成するには、ハードウェア関数間を直接接続データフローを使用してアプリケーション コードを構成するのが最適な方法です。データ ストリームで接続されたアクセラレータの多段パイプランを作成することにより、同時実行の可能性が高くなります。

sds++ システム コンパイラを使用して並列処理と同時処理を増加させるには、もう 1 つ方法があります。ハードウェア関数を呼び出す直前に次のプラグマを挿入して、ハードウェア関数の複数のインスタンスが作成されるようにできます。

#pragma SDS resource(<id>) // <id> a non-negative integer

このプラグマは、<id> で参照されているハードウェア インスタンスを作成します。

次に、ハードウェア関数 mmult の 2 つのインスタンスを作成するコード例を示します。

{
#pragma SDS resource(1)
 mmult(A, B, C); // instance 1
#pragma SDS resource(2)
 mmult(D, E, F); // instance 2
}

アクセラレータのインスタンスを複数作成しない場合は、sds_async メカニズムにより、ハードウェア スレッドを明示的に処理して非常に高レベルの並列処理および同時処理を達成することもできますが、明示的なマルチスレッド プログラミング モデルでは同期に細心の注意を払い、非決定の動作やデッドロックを回避する必要があります。詳細は、SDSoC 環境プログラマ ガイド を参照してください。

SDSoC でのデータ モーション ネットワークの生成

このセクションでは、次について説明します。

  • SDSoC 環境のデータ モーション ネットワークに使用されるコンポーネントのほか、SDSoC で生成されるデータ モーション ネットワークについて。
  • 適切な SDSoC プラグマを使用してデータ モーション ネットワークを生成するためのガイドライン。

ソフトウェア プログラムとハードウェア関数の間の各転送には、データ ムーバーが必要です。データ ムーバーは、データを移動するハードウェア コンポーネントとオペレーティング システム特定のライブラリ関数で構成されます。次の表に、サポートされるデータ ムーバーとそれぞれの特性を示します。

表 1. SDSoC でサポートされるデータ ムーバー
SDSoC データ ムーバー Vivado® IP データ ムーバー アクセラレータ IP のポート タイプ 転送サイズ 連続するメモリのみ
axi_dma_simple axi_dma bram、ap_fifo、axis ≤ 32 MB
axi_dma_sg axi_dma bram、ap_fifo、axis なし X

(推奨されない)

axi_fifo axi_fifo_mm_s bram、ap_fifo、axis ≤ 300 B X
zero_copy accelerator IP aximm master なし
  • 配列引数の場合は、転送サイズ、ハードウェア関数のポート マップ、および関数呼び出しサイト情報に基づいてデータ ムーバーが推論されます。データ ムーバーには、それぞれ次のようにパフォーマンスとリソースのトレードオフがあります。
    • axi_dma_simple データ ムーバーは、最も効率的なバルク転送エンジンですが、32 MB の転送までしかサポートされていないので、これより大きい転送には向いていません。
    • axi_fifo データ ムーバーには、DMA ほど多くのハードウェア リソースは必要ありませんが、転送レートが遅いので、最大 300 バイトのペイロードまでに使用することをお勧めします。
    • axi_dma_sg (スキャッター ギャザー DMA) データ ムーバーは、DMA パフォーマンスが遅く、ハードウェア リソースの消費が多くなりますが、制限が少なく、プラグマ指示子がない場合に最適なデフォルト データ ムーバーとなることがよくあります。

プログラム ソースの関数宣言の直前に次のようなプラグマを挿入すると、別のデータ ムーバーを選択できます。

#pragma SDS data data_mover(A:AXI_DMA_SIMPLE)
注記: #pragma SDS は常にヒントではなく規則として処理されるので、前の表のデータ ムーバーの要件に必ず従うようにしてください。

SDSoC 環境のデータ モーション ネットワークには、次の 3 つのコンポーネントが含まれます。

  • PS のメモリ システム ポート (A)
  • PS とアクセラレータ間およびアクセラレータ間のデータ ムーバー (B)
  • アクセラレータのハードウェア インターフェイス (C)

次の図は、これらの 3 つのコンポーネントを示しています。

図: データ モーション ネットワーク コンポーネント

SDS プラグマがない場合、SDSoC 環境ではソース コードの解析に基づいてデータ モーション ネットワークが自動的に生成されますが、SDSoC 環境にはデータ モーション ネットワーク生成をガイドするためのプラグマも提供されています。詳細は、 を参照してください。

システム ポート

システム ポートは、データ ムーバーを PS に接続します。システム ポートとしては、Zynq®-7000 SoC または Zynq® UltraScale+™ MPSoC プロセッサのアクセプタンスフィルター ID (AFI - ハイ パフォーマンス ポートに対応)、メモリ インターフェイス ジェネレーター (MIG - PL ベースの DDR メモリ コントローラー)、またはストリーム ポートを使用できます。

AFI ポートは、キャッシュ コヒーレンシ ポートではありません。キャッシュ フラッシュおよびキャッシュ無効化などのキャッシュ コヒーレンシは、必要に応じてソフトウェアにより維持されます。

AFI ポートは、転送データ、データのキャッシュ属性、およびデータ サイズ要件によって異なります。データが sds_alloc_non_cacheable() または sds_register_dmabuf() で割り当てられる場合は、キャッシュのフラッシュ/無効化を避けるために AFI ポートに接続することをお勧めします。

注記: これらの関数は、sds_lib.h に含まれており、『SDSoC 環境プログラマ ガイド』 (UG1278) の環境 API の部分で説明されています。

SDSoC システム コンパイラでは、アクセラレータとのデータ転送のためにこれらのメモリ属性が解析され、データ ムーバーが適切なシステム ポートに接続されるようになっています。

コンパイラの指定を上書きする場合や、コンパイラでこのような解析が実行できない場合は、次のプラグマを使用するとシステム ポートを指定できます。

#pragma SDS data sys_port(arg:ip_port) 

たとえば、次の関数は直接 FIFO AXI インターフェイスに接続します。ip_portAFI または MIG のいずれかにできます。

#pragma SDS data sys_port:(A:fifo_S_AXI)*
void foo(int* A, int* B, int* C);

この関数は、ストリーミング インターフェイスにも使用できます。

#pragma SDS data sys_port:(A:stream_fifo_S_AXIS)*
void foo(int* A, int* B, int* C)
注記: Vivado® 高位合成 (HLS) ツールでの AXI の機能については、『Vivado Design Suite ユーザー ガイド: 高位合成』 (UG902) を参照してください。

次の sds++ システム コンパイラ コマンドを使用すると、プラットフォームのシステム ポートがリストされます。

sds++ -sds-pf-info <platform> -verbose

データ ムーバー

データ ムーバーは、PS とアクセラレータ間、およびアクセラレータどうしの間でデータを転送します。SDSoC 環境では、転送されるデータの特性とサイズに基づいてさまざまなタイプのデータ ムーバーを生成できます。

スカラー
スカラー データは、常に AXI_LITE データ ムーバーで転送されます。
配列
sds++ システム コンパイラでは、次のデータ ムーバーを生成します。
  • AXI_DMA_SG
  • AXI_DMA_SIMPLE
  • AXI_FIFO
  • zero_copy (アクセラレータ マスター マスターの AXI4 バス)
  • AXI_LITE (メモリ属性と配列のデータ サイズによって異なる)

たとえば、配列が malloc() を使用して割り当てられている場合、メモリが物理的に隣接していない場合、SDSoC 環境では通常スキャッター ギャザー DMA (AXI_DMA_SG) が生成されますが、データ サイズが 300 バイト未満の場合は AXI_FIFO が代わりに生成されます。これは、この方がデータ転送時間が AXI_DMA_SG よりも短く、PL リソースの使用量も少ないからです。

構造体またはクラス
struct のインプリメンテーションは、構造体 (struct) がハードウェアにどのように渡されるか (値渡し、参照渡し、または structs の配列として渡されるか) によって異なります。次の表に、さまざまなインプリメンテーションを示します。
表 2. 構造体のインプリメンテーション
構造体の渡し方 デフォルト (プラグマなし) #pragma SDS data zero_copy (arg) #pragma SDS data zero_copy (arg[0:SIZE]) #pragma SDS data copy (arg) #pragma SDS data copy (arg[0:SIZE])
値渡し (struct RGB arg) 各フィールドがフラットになり、それぞれがスカラーまたは配列として渡されます。 これはサポートされておらず、エラーになります。 これはサポートされておらず、エラーになります。 struct が 1 つの幅のスカラーにパックされます。

各フィールドがフラットになり、それぞれがスカラーまたは配列として渡されます。

SIZE の値が無視されます。

ポインター渡し (struct RGB *arg) または参照渡し (struct RGB &arg) 各フィールドがフラットになり、それぞれがスカラーまたは配列として渡されます。

struct が 1 つの幅のスカラーにパックされ、1 つの値として転送されます。

データは AXI4 バスを介してハードウェア アクセラレータに転送されます。

struct が 1 つの幅のスカラーにパックされます。

AXI4 バスを介してハードウェア アクセラレータに転送されるデータ値の数は、SIZE の値で定義されます。

struct が 1 つの幅のスカラーにパックされます。

struct が 1 つの幅のスカラーにパックされます。

AXIDMA_SG または AXIDMA_SIMPLE を使用してハードウェア アクセラレータに転送されるデータ値の数は、SIZE の値で定義されます。

struct の配列

(struct RGB arg[1024])

配列の各 struct 要素が 1 つの幅のスカラーにパックされます。

配列の各 struct 要素が 1 つの幅のスカラーにパックされます。

データは AXI4 バスを使用してハードウェア アクセラレータに転送されます。

配列の各 struct 要素が 1 つの幅のスカラーにパックされます。

データは AXI4 バスを使用してハードウェア アクセラレータに転送されます。

SIZE の値が配列サイズよりも優先され、アクセラレータに転送されるデータ値の数が決まります。

配列の各 struct 要素が 1 つの幅のスカラーにパックされます。

データは AXI_DMA_SG または AXI_DMA_SIMPLE などのデータ ムーバーを使用してハードウェア アクセラレータに転送されます。

配列の各 struct 要素が 1 つの幅のスカラーにパックされます。

データは AXI_DMA_SG または AXI_DMA_SIMPLE などのデータ ムーバーを使用してハードウェア アクセラレータに転送されます。

SIZE の値が配列サイズよりも優先され、アクセラレータに転送されるデータ値の数が決まります。

配列を転送するのにどのデータ ムーバーを使用するかは、配列の 2 つの属性 (データ サイズと物理メモリの連続性) によって異なります。たとえば、メモリ サイズが 1 MB で物理的に隣接していない (malloc() で割り当てられる) 場合、AXI_DMA_SG を使用する必要があります。次の表に、これらのデータ ムーバーの適用基準を示します。

表 3. データ ムーバーの選択
データ ムーバー 物理メモリの連続性 データ サイズ (バイト)
AXI_DMA_SG 連続または非連続 > 300
AXI_DMA_Simple 連続 < 32M
AXI_FIFO 非連続 < 300

通常、SDSoC クロス コンパイラではこれら 2 つの属性用にハードウェア アクセラレータへ転送される配列が解析され、適切なデータ ムーバーが選択されますが、このような解析ができないこともあります。この場合、SDSoC クロス コンパイラで SDS プラグマを使用してメモリ属性が指定できないことを示す警告メッセージが表示されます。

WARNING: [DMAnalysis 83-4492] Unable to determine the memory attributes passed to rgb_data_in of function img_process at 
C:/simple_sobel/src/main_app.c:84

メモリ属性を指定するプラグマは、次のようになります。

#pragma SDS data mem_attribute(function_argument:contiguity)

contiguityPHYSICAL_CONTIGUOUS または NON_PHYSICAL_CONTIGUOUS のいずれかにできます。次のプラグマを使用して、転送するデータのサイズを指定します。

#pragma SDS data copy(function_argument[offset:size])

size は、数値または任意の演算式にできます。

zero_copy データ ムーバー

zero_copy はアクセラレータ インターフェイスとデータ ムーバーの両方に使用できる固有のデータ ムーバーです。このプラグマの構文は、次のとおりです。

#pragma SDS data zero_copy(arg[offset:size])

[offset:size] はオプションで、配列のデータ転送サイズをコンパイル時に決定できない場合にのみ必要です。

デフォルトでは、SDSoC 環境は配列引数の copy を実行します。つまり、データはデータ ムーバーを介して PS からアクセラレータに明示的にコピーされますが、この ZERO_COPY プラグマを使用すると、SDSoC でアクセラレータの指定した引数に対して AXI-Master インターフェイスが生成され、アクセラレータ コードで指定したとおりに PS からデータが取得されます。

ZERO_COPY プラグマを使用するには、配列に該当するメモリは物理的に連続している (sds_alloc で割り当てる) 必要があります。

アクセラレータのインターフェイス

生成されるアクセラレータのインターフェイスは、引数のデータ型によって異なります。
スカラー
スカラー引数の場合、アクセラレータの入力および出力を通すためにレジスタ インターフェイスが生成されます。
配列
配列を転送するためのアクセラレータのハードウェア インターフェイスは、そのアクセラレータが配列内のデータにどのようにアクセスするかによって、RAM インターフェイスかストリーミング インターフェイスのいずれかにできます。

RAM インターフェイスでは、アクセラレータ内でデータにランダムにアクセスできますが、アクセラレータ内でメモリ アクセスを実行する前に、配列全体をアクセラレータに転送する必要があります。さらに、このインターフェイスを使用すると、配列を格納するため、アクセラレータ側にブロック RAM リソースが必要となります。

ストリーミング インターフェイスでは、配列全体を格納するためのメモリは必要ないので、配列要素の処理をパイプライン処理できます。たとえば、アクセラレータで前の配列要素を処理中に次の配列要素の処理を開始できます。ただし、ストリーミング インターフェイスを使用する場合、アクセラレータが厳密な順序で配列にアクセスする必要があり、転送されるデータ量はアクセラレータで予測されるデータ量と同じである必要があります。

SDSoC 環境では、デフォルトで配列に対して RAM インターフェイスが生成されるようになっています。ただし、SDSoC 環境ではストリーミング インターフェイスを生成するよう指定するプラグマが含まれています。

構造体またはクラス
struct のインプリメンテーションは、struct がハードウェアにどのように渡されるか (値渡し、参照渡し、または structs の配列として渡されるか) によって変わります。前の表は、さまざまなインプリメンテーションを示しています。

次の SDS プラグマは、アクセラレータのインターフェイス生成をガイドするために使用できます。

#pragma SDS data access_pattern(function_argument:pattern)

pattern は、RANDOM または SEQUENTIAL のいずれか、arg はアクセラレータ関数の配列引数名にできます。

配列引数のアクセス パターンを RANDOM に指定すると RAM インターフェイスが生成され、SEQUENTIAL に指定するとストリーミング インターフェイスが生成されます。

注記:
  • 配列引数のデフォルトのアクセス パターンは RANDOM です。
  • 指定したアクセス パターンは、アクセラレータ関数のビヘイビアーと一貫している必要があります。SEQUENTIAL アクセス パターンの場合、関数がすべての配列要素に厳密な順序でアクセスする必要があります。
  • このプラグマは、zero_copy プラグマがない引数にのみ適用できます。