我们上一节谈到使用 DMA(直接内存访问)的好处已经变得显而易见。到了这一步,我们留下了人类长期以来一直在思考的问题:DMA到底是什么?
DMA介绍
在最基本的层面上,一旦处理器设置了传输,DMA 将数据传入或传出内存而无需CPU干预。DMA 可以显着提高系统性能,具体取决于所采用的方法。在我们更详细地了解 Zynq DMA 之前,我想先解释一些 DMA 控制器的通用原理。通常 DMA 控制器以以下三种模式之一运行:
- Burst Mode突发模式 - 在一次连续操作中传输整个数据块。在许多应用中,突发模式传输时拒绝总线访问处理器。这种模式好与坏还是取决于系统。
- Cycle Stealing – 为了克服上诉的不足,DMA支持Cycle Stealing将单个 DMA 字节或字传输与处理器访问系统总线交错运行。
- 透明模式——最有效的模式。仅当处理器执行不需要访问系统总线的任务时才传输数据。
DMA 控制器的一项非常有用的功能是支持分散/收集(scatter/gather)操作的能力。此功能允许将多个数据源传输到单个目标地址或允许单个源地址提供多个输出目标(也称为“缓冲区”)。Zynq SoC 的基于 ARM 的处理系统 (PS) 有一个 DMA 控制器 (DMAC),它连接到 Zynq 的 AXI4 互连并使用 AXI 总线执行传输。DMAC 在系统存储器和 Zynq 的可编程逻辑 (PL) 之间采用 64 位 AXI 传输。如下所示,Zynq DMAC 有 8 个通道,允许 DMAC 同时执行 8 个 DMA 线程,并通过 AXI 互连实现流控制。
虽然 Zynq DMAC 允许在系统存储器和 PL(包括 PL 中的 Zynq 外设)之间进行双向传输,但它不支持 Zynq PS 中的外设的 DMA,因为这些外设没有流控制信号来支持 DMA 操作。然而,Zynq SoC 中的一些 IO 外设具有自己的 DMA 控制器,以支持进出 IOP 和系统内存的高数据速率传输。这些外围设备是:
- GigE Controller 千兆以太网控制器
- SDIO Controller SDIO 控制器
- USB Controller USB控制器
- Device Configuration Controller 设备配置控制器
如果设备使用 ARM TrustZone,Zynq SoC 还支持安全寄存器访问。Xilinx 同时也提供了一个简单的驱动程序文件 (xdmaps.h),我们可以在独立 BSP 中使用它来配置和启动 DMA 传输。在下一节中,我们将了解如何使用此文件创建简单的 DMA 传输。
示例演示
本节创建一个非常简单的示例来演示如何设置和使用 DMA。
为了演示这示例,将使用一个 DMA 控制器通道将一个内存位置传输到另一个内存位置。
首先需要在BSP中包含一部分Vivado中生成的头文件。这些头文件提供了我们可以用来驱动 DMA 的宏和函数。对于这个例子,我们需要包括:
#include "xscugic.h"
#include "xdmaps.h"
#include "xil_exception.h"
Xscugic.h 和 xil_exceptions.h 允许使用中断控制器,而 xdmaps.h 允许配置并使用DMA。
使用 xparamters.h 提供的参数,我们可以定义 DMA 和中断控制器的设备标识、将使用的中断以及我们将传输的数据长度:
#define DMA_DEVICE_ID XPAR_XDMAPS_1_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define DMA_FAULT_INTR XPAR_XDMAPS_0_FAULT_INTR
#define DMA_DONE_INTR_0 XPAR_XDMAPS_0_DONE_INTR_0
#define DMA_LENGTH 1024
开发的下一阶段是编写三个函数来配置DMA,配置中断控制器,并在DMA传输完成时充当中断服务程序。
在 DMA 配置函数中,我们首先使用 xdmaps.h 提供的命令结构创建一个 DMA 命令。DMA 命令由通道控制、块描述符、用户定义的程序、指向生成的程序的指针和传输结果组成。由于这是一个简单的示例,我们不需要所有这些组件,但是我们将配置 DMA 控制器,如下所示:
DmaCmd.ChanCtrl.SrcBurstSize = 4;
DmaCmd.ChanCtrl.SrcBurstLen = 4;
DmaCmd.ChanCtrl.SrcInc = 1;
DmaCmd.ChanCtrl.DstBurstSize = 4;
DmaCmd.ChanCtrl.DstBurstLen = 4;
DmaCmd.ChanCtrl.DstInc = 1;
DmaCmd.BD.SrcAddr = (u32) Src;
DmaCmd.BD.DstAddr = (u32) Dst;
DmaCmd.BD.Length = DMA_LENGTH * sizeof(int);
下一步设置运行中断函数以将 DMA 中断连接到中断控制器之前初始化和配置 DMA 控制器:
DmaCfg = XDmaPs_LookupConfig(DeviceId);
XDmaPs_CfgInitialize(DmaInst,DmaCfg,DmaCfg->BaseAddress);
SetupInterrupt(&GicInstance, DmaInst);
在此之后,在我们连接完成处理程序并开始传输之前,源内存位置被设置并清除目标位置,为了跟踪进度,我们还调用了 DMA 进度函数:
DmaCfg = XDmaPs_LookupConfig(DeviceId);
XDmaPs_CfgInitialize(DmaInst,DmaCfg,DmaCfg->BaseAddress);
SetupInterrupt(&GicInstance, DmaInst);
XDmaPs_Print_DmaProg(&DmaCmd);
当附加的源代码文件在ZYNQ上运行时,以下结果显示串口输出上。
源码:
https://gitee.com/openfpga/zynq-chronicles/blob/master/part_29.c
原文:OpenFPGA
作者:碎碎思
相关文章推荐
更多FPGA技术干货请关注FPGA 的逻辑技术专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。