タスクおよびチャネル - 2023.2 日本語

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

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

元の 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) の呼び出し間で変数および下位のスレッドをアクティブにする必要があります。タスク オブジェクトは、上記の例では func1func2func3 といった関数を連続して実行するスレッドを暗示的に管理します。この関数はタスク本体であり、暗示的に無限ループが使用されます。

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 に追加すると、このエラーは警告に変わります。