AI 引擎至 DDR 存储器连接的编程模型 - 2022.1 简体中文

Versal ACAP AI 引擎编程环境 用户指南 (UG1076)

Document ID
UG1076
Release Date
2022-05-25
Version
2022.1 简体中文

input_gmio/output_gmio 端口属性可用于在 PS 程序内发起 AI 引擎至 DDR 存储器读取和写入传输事务。这样即可支持 AI 引擎与 DDR 控制器之间通过 PS 程序内编写的 API 来进行数据传输。以下示例显示了如何使用 GMIO API 将数据发送至 AI 引擎以供处理并通过 PS 程序将处理后的数据检索回 DDR。

graph.h
class mygraph: public adf::graph
{
private:
  adf::kernel k_m;

public:
  adf::output_gmio gmioOut;
  adf::input_gmio gmioIn;
  mygraph()
  { 
    k_m = adf::kernel::create(weighted_sum_with_margin);
    gmioOut = adf::output_gmio::create("gmioOut", 64, 1000);
    gmioIn = adf::input_gmio::create("gmioIn", 64, 1000);

    adf::connect<adf::window<1024,32>>(gmioIn.out[0], k_m.in[0]);
    adf::connect<adf::window<1024>>(k_m.out[0], gmioOut.in[0]);
    adf::source(k_m) = "weighted_sum.cc";
    adf::runtime<adf::ratio>(k_m)= 0.9;
  };
};

graph.cpp

myGraph gr;
int main(int argc, char ** argv)
{
    const int BLOCK_SIZE=256;
    int32 *inputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
    int32 *outputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
 
    // provide input data to AI Engine in inputArray
    for (int i=0; i<BLOCK_SIZE; i++) {
        inputArray[i] = i;
    }
 
    gr.init();
          
    gr.gmioIn.gm2aie_nb(inputArray, BLOCK_SIZE*sizeof(int32));
    gr.gmioOut.aie2gm_nb(outputArray, BLOCK_SIZE*sizeof(int32));
 
    gr.run(8);
 
    gr.gmioOut.wait();
 
    // can start to access output data from AI Engine in outputArray
	... 

    GMIO::free(inputArray);
    GMIO::free(outputArray);
    gr.end();
}

此示例声明了 2 个 I/O 对象:gmioIn 表示供 AI 引擎读取的 DDR 存储器空间,gmioOut 表示供 AI 引擎写入的 DDR 存储器空间。构造函数会指定 GMIO 的逻辑名称、AXI4 存储器映射传输事务的突发长度(可取 64、128 或 256 字节)以及所需的带宽 (MB/s)。

gmioOut = adf::output_gmio::create("gmioOut", 64, 1000);
gmioIn  = adf::input_gmio::create("gmioIn", 64, 1000);

应用 graph (myGraph) 包含连接到处理内核的输入端口 (myGraph::gmioIn) 和从内核生成处理后的数据的输出端口 (myGraph::gmioOut),以下代码会连接 graph 的输入端口,并将该端口连接到该 graph 的输出端口。

adf::connect<adf::window<1024,32>>(gmioIn.out[0], k_m.in[0]);
adf::connect<adf::window<1024>>(k_m.out[0], gmioOut.in[0]); 

在 main 函数内部,GMIO::malloc 分配了 2 个 int32 阵列(各含 256 个元素)。inputArray 指向供 AI 引擎读取的存储器空间,outputArray 指向供 AI 引擎写入的存储器空间。在 Linux 中,传递到 GMIO::gm2aie_nbGMIO::aie2gm_nbGMIO::gm2aieGMIO::aie2gm 的虚拟地址必须由 GMIO::malloc 分配。分配输入数据后,即可对其进行初始化。

const int BLOCK_SIZE=256; 
int32 *inputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
int32 *outputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
gr.gmioIn.gm2aie_nb() 用于初始化 AXI4 存储器映射的传输事务,以供 AI 引擎从 DDR 存储器空间中读取。gr.gmioIn.gm2aie_nb() 中的第一个实参是指向该传输事务的存储器空间的开始地址。第二个实参则是传输事务大小(以字节为单位)。传输事务的存储器空间必须在 GMIO::malloc 所分配的存储器空间内。同样,gr.gmioOut.aie2gm_nb() 用于初始化 AXI4 存储器映射的传输事务,以供 AI 引擎写入 DDR 存储器空间。gr.gmioOut.gm2aie_nb()gr.gmioOut.aie2gm_nb() 属于非阻塞函数,会在发出传输事务时立即返回,即,它不等待传输事务完成。相反,gr.gmioIn.gm2aie()gr.gmioOut.aie2gm() 的行为则以阻塞方式来操作。
在此示例中,假定在一次迭代内,graph 耗用来自输入端口的 32 项 int32 数据,并向输出端口生成 32 项 int32 数据。要运行 8 次迭代,此 graph 会耗用 256 项 int32 数据,并生成 256 项 int32 数据。对应的 AXI4 存储器映射传输事务是使用以下代码发起的,调用一次 gr.gmioIn.gm2aie_nb() 为相当于 8 次迭代的数据发出读取传输事务,调用一次 gr.gmioOut.aie2gm_nb() 为相当于 8 次迭代的数据发出写入传输事务。
gr.gmioIn.gm2aie_nb(inputArray, BLOCK_SIZE*sizeof(int32));
gr.gmioOut.aie2gm_nb(outputArray, BLOCK_SIZE*sizeof(int32));

gr.run(8) 同样属于非阻塞调用,用于运行 graph 的 8 次迭代。要在 PS 与 AI 引擎之间同步进行 DDR 存储器读/写访问,您可使用 gr.gmioOut.wait() 阻塞 PS 执行,直至 GMIO 传输事务完成为止。在此示例中,通过调用 gr.gmioOut.wait() 以等待将输出数据写入 outputArray DDR 存储器空间。

注释: 在 Linux 中,该存储器对于 GMIO 不可缓存。

随后,PS 程序即可访问该数据。当 PS 完成处理后,由 GMIO::malloc 分配的存储器空间即可通过 GMIO::free 来释放。

GMIO::free(inputArray);
GMIO::free(outputArray);

input_gmio/output_gmio API 可通过多种方式用于执行不同级别的控制,以便对 AI 引擎、PS 和 DDR 存储器之间的读/写访问和同步加以控制。input_gmio::gm2aieoutput_gmio::aie2gminput_gmio::gm2aie_nboutput_gmio::aie2gm_nb 均可多次调用,以便在 graph 的不同执行阶段内对相同 input_gmio/output_gmio 对象的不同存储器空间加以关联。对于就地执行的 AI 引擎–DDR 读/写,可将不同 input_gmio/output_gmio 对象与相同存储器空间加以关联。input_gmio::gm2aieoutput_gmio::aie2gm API 的阻塞版本本身均为数据传输与内核执行的同步点。调用 input_gmio::gm2aie(或 output_gmio::aie2gm)等同于调用 input_gmio::gm2aie_nb(或 output_gmio::aie2gm_nb)随后紧接调用 output_gmio::wait。以下示例显示了前述用例的组合。

myGraph gr;

 int main(int argc, char ** argv)
{

    const int BLOCK_SIZE=256;
    // dynamically allocate memory spaces for in-place AI Engine read/write access
    int32* inoutArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32)); 
    gr.init();
 
    for (int k=0; k<4; k++)
    {
        // provide input data to AI Engine in inoutArray
        for(int i=0;i<BLOCK_SIZE;i++){
            inoutArray[i]=i;
        }

        gr.run(8);
        for (int i=0; i<8; i++)
        {
            gr.gmioIn.gm2aie(inoutArray+i*32, 32*sizeof(int32)); //blocking call to ensure transaction data is read from DDR to AI Engine
            gr.gmioOut.aie2gm_nb(inoutArray+i*32, 32*sizeof(int32));
        }
        gr.gmioOut.wait();
     
        // can start to access output data from AI Engine in inoutArray
	//	...        
    }
    GMIO::free(inoutArray);
    gr.end();
    return 0;
}

在前述示例中,有 2 个 GMIO 对象 gmioIngmioOut 使用由 inoutArray 分配的同一个存储器空间来就地执行读取和写入访问。

如果 graph 内的内核之间的数据流依赖关系未知,并且为了确保 inoutArray 存储器空间先读取后写入,则会调用 gr.gmioIn.gm2aie() 的阻塞版本来确保将传输事务数据从 DDR 复制到 AI 引擎本地存储器,随后对 gr.gmioOut.aie2gm_nb() 内的相同存储器空间发出写入传输事务。

gr.gmioIn.gm2aie(inoutArray+i*32, 32*sizeof(int32)); //blocking call to ensure transaction data is read from DDR to AI Engine
gr.gmioOut.aie2gm_nb(inoutArray+i*32, 32*sizeof(int32));

gr.gmioOut.wait() 用于确保数据已移植到 DDR 存储器。完成后,PS 即可访问输出数据进行后处理。

graph 执行在 for 循环内分为 4 个阶段:for (int k=0; k<4; k++)inoutArray 可在 for 循环内重新初始化,将不同数据分为不同阶段进行处理。