Zynq PS DMA应用笔记
Hello,Panda
Zynq-7000系列器件PS端的DMA控制器采用ARM的IP核DMA-330(PL-330)实现。有关DMA控制器的硬件细节及相关指令集、编程实例内容参考ARM官方文档:
DDI0424D:dma330_r1p2_trm.pdf
DAI0239A:dma330_example_programs.pdf
本文开发环境为Xilinx SDK2015.2,DMA库版本为dmaps_v2_1。
DMA控制器具有以下的特点:
n 8个独立的通道,4个可用于PL—PS间数据管理,每个通道有1024Byte的MFIFO;
n 使用CPU_2x 时钟搬运数据,CPU_2x = (CPU frq/6)*2;
n 执行自定义内存区域内的DMA指令运行DMA;
n AHB控制寄存器支持安全和非安全模式;
n 每个通道内置4字Cache;
n 可以访问SoC的以下映射物理地址:
DDR、OCM、PL、Linear QSPI Read、SMC和M_AXI_GP设备,访问设备的互联结构如图1所示。
从图1可以看出DMA控制器可以访问连接到Central Interconnect上的所有设备,并提供了四个通道的外设管理接口可用于控制PL的数据搬运。
Zynq系列器件中DMA控制器采用ARM PL-330 IP和r1p1版,结构框图如图2所示。
图2 Zynq DMA控制器结构框图
如图2所示,DMA控制器由指令加速引擎,AXI Master数据接口,AXI APB寄存器访问接口以及可以连接到PL的外设请求接口,数据缓冲FIFO和控制及状态产生单元组成。
从图2可以看到,DMA PL330的设计思想是:DMA控制器通过DMA指令执行引擎执行自己的指令,并将执行状态通过APB总线和中断等形式反馈给CPU,达到数据搬运不占用CPU的目的。
DMA控制器共有八个通道,其中四个通道负责互联到Central Interconnectcun存储单元上的数据搬运;四个数据通道为外设请求接口,可用于PL AXI互联接口的数据访问管理。
每个DMA通道都执行自己的指令,拥有自己的独立线程,通道间互不影响。指令执行引擎有自己独立的Cache线。
本文不考虑外设请求接口,DMA控制器编程分为以下几个部分:
u DMA控制器初始化;
u 组织DMA引擎执行代码;
u 启动或停止DMA传输;
u 异常处理。
DMA控制器初始化,DMA初始化包括配置时钟、复位,安全状态,中断服务等,如下表1所示。
表1 DMAC初始化配置
步骤 |
配置项 |
相关寄存器 |
描述 |
1 |
配置时钟 |
|
使能APB时钟,一般已经使能 |
slcr.AER_CLK_CTRL [DMA_CPU_2XCLKACT]=1’b1 |
使能CPU_2x时钟给AXI |
||
2 |
配置安全状态 |
SLCR. TZ_DMA_NS = 1 |
非安全 |
SLCR. TZ_DMA_IRQ_NS=1 |
非安全 |
||
SLCR. TZ_DMA_PERIPH_NS=1 |
非安全 |
||
3 |
复位 |
slcr.DMAC_RST_CLTR[DMAC_RST] |
复位DMAC |
4 |
中断 |
|
设置中断服务函数 |
一般的时钟和复位都在FSBL里面完成,用户只需要设置DMA的APB总线安全模式和中断服务函数即可。特别要注意对安全模式的设置,否则在非安全模式下访问安全模式寄存器无任何有效应答。
Xilinx SDK(gcc)不支持编译DMA引擎指令,因此需要自己对照ARM官方文档”DDI0424D_dma330_r1p2_trm.pdf”对指令集的描述一条一条的组织指令,特别注意执行指令的长度应为Cache线的整数倍,不足的用NOP补齐。
下面以DMAMOV指令为例,说明如何编写指令引擎执行的机器码,图3是DMAMOV指令的编码。
图3 DMAMOV指令的编码
本条指令编码由48位(6字节)构成,如图3中的编码:
① rd[2:0]:表示寄存器地址,000为源地址寄存器SAR,001为通道控制寄存器CCR,DAR为目的地址寄存器。
② imm[31:0],为以上三个寄存器的配置值。
CCR为AXI总线配置寄存器,参见文档DDI0424D中 3.3.15 Channel Control Registers章节的描述;有关AXI总线协议接口描述自行参照ARM官方文档IHI 0022D:AMBA AXI andACE Protocol Specification。
那么生成DMAMOV机器码的函数如下所示:
INLINE int XDmaPs_Instr_DMAMOV(char *DmaProg, unsigned Rd, u32 Imm)
{
/*
* DMAMOV encoding
* 15 4 3 2 1 10 ... 8 7 6 5 4 3 2 1 0
* 0 0 00 0 |rd[2:0]|1 0 1 1 1 1 0 0
*
* 47 ... 16
* imm[32:0]
*
* rd: b000 for SAR, b001 CCR, b010 DAR
*/
*DmaProg= 0xBC;
*(DmaProg+ 1) = Rd & 0x7;
XDmaPs_Memcpy4(DmaProg+ 2, (char *)&Imm);
return 6;
}
那么,看下面一段汇编代码,代码完成160K数据搬运,数据源和目的都是DDR:
DMAMOV SAR #SrcAddr
DMAMOV DAR #DstAddr
DMAMOV CCR #CCRn
DMALP lc1 outerloop
DMALP lc0 innerloop
DMALD
DMAST
DMALPEND lc0
DMALD
DMAST
DMALPEND lc1
DMASEV
DMAEND
DMAC提供了两个计数器lc0和lc1,都是8bit的,因此每层循环的次数都不能超过256次。指令执行到DMASEV时控制器发出完成中断,执行过程中遇到任何错误或异常均发出Abrot中断。
在本例中SrcAddr=0x10000000,DstAddr = 0x11000000,数据长度为160KB,CCRn的参数如下:
CCRn.EndianSwapSize = 0x00;
CCRn.DstCacheCtrl =0x00;
CCRn.DstProtCtrl = 0x00;
CCRn.DstBurstLen = 0x07;
CCRn.DstBurstSize = 0x03;
CCRn.DstInc =0x01;
CCRn.SrcCacheCtrl = 0x00;
CCRn.SrcProtCtrl = 0x00;
CCRn.SrcBurstLen = 0x07;
CCRn.SrcBurstSize = 0x03;
CCRn.SrcInc =0x01;
那么通过库函数XDmaPs_BuildDmaProg生成的可执行机器码就是:
[Addr]Code
[0] BC
[1] 0
[2] 0
[3] 0
[4] 0
[5] 10
[6] BC
[7] 2
[8] 0
[9] 0
[A] 0
[B] 11
[C] BC
[D] 1
[E] 77
[F] C0
[10] 1D
[11] 0
[12] 22
[13] 9
[14] 20
[15] FF
[16] 4
[17] 8
[18] 38
[19] 2
[1A] 3C
[1B] 6
[1C] 34
[1D] 0
[1E] 0
代码正常执行完成后产生SEV中断,需在中断服务程序中清除当前的中断状态。
在DMA控制器的诸多寄存器中,绝大部分仅是反映DMA引擎的当前执行情况,用于实施跟踪执行状态。至关重要的寄存器DBGSTATUS、DBGCMD、DBGINST0、DBGINST1用于控制DMA的开始、中断和终止。也就是DMA指令中的DMAGO、DMASEV和DMAKILL,由于APB接口和DMA引擎间的异步关系,指令执行存在一定的延时,在下发下条指令前一定要通过读取DBGSTATUS的值轮询上次指令是否执行完毕。
遇到DMA Abrot中断事件,都必须下发DMAKILL指令终止当前线程。
ARM PL330 DMA控制器乍看来似乎难以理解,实际上它是把DMAC当成一个协处理来看待,有自己的指令执行引擎。用户组织好执行机器码后存到内存,用户通过APB接口发出DMAGO指令告诉DMAC的指令执行引擎执行代码的首地址并启动DMA指令引擎直到指令执行到DMAEND停止。
备注:比如将SDK安装到了D:\Vivado\SDK目录下,版本为2015.2,那么DMAC参考示例代码就存放在“D:\Vivado\SDK\2015.2\data\embeddedsw\
XilinxProcessorIPLib\drivers\dmaps_v2_1\examples”路径下。