バースト転送の前提条件と制限 - 2023.2 日本語

Vitis 高位合成ユーザー ガイド (UG1399)

Document ID
UG1399
Release Date
2023-12-18
Version
2023.2 日本語

バーストの前提条件

バーストは、連続するメモリ アクセス要求をまとめることです。バースト最適化が正しく実行されるようにするには、連続するアクセスが次の前提条件を満たしている必要があります。

  • すべてが読み出しまたはすべて書き込みである必要があります。読み出しと書き込みをバースト転送することはできません。
  • アクセスするメモリのロケーションおよび時間が単調に増加する必要があります。以前にアクセスされたメモリ ロケーションの間のメモリ ロケーションにアクセスすることはできません。
  • メモリ内でギャップまたはオーバーラップなしで進行順に連続している必要があります。
  • 読み出し/書き込みアクセスの数 (バースト長) が要求の送信前に決定されている必要があります。これは、バースト長が実行時に計算される場合ですら、読み出し/書き込み要求の送信前に計算される必要があるということを意味します。
  • 2 つの配列を同じ M-AXI ポートにまとめる場合、一度に 1 方向に 1 つの配列にしかバーストを実行できません。
  • 同じ領域の同じバンドルの同じチャネルに同じ方向のアクセスがある場合、これらのすべてのアクセスに対してバーストは発生しません。
  • バースト要求が発行されてから終了するまでに依存の問題がないことが必要です。
ヒント: volatile 修飾子は、変数への、または変数からのバースト アクセスを回避します。

メモリ アクセスのオーバーラップのため外側のループのバースト推論は不可

次の例では、ループ L1 の反復 0 と反復 1 が配列 a および b の同じ要素にアクセスするので、外側ループにバーストは推論されません。現在のところ、バーストの推論はすべてありかなしかの最適化であり、一部のみをバースト推論することはできません。これは、バースト長を最適化しようとする貪欲アルゴリズムです。自動バースト推論では、内側ループから外側ループにボトムアップ方式でバーストが推測され、前提条件のいずれかが満たされないと停止します。次の例では、要素 8 が再び読み出されるとバースト推論が停止するので、長さ 9 の内側ループ バーストが推論されます。

L1: for (int i = 0; i < 8; ++i)
    L2: for (int j = 0; j < 9; ++j)
            b[i*8 + j] = a[i*8 + j];

itr 0: |0 1 2 3 4 5 6 7 8|
itr 1: |                8 9 10 11 12 13 14 15 16|

ap_int/ap_uint 型をループ帰納変数として使用

バーストの推論はループ帰納変数およびトリップカウントによるので、ネイティブでないデータ型を使用すると、最適化が実行されない可能性があります。ループ帰納変数には、常に符号なし整数型を使用することをお勧めします。

1 回以上ループに入る必要がある

場合によって、コンパイラでループ帰納変数の最大値は 0 にならないということが推論されない (常にループに入るかどうかを判断できない) ことがあります。このような場合は、assert 文を使用するとコンパイラでこれを推論できるようになることがあります。

assert (N > 0);
L1: for(int a = 0; a < N; ++a) { … }

配列でのループ間またはループ内依存

配列内のあるロケーションに書き込み、同じ反復または次の反復でそれを読み出す場合、最適化でこのような配列依存を解釈するのが困難になる可能性があります。このような場合、書き込みが読み出しの前に実行されることを確実にできないので、最適化を実行できません。

メモリへの条件付きアクセス

メモリ アクセスを条件付きにすると、条件文を解釈できないので、バースト推論アルゴリズムが機能しません。この場合、コンパイラで条件が単純化されるか削除されます。そのため、メモリ アクセスには条件文を使用しないことをお勧めします。

ループから呼び出された関数内からの M-AXI アクセス

関数間の配列アクセスは、バースト推論などのコンパイラによる変換ではうまく解析されません。このような場合は、INLINE プラグマまたは指示子を使用して関数をインライン展開すると、バーストが推論されない問題を回避できます。

void my_function(hls::stream<T> &out_pkt, int *din, int input_idx) {
    T v;
    v.data = din[input_idx];
    out_pkt.write(v);
}

void my_kernel(hls::stream<T> &out_pkt,
               int            *din,
               int            num_512_bytes,
               int            num_times) {
#pragma HLS INTERFACE mode=m_axi port = din offset=slave bundle=gmem0
#pragma HLS INTERFACE mode=axis port=out_pkt
#pragma HLS INTERFACE mode=s_axilite port=din bundle=control
#pragma HLS INTERFACE mode=s_axilite port=num_512_bytes bundle=control
#pragma HLS INTERFACE mode=s_axilite port=num_times bundle=control
#pragma HLS INTERFACE mode=s_axilite port=return bundle=control

unsigned int idx = 0;
L0: for (int i = 0; i < ntimes; ++i) {
    L1: for (int j = 0; j < num_512_bytes; ++j) {
#pragma HLS PIPELINE
        my_function(out_pkt, din, idx++);
    }
}

バーストが推論されないのは、メモリ アクセスが呼び出し元の関数から実行されていることが原因です。バーストが推論されるようにするには、M-AXI メモリにアクセスする関数をインライン展開することをお勧めします。

この例でバーストが推論されないもう 1 つの理由は、my_functiondin を介してアクセスされるメモリが変数 (idx) で定義されていますが、この変数はループ帰納変数 i および j の関数でないため、順次または単調でない可能性があることです。idx を渡す代わりに、(i*num_512_bytes+j) を使用してください。

データフロー ループのパイプライン バーストの推論

DATAFLOW プラグマまたは指示子が指定されているループでは、バーストの推論はサポートされません。ただし、DATAFLOW プラグマまたは指示子が指定されているループ内の各プロセス/タスクはバースト可能です。また、データフロー領域ではタスクを並列実行できるので、M-AXI ポートの共有もサポートされません。