Zynq学习笔记-AXI DMA (Simple)简介和示例
一、DMA简介
**DMA(Direct Memory Access,直接存储器访问)**技术允许某些硬件子系统直接读写内存,使CPU从数据搬运中解放出来。DMA通常用于进行大量数据的移动,其具体过程为:
- 先由CPU向DMA控制器设定传输指令(源地址,目的地址,大小,猝发长度)
- DMA控制数据转移,CPU转而去做别的工作
- DMA转移数据完毕,向CPU发出中断
- CPU进行中断处理,结束DMA传输
Zynq AXI-DMA是PL端DMA的实现方式。DMA事实上,PS端也有DMA控制器,具体的信息可以在芯片的Technical Reference中查到。大致如下:
方法
特性
问题
适用情况
速度
PS DMAC
资源占用少
多通道
逻辑接口简单
DMAC配置不方便
吞吐量率中等
PL的DMA资源不够时
600MB/s
PL DMA配AXI_HP
吞吐率最高
接口多
HP自带FIFO缓存
只能访问OCM和DDR
逻辑设计复杂
大块数据、高性能
1200MB/s
(每个接口)
PL DMA配AXI_ACP
吞吐率最高
延时最低
可选的Cache一致性
大块数据传输引起Cache问题
共享CPU互联带宽
更复杂的逻辑设计
小块与Cache直接相关的高速传输
1200MB/s
PL DMA配AXI_GP
-
吞吐率中等
更复杂的逻辑设计
PL到PS的控制(一般是AXILITE)
PS I/O外设访问
600MB/s
各接口传输比较
在传输上,AXI DMA利用AXI Interconnect进行数据传输,如下:
DMAController读取数据流向
在AXI-DMA IP中,提供了3种存储模式:
- Direct Register (Simple)简单DMA传输,仅需要地址和长度就能进行传输
- Scatter/Gather 允许在单个DMA事务中将数据传输到多个存储区域
- Cyclic DMA 循环DMA,类似于循环队列,待补充
二、从AXI-DMA简单应用中熟悉IP
本Demo利用了AXI-DMA+AXI Stream Data FIFO实现利用DMA从PS读取数据到FIFO,再利用DMA写回PS的过程,没有用到中断,采用轮询请求判断传输结束。
打开Vivado,新建工程。注意选择好芯片或板子的型号,不然在IP Catalog里面可能搜索不到IP.
IP Generator -> Create Block Design
添加 Zynq IP并Run block Automation
新建AXI DMA IP
双击IP进入配置页面,如下所示:
对部分选项解释一下(详见PG021 AXI DMA P77)
- Enable Scatter Gather Engine 启动Scatter/Gather模式,上面有介绍过,取消勾选变成Direct Register模式,本Demo为简单起见暂时取消勾选。
- Enable Micro DMA 耗费资源少,但是数据传输速度也有所下降,暂时不选择。
- Width of Buffer Length Register:指定用于控制字段缓冲区长度和在Scatter/Gather模式描述符中传输的状态字段字节的有效位数。 对于Direct Register模式,它指定 MM2S_LENGTH 和 S2MM_LENGTH 寄存器中的有效位数。 长度宽度与 Scatter/Gather 描述符中指定的字节数、MM2S_LENGTH 或 S2MM_LENGTH 中指定的字节数直接相关。 字节数等于 2^Length Width。 对于多通道模式,该值应设置为 23。
- Enable Read Channel
- Memory Map Data Width 指定AXI_MM2S位宽(用于从存储器读取数)
- Stream Data Width 指定AXIS_MM2S 数据位宽,应该小于或等于Memory Map Data Width
- Max Burst Size 最大猝发长度,即AXI_MM2S中MM侧的猝发周期最大值。
- Enable Write Channel 同上
经过设置以后,端口减少为以下:
下面解释端口含义:
- 红色部分(IP控制端口)
- S_AXI_LITE 接收AXI DMA控制器指令(本实验中连接到Zynq的Master AXI GP)aclk是时钟
- 黄色部分(AXI-Stream协议端口)
- S_AXIS_S2MM AXI-Stream Slave端口,获取AXI-Stream数据流并通过M_AXI_S2MM进行Memory Map(写入存储器)
- M_AXIS_MM2S AXI-Stream Master端口,获取从M_AXI_MM2S得到的数据并转换成AXI-Stream协议进行发送
- 剩下两个aclk是各自的时钟
- 绿色部分(中断)
- mm2s_introut mm2s中断,代表指定的长度已经全部作为AXIS发送完毕
- s2mm_introut s2mm中断,代表AXIS的数据已经全部映射到内存
接下来,配置Zynq参数,需要配置的有:
- 使能一个M_AXI_GP用于传输控制数据
- 使能一个S_AXI_ACP或者HP来进行数据高速传输
- 使能中断
使能ACP
使能中断引脚
Run Connection Automation,进行基础连线
可以先来分析一下这个图:红色部分两个AXI Master和Slave之间的连线,通过AXI Interconnect操作Zynq的ACP端口(黄色部分),实现向Zynq读取/写入数据。绿色部分给AXI DMA IP发送操作指令。
接下来,为了实现本DEMO的目标:PS->DMA->FIFO->DMA->PS,需要例化一个AXI FIFO
在这里我们接受的是AXI-Stream格式的数据,因此选择AXI Stream Data FIFO。
选择FIFO
接下来,将FIFO连接到DMA上。
发现现在DMA IP有两个最关键的端口没有链接:S_AXIS_S2MM和M_AXIS_MM2S,后者是从AXI ACP读到了数据并输出为Stream的端口,因此应该连接到FIFO的S_AXIS端口,前者则相反。连接完成后,再次运行Connection Automation完成时钟和reset的链接。
添加FIFO的连线
为了方便观测信号,设置一个调试模块ILA,搜索ILA,实例化一个System ILA
ILA
我们需要对ILA核进行一些配置来让他可以检测AXI-STREAM协议。双击IP核,首先将端口调整为两个,同时观测FIFO的输入和输出。
调整ILA参数
进入Interface Options,修改端口种类为 xilinx.com:interface:axis rtl:1.0 来检测AXI-Stream协议。对于SLOT2同理。接着将ILA连接到FIFO两端进行监控。接下来Run Connection Automation让Vivado完成接下来的连接。
最终的Block Design
差点忘了一步:整合中断,在这里DMA有两个中断端口,增加一个Concat IP核来拼接这两个中断并连接到Zynq的中断控制端口上去。(虽然本实验暂时用不到中断)
中断传输
点击左侧 Generate Block Design,生成完毕后,在Source面板内右键单击生成的block design,选择 Create HDL Wrapper生成顶层。
进行综合布线,生成比特流,生成后导出硬件描述文件(.xsa)以便进行Vitis IDE开发。
导出xsa
warning 有关是否包含Bitstream
导出时,往往会询问用户是否要包含bitstream,在本实验中建议不包含。在实际调试过程中,先由vivado进行烧录,配置好ILA的trigger,再用Vitis对ARM核心进行编程,可以准确捕捉数据流向。若包含Bitstream,Vitis会自动重新烧写PL,造成不便。
按照正常方式进行Vitis项目创建,New一个Application Project,选择刚刚导出的xsa文件。后面全部默认,生成一个hello world示例工程。
选择描述文件
下一步,进行代码编辑,这里给出我的代码,修改自官方驱动,在zedboard上测试通过。
1 |
|
在运行之前,建议先进行一下Run Configuration
Run Configuration
确保Reset和Program关闭
确认一下Reset和Program FPGA关闭,不然会重新给PL编程导致ILA断线。
打开Vivado的Hardware Manager,烧写Bitstream到Target
warning 为什么ILA无法识别?
有些情况下ILA可能无法立刻被识别,Vivado的ILA要求有一个自由时钟(供电后立刻起振),少部分情况可能出现烧写完不识别的情况,可以点击一下Refresh device,若还没有,则可以尝试先让Vitis IDE先烧一次再进行refresh。
在Vitis烧写ARM之前,需要先配置一下ILA的Trigger。在本实验中可以看到如下的ILA检测面板
设置如下的Trigger:
- TREADY信号在数据准备好进行传输的时候置1,因此在FIFO出端进行监听
- TVALID信号在数据准备好接受时置1,因此在FIFO入端进行监听
点击Run Trigger的按钮,再利用Vitis进行数据烧录。
对PS进行编程
Vivado ILA成功触发并画出数据:
简单分析一下传输数据:
传输数据
红色:当TVALID=1时,握手成功,数据开始传输,直到传输了3*32bit数据后,FIFO出端的TVALID变为1(黄色),数据开始被读出,蓝色部分,最后一个数据被读出后,TLAST被置为1。
为什么是三个周期后开始允许读出?注意到FIFO IP内部有一个CDC (Clock Domain Crossing) sync stages=3,意味着在跨时钟域同步时,要延迟三个周期允许读出,本demo为同时钟域,暂且未考虑此需求。
接着来分析一下传输的数据内容。在Vitis IDE里面设置了初始值为(u8)0xC,以后每个8位数+1,因此传输内容应该为00001100 00001101 00001102 … 事实上为:
传输的数据内容
与预期相符。
还发现,单次传输Stream共传输了8拍,一共8*32 = 256bit数据,这是也和事实相符,IDE中设置了传输TxBuffer大小是0x20,共2^5*8bit = 256位。
至此,本实验成功告一段落。