Vitis Accel Examples を試す(2/3)
2021-9-17 22:41 JST

Vitis_Accel_Examples内の cpp_kernels/burst_rw のソースを読みます。

3部作になっています。

Vitis_Accel_Examplesの本家はこちら

ソースを眺める

ソースは src のしたにあります

さて、ポイントは HLS のソースでもある vadd.cpp です。冒頭部分を引用します。

extern "C" {
void vadd(int* a, int size, int inc_value) {
// Map pointer a to AXI4-master interface for global memory access
#pragma HLS INTERFACE m_axi port = a offset = slave bundle = gmem max_read_burst_length = 256 max_write_burst_length = \
    256
// We also need to map a and return to a bundled axilite slave interface
#pragma HLS INTERFACE s_axilite port = a
#pragma HLS INTERFACE s_axilite port = size
#pragma HLS INTERFACE s_axilite port = inc_value
#pragma HLS INTERFACE s_axilite port = return

ここでのポイントは2つ。1つは gmem で bundle された HLS INTERFACE m_axi の変数 a。もう1つは HLS INTERFACE s_axilite 。Vivado HLS(Vitis HLS) に慣れた人なら見覚えがある記述でしょう。OpenCL であるため extern "C" というのがちょっとした違いかもしれません。

通常の HLS とは違い、XRT でつかえるカーネルはある一定の命名規則とインタフェースを持っている必要があります。どうやら ap_fifo などは使えないようです。

XRT(Xilinx Runtime library)

突然出てきたキーワード XRT。ソースも github で公開されています。FPGA とユーザの間の API はSDSoC では UIO とか使ってました(Deprecated!!)が、XRT の導入によって整理されました。OpenCL をつかっているので、メモリの管理も整理されたと言ってよいでしょう。

XRT Controlled Kernel Execution Modelsでは幾つかの Model のケースを掲げています。図中の CU は Compute Unit の略。(Host Programingを参照)

XRT のモデルは Vitis HLS で生成される信号線(ap_start, ap_ready, ap_done, ap_continue)で見覚えがあるものばかりです。それが U50 のような PCIe の場合、レジスタとして CPU 側から見えます。(:a :href "https://www.xilinx.com/html_docs/xilinx2021_1/vitis_doc/devrtlkernel.html" "RTL Kernels")参照

オフセット名称説明
0x0 制御 カーネル ステータスを制御および示します。
0x4 グローバル割り込みイネーブル ホストへの割り込みをイネーブルにします。
0x8 IP 割り込みイネーブル 割り込みの生成に使用する IP で生成された信号を制御します。
0xC IP 割り込みステータス 割り込みステータスを示します。
0x10 カーネル引数 (アドレス 0x10 で開始) スカラーおよびグローバル メモリ引数を含みます。

このように規定されており、0x10 以降が HLS の axilite と対応します。制御の為の信号線を on/off しないといけないか等は気にしなくてよく、OpenCL の仕組み(API)で吸収されます。

ホスト側の呼び出し

    OCL_CHECK(err, err = krnl_add.setArg(0, buffer_rw));
    OCL_CHECK(err, err = krnl_add.setArg(1, size));
    OCL_CHECK(err, err = krnl_add.setArg(2, inc_value));

確認はしていませんが、、、buffer_rw はアドレスで AXIM は 64bit のアドレスを用意しているので、64bit になるはずです。また buffer はユーザ側が用意しなければならず、このソースでは次のようにしています。

    OCL_CHECK(err, cl::Buffer buffer_rw(context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, vector_size_bytes, source_inout.data(), &err));
    <中略>
    OCL_CHECK(err, err = q.enqueueMigrateMemObjects({buffer_rw}, 0 /* 0 means from host*/));

結果を得る

次のようにして結果を得ています。 enqueueTask がカーネルの実行。つまり、なかで ap_start とかの信号線をアサートしているのでしょう。enqueueMigrateMemObjects で buffer_rw から情報を書き戻してます。つまり FPGA 側にあるメモリから HOST(すなわち Intel CPU) へ転送しています。

    // Launch the Kernel
    OCL_CHECK(err, err = q.enqueueTask(krnl_add));

    // Copy Result from Device Global Memory to Host Local Memory
    OCL_CHECK(err, err = q.enqueueMigrateMemObjects({buffer_rw}, CL_MIGRATE_MEM_OBJECT_HOST));
    OCL_CHECK(err, err = q.finish());

vadd 側のソース

これが内部バッファを使っていてバースト転送するソースになっていてちょっと難しいですが、やっていることは a というバッファのアドレスから値を読んで値を足して書き戻しています。

<前略>
           burstbuffer[j] = a[i + j];
<中略>
           burstbuffer[j] = burstbuffer[j] + inc_value;
           a[i + j] = burstbuffer[j];
<後略>

burstbuffer をつかって内部の SRAM を使うようにしています。a のバッファは FPGA 内のバッファです。あれ?いつ誰が FPGA 内のバッファへ情報を転送したのだ?

どうやってメモリを転送しているのだ?

OpenCL として host.cpp から転送命令はあるものの、vadd.cpp のソースに現れない誰かが HOST(CPU) から DEVICE(FPGA) へそしてその逆の転送をしています。