OpenEM 简介和基于 OpenEM 的大矩阵乘实现
Shannon DSP (6678)的内存系统包括片内的 LL2(local L2)和 SL2(shared L2)。加上片外的 DDR。LL2 的 size 是 512 Kbytes,每个核有一份 LL2。 SL2 的 size 是 4Mbytes,8 个核共享 SL2。DDR size 和硬件板卡设计有关,一般在 1G bytes 以上。 C66x 核对 LL2 的访问效率最高,对 SL2 的访问效率稍差,对 DDR 的访问效率最低。基于多种存储区间的不同特性,我们对数据存储位置按如下规划(参见图 3):
本文引用地址:https://www.eepw.com.cn/article/201809/388587.htm– 矩阵 X 的 size 是 800 Kbytes,存储是 shared L2
– 矩阵 Y 的 size 是 16 Mbytes,存储是 DDR
– 矩阵 Z 的 size 是 800 Kbytes,存储是 shared L2
虽然矩阵 Y 存储在 DDR,但是我们启用了 OpenEM 的 preload 功能。Preload 就是通过 QMSS 的 packet DMA 把待处理的 event 数据(通常位于 DDR)搬到被调度 core 的 LL2。所以 DSP 核运行的时候不直接从 DDR 取数。这保证了 DSP 核的数据访问效率。
2.1.2 处理流程
OpenEM中要有一个 DSP 核作为主核,其他核就是从核,主核要完成的工作较多。本文的演示用例中,核 0 是主核,核 1~7 是从核。主从核的分工差异如图 4:
1. 初始化 QMSS 和 free pool。
2. OpenEM 的 global 初始化和 local 初始化。global 初始化是主核执行。local 初始化是每个核各自执行。Local 初始化要等 global 初始化完成才能开始。所以,中间需要加一个barrier。Barrier 可以理解成一个同步点,所有 DSP 核在这个点完成一次同步再继续向下执行。本演示用例的 Barrier 是通过共享内存的软件信号量实现的。
3. 主核构造生产者/消费者场景并产生待处理的 event。生产者在 OpenEM 中不是一个软件对象。我们可以把产生 event 并发送到 queue 的函数认为是生产者。消费者就是 execution object,沟通生产者和消费者的管道就是 queue。构造生产者/消费者场景就是创建execution object 和 queue 并且把它们关联起来。
4. 主核和从核进入 event 处理的过程。
5. 主核检测到所有 event 都处理完成后为每个 DSP 核(包括它自己)产一个 exit job。
6. 主核和从核处理 exit job。从核直接调用 exit(0)退出。主核先做结果验证然后调用 exit(0)退出。
本文演示用例实现的几个特点是:
• OpenEM 的 free pool 是由用户初始化的。在初始化 free pool 的时候 event 描述符不指向数据缓冲区。等分配了一个 event 的时候再在这个 event 对应的描述符上挂数据缓冲区。这样可以避免不必要的数据拷贝(从 global buffer 拷贝到 event buffer)。
• 主核通过查询 free pool 中的 event 个数是否恢复回初始值来判断是否所有“矩阵乘 event”都处理。因为:
– Free pool 在初始化以后有 N 个 free event,
– 从中分配了若干个 event 后,free event 就减少了相应的个数,
– 每个 core 每处理完一个 event 就把这个 event 回收到 free pool,free pool 的 event 个数就加一。当 free pool 的 event 个数恢复回 N,就说明所有 event 都处理完了。
2.2 基于 OpenEM 的大矩阵乘实现
在初始化 OpenEM之前首先要做 multicore Navigator 的初始化。包括:PDSP firmware 的download, Link RAM 的初始化, Memory region 的初始化还有 free pool (也就是 free descriptorqueue)的初始化。这不属于本文介绍的范畴,本文直接介绍 OpenEM的初始化。
2.2.1 OpenEM 的 Global 初始化
OpenEM的 global 初始化通过调用 API 函数 ti_em_init_global()完成的。这个 API 的入参是下面所示的结构体。其中所列的参数是本文的演示用例使用的配置参数。本文针对每个参数的作用做了注释。了解了参数了含义,就能了解 OpenEM 的 global 初始化的大致做了些什么。
注释:
1. OpenEM要使用 hardware queue 资源。hw_queue_base_idx 用来指定 OpenEM 从哪个hardware queue 开始可用。
2. OpenEM 的少量操作需要多 DSP 核访问共享的数据结构。是通过 hardware semaphore 实现多核lock/unclock 的。所以通过 hw_sem_idx 告诉 OpenEM该使用哪一个 hardware semaphore。
3. 指定 preload 使用的 QMSS packet DMA 的通道的起始索引。QMSS packet DMA 有 32 个 RX/TX channel。在 OpenEM 中,每个 DSP core 要占用一个 TX/RX channel。
4. 指定 preload 使用的 QMSS Tx queues 的起始索引。要和 dma_idx 对应起来。QMSS 有 32 个 TX queue,索引是 800~831。对应 QMSS packet DMA 的 TX channel 0~31。所以,如果前面配置的 dma_idx 是 0,那么这里配置的 dma_queue_base_idx 应该是 800。
5. 指定 OpenEM local free pool 对应的 free queue index。Local free pool 是和 preload 相关的。local free pool 在物理上是一个 free descriptor queue。里面存储着 2 个 host 描述符。每个描述符对应一个 local L2 buffer。如果发生 preload,packet DMA 就从 free descriptor queue pop 描述符,然后把数据传到描述符指向的 local L2 buffer。每个 DSP 核有一个 local free pool。例如,在我们的演示用例中 core0~7 对应的 free descriptor queue 索引是 2050~2057。
6. 指定 OpenEM global free pool 的个数。每个 global free pool 包括 4 个初始化参数,例如{ globalFreePoolFdqIdx, TI_EM_COH_MODE_ON,TI_EM_BUF_MODE_GLOBAL_TIGHT,0}。参数 1是这个 global free pool 对应的 free queue index。接下来几项是这个 pool 中的 buffer 的属性。Global free pool 是用来从中分配 free event 的。调用 em_alloc()的入参之一就是 free pool index。
评论