Cadence DSP 算子开发上手指南

前言

Cadence 的 Vision P6/Q6/Q7 系列 DSP 在很多的 ISP (“Image Signal Processor”) 芯片中都有部署,可以在图像处理场景补充甚至碾压 CPU 算力。而且 Cadence 官方提供了一个比较全的基础算子库 libxi,很多标准算子在 libxi 中都有特定参数组合下的参考实现。

但是鉴于 Cadence DSP 开发群体比较小,网络上能找到的中文资源几乎没有,从零进入开发状态的门槛还是不低的。本文梳理了一些 Cadence DSP 算子开发中的重点,希望可以给对 Cadence DSP 开发有兴趣的同学带来帮助。

01

DSP 架构特点

首先,以 Cadence 的 Q7 为例,介绍一下 DSP 架构上的特性。下图是 Q7 硬件架构的简化。

e097e8e01780190f6a505a6e48da5df9.png

从图中可以直观的得到 DSP 处理器的算力、寄存器等信息,注意 DSP 上有两块 data ram(简称 dram),每一块 dram 又分为两个宽为 512bit 的 bank。同时,DSP 上有两个 Load/Store 单元,Load/Store 模块访问 dram 的带宽都是 512bit,所以理论上的访存带宽是 1024bit/cycle,而独立于 Load/Store的SuperGather模块是为了支持 DSP 上高效的 gather/scatter 操作。另外,可以看到 DSP 还有一个 dma 模块,该模块用于片外空间和 dram 之间的数据传输。

为了充分利用算力和访存能力,Cadence DSP 支持了 SIMD(Single Instruction, Multiple Data) 和 VLIW(Very Long Insruction Word) 两种特性。前者支持 64lanes * 8bit 或 32lanes * 16bit 等总位宽为 512bit 的向量访存和向量计算,后者是一种谋求指令级并行 (ILP, instruction level parallelism) 的技术。VLIW 可以将多个指令打包后在一起同时发射,从而获取指令级的并行度。与超标量、乱序执行等其他 ILP 技术不同的是,VLIW 的并行指令排布是在编译期就确定好的,而不需要 CPU 进行复杂的运行时调度。VLIW 使得 DSP 处理器在不需要大幅增加硬件复杂度的情况下,就可以获取 ILP 的加速收益。

还要补充一点,Cadence DSP 是哈弗架构,其指令和数据独立编址,具体的编址规格由 LSP(Linker Support Package) 决定,而用户可以通过名为 memmap.xmm 的内存配置文件来定义和修改 LSP。截取了一段 xmm 文件的内容,简单注释如下:

// 存指令的地址段
BEGIN iram0
0xe000000: instRam : iram0 : 0x8000 : executable,writable ;
 iram0_0 : F : 0xe000000 - 0xe007fff : .iram0.literal .iram0.text ...
END iram0

// 256k 的 dram0
BEGIN dram0
0xe080000: dataRam : dram0 : 0x40000 : writable ;
 dram0_0 : C : 0xe080000 - 0xe0bffff : .dram0.rodata .dram0.data .dram0.bss;
END dram0

// 240k 的 dram1
BEGIN dram1
0xe0c0000: dataRam : dram1 : 0x3c000 : writable ;
 dram1_0 : C : 0xe0c0000 - 0xe0fbfff : .dram1.rodata .dram1.data .dram1.bss;
END dram1

// 16k 的栈空间,创建在 dram1 的尾巴后面
BEGIN dram1_stack
0xe0fc000: dataRam : dram1_stack : 0x4000 : writable ;
 dram1_stack : C : 0xe0fc000 - 0xe0fffff : STACK : ;
END dram1_stack

// 存 os 相关的地址段
BEGIN sram0
0x10000000: instRam : sram0 : 0x2000000 : executable,writable ;
 sram0 : F : 0x10000000 - 0x11ffffff: HEAP : .sram.rodata .rtos.data
END sram0

从注释中我们可以看出,xmm 文件规定了运行时的数据、指令、栈、os 等各部分的地址范围。

02

算子调用流程

有了上一节的背景知识,我们来感性地了解下一个 DSP 算子是如何被调起来的。

我们从 CPU 侧发起调用,通过 rpc 协议调起 DSP 侧提供的服务,将 CPU 侧程序称为 rpc_host,而 DSP 侧程序称为 rpc_dsp。rpc_dsp 负责起一个线程监听来自 rpc_host 的 message,并从 message 解析出需要进行的动作,并在执行完该动作后回复 rpc_host 一个 message。我们需要预先将 rpc_dsp 编译成可执行程序,再将可执行程序 dump 成 bin 文件,这里称为 dsp_bin(包含 iram.bin 和 sram.bin)。而 CPU 侧负责准备算子调用的所有输入,并装载编译好的 dsp_bin 到 DSP 的 dram 中(前文介绍 LSP 的部分有说明应该如何进行内存映射),同时把 rpc_dsp 侧的监听线程 run 起来,最后 rpc_host 发起 rpc 调用并等待 rpc 返回。

需要说明一点,CPU 和 DSP 之间一般会使用 IPCM(核间通信模块)实现对一段 ddr 地址空间的共享。但是 DSP 直接访问这段 ddr 的延迟是远大于访问 dram 的延迟,所以对于算子执行过程中需要频繁访问的 ddr 数据,一般是先使用 dma 将其搬运到 dram 上,算子执行结束后,计算的输出再通过 dma 搬回到 ddr。

以上就是算子调用流程的概述,搭配了一张时序图,图中用虚线框标出了具有时序关系的若干步骤,如下所示:

cb18d57fb45b86da5f309ef15a4eb8bd.png

03

工具链介绍

Cadence 为 DSP 开发者提供了 Xtensa 开发包,里面包含了一整套编译、链接、执行、调试等相关的命令行工具。这些命令用法上很类似 GUN 的标准工具,而 Cadence 主要是加强了编译的部分,因为前面提到 Cadence DSP 使用 VLIW 进行加速,而 VLIW 技术要求编译器做更多的事情,来尽可能获得一个更优的编译期指令排布。

上一节讲述的调用流程是在 DSP 硬件上跑算子的流程,看上去不是很友好。好在 Xtensa 工具包里还提供了 Cadence DSP 的模拟器,使用 xt-run 命令就可以在模拟器中执行算子,从而使得开发验证、性能调试都可以脱离真实的硬件。

下面就以"hello world"为例,介绍一下命令行工具的使用:

// file: hello_world.c
#include <stdio.h>int main() {
    printf("hello world\n");
    return 0;
}

编译:

xt-xcc hello_world.c -o hello_world.bin

不带内存模型执行,用于算子初版实现,不模拟访存延迟:

xt-run ./hello_world.bin

带内存模型执行,仿真性能非常逼近 DSP 硬件上的速度:

xt-run --mem_model ./hello_world.bin

带--summary 选项执行,可以对 cycle 分布有一个统计结果,比如 retaired inrstuction、branch delay、cache_miss 等各部分的 cycle 占比:

xt-run --summary ./hello_world.bi

如果需要 gdb 调试的话,可以用 xt-gdb:

xt-gdb ./hello_world.bin

如果需要 profiling 的话,需要先在执行期加--client_cmds="profile --all gmon.out 选项,用于在当前目录下生成各种 profiling 文件,包括 gmon.out.cyc, gmon.out.bdelay, gmon.out.interlock 等,然后使用 xt-gprof 工具查看上一步生成的 profiling 文件,比如执行下面两行命令就可以查看函数级别的 cycle 分布:

xt-run --client_cmds="profile --all gmon.out" ./hello_world.bin
xt-gprof ./hello_world.bin ./gmon.out.cyc  > hello_world_cyc.txt

04

分块计算

Cadence DSP 主要应用场景是图像处理,现实的业务中图片尺寸经常都是 1080P 甚至 4K 的分辨率,而 DSP 的 dram 容量虽然可配置,但是通常都是 200KB 左右的级别(壕配十几兆 dram 的是例外),根本放不下一张大图,这就是导致了我们的算子必须分块计算。通过将大图分成一个个小块(tile), 每次通过 dma 从 ddr 搬运一个 src_tile 到 dram 上,执行算子得到一个 dst_tile, 再通过 dma 把 dst_tile 搬到 ddr 上。

认识 tile

拿一张图说明一下 tile 的具体参数:

7f8a8a8aa112286e2db098b38e10570d.png

可以看到 tile 分两层,里层的红色区域是原始数据区域,尺寸即 tile_width*tile_height, 外层是一圈 edge, 因为有些算子操作,比如 filter2d,

  • 16
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值