フル ブロックと空ブロックの確認 - 2023.2 日本語

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

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

read_lock および write_lockwhile(empty) or while (full) ループのようなもので、リソースが取得されるまでリソースを取得しようし続けるので、ロックが取得されるまでコードの実行は停止します。次の例に示すように、empty() メソッドと full() メソッドを使用すると、取得可能なブロックが不足している場合に read_lock または write_lock への呼び出しを停止すかどうかを指定できます。

#include "hls_streamofblocks.h"
  
void reader(hls::stream_of_blocks<buf> &in1, hls::stream_of_blocks<buf> &in2, int out[M][N], int c) {
    for(unsigned j = 0; j < M;) {
        if (!in1.empty()) {
            hls::read_lock<ppbuf> arr1(in1);
            for(unsigned i = 0; i < N; ++i) {
                out[j][i] = arr1[N-1-i];
            }
            j++;
        } else if (!in2.empty()) {
            hls::read_lock<ppbuf> arr2(in2);
            for(unsigned i = 0; i < N; ++i) {
                out[j][i] = arr2[N-1-i];
            }
            j++;
        }
    }
}
  
void writer(hls::stream_of_blocks<buf> &out1, hls::stream_of_blocks<buf> &out2, int in[M][N], int d) {
    for(unsigned j = 0; j < M; ++j) {
        if (d < 2) {
            if (!out1.full()) {
                hls::write_lock<ppbuf> arr(out1);
                for(unsigned i = 0; i < N; ++i) {
                    arr[N-1-i] = in[j][i];
                }
            }
        } else {
           if (!out2.full()) {
               hls::write_lock<ppbuf> arr(out2);
               for(unsigned i = 0; i < N; ++i) {
                   arr[N-1-i] = in[j][i];
               }
           }
        }
    }
}
  
void top(int in[M][N], int out[M][N], int c, int d) {
#pragma HLS dataflow
    hls::stream_of_blocks<buf, 3> strm1, strm2; // Depth=3
    writer(strm1, strm2, in, d);
    reader(strm1, strm2, out, c);
}

プロデューサー プロセスとコンシューマー プロセスは、本文のどの範囲内でも次の動作を実行できます。さまざまな例に示すように、範囲は通常ループになりますが、これは必須ではありません。条件文などのほかの範囲もサポートされます。サポートされる動作は次のとおりです。

  • ブロック、つまりサポートされるデータ型の配列を取得します。
    • プロデューサーの場合、配列は空になります。つまり、基盤となるデータ型のコンストラクター (存在する場合) に従って初期化されます。
    • コンシューマーの場合、配列はフルになります (プロデューサーがいっぱいにした分で、PIPO バッファーと同じ要件、つまり必要あればフル書き込みが適用されます)。
  • ブロックをプライベート ローカル メモリのように読み出しと書き込みの両方に、ストリーム オブ ブロックに指定された BIND_STORAGE プラグマまたは指示子 (各サイドが認識するポートを指定) に基づいて割り当てられた最大ポート数まで使用します
    • 1 ポートということは、各サイドが 1 つのポートにしかアクセスできないことを意味し、最終的なストリーム オブ ブロックはインプリメンテーションに 1 つのデュアル ポート メモリを使用できます。
    • 2 ポートということは、各サイドがスケジュールによって 1 つまたは 2 つのポートを使用できることを意味します。
      • スケジューラがー少なくとも 1 つの側で 2 つのポートを使用する場合、マージは実行されません。
      • スケジューラが 1 つのポートを使用する場合は、マージが実行される可能性があります。
    • プラグマが指定されていない場合は、スケジューラが現在ローカル配列に使用されているのと同じ条件に基づいて決定します。また、次もサポートされます。
      • プロデューサーは、取得したブロックの書き込みおよび読み出しの両方を実行できます。
      • コンシューマーは、取得したブロックを読み出すことしかできません。
  • ブロックが取得された範囲を終了すると、ブロックは自動的に解放されます。解放されたブロック:
    • プロデューサーが解放した場合は、コンシューマーが取得できます。
    • コンシューマーが解放した場合は、コンストラクターがあればそれで再初期化された後で、プロデューサーが取得して再利用できます。この初期化はデザイン速度を低下させる可能性があるため、ほとんどの場合必要ありません。__no_ctor__ 属性 (std::complex で説明) を使用すると、配列要素のコンストラクターを呼び出さないようにできます。
ストリーム オブ ブロックは、PIPO バッファーと非常に似ています。PIPO の場合、「取得」がプロデューサーまたはコンシューマー プロセス関数への「呼び出し」、「解放」はこの関数からの「戻り」と同じです。これは、次を意味します。
  • PIPO のハンドシェイク:
    • コンシューマー側に ap_start/ap_ready
    • プロデューサー側に ap_done/ap_continue
  • ストリーム オブ ブロックのハンドシェイク:
    • コンシューマー側に独自の read/empty_n
    • プロデューサー側に write/full_n