一方、ストリーム オブ ブロックの場合、プロデューサーとコンシューマー間の通信は配列のようなオブジェクトのストリームとして記述され、PIPO を介した配列転送よりも利点があります。
コードでストリーム オブ ブロックを使用するには、次のインクルード ファイルが必要です。
#include "hls_streamofblocks.h"
ストリーム オブ ブロック オブジェクトのテンプレートは、hls::stream_of_blocks<block_type, depth> v
です。
説明:
-
<block_type>
は、ストリーム オブ ブロックによって保持される配列または多次元配列のデータ型を指定します -
<depth>
は、hls::stream
または PIPO と同様に深さ制御するオプションの引数で、指定した時間でプロデューサーが取得したブロックとコンシューマーが取得したブロックを含むブロックの合計数を指定します。デフォルト値は 2 です。 -
v
は、ストリーム オブ ブロックの変数名を指定します。
ストリーム オブ ブロックのブロックにアクセスするには、次の手順を実行します。
- ストリームに最初にアクセスするプロデューサーまたはコンシューマー プロセスは、
hls::write_lock
またはhls::read_lock
オブジェクトを使用して、ストリームへのアクセス権を取得する必要があります。 -
プロデューサーがロックを取得したら、取得したブロックの書き込み (読み出し) を開始できます。ブロックが完全に初期化されたら、
write_lock
オブジェクトが有効範囲外になったときに、プロデューサーにより解放できます。注記: 新しく取得されたバッファーには初期化されていないデータが含まれていると想定されるため、既に書き込まれた場所からの読み出すだけであれば、
write_lock
を使用したプロデューサー プロセスがブロックを読み出すこともできます。ブロックの書き込みと読み出しの機能は、プロデューサー プロセスに固有であり、コンシューマーではサポートされません。 - 次に、ブロックが FIFO 形式でブロック オブ ストリームのキューに入れられ、コンシューマーが
read_lock
オブジェクトを取得すると、このブロックをコンシューマー プロセスにより読み出すことができます。
前の例で示されている hls::stream_of_blocks
と PIPO メカニズムの主な違いは、ブロックがプロデューサー プロセスの戻り値が出力される際だけでなく、write_lock
が範囲外になるとすぐにコンシューマーに提供できるようになる点です。このため、元の例 (データフロー ループなし) を管理するために必要なストレージのサイズは、PIPO のみの場合よりもストリーム オブ ブロックの方がかなり小さくなります。この例の場合、2xMxN ではなく、2N になります。
次は、前の例を hls::stream_of_blocks
を使用するように書き直したものになります。プロデューサーは、b
という hls::write_lock
オブジェクトを作成し、それを s
という名前のストリーム オブ ブロック オブジェクトへの参照に渡して、ブロックを取得します。write_lock
オブジェクトは、オーバーロードされた配列アクセス演算子を提供するので、次の例に示すように、基盤となるストレージにランダムな順序で配列としてアクセスできるようになります。
ロックの取得は write_lock
/read_lock
オブジェクトを構築することによって実行され、そのオブジェクトが範囲外になったときにそのオブジェクトが破棄されると、自動的に解放されます。このアプローチでは、よくある RAII (Resource Acquisition Is Initialization) 形式のロックおよびアンロックを使用します。
#include "hls_streamofblocks.h"
typedef int buf[N];
void producer (hls::stream_of_blocks<buf> &s, ...) {
for (int i = 0; i < M; i++) {
// Allocation of hls::write_lock acquires the block for the producer
hls::write_lock<buf> b(s);
for (int j = 0; j < N; j++)
b[f(j)] = ...;
// Deallocation of hls::write_lock releases the block for the consumer
}
}
void consumer(hls::stream_of_blocks<buf> &s, ...) {
for (int i = 0; i < M; i++) {
// Allocation of hls::read_lock acquires the block for the consumer
hls::read_lock<buf> b(s);
for (int j = 0; j < N; j++)
... = b[g(j)] ...;
// Deallocation of hls::write_lock releases the block to be reused by the producer
}
}
void top(...) {
#pragma HLS dataflow
hls::stream_of_blocks<buf> s;
producer(b, ...);
consumer(b, ...);
}
このアプローチの主な特長は、次のとおりです。
- 上記のプロデューサーの外部ループのパフォーマンスは、全体の開始間隔 (II) = 1 を達成できるはずです。
- ロックされたブロックは、解放されるまで、プロデューサーまたコンシューマー プロセスに対してプライベートであるかのように使用できます。
- プロデューサーの配列オブジェクトの初期状態は未定義ですが、コンシューマー用にプロデューサーによって書き込まれた値が含まれます。
- ストリーム オブ ブロックの主な利点は、コンシューマーとプロデューサーの複数の反復をオーバーラップして実行し、スループットを向上させるところです。