パイプライン内の PE は、通過するトランザクションで同期に動作します。すべての compute()
呼び出しで PE は 1 回だけ開始および停止します。ただし、PE が ガイダンス マクロ で説明されるように FREE_RUNNING
とマークされている場合は、次のハードウェア セマンティクスがあります。
- PE は、トランザクションまたは
compute()
呼び出しごとに開始、停止、リセットをしません。これは、「ブロックレベル制御プロトコル」で説明されるように、ap_none
制御インターフェイスを備えた HLS カーネルです。 - インターフェイスには、AXI4-Stream 引数またはスカラー入力のみを含める必要があります。
- 演算はデータ駆動型で、PE は入力ストリーム ワードでのみ動作し、トランザクションのペイロード サイズは認識されません。
- PE は、ハードウェア ビットストリームがデバイスにプログラムされた直後に実行を開始します。
上記の図は、フリーランニング PE を示しています。このアクセラレータには、グローバル メモリ アクセスを持つ LdStr
PE、およびフリーランニング PE である fsk_incr
の 2 つの PE が含まれます。compute()
スコープでは、これらの PE は 2 つの AXI4-Stream インターフェイス (LdStr
から fsk_incr
にワードを移動する AS
とフィードバック パスである XS
) によって接続されます。
次に、この例のコードを示します。
class fsk_acc : public VPP_ACC<fsk_acc, NCU>
{
ZERO_COPY(A);
ZERO_COPY(X);
SYS_PORT(A, DDR[0]);
SYS_PORT(X, DDR[0]);
FREE_RUNNING (fsk_incr);
public:
static void compute(DT* A, DT* X, int sz);
static void loadstore(DT* A, DT* X, hls::stream<DT>& AS,
hls::stream<DT>& XS);
static void fsk_incr(hls::stream<DT>& AS, hls::stream<DT>& XS);
};
Void fsk_acc::compute(DT* A, DT* X, int sz)
{
static vpp::stream<DT> AS, XS;
ldst(A, X, sz, AS, XS);
fsk_incr(AS, XS);
}
void fsk_acc::ldst(DT* A, DT* X, int sz, hls::stream<DT>& AS,
hls::stream<DT>& XS)
{
for (int i = 0; i < sz; i++) {
AS.write(A[i]);
}
for (int i = 0; i < sz; i++) {
XS.read(X[i]);
}
}
void fsk_acc::fsk_incr(hls::stream<DT>& AS, hls::stream<DT>& XS)
{
DT val;
AS.read(val);
XS.write(val + 1);
}
LdSt
PE は、グローバル メモリ ポート A および X にそれぞれ読み書きする sz
ワードで動作します。一方、フリーランニング PE fsk_incr
は、sz
には依存せず、入ってくる AS
ストリームのワードにのみ反応します。
前述のフリーランニング セマンティクスは、フリーランニング PE のインプリメンテーションを大幅に簡素化し、多くの場合、必要な FPGA 使用率と配線リソースを簡素化します。これにより、中間 PE をフリーランニングにできる (結果、入力 AXI4-Stream でのみ動作する) ストリーミング パイプライン デザインの設計ができるようになります。
どのパイプライン構成でも、ハードウェア複製がイネーブル (NCU が 1 以上) の場合、ハードウェアには複製されたパイプラインがいくつも含まれ、compute()
ジョブが使用可能なパイプライン スロットで実行されます。したがって、アプリケーション層はシンプルなままで、ハードウェア内の複数のパイプラインを使用してデータの実行を自動化します。