説明
FPGA デザインでは、オフチップの DDR メモリへの非バースト アクセスはコストが高く、パフォーマンスを低下させる可能性があります。これを軽減するために、Vitis HLS は M_AXI アダプターにおいて、参照の局所性 (時間的および空間的) を活用して平均アクセス レイテンシを低減させるキャッシュ メカニズムをサポートしています。メモリ領域がアクセスされると、その領域はすぐに再びアクセスされる可能性が高く、近くの領域もアクセスされる可能性があります。
キャッシュ メカニズムでは、シングル ポート キャッシュとマルチ ポート階層の両方がサポートされています。
- シングル ポート キャッシュ: 1 つの m_axi ポートに接続された、ダイレクト マップ方式の読み出し専用キャッシュです。これにより、カーネルが同じラインを再参照する際、DRAM の繰り返し読み出しが削減されます。
- マルチ ポート キャッシュ: 各ポート専用の L1 キャッシュと、共有 L2 キャッシュを備えた階層型キャッシュです。これにより、ソース コードを変更せずに、同じ最上位 m_axi ポインターに対して最大 N 回の読み出しアクセスが 1 クロック サイクル内で可能になります。これは、コンパイラがウィンドウ バッファーを推測できないステンシル型およびその他の動的アクセス パターンを対象としています。コンパイラが推論して生成するウィンドウ バッファーと比べると、マルチ ポート キャッシュはより柔軟 (動的なアクセス パターンに対応可能) ですが、一般的により多くのリソースを使用します。
マルチ ポート キャッシュのアーキテクチャ
- L2 キャッシュ (共有):
-
すべてのポートで共有される、ダイレクト マップ方式のキャッシュが 1 つあります。
-
オフチップ DRAM からバースト転送でロードされます。
-
キャッシュ ラインは、それを要求する L1 キャッシュにブロードキャストされ、冗長な DRAM ロードを回避することでメモリ帯域幅を最適化します。
-
- L1 キャッシュ (ポートごと):
-
各キャッシュ ポートにダイレクト マップ方式の L1 キャッシュが 1 つあります。
-
各 L1 は 1 サイクルあたり 1 つのデータ項目を供給でき、同じ m_axi ポインターに対して 1 サイクルあたり最大 N 回の読み出しアクセスを可能にします。
-
L1 キャッシュ ラインは、共有 L2 キャッシュからロードされます。
-
syn.interface.m_axi_cache_impl コマンドを使用して、キャッシュ インプリメンテーション リソースを制御します。構文
pragma HLS cache port=<name> lines=<value> depth=<value>
説明:
-
port=<name> - キャッシュを追加する対象となる読み出し専用ポート (最上位 m_axi ポインター) を指定します。
-
lines=<value> - 各 L1 キャッシュに含まれるキャッシュ ラインの数 (シングル ポートの場合、その 1 つのキャッシュのライン数)。キャッシュ ラインが 1 行の場合は 1、複数行の場合は 2 のべき乗で 1 よりも大きい値を指定します。この設定はオプションであり、指定しない場合はデフォルト値として 1 が使用されます。
-
depth=<value> - 各キャッシュ ラインのサイズをワード単位で指定します (値は 2 のべき乗である必要があります)。ポインターの要素型に適用されます (たとえば、depth は int や float の単位で指定されます)。オプションで指定可能で、デフォルト値は最大バースト長が使用されます。最大バースト長はデフォルトで 16 ですが、syn.interface.m_axi_max_read_burst_length を用いてグローバルに指定することも、syn.directive.interface を用いてインターフェイスごとに指定することもできます。注記: マルチ ポート キャッシュでは、L1 キャッシュと L2 キャッシュは同じライン深さを共有しており、この深さは両方のレベルに適用されます。
-
ports=<N> - マルチ ポート キャッシュ機能を有効にします。対応する最上位 m_axi ポインターに対して、1 クロック サイクルでスケジューリングできる読み出しアクセスの回数を定義します。これは、m_axi バンドルにマップされた読み出し専用ポインターにのみ適用されます。ポート数が 1 よりも大きい場合、そのバンドルにマップできる読み出し専用の配列は 1 つだけです (「制限」を参照)。
-
l2_lines=<M> - オプションで指定可能です。デフォルトでは、L1 キャッシュと L2 キャッシュの両方のライン数は depth オプションで決まります。共有 L2 キャッシュのライン数を明示的に設定します。各 L2 キャッシュ ラインのサイズは L1 キャッシュ ラインのサイズと同じで、depth によって制御されます。ライン数はより多くなければなりません。つまり、L2 キャッシュ ラインの数は L1 キャッシュ ラインの数よりも多くする必要があります。
L2 キャッシュなしのシングル ポート キャッシュの例:
#pragma HLS cache port=A lines=4 depth=32
明示的にサイズを指定した共有 L2 キャッシュを備えた、1 サイクルに最大 3 回の読み出しが可能なマルチ ポート キャッシュ:
#pragma HLS cache port=A lines=4 depth=32 ports=3 l2_lines=16
動作とパフォーマンスに関する注意事項
- マルチ ポート キャッシュは、ウィンドウ バッファーが推測されない、ステンシルのような動的アクセス パターン向けに設計されています。共有 L2 を通じて冗長な DRAM バーストを削減しながら、1 サイクルあたり複数の読み出し要求を可能にすることで、スループットを向上させることができます。
- L1 キャッシュと L2 キャッシュは共にダイレクト マップ方式です。キャッシュのヒット/ミスの動作は局所性によって決まります。キャッシュ ライン数を増やしたり適切な深さを使用することでヒット率を改善できますが、使用するリソースが増加します。
- 共有 L2 キャッシュは、ミスが発生した際に DRAM から 1 回だけラインをロードし、それを必要とする L1 キャッシュにブロードキャストすることで、ウィンドウ バッファーに似た方法で帯域幅を最適化しつつ、動的なパターンにも対応します。
制限
- キャッシュは読み出し専用ポートに対してのみサポートされます。書き込みポートは、独立したチャネルを使用するため、キャッシュ付きの読み出し専用ポインターと同じバンドルを共有できますが、入出力ポートはサポートされていません。
- マルチ ポート モード (ポート数が 1 よりも大きい) の場合:
- キャッシュ付きの最上位 m_axi ポインターは、そのバンドルにマップされる唯一の読み出しポートでなければならなりません。
- 書き込みポートも同じバンドルにマップできますが、入出力ポートはマップできません。
- キャッシュは、L1 レベルと L2 レベルの両方でダイレクト マップ方式 (一方向) です。
- l2_lines を指定する場合、その値は lines よりも大きくする必要があります。
- マルチ ポートのキャッシュ付きポインターの m_axi バンドルに対して、読み出し専用配列は 1 つしかマップできません。
追加のコントローラー
-
syn.interface.m_axi_cache_implは、キャッシュのインプリメンテーション リソースを制御するために使用できます。 -
syn.interface.m_axi_max_read_burst_lengthとsyn.directive.interfaceを使用すると、最大バースト長を通じてデフォルトの深さを制御できます。
例
次の例は、アクセスが重なるとバーストがエラーになるデザインを示しています。CACHE プラグマまたは指示子を使用すると、デザインのパフォーマンスが改善します。
extern "C" {
void dut(
const double *in, // Read-Only Vector 1
double *out, // Output Result
int size // Size in integer
)
#pragma HLS INTERFACE m_axi port=in bundle=aximm depth = 1026
#pragma HLS INTERFACE m_axi port=out bundle=aximm depth = 1024
#pragma HLS cache port=in lines=8 depth=128
for(int i = 0; i < size; i++)
{
out[i] = in[i] + in[i + 1];
}
}