本文目录
1 J5 DSP简介
2 官方文档说明
3 地平线开发框架简介
4 OE包DSP开发示例简介
5 DSP最简开发流程简介(对应OE 1.1.45版本及之后)
附 旧版本OE对应的最简开发代码(OE 1.1.25a - 1.1.44)
1 J5 DSP简介

图中白色区域为 VP6 计算相关硬件,主要支持标量计算(Scalar Processing Units)、矢量浮点计算(VFPU)和矢量定点计算(Vector Processing Units),其中:
红叉表示 J5 芯片的 VP6 没有做对应的硬件选型,即指令内存 IRAM 只有一个;VFPU 只支持 FP32 的浮点精度,不支持 FP16 的半精度。
VP6 的整型乘加器可以支持 256 个 8x8 或 128 个 8x16 或 64 个 16x16 或 16 个 16x32 MACs。
VP6 的计算主频为 660MHz,并且最多支持 5 路 VLIW(超长指令字)和 512bit 的 SIMD(单指令多数据流)。
图中蓝色区域为 VP6 内存相关硬件,其中:
单颗 VP6 共有 2 个 128KB 可编程 DRAM(也称为 TCM),能提供与 Cache 相当的性能。
芯片上的 VP6 与 ARM/BPU 共享 DDR,即支持零拷贝机制。iDMA(Integrated DMA)硬件则用于 DDR 和 TCM 之间的数据搬运(当数据量较大时还需要实现),J5 选用的是 128-bit 的 AXI4 总线接口。
下表为 VP6 主要资源汇总:

2 官方文档说明
当大家参考 J5 工具链文档完成 DSP 开发包的安装后,可以在以下两个路径查找到详细的官网文档,分别点击文件夹中的 index.html 即可查看。
XtDevTools/downloads/RI-2021.7/docs

XtDevTools/install/builds/RI-2020.4-linux/vdsp_vp6_RI4

其中很多 dsp 底层接口都可以在 proccessor ISA 文档中找到说明。

3 地平线开发框架简介
如下图所示,J5 芯片中的 VP6 与 ARM 以 Server-Client 的形式,通过 rpmsg 进业务通讯。如前文所述,因为 VP6 和 CPU/BPU 共享内存,所以数据存储在 DDR 上,rpmsg 只需要传递一些数据量很小的 meta data,比如数据地址。DSP 基于内部的 LiteMMU 硬件自动完成地址位宽(ARM 为 64 位位宽、DSP 为32 位位宽)映射后即可使用该数据。

为了方便用户使用,地平线还封装了一套基于 RPC 的开发框架,并提供了相关的 API 接口,接口说明请见工具链文档,接口使用请参考 OE 包 ddk/samples/vdsp_rpc_sample 路径提供的示例。
4 OE包DSP开发示例简介
DSP示例包展示了如何在j5上使用dsp进行任务处理。DSP示例包中包含CV示例和NN示例:
CV示例中封装了常见的cv api,并通过Sample介绍了各个api的使用方法。
NN示例中包含quantize、dequantize、nn_plugin、softmax以及pointpillar前处理算子api,并展示了示例用法。
开发者可以体验并基于这些示例进行应用开发,降低开发门槛。
5 DSP最简开发流程简介
自定义算子示例说明

其中ARM端主要负责计算资源的分配以及DSP任务的发起和回收。 DSP端主要负责执行计算逻辑,将ARM端分配的计算任务按照调度逻辑逐个完成,并返回计算结果。 算子的执行过程如下:
实现DSP端自定义算子,注册该op并启动DSP镜像;
实现ARM端用户自定义算子的推理类;
ARM端初始化推理资源,准备进行模型推理;
ARM端准备好DSP调度需要的资源,并封装需要传递给DSP的参数,通过用户自定义算子发起RPC调用任务;
DSP schedule接收到RPC命令,根据调度优先级顺序执行已注册的DSP op进行运算;
DSP计算结束后,通过RPC将计算结果返回至ARM端;
ARM端接收到DSP返回的算子计算结果,根据返回值继续执行后续逻辑。
直接开发示例说明
除了模型中的 CPU 算子可以使用 DSP 进行推理外,用户也可以直接基于地平线封装的 RPC 框架及接口将模型的前后处理或者其他算法部署在 DSP 上。
该章节适用于天工开物OpenExplorer1.1.45及之后的版本,若您使用的工具链版本较老,在1.1.45之前,可以查看最文章下方历史版本对应的代码编写方法。
对于ARM调用DSP这项任务来说,需要编写编译DSP和ARM两部分代码。
在DSP侧编写好任务的执行步骤并注册算子,编译成DSP镜像部署到J5板端。
ARM侧的完整编程步骤如下方流程图所示,代码完成后,编译成可执行文件在J5板端运行。

完成以上两步后,即可在J5上使用ARM调用DSP执行自定义的计算任务。接下来,以实现两个浮点数的加法计算为例,介绍代码的具体编写方法。
DSP
首先进入dsp/src文件夹,新建add.h:
之后在同目录下新建add.cc:
float input1;
float input2;
} addDspParam; //定义结构体,包含两个加数
//tm用于tile_manager,本示例可忽略
addDspParam *ptr = (addDspParam *)(input);
float *dst = (float *)(output);
*dst = ptr->input1 + ptr->input2;
return 0;
}
之后运行同目录下的build_dsp.sh脚本,会在script/image目录下生成vdsp0和vdsp1镜像文件。
ARM
进入arm/nn/src文件夹,新建test_add.cc:
float input1;
float input2;
} addDspParam; //定义结构体,包含两个加数
float b = 20; //设置两个加数的值
std::cout<<"input2 = "<< b <<std::endl;
std::cout<<"DSP ADD START!"<<std::endl;
hbSysAllocMem(&input_mem, sizeof(addDspParam));
hbSysAllocMem(&output_mem, sizeof(float)); //为输入输出分配内存
addDspParam ptr = (addDspParam)(input_mem.virAddr);
ptr->input1 = a;
ptr->input2 = b; //用指针传递输入数据
param.rpcCmd = 0x1200; //0x1200与DSP算子注册的编号一致
param.priority = 0;
param.dspCoreId = 0;
hbDSPRpc(&task, &input_mem, &output_mem, &param);
hbDSPWaitTaskDone(task, 0);
hbDSPReleaseTask(task);
std::cout<<"DSP ADD SUCCESS!"<<std::endl;
}
之后运行同目录下的build_arm.sh脚本,会在nn/script/lib中生成依赖文件,并在nn/script/bin中生成可执行文件test_nn。
板端运行
将整个script文件夹复制到J5开发板上的userdata目录,之后编写deploy.sh脚本,该脚本用于配置DSP镜像:
之后运行export HB_DSP_ENABLE_DIRECT_MODE=true用于将DSP配置为直连模式。
先后运行sh deploy.sh和sh run_nn_test.sh,即可执行加法计算。

可以看到ARM成功调用DSP执行了多输入的加法计算任务。
附 旧版本OE对应的最简开发代码
1.1.25a - 1.1.36
DSP
在dsp目录下新建文件夹add,并建立头文件add_dsp.h和源码文件add_dsp.cpp。在头文件中,需要定义DSP侧计算的函数dsp_add,具体代码如下:
}
#endif
#endif //HOBOT_ADD_DSP_H
dsp_add的具体实现在源码add_dsp.cpp中编写。这里需要先定一个结构体,结构体里包含两个加数,在函数dsp_add中,定义指针ptr_add_param以在后续对结构体参数进行加法操作。
float input1;
float input2;
} addDspParam; //定义结构体,包含两个加数
addDspParam *ptr_add_param = (addDspParam *)(in);
addDspParam p;
memcpy((void *)(&p), (void *)(ptr_add_param), sizeof(addDspParam));
ptr_add_param = &p; //定义指针
*dst = ptr_add_param->input1 + ptr_add_param->input2;
}
之后,在当前目录下创建CMakeLists.txt,内容如下:
以上操作完成后,在dsp目录下的main.cc中注册刚刚编写好的dsp算子,首先添加头文件行#include "add/add_dsp.h",再在hb_dsp_env_init()后添加hb_dsp_register_fn(0x51, dsp_add, 0); 即可,这里的编号0x51可以自定义。
ARM
对于ARM侧的代码来说,需要先定义和分配输入输出内存,同时定义好DSP的调用参数,这里需要注意3点:①定义内存时使用hbSysMem,②参数拷贝时使用虚拟地址virAddr,③rpcCmd需要和DSP侧注册算子时使用的编号相同。之后,使用hbDSPRpc接口让DSP启用计算任务,使用hbDSPWaitTaskDone接口等待任务执行结束,使用hbDSPReleaseTask接口释放任务,再通过零拷贝的方式将DSP的计算结果传递回ARM侧,打印输出结果后释放申请的内存资源,从而结束整个调用流程。
加法计算需要两个输入值,可以定义一个结构体,让结构体包含两个加数,再给DSP传递结构体的地址。
float input1;
float input2;
} addDspParam; //定义结构体,包含两个加数
float b = 20.0;
float c = 0.0; //a与b是加数,c是未计算的和
hbSysMem input_mem, output_mem;
hbSysAllocMem(&output_mem, sizeof(float)); //为输入输出分配内存
a,
b,
}; //实例化结构体
std::cout<<"input2 = "<< b <<std::endl;
std::cout<<"DSP ADD START!"<<std::endl; //输入数据,input1=10,input2=20
param.rpcCmd = 0x51; //0x51与DSP算子注册的编号一致
param.priority = 0;
param.dspCoreId = 0;
hbDSPRpc(&task, &input_mem, &output_mem, &param);
hbDSPWaitTaskDone(task,0);
hbDSPReleaseTask(task);
std::cout<<"DSP ADD SUCCESS!"<<std::endl;
hbSysFreeMem(&output_mem);
}
板端运行
对于DSP镜像,可编写deploy.sh脚本部署,其中第三行的目录可以是板端的任意可写文件夹,用于存放vdsp0和vdsp1镜像文件。
对于lib依赖库的加载及可执行文件的运行,可编写add_test.sh脚本:
执行sh deploy.sh部署DSP环境并执行sh add_test.sh
1.1.37 - 1.1.44
DSP
首先进入nn/dsp/src文件夹,新建add.h:
之后在同目录下新建add.cc:
float input1;
float input2;
} addDspParam; //定义结构体,包含两个加数
//tm用于tile_manager,本示例可忽略
addDspParam *ptr = (addDspParam *)(input);
float *dst = (float *)(output);
*dst = ptr->input1 + ptr->input2;
return 0;
}
修改nn/dsp下的main.cc,新增头文件引用#include "src/add.h",之后在hb_dsp_start();代码前添加一行hb_dsp_register_fn(0x800, dsp_add, 0);即可,编号0x800可以自定义,限定uint16范围。
之后运行同目录下的build_dsp.sh脚本,会在nn/script/image目录下生成vdsp0和vdsp1镜像文件。
ARM
进入nn/arm/src文件夹,新建test_add.cc:
float input1;
float input2;
} addDspParam; //定义结构体,包含两个加数
float b = 20; //设置两个加数的值
std::cout<<"input2 = "<< b <<std::endl;
std::cout<<"DSP ADD START!"<<std::endl;
hbSysAllocMem(&input_mem, sizeof(addDspParam));
hbSysAllocMem(&output_mem, sizeof(float)); //为输入输出分配内存
ptr->input1 = a;
ptr->input2 = b; //用指针传递输入数据
param.rpcCmd = 0x800; //0x800与DSP算子注册的编号一致
param.priority = 0;
param.dspCoreId = 0;
hbDSPRpc(&task, &input_mem, &output_mem, &param); //提交DSP任务
hbDSPWaitTaskDone(task, 0);
hbDSPReleaseTask(task);
std::cout<<"DSP ADD SUCCESS!"<<std::endl; //打印输出结果
hbSysFreeMem(&output_mem);
}
修改nn/arm目录的main.cc,新增extern int32_t test_add(int argc, char **argv);并将主函数里的test_all(argc, argv);修改为test_add(argc, argv);完成ARM侧代码调整。
之后运行同目录下的build_arm.sh脚本,会在nn/script/lib中生成依赖文件,并在nn/script/bin中生成可执行文件test_nn。
板端运行
将整个nn/script文件夹复制到J5开发板上的userdata目录,之后编写deploy.sh脚本,该脚本用于配置DSP镜像:
先后运行sh deploy.sh和sh run_nn_test.sh,执行加法计算。
由于1.1.24及之前版本的OE,使用的DSP开发软件(Xtensa Xplorer)版本较老,因此相关代码不再提供和维护。


