説明
FPGA では、DDR メモリへの非バースト アクセスは非常に高価であり、デザイン全体のパフォーマンスに影響することがあります。このため、必要な情報にアクセスするのに必要な時間を短縮するスキームを考案することが重要です。効率的な解決策は、コードを書き直すか、手動バーストを使用することですが、それでもうまくいかない場合は、キャッシュ メモリを使用するという方法もあります。
キャッシュは、M_AXI アダプターに一時的な記憶領域を提供して、デザインがより迅速にデータを取得できるようにします。キャッシュ メカニズムの有効性は、ヒット/ミス比率で測定され、「参照の局所性」と呼ばれるコンピューター プログラムの特性、つまり、プログラムが短時間に同じメモリ位置に繰り返しアクセスする傾向に基づきます。これは、特定のメモリ ブロックがアクセスされたとき、そのメモリが近い将来に再びアクセスされる可能性が高いということです。たとえば、メモリ内で連続した命令を実行する場合、次にアクセスする命令セットは、近くの連続したデータ ブロック内にある可能性が高くなります。
ヒント:
syn.interface.m_axi_cache_impl
コマンドを使用して、キャッシュ インプリメンテーション リソースを制御します。構文
syn.directive.cache=<location> port=<name> lines=<value> depth=<value>
説明:
-
<location>
- 指定されたポートが見つかる関数を指定します。これが最上位関数です。
-
port=<name>
- キャッシュを追加するポートを指定します。この引数は必須です。
-
lines=<value>
- キャッシュ ラインの数を指定します。ライン数は、1 キャッシュ ラインを示す 1 か、複数のキャッシュ ラインを示す 2 のべき乗で示される 1 より大きい値で指定できます。これはオプションの値で、指定しない場合のデフォルトは 1 になります。
-
depth=<value>
- 各ラインのサイズをワード数で指定します。深さは 2 のべき乗で指定する必要があり、各ラインごとにポインター データ型のサイズをワードで示します。
制限
CACHE 指示子またはプラグマには、次の制限があります。
- キャッシュは読み出し専用ポートに対してのみサポートされます。
- キャッシュはシングル ポート、一方向キャッシュとしてインプリメントされます。
- キャッシュは、m_axi ポートの各読み出しチャネルに関連付けられます。バンドルにチャネルが指定されていない場合、バンドルに割り当てられるすべての読み出しポートには、次のルールを適用する必要があります。
- キャッシュを使用しない、またはすべてにおいてライン サイズ (バイト単位) が同じで、ラインの数が同じキャッシュを使用します。
- ワード サイズが異なる場合、同じライン サイズ (バイト単位) となるように、深さも変える必要があります。
- 次のコードでは、char* の 1 ラインの深さ (たとえば、ワード数) は、int* の 4 倍となる必要があります。
void top(int *int_arr, char *char_arr, …) { #pragma HLS interface m_axi port=int_arr bundle=gmem … #pragma HLS interface m_axi port=char_arr bundle=gmem … #pragma HLS cache port=int_arr lines=8 depth=8 #pragma HLS cache port=char_arr lines=8 depth=32
- 一方、次のコードは、ラインのサイズが異なるため無効となります。
void top(int *int_arr, char *char_arr, …) { #pragma HLS interface m_axi port=int_arr bundle=gmem … #pragma HLS interface m_axi port=char_arr bundle=gmem … #pragma HLS cache port=int_arr lines=8 depth=8 #pragma HLS cache port=char_arr lines=8 depth=16
- 次のコードは、ラインの数が異なるため無効となります。
void top(int *int_arr, char *char_arr, …) { #pragma HLS interface m_axi port=int_arr bundle=gmem … #pragma HLS interface m_axi port=char_arr bundle=gmem … #pragma HLS cache port=int_arr lines=8 depth=8 #pragma HLS cache port=char_arr lines=4 depth=32
- 最後に、次のコードは、キャッシュを持つ配列が 1 つだけなので無効となります。
void top(int *int_arr, char *char_arr, …) { #pragma HLS interface m_axi port=int_arr bundle=gmem … #pragma HLS interface m_axi port=char_arr bundle=gmem … #pragma HLS cache port=int_arr lines=8 depth=8 y
- バンドルにチャネルが指定されている場合、各チャネルのキャッシュ サイズは異なる可能性があり、一部のチャネルにキャッシュがあれば、その他のチャネルにはない場合があります。チャネル内では、上記と同じルールが適用されます。たとえば、次のコードはまったく問題のない有効なものですが、2 つのキャッシュが存在するため、前の例よりも多くのリソースを消費します。
void top(int *int_arr, char *char_arr, …) { #pragma HLS interface m_axi port=int_arr bundle=gmem channel=0 … #pragma HLS interface m_axi port=char_arr bundle=gmem channel=1 … #pragma HLS cache port=int_arr lines=8 depth=32 #pragma HLS cache port=char_arr lines=8 depth=32
- キャッシュはライン サイズの読み出し要求を生成します。つまり、協調シミュレーションでは、配列サイズ (指定のデータ型のワード数) はライン サイズ (同じく指定のデータ型のワード数で指定) の整数倍である必要があります。
- 運用の際、ホスト コードによって実行される DRAM 内の配列の割り当ては、ライン サイズに揃えられる必要があり、配列の合計サイズはライン サイズの倍数である必要があります。
- 上記ルールが適用されなければ、協調シミュレーション実行中に次のエラー メッセージが表示されます。
ERROR: Index ... out of bound 0 to ...
例
次の例は、アクセスが重なるとバーストがエラーになるデザインを示しています。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];
}
}