元の DATAFLOW モデルでは、シーケンシャル関数の記述が可能で、AMD Vitis™ HLS ツールでデータフロー プロセス (タスク) の識別と並列処理、依存性の解析と管理、スカラー伝搬、および array-to-stream などの最適化が実行されます。または hls::task
オブジェクトを使用する場合、タスクおよびチャネルを明示的にインスタンシエートし、アルゴリズム設計で並列処理を管理する必要があります。hls::task
は、ストリーミングデータ チャネルのみを使用して並列タスクをサポートするプログラミング モデルを定義するために使用します。タスクは、関数の呼び出し/戻りによって制御されず、入力ストリーにデータが存在すると実行されます。
hls::task
ライブラリは同時実行セマンティクスを提供し、C シミュレーションが RTL と一致するようにします。これにより、順次データフロー モデルの問題が解消されます。次に、タスクおよびチャネルの例を示します。ストリーミング インターフェイス (hls::stream
または hls::stream_of_blocks
) のみが使用されていることがわかります。また、最上位関数では、hls_thread_local
キーワードを使用してタスクおよびストリーム チャネルが定義されています。
void func1(hls::stream<int> &in, hls::stream<int> &out1, hls::stream<int> &out2) {
int data = in.read();
if (data >= 10)
out1.write(data);
else
out2.write(data);
}
void func2(hls::stream<int> &in, hls::stream<int> &out) {
out.write(in.read() + 1);
}
void func3(hls::stream<int> &in, hls::stream<int> &out) {
out.write(in.read() + 2);
}
void top-func(hls::stream<int> &in, hls::stream<int> &out1, hls::stream<int> &out2) {
hls_thread_local hls::stream<int> s1; // channel connecting t1 and t2
hls_thread_local hls::stream<int> s2; // channel connecting t1 and t3
hls_thread_local hls::task t1(func1, in, s1, s2); // t1 infinitely runs func1, with input in and outputs s1 and s2
hls_thread_local hls::task t2(func2, s1, out1); // t2 infinitely runs func2, with input s1 and output out1
hls_thread_local hls::task t3(func3, s2, out2); // t3 infinitely runs func3, with input s2 and output out2
}
上記の例では、hls::task
オブジェクトは変数で、hls_thread_local
と宣言して複数回のインスタンシエーション関数 (top_func
) の呼び出し間で変数および下位のスレッドをアクティブにする必要があります。タスク オブジェクトは、上記の例では func1
、func2
、func3
といった関数を連続して実行するスレッドを暗示的に管理します。この関数はタスク本体であり、暗示的に無限ループが使用されます。
各 hls::task
には、関数名、入出力チャネル hls::streams
または hls::stream_of_blocks
を含む引数セットを渡す必要があります。hls::task
オブジェクトは、通常ストリーミング チャネルの hls::stream
および hls::stream_of_blocks
に対してのみ読み出しおよび書き込みできます。
hls::task
と、それに接続するチャネルは、いずれも hls_thread_local
と宣言する必要があります。hls_thread_local
を宣言して、複数回の最上位関数の呼び出し間でチャネルをアクティブに維持する必要があります。スカラー変数および配列変数といったストリーム データ以外のデータはすべて、タスク関数に対してローカルにする必要があり、次の「安定した M_AXI および S_AXILITE のアクセス」の場合を除き、引数として渡すことはできません。
hls_task.h
を含めると、C シミュレーションでは hls::stream
および hls::stream_of_blocks
読み出し要求がブロッキングとなります。つまり、空のストリームの読み出しのみを使用していたコードで、シミュレーション時にデッドロックが発生することがあります。配列およびスカラー I/O アクセスへの非同期ポインター
スカラー値 (ローカル引数と最上位引数の両方) やアレイ引数へのポインターを最上位関数に渡すこともできます。ただし、データ駆動型 TLP の非同期 I/O で説明されるように、STABLE
プラグマまたは指示子でマークしておく必要があります。また、カーネル実行中にこれらの引数の値が変更されないように注意する必要があります (これは、通常のデータフロー プロセスがない場合、最上位に単独でインスタンシエートされた hls::task
では実際に不可能)。または、カーネル動作がこれらの引数の値の変更時に依存しないようにする必要があります。たとえば、プロセスが任意の時点で値の変更を許容できたり、ほかのストリーム ベースの同期メカニズムがそれらのアクセスを規制するために使用されたりといったことです。
スカラー値は参照渡しです。
void test(hls::stream<int> &in, hls::stream<int> &out, int &n)
m_axi
プロトコルと s_axilite
オフセットを持つ最上位 STABLE ポインターは、協調シミュレーション設定 で説明されるように、cosim.enable_tasks_with_m_axi
コマンドを使用して C/RTL 協調シミュレーションでイネーブルにする必要があります。
次は、参照渡しのスカラー引数を使用する hls::task
デザインの例で、引数の値が変化する正確なタイミングにほとんど影響を受けません。
void task1(hls::stream<int> &in, hls::stream<int> &out) {
...
}
void task2(hls::stream<int> &in, hls::stream<int> &out) {
...
}
void task3(hls::stream<int> &in, hls::stream<int> &out, int &n) {
int c = in.read();
out.write(c + n);
}
void test(hls::stream<int> &in, hls::stream<int> &out, int &n) {
#pragma HLS stable variable=n
HLS_TASK_STREAM<int> s1;
HLS_TASK_STREAM<int> s2;
HLS_TASK t1(task1, in, s1);
HLS_TASK t2(task2, s1, s2);
HLS_TASK t3(task3, s2, out, n);
}
次の例は、最上位関数に安定した m_axi
ポインター引数を使用した hls::task
デザインを示しています。基礎となる DRAM バッファーへのアクセスはすべて、関数のプロセスを使用して非同期になります。if (mem)
文を使用すると、ホスト コードがオフセット レジスタを DRAM 内のバッファーのアドレスで初期化した後にのみ、DRAM バッファーがアクセスされるようにできます。
m_axi
インターフェイスのオフセット レジスタは自動的に ap_none
プロトコルを使用するため、C++ と RTL は write_process
が再び実行されたときにのみ、その値を再読み込みします。...
void write_process(hls::stream<int>& in, hls::stream<int>& out, int* mem)
{
#pragma HLS PIPELINE style=flp
...
if (mem) {
mem[...] = ...;
...
... = mem[...];
}
...
}
...
void stable_pointer(int* mem, hls::stream<int>& in, hls::stream<int>& out)
{
#pragma HLS INTERFACE mode=m_axi port=mem ...
#pragma HLS stable variable=mem
hls_thread_local hls::stream<int> int_fifo("int_fifo");
hls_thread_local hls::stream<int> int_fifo2("int_fifo2");
hls_thread_local hls::task t1(process_23, in, int_fifo);
hls_thread_local hls::task t2(process_11, int_fifo, int_fifo2);
hls_thread_local hls::task t3(write_process, int_fifo2, out, mem);
}
フラッシュ パイプラインの使用法
通常、hls::task
デザインでは、パイプラインのフラッシュとパイプラインのタイプ で説明されるように、フラッシング パイプライン (flp
) またはフリーランニング パイプライン (frp
) を使用する必要があります。フラッシング以外のパイプラインを使用すると、プロセス実行間に依存関係が発生し、予期しないデッドロックが発生する可能性があります。
syn.compile.pipeline_flush_in_task
を使用すると、hls::tasks
でデフォルトのフラッシング動作を設定できます。入れ子のタスク
次の例では、task2 で task1 のインスタンスが 2 つ使用されていますが、両方とも hls::task
インスタンスとしてインスタンシエートされています。この例では、hls::task
の本体はシーケンシャル関数に加え、hls::task
オブジェクトのみを含む関数にできます。
void task1(hls::stream<int> &in, hls::stream<int> &out) {
hls_thread_local hls::stream<int> s1;
hls_thread_local hls::task t1(func2, in, s1);
hls_thread_local hls::task t2(func3, s1, out);
}
void task2(hls::stream<int> &in1, hls::stream<int> &in2, hls::stream<int> &out1, hls::stream<int> &out2) {
hls_thread_local hls::task tA(task1, in1, out1);
hls_thread_local hls::task tB(task1, in2, out2);
}
hls_thread_local
の使用は、中間ネットワークのインスタンシエーションを複数回安全に実行するために必要です (tA
および tB
、この例では両方とも task1
のインスタンス。つまり、tA
および tB
内の最下位レベルのプロセス t1
(どちらも func2
の異なるコピーを実行) と、tA
および tB
内の t2
の安全なインスタンス)。
シミュレーションおよび協調シミュレーション
タスクおよびチャネル モデルの C シミュレーション動作は、C/RTL 協調シミュレーションと同様のものとなる可能性があります。以前は空のストリームからの読み出しは可能で、シミュレーションが途中で停止する可能性があるという警告メッセージが表示されるだけでした。Vitis HLS 2022.2 では、空のストリームから読み出すと C シミュレーションでもデッドロックが発生するため、次のメッセージが表示されてエラー状態となります。
-
hls::task
オブジェクトを使用するデザインの場合ERROR [HLS SIM]: deadlock detected when simulating hls::tasks. Execute C-simulation in debug mode in the GUI and examine the source code location of all the blocked hls::stream::read() calls
-
hls::task
を使用しないデザインの場合ERROR [HLS SIM]: an hls::stream is read while empty, which may result in RTL simulation hanging. If this is not expected, execute C simulation in debug mode in the GUI and examine the source code location of the blocked hls::stream::read() call to debug. If this is expected, add -DHLS_STREAM_READ_EMPTY_RETURNS_GARBAGE to -cflags to turn this error into a warning and allow empty hls::stream reads to return the default value for the data type.
ヒント:-DHLS_STREAM_READ_EMPTY_RETURNS_GARBAGE
を-cflags
に追加すると、このエラーは警告に変わります。