使用用户管理的内核 - 2023.2 简体中文

Vitis 统一软件平台文档 应用加速开发 (UG1393)

Document ID
UG1393
Release Date
2023-12-13
Version
2023.2 简体中文
提示: 如需获取搭配用户管理的 RTL 内核一起使用的软件应用的示例,请参阅 Vitis 教程:硬件加速 或 GitHub 上 Vitis_Accel_Examples 中的 rtl_user_managed 示例。

用户管理的内核需要为软件应用使用 XRT 本机 API,这些内核被指定为 xrt::ip 类的 IP 对象。以下高层次综述解释了如何构造您的主机应用,以从 .xclbin 文件访问用户管理的内核。

重要: XRT 给 s_axilite 接口的地址宽度施加了 16 位 (64K) 限制。
  1. 添加以下头文件以包含 XRT 本机 API:
    #include "experimental/xrt_ip.h"
    #include "xrt/xrt_bo.h"
    
    • experimental/xrt_ip.h:将 IP 定义为 xrt::ip 对象。
    • xrt/xrt_bo.h:在 XRT 本机 API 中创建缓冲器对象。
  2. 指定器件 ID 并加载 XCLBIN 中所述,设置应用环境。
  3. IP 对象 (xrt::ip) 是根据 xrt::device 对象、所加载的 .xclbinuuid 以及用户管理的内核的 name 来构造的:
    //User Managed Kernel = IP
    auto ip = xrt::ip(device, uuid, "Vadd_A_B");
  4. (可选)对于含 AI 引擎计算图应用的 Versal AI Core 器件,您也可以指定要在运行时加载的计算图应用。此进程需执行下列几个子任务:
    1. 将所需头文件添加到 #include 语句中:
      #include <experimental/xrt_aie.h>
      
    2. 识别来自 xrt::device 对象的 AI 引擎计算图、已加载的 .xclbinuuid 以及此计算图应用的 name
        auto my_graph  = xrt::graph(device, uuid, "mygraph_top");
      
    3. 按需从软件程序复位并运行计算图应用:
        my_graph.reset();
        std::cout << STR_PASSED << "my_graph.reset()" << std::endl;
        my_graph.run(0);
        std::cout << STR_PASSED << "my_graph.run()" << std::endl;
      
    提示: 如需了解有关构建和运行 AI 引擎应用的更多信息,请参阅 AI 引擎工具和流程用户指南(UG1076)
  5. 为 IP 实参创建缓冲器:
    auto <buf_name> = xrt::bo(<device>,<DATA_SIZE>,<flag>,<bank_id>);

    其中缓冲器对象构造函数使用以下字段:

    • <device>:加速器卡的 xrt::device 对象。
    • <DATA_SIZE>:缓冲器大小,由数据宽度和数量来定义。
    • <flag>:用于创建缓冲器对象的标志。
    • <bank_id>:在器件上定义存储体,应在此存储体中为 IP 访问分配缓冲器。指定的存储体必须与 .xclbin 文件内对应的 IP 端口连接相匹配。否则,运行应用时,将出现 bad_alloc。您可使用 --connectivity.sp 命令指定内核实参的赋值,如 将内核端口映射到存储器 中所述。

    例如:

    auto buf_in_a = xrt::bo(device,DATA_SIZE,xrt::bo::flags::normal,0);
    auto buf_in_b = xrt::bo(device,DATA_SIZE,xrt::bo::flags::normal,0);
    
    提示: 请验证 IP 连接以确定特定的存储体,否则在 Vitis 生成的 .xclbin.info 文件中可能会出现此信息。

    例如,.xclbin 为用户管理的内核生成如下信息可以为主机代码中缓冲器对象的构造过程提供指导信息:

    Instance:        Vadd_A_B_1
       Base Address: 0x1c00000
    
       Argument:          scalar00
       Register Offset:   0x10
       Port:              s_axi_control
       Memory:            <not applicable>
    
       Argument:          A
       Register Offset:   0x18
       Port:              m00_axi
       Memory:            bank0 (MEM_DDR4)
    
       Argument:          B
       Register Offset:   0x24
       Port:              m01_axi
       Memory:            bank0 (MEM_DDR4)
    
  6. 获取缓冲器地址,并在主机与器件之间传输数据:
        auto a_data = buf_in_a.map<int*>();
        auto b_data = buf_in_b.map<int*>();
    
        // Get the buffer physical address
        long long a_addr=buf_in_a.address();
        long long b_addr=buf_in_b.address();
    
        // Sync Buffers
        buf_in_a.sync(XCL_BO_SYNC_BO_TO_DEVICE);
        buf_in_b.sync(XCL_BO_SYNC_BO_TO_DEVICE);
    

    xrt::bo::map() 允许将主机侧缓冲器反向指针映射到用户指针。但在读取映射的指针之前或者写入映射的指针之后,应使用 xrt::bo::sync()(含方向标志)来执行 DMA 操作。

  7. 准备好缓冲器(如上所示缓冲器创建、同步操作)之后,您即可通过定向寄存器写入操作将所有必要信息都传递到 IP。

    重要: xrt::ip 不同于标准 xrt::kernel,前者表示 XRT 不管理 IP,但提供对其中寄存器的读取或写入访问权。

    例如,以下所示代码显示了有关通过 xrt::ip::write_register() 命令传递缓冲器基址的信息。

        ip.write_register(REG_OFFSET_A,a_addr);
        ip.write_register(REG_OFFSET_A+4,a_addr>>32);
    
        ip.write_register(REG_OFFSET_B,b_addr);
        ip.write_register(REG_OFFSET_B+4,b_addr>>32);
    
  8. 启动 IP 执行。由于此 IP 为用户管理的 IP,您可采用任意数量的寄存器写入/读取操作来控制 IP 的启动、检查状态或重新启动,以便触发 IP 执行。以下示例使用 s_axilite 接口来访问控制寄存器中的控制信号:
        uint32_t axi_ctrl = 0;
        std::cout << "INFO:IP Start" << std::endl;
        axi_ctrl = IP_START;
        ip.write_register(CSR_OFFSET, axi_ctrl);
    
        // Wait until the IP is DONE 
        axi_ctrl =0;
        while((axi_ctrl & IP_IDLE) != IP_IDLE) {
            axi_ctrl = ip.read_register(CSR_OFFSET);
        }
     
  9. 完成 IP 执行后,您可通过 xrt::bo::sync 命令将数据传回,只需在此命令中包含相应的标志以指明缓冲器传输方向即可。
        buf_in_b.sync(XCL_BO_SYNC_BO_FROM_DEVICE);
    
  10. (可选)对应用进行剖析。

    由于 XRT 不负责启动或停止内核,您无法像处理 XRT 管理的内核那样对 user_managed 内核的操作进行直接剖析。但您可以使用 user_rangeuser_event 对象(如 主机应用的定制剖析 中所述)来对主机应用的元素进行剖析。例如,以下代码可捕获从主机应用写入寄存器所耗费的时间:

        // Write Registers
        range.start("Phase 4a", "Write A Register");
        ip.write_register(REG_OFFSET_A,a_addr);
        ip.write_register(REG_OFFSET_A+4,a_addr>>32);
        range.end();
        range.start("Phase 4b", "Write B Register");
        ip.write_register(REG_OFFSET_B,b_addr);
        ip.write_register(REG_OFFSET_B+4,b_addr>>32);
        range.end()
    您可在 Vitis 分析器中对应用和内核操作的某些方面进行观测,如下图所示。