Vitis™ アプリケーション アクセラレーション開発フロー チュートリアル

ホスト コードのプログラミング

ユーザー管理 RTL カーネルは、XRT の資料に説明されているように、XRT ネイティブ API を使用して記述されたホスト アプリケーションでのみサポートされます。XRT ネイティブ API は、XRT 管理カーネル (XRT ネイティブ API ホスト アプリケーションのチュートリアルで説明) およびユーザー管理カーネル (ここで説明) に対して多数のクラス オブジェクトおよび関数を提供します。

reference-files/src/host/user-host.cpp ファイルを開き、内容および構造を確認します。『Vitis 統合ソフトウェア プラットフォームの資料』 (UG1393) のホスト プログラミングに説明されているように、ホスト アプリケーションが従う必要のある重要な手順がいくつかあります。

  1. アクセラレータ デバイス ID を指定して .xclbin をロードします。

  2. カーネルとカーネル引数を設定します。

  3. ホストとカーネル間でデータを転送します。

  4. カーネルを実行し、結果を返します。

このチュートリアルでは、これらの各手順を説明します。また、ユーザー管理 RTL カーネルを使用する場合におけるこれらの手順の要件も説明します。

XRT ネイティブ API の設定

#include <iostream>
#include <cstring>

// XRT includes
#include "xrt/xrt_bo.h"
#include "xrt/xrt_device.h"
#include <experimental/xrt_xclbin.h>
#include <experimental/xrt_ip.h>

XRT ネイティブ API は、システムの異なる要素のさまざまなクラス オブジェクトと関数をサポートするために、いくつかの #include 文を必要とします。上のコード サンプルを見ると、ザイリンクス デバイス、デバイス バイナリ (.xclbin)、バッファー オブジェクト (xrt_bo)、およびユーザー管理カーネル (xrt_ip) に対するヘッダー ファイルのインクルードがあるのがわかります。この最後の部分が、XRT 管理カーネルの場合とユーザー管理カーネルの場合における XRT ネイティブ API での重要な違いです。

XRT 管理カーネル (xrt::kernel) は、RTL カーネルの要件に説明されているように、制御プロトコルおよびインターフェイスの特定の要件を満たすため、XRT API の機能セットがより豊富です。ユーザー管理カーネルは、少し非構造化されているため、機能が制限された IP クラスで記述されます。

デバイス ID の指定と XCLBIN のロード

    // Read settings
    std::string binaryFile = "./vadd.xclbin";
    auto xclbin = xrt::xclbin(binaryFile);
    int device_index = 0;

    std::cout << "Open the device" << device_index << std::endl;
    auto device = xrt::device(device_index);
    std::cout << "Load the xclbin " << binaryFile << std::endl;
    auto uuid = device.load_xclbin(binaryFile);

Vitis アプリケーション アクセラレーション開発フローでは、ザイリンクス デバイス バイナリ ファイル (xclbin) を指定する必要があります。これは、ザイリンクス デバイスにロードされる実行ファイルで、アクセラレータ カードまたはプラットフォームによって実行されます。xclbin をロードする対象となるザイリンクス デバイスを識別する必要もあります。システムによっては、複数のデバイスを持つ複数のアクセラレータ カードがあることがあります。このため、デバイスおよび xclbin を指定する必要があります。

実行中にデバイスとバイナリ ファイルの両方を識別する方法はいくつかあります。これらは、このチュートリアルでデバイスに対して実行されているようにハード コードしたり、ここで .xclbin ファイルに対して実行されているようにコマンド ラインから指定したり、Vitis_Accel_Examples に示されているようにその他の方法で指定したりできます。

上のコード例 (このチュートリアルの user-host.cpp ファイルからのもの) では、デバイスは単に device_index 0 で、バイナリ ファイルはコマンド ラインから渡されます。コードは次のとおりです。

  1. ザイリンクス バイナリ ファイルを識別: xrt::xclbin(binaryFile)

  2. デバイスを開く: xrt::device(device_index)

  3. xclbin をデバイスにロード: device.load_xclbin(binaryFile)

カーネルおよびカーネル引数の設定

XRT 管理カーネルの場合、カーネルは xrt::kernel オブジェクトとして識別されます。ユーザー管理 RTL カーネルの場合、下のコード例に示すように、ホスト アプリケーションは xrt::ip オブジェクトを使用して xclbin のカーネルを識別します。また、ホスト アプリケーションは、xclbin のカーネルの計算ユニット (CU) も識別します。複数のカーネル インスタンスの作成に説明されているように、CU はカーネルのインスタンスです。下のコード例では、ホスト アプリケーションによって識別されたユーザー管理カーネルが 3 つ (ip1、ip2、および ip3) あります。これにより、ホストは複数のカーネル インスタンスを同時に実行できるようになります (xclbin にある場合)。

注記: ip2 および ip3 は例として追加されており、実際にはホスト アプリケーションで使用されていません。

    auto ip1 = xrt::ip(device, uuid, "Vadd_A_B:{Vadd_A_B_1}");
    auto ip2 = xrt::ip(device, uuid, "Vadd_A_B:{Vadd_A_B_2}");
    auto ip3 = xrt::ip(device, uuid, "Vadd_A_B:{Vadd_A_B_3}");

    std::cout << "Allocate Buffer in Global Memory\n";
    //auto boA = xrt::bo(device, vector_size_bytes, krnl.group_id(1)); //Match kernel arguments to RTL kernel
    //auto boB = xrt::bo(device, vector_size_bytes, krnl.group_id(2));
    auto ip1_boA = xrt::bo(device, vector_size_bytes, 0);
    auto ip1_boB = xrt::bo(device, vector_size_bytes, 1);
    //auto ip2_boA = xrt::bo(device, vector_size_bytes, 0);
    //auto ip2_boB = xrt::bo(device, vector_size_bytes, 1);
    //auto ip3_boA = xrt::bo(device, vector_size_bytes, 0);
    //auto ip3_boB = xrt::bo(device, vector_size_bytes, 1);

XRT 管理カーネルでは、XRT API は、特定の引数によって使用されるメモリ バンクのバッファーを作成できます。これは、上の例のバッファー オブジェクト boA および boB に対して示されているように、krnl.group_id(1) を使用して実行されます。XRT 管理カーネルと同様、xrt::bo オブジェクトは、関数引数のバッファー、つまり RTL カーネルのポートを作成するために使用されます。XRT 管理カーネルとは異なり、カーネル引数がマップされる対象のメモリは、上のコード例のバッファー オブジェクト ip1_boA および ip1_boB に対して示されているように、手動で指定される必要があります。

ユーザー管理カーネルの場合、使用するメモリ バンクにカーネル引数をマップする同等の方法はありません。この情報は、ご自身でユーザー管理カーネルをビルドしたので、既にご存じのはずです。または、Vitis 環境リファレンス資料に示すように、xclbinutil コマンドで生成された xclbin.info ファイルを確認すると、特定できます。

ヒント: カーネルをリンクして xclbin ファイルを生成する際に、xclbin.info レポートが Vitis コンパイラによって生成されます。

カーネルの特定の引数に使用されるメモリ バンクを判断し、そのメモリ バンクに関連付けられている引数のバッファーを作成する必要があります。そのバッファーを、次に示すように、read_register または write_register コマンドをカーネルに対して実行する際に指定のカーネル引数に使用する必要があります。

ip.write_register(register offset for argument A, address of buffer for argument A)`  

この情報は、この user-host.cpp アプリケーションではハード コードされていますが、Vitis_Accel_Examples に例が示されているように異なるコーディング手法があります。

データの転送

バッファーを作成し、ホスト アプリケーションからのデータを書き込んだら、そのバッファーをザイリンクス デバイスに同期して、デバイス バイナリ (xclbin) からデータにアクセスできるようにする必要があります。データは、下のコード例に示すように、ホスト コンピューターから、カーネルで使用可能なザイリンクス デバイスおよびアクセラレータ カードに転送されます。

    // Map the contents of the buffer object into host memory
    auto bo0_map = ip1_boA.map<int*>();
    auto bo1_map = ip1_boB.map<int*>();
 
    std::fill(bo0_map, bo0_map + DATA_SIZE, 0);
    std::fill(bo1_map, bo1_map + DATA_SIZE, 0);

    // Create the test data
    int bufReference[DATA_SIZE];
    for (int i = 0; i < DATA_SIZE; ++i) {
        bo0_map[i] = i;
        bo1_map[i] = i;
        bufReference[i] = bo0_map[i] + bo1_map[i]; //Generate check data for validation
    }

    std::cout << "loaded the data" << std::endl;
    uint64_t buf_addr[2];
    // Get the buffer physical address
    buf_addr[0] = ip1_boA.address();
    buf_addr[1] = ip1_boB.address();

    // Synchronize buffer content with device side
    std::cout << "synchronize input buffer data to device global memory\n";
    ip1_boA.sync(XCL_BO_SYNC_BO_TO_DEVICE);
    ip1_boB.sync(XCL_BO_SYNC_BO_TO_DEVICE);

コード例を見ると、バッファーのアドレスが buf_addr ベクターでキャプチャされているのがわかります。この情報は、カーネル引数に関連付けられているレジスタに対して書き込みまたは読み出しを実行する際に必要となります。

カーネルの実行と結果の生成

XRT 管理カーネルの場合、カーネルの実行は簡単で、下のコードに示すように run オブジェクトを作成します。XRT でカーネルの開始と停止が管理されます。あとは、結果が生成されるのを待つだけです。

    //std::cout << "Execution of the kernel\n";
    //auto run = krnl(DATA_SIZE, boA, boB); //DATA_SIZE=size
    //run.wait();

ユーザー管理カーネルの場合、XRT ではカーネルの開始または停止がわからないため、ユーザーが実行することになります。ユーザー管理カーネルは、ap_ctrl_hs または ap_ctrl_chain 制御プロトコルに制約されないため、柔軟性に優れており、デザイン問題に対してクリエイティブなソリューションを可能にします。多数の機能が提供されますが、管理はユーザーが実行します。

まず、レジスタに書き込んで、さまざまなカーネル引数用に関連するバッファー アドレスを渡して、カーネルを設定する必要があります。次に、カーネル実行をトリガーする必要があります。これは、ユーザーによって設計されカーネルでインプリメントされる制御プロトコルに従って、レジスタ値を設定するか信号ビットをイネーブルにすることにより実行します。

    std::cout << "INFO: Setting IP Data" << std::endl;
    std::cout << "Setting Register \"A\" (Input Address)" << std::endl;
    ip1.write_register(A_OFFSET, buf_addr[0]);
    ip1.write_register(A_OFFSET + 4, buf_addr[0] >> 32);

    std::cout << "Setting Register \"B\" (Input Address)" << std::endl;
    ip1.write_register(B_OFFSET, buf_addr[1]);
    ip1.write_register(B_OFFSET + 4, buf_addr[1] >> 32);

    uint32_t axi_ctrl = IP_START;
    std::cout << "INFO: IP Start" << std::endl;
    //axi_ctrl = IP_START;
    ip1.write_register(USER_OFFSET, axi_ctrl);

    // Wait until the IP is DONE
    int i = 0;
    //axi_ctrl = 0;
    while (axi_ctrl != IP_IDLE) {
    //while ((axi_ctrl & IP_IDLE) != IP_IDLE) {
        axi_ctrl = ip1.read_register(USER_OFFSET);
        i = i + 1;
        std::cout << "Read Loop iteration: " << i << " and Axi Control = " << axi_ctrl << "\n";
        if (i > 100000) {
	  axi_ctrl = IP_IDLE;
          ip1.write_register(USER_OFFSET, axi_ctrl);
        }
    }

次の手順

Vivado IP から RTL カーネル (.xo) ファイルを作成し、XRT ネイティブ API のホスト コーディング要件を確認したので、Vitis アプリケーション プロジェクトを作成して RTL カーネルをテストします。


メイン ページに戻るこのチュートリアルの開始に戻る

Copyright© 2021 Xilinx