DSP 内存
DSP 内存架构
HiFi5 DSP 内存分为片内和片外两个区域。
DSP 片内内存 (DSP 独占)
ICache
DCache
DTCM
DSP 片外内存 (与 KM4, KR4 共享)
SRAM
PSRAM
这些内存在访问速度和容量上呈现显著差异。
在访问性能方面:
DTCM 和 DCache 具有最优的实时性,与 DSP 同频运行,可实现单周期数据获取;
SRAM 以 240MHz 频率和 64-bit 位宽提供次级性能;
PSRAM 虽标称 250MHz 频率,但因采用 16-bit 物理位宽(8-bit DDR),实际带宽最低。
在容量配置上呈现反向特性:
PSRAM 提供最大 16MB 可扩展空间(具体容量依芯片型号而定);
SRAM 总容量 512KB,扣除 KM4,KR4 处理器占用的 65KB 后,DSP 实际可用 447KB;
DTCM 和 DCache 作为专用高速存储,容量最小但延迟最低。
DSP 内存访问速度
源 |
目标 |
内存访问速度 (MB/s) |
搬运方式 |
|---|---|---|---|
SRAM |
DTCM |
1899 |
iDMA |
SRAM |
DCache |
1791 |
memcpy |
PSRAM |
DTCM |
430 |
iDMA |
PSRAM |
DCache |
425 |
memcpy |
Note
实验条件: DSP 500MHz, SRAM 240MHz, PSRAM 250MHz。
不同的实验条件下,内存访问速度可能不同。表格中的数据为测得的上限值。
DSP 内存访问方式
由于内存的访问速度会影响 DSP 的运算性能,甚至成为瓶颈。因此,运行在 DSP 上的算法应尽可能优先使用 DTCM, SRAM, 再使用 PSRAM。
实际应用时,可根据算法模型大小,选择不同的数据存放位置。例如:
若模型小于 256KB,在程序启动后,可预先将数据全部加载到 DTCM 中并常驻。
若模型很大,则可以在 PSRAM 和 DTCM 之间搬运数据。
有两种方式可以主动将数据从 PSRAM 搬运到 DTCM:
memcpy
iDMA
和 memcpy 相比, iDMA 的优势是可以释放 CPU 算力,在 iDMA 传输过程中 DSP 可以继续执行其他任务。然而, 在访问 PSRAM 的速度上,iDMA 并不具有明显优势。
搬运大数据块(64KB/128KB)时,iDMA 稍快。
搬运小数据块(8KB/16KB/32KB)时, 反而 memcpy 更快。
iDMA 双缓冲区数据搬运样例
iDMA 的使用方式详见 Xtensa 文档。
本样例演示使用双缓冲区将数据从 PSRAM 搬运到 DTCM,边搬边算实现加速。
伪代码
1#define ALIGN(x) __attribute__((aligned(x)))
2#define DRAM0 __attribute__((section(".dram0.data")))
3#define DRAM1 __attribute__((section(".dram1.data")))
4
5int8_t ALIGN(16) DRAM0 dst_ping[USER_BUFFER_SIZE];
6int8_t ALIGN(16) DRAM1 dst_pong[USER_BUFFER_SIZE];
7
8#define NUM_DESCRIPTORS 2
9IDMA_BUFFER_DEFINE(dmaBuffer, NUM_DESCRIPTORS, IDMA_1D_DESC);
10
11void idma_pingpong_buffers_example(void) {
12 idma_init(0, MAX_BLOCK_16, 16, TICK_CYCLES_1, 0, NULL);
13 idma_init_loop(dmaBuffer, IDMA_1D_DESC, NUM_DESCRIPTORS, NULL, NULL);
14
15 // prepare the first data
16 idma_copy_desc(dst_ping, ...);
17
18 // wait for the first idma finish
19 while (idma_buffer_status() > 0) {}
20
21 // prepare the second data
22 idma_copy_desc(dst_pong, src, size, 0);
23
24 // do the first process
25 user_process_1(dst_ping,....)
26
27 // wait for the second idma finish
28 while (idma_buffer_status() > 0) {}
29
30 // prepare the third data
31 idma_copy_desc(dst_ping, ...);
32
33 // do the second process
34 user_process_2(dst_pong,....)
35
36 // wait for the third idma finish
37 while (idma_buffer_status() > 0) {}
38 // prepare the fourth data
39 idma_copy_desc(dst_pong, src, size, 0);
40 // do the third process
41 user_process_3(dst_ping,....)
42 // wait for the fourth idma finish
43 while (idma_buffer_status() > 0) {}
44 // prepare the fifth data
45 idma_copy_desc(dst_ping, ...);
46 // do the fourth process
47 user_process_4(dst_pong,....)
48 ......
49}
代码包含以下几个部分:
定义 iDMA 缓冲区
#define NUM_DESCRIPTORS 2 IDMA_BUFFER_DEFINE(dmaBuffer, NUM_DESCRIPTORS, IDMA_1D_DESC);
初始化 iDMA
idma_init(0, MAX_BLOCK_16, 16, TICK_CYCLES_1, 0, NULL); idma_init_loop(dmaBuffer, IDMA_1D_DESC, NUM_DESCRIPTORS, NULL, NULL);
定义两块位于 DTCM 上的数据缓冲区
#define ALIGN(x) __attribute__((aligned(x))) #define DRAM0 __attribute__((section(".dram0.data"))) #define DRAM1 __attribute__((section(".dram1.data"))) int8_t ALIGN(16) DRAM0 dst_ping[USER_BUFFER_SIZE]; int8_t ALIGN(16) DRAM1 dst_pong[USER_BUFFER_SIZE];
第 N 次搬运,更新描述符并调度
idma_copy_desc(dst_X, src, size, 0); while (idma_buffer_status() > 0) {} user_process_N(dst_X,....)
在伪代码中,为了方便理解,将顺序执行的代码分为两列:
左侧是 1,3,5,… 奇数次搬运和运算,使用 ping 数据缓冲区。
右侧是 2,4,6,… 偶数次搬运和计算,使用 pong 数据缓冲区。
第 N 次的搬运和计算与第 N 以及第 N+1 次交杂在一起。搬运第 N 份数据的同时计算第 N-1 份数据,从而达到边搬运边计算的目的。
Note
用到 iDMA 时,需要在 DTCM 中放置少量的 iDMA 描述符数据,约几百字节,因此用户可使用的 DTCM 空间略小于 256KB。
DSP 内存布局
DSP 默认布局
DSP 工程是通过 Linker Support Package (LSP) 来描述内存布局。LSP 指定了用于生成可执行文件的目标文件及其内存分布,并为特定目标环境的链接器提供配置便利。 详见 Xtensa 文档。
默认布局示意图如下:
在 Xplorer 中看到的 LSP 示例如下:
DSP 可使用 sram_dsp、entry_table 和 extra_reset_mem 作为系统内存,DRAM0/1 作为本地数据内存。Reset vector 存放于 entry_table。DRAM0/1 仅能存储数据。
Call0 ABI:可以在 sram_dsp 和 extra_reset_mem 放置代码和数据。
Window ABI:代码只能放在 extra_reset_mem,数据可放在 sram_dsp 和 extra_reset_mem。
将代码/数据放到 SRAM
SRAM 的带宽和延迟显著优于 PSRAM。在默认 LSP(RTK_LSP)中包含一个名为 sram_dsp 的段,将代码或数据放入 SRAM 有助于提高计算速度。
如下图所示,RTK_LSP 默认在 SRAM 中定义了三个段内存区间: .sram_dsp.text 、 .sram_dsp.data 和 .sram_dsp.literal 。
若需要将单个函数放到 SRAM,可这样声明和定义:
extern void place_into_sram()__attribute__ ((section(".sram_dsp.text"))); void place_into_sram(){ //detailed implentation }
若需要将数据(如数组)放到 SRAM,可这样:
__attribute__ ((section(".sram_dsp.data"))) int array_in_psram[100];
若需要将某个源文件(如
ameba_clk_rom.c)的所有函数都放到 SRAM:
更改 LSP 的默认布局
MCU 与 DSP 共享 PSRAM 和 SRAM,但 DTCM 仅供 DSP 使用。当 MCU 的 PSRAM 布局 发生变化时,需同步修改 DSP 的 LSP。 通常 DTCM 和 SRAM 地址保持默认,仅调整 PSRAM 的起止地址。PSRAM 的地址调整方法如下:
进入目录
{SDK}/project/img_utilitycd {SDK}/project/img_utility
运行
python lsp_modify.py可查看当前 DSP PSRAM 地址:>> python lsp_modify.py Current LSP psram: Start Address: 0x60300000, End Address: 0x61000000, Size: 0xd00000 Invalid input. Please enter the start and end addresses in hex. Example: "python lsp_modify.py 0x60300000 0x61000000" DSP link script change FAIL.
运行
python lsp_modify.py <start psram address in hex> <end psram address in hex>可直接生成新的 LSP 并输出新的 LSP 信息:>> python lsp_modify.py 0x60400000 0x60A00000 Current LSP psram: Start Address: 0x60300000, End Address: 0x61000000, Size: 0xd00000 New LSP psram: Start Address: 0x60400000, End Address: 0x60a00000, Size: 0x600000 Warning : 'entry_table' start address not aligned to MPU min alignment (is 0x60400020, min alignment is 0x00001000) Warning : MPU region size smaller than min region size (0x60400020 - 0x60400040, is 32 must be at least 4096 bytes) Warning : 'unused' start address not aligned to MPU min alignment (is 0x60400040, min alignment is 0x00001000) New linker scripts generated in ../../project/RTK_LSP/RI-2021.8/HIFI5_PROD_1123_asic_UPG/RTK_LSP/ldscripts Change MCU layout (amebalite_layout.ld) PSRAM_DSP_START to 0x60400000
本例将 PSRAM 起止地址调整为
0x60400000/0x60A00000,因脚本会自动生成 MPU table,上述的地址对齐警告可忽略,无实际影响。在 link 脚本
RTK_LSP/ldscripts/elf32xtensa.x中增加 KEEP 关键字:.ipc_table : ALIGN(4) { _ipc_table_start = ABSOLUTE(.); KEEP(*(.ipc_table)) . = ALIGN (4); _ipc_table_end = ABSOLUTE(.); } >psram0_seg :psram0_phdr .command : ALIGN(4) { _command_start = ABSOLUTE(.); KEEP(*(.command)) . = ALIGN (4); _command_end = ABSOLUTE(.); } >psram0_seg :psram0_phdr
根据上述脚本输出修改 MCU 的内存布局文件(
{MCU_SDK}/amebalite_gcc_project/amebalite_layout.ld):#define PSRAM_DSP_START (0x60400000)重新编译 DSP 与 MCU 工程。
Note
使用脚本前,需确保 Xtensa 工具链的 bin 目录已加入系统/用户 PATH,否则找不到可执行文件。
若曾修改过 MPU 其它属性,请在新生成的
mpu_table.c上同步修改。确保新生成的
mpu_table.c已加入编译工程。