环境
vivado 2022
vitis 2022
简介
DMA,即Direct Memory Access,指直接存储器访问。这是一种内存访问技术,允许某些计算机内部的硬件子系统(如计算机外设)独立地直接读写系统内存,而无需中央处理器(CPU)的介入。DMA可以在外设与存储器之间以及存储器与存储器之间提供高速数据传输,从而节省CPU资源,使其能够更专注于计算、控制等更加实用的操作。使用DMA时,数据从一个地址空间复制到另一个地址空间,且数据在传输过程中不会丢失。
为了发起传输事务,DMA 控制器必须得到以下数据:
• 源地址 — 数据被读出的地址;
• 目的地址 — 数据被写入的地址;
• 传输长度 — 应被传输的字节数。
DMA 存储传输的过程如下:
1、处理器向DMA控制器发送控制指令
2、DMA控制器自己把数据从源地址写入到目的地址
3、数据传输完成后,DMA 控制器向 CPU 发出一个中断,来通知处理器 DMA 传输完成
本实验选择PL 端的 AXI DMA,在 PL 中添加 AXI DMAIP 核,并利用 AXI_HP 接口完成高速的数据传输。
AXI DMA IP 核
AXI Direct Memory Access(AXI DMA)IP 核在 AXI4 内存映射和 AXI4-Stream IP 接口之间提供高带宽
直接储存访问。其可选的 scatter gather 功能还可以从基于处理器系统中的中央处理单元(CPU)卸载数据移
动任务。初始化、状态和管理寄存器通过 AXI4-Lite 从接口访问。核心的功能组成如下图所示:
3种模式介绍
- Direct Register Mode(直接寄存器模式)
基本功能:此模式提供了在MM2S(Memory Map to Stream,存储器映射到AXI-Stream)和S2MM(Stream to Memory Map,AXI-Stream到存储器映射)通道上进行简单DMA传输的配置。
特点:只需要较少的FPGA资源,通过访问DMACR(DMA控制寄存器)、源地址或目的地址和长度寄存器即可发起DMA传输。当传输完成后,如果使能了中断输出,那么DMASR(DMA状态寄存器)与存储器相关联的通道位会有效。在此模式下,配置完一次寄存器之后只能完成存储器连续地址空间的读写,如果有需求往不同地址空间搬运数据,那就需要重新配置寄存器开启一次新的传输。S2MM和MM2S不支持多个通道。 - Scatter/Gather Mode(分散/聚集模式)
基本概念:把关于传输的基本参数(比如起始地址、传输长度、包信息等)存储在存储器中,一套参数称之为Buffer Descriptor(简称BD)。在工作过程中,通过SG(Scatter/Gather)接口来加载BD并且更新BD中的状态。
特点:此模式允许在单个DMA事务中将数据传输到多个存储区域。在此模式下的寄存器列表中没有了Address(地址)、Length(长度)相关的寄存器,取而代之的是CURDESC(当前描述符指针寄存器)和TAILDESC(结尾描述符指针寄存器)。S2MM和MM2S都支持多个通道。MM2S多个通道共用一份CURDESC和TAILDESC寄存器,而S2MM的每个通道都有独立的CURDESC和TAILDESC寄存器,CR(控制寄存器)和SR(状态寄存器)则是共用的。 - Cyclic DMA Mode(循环模式)
基本概念:循环模式是在Scatter/Gather模式下的一种独特工作方式,在Multichannel Mode(多通道模式)下不可用。
特点:正常情况下的Scatter/Gather模式在遇到Tail BD(结尾描述符)时应该结束当前的传输,但如果使能了Cyclic模式,在遇到Tail BD时会忽略completed(已完成)位,并且回到First BD(首个描述符),这一过程会一直持续直到遇到错误或者人为中止。Cyclic模式只需要在开启传输前设置好BD链条,之后就不需要再进行其他操作。
实验任务
PL 端的 AXI DMA IP 核实现对 DDR3 中数据的读取与写入,实现环回的效果,具体流程为:PS 端产生测试数据并写入到 DDR3 中,然后 PL 端的 AXI DMA IP核从 DDR3 中读取数据,将读取到的数据存储到 AXI Stream Data FIFO 中,然后再将 AXI Stream Data FIFO中的数据写回到 DDR3 中。判断从 DDR3 中读取的数据和写入的数据是否一致。
实际操作流程
创建 Vivado 工程
使用hello_world工程作为基础,将工程另存为新工程
使用 IP Integrator 创建 Processing System
点击Open Block Design,创建设计
打开时钟复位,打开GP接口
PL时钟配置为100MHz
打开PL和PS的中断,点击OK ,配置完成
添加 DMA IP
DMA 模块添加完成后,双击 DMA 模块,进入 DMA 模块的配置界面
取消勾选 Enable Scatter Gather Engine 即可
添加 axis_data_fifo IP,该 IP 保持默认设置即可
添加 Concat IP,保持默认配置
Concate IP 实现了单个分散的信号,整合成总线信号。这里 2 个独立的中断信号,可以合并在一起接入到 ZYNQ IP 的中断信号上
模块连接
自动连接完成后,发现 Concat IP 未连接,我们手动进行连接
另外添加的 axis_data_fifo 也未连接,我们同样手动连接。首先将 DMA 的 M_AXIS_MM2S 端口与axis_data_fifo 的 S_AXIS 进行连接
将 axis_data_fifo 上的 M_AXIS 端口连接到 DMA 的 S_AXIS_S2MM 端口
axis_data_fifo 的时钟和复位,这里也可以使用自动连接
记得ctrl+S 保存
设计完成,点击验证
本次传输的数据流
使用M_AXIS_MM2S连接到FIFO的S_AXI,就是将数据通过DAM从DDR发送到FIFO当中
使用M_AXIS连接到S_AXI_S2MM,就是将数据通过DAM从FIFO发送到DDR当中
生成顶层 HDL 模块
在Sources 窗口中,选中 Design Sources 下的XXX.bd, 这就是我们刚刚完成的 Block Design 设计。右键点击 sysetm.bd,然后执行“Generate Output Products”
Out-of-context module runs were launched for generating output products
启动了脱离上下文的模块运行以生成输出产品
生成 Bitstream 文件并导出到 Vitis
在左侧 Flow Navigator 导航栏中找到 PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”。在连续弹出的对话框中依次点击“YES”、“OK”。然后 Vivado 工具开始依次对设计进行综合、实现、并生成 Bitstream 文件。
编译完成,点击cancel
在生成 Bitstream 之后,在菜单栏中选择 File > Export > Export hardware 导出硬件,并在弹出的对话框
中,勾选“Include bitstream”。然后在菜单栏选择 Tools> Launch Vitis IDE,启动 Vitis 软件
得到XXX.xsa文件
软件设计
打开vitis
您当前的工作区位置在其路径中包含空格。Xilinx 工具可以
在这种情况下无法正常工作。
请切换到路径中没有空格的工作区。
换一个目录就行
创建工程
打开 Create a new platform from hardware(XSA)标签页,点击“Browse”添加 xsa 文件
选择自己工程目录下的XXX.xsa 文件,然后点击“Next”
在弹出的对话框中,输入应用工程名axi_dma_loop”,此时系统工程名“axi_dma_loop_system”会自
动生成,其它选项保持默认即可,点击“Next”,
在弹出的页面中,保持默认,然后点击“Next”,
在弹出的工程模板选择页面里,我们选择已有的 一个空的模板, 然后点击 “Finish”
可以看到生成了三个工程,一个是硬件平台工程,即 platform 工程,一个是应用工程 axi_dma_loop,一个是系统工程 axi_dma_loop_system。系统工程可以理解为一个容器,里面可以包含一个或多个应用工程
代码编辑
自己创建main.c
添加代码
这里需要指定从 DDR3 中搬出数据的起始地址、数据搬回 DDR3 的存
储地址以及搬运的字节长度。关于从 DDR3 搬出和搬回的起始地址只需要在 DDR3 基地址和最大地址之间
即可,这里选择 DDR3 搬出地址为 0x1200000,搬回的起始地址为 0x1400000,搬运的字节长度此处选择 256
个字节
参考官方提供的模版函数
双击硬件平台目录下的 platform.spr 文件,找到点击板级支持包“Board_Support_Package”,点击展开
“Drivers”,右侧有相关文档和示例
点击应用工程 右键
点击 Build Project 对工程进行编译
编译完成后显示“Finished building:XXX.elf”
下载验证
菜单栏中选择 Window > Show View > Terminal,在 Show View 窗口中搜索添加Terminal
点击run 查看输出
代码介绍
1、tx_done被声明为volatile,以确保主程序能够获取到中断服务程序更新后的最新值。如果没有volatile关键字,编译器可能会对主程序中tx_done的访问进行优化,导致主程序不能正确检测到中断发生的次数
#include "xaxidma.h"#include "xparameters.h"#include "xil_exception.h"#include "xscugic.h"/************************** Constant Definitions *****************************/#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID //DMA 设备ID#define RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID //DMA 接收中断ID#define TX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID //DMA 发送中断ID#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //中断器件ID#define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR //0x00100000 DDR3 基地址#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1000000) //0x01100000 存储基地址 具体加多少自己决定 不要超过上限 够下面使用#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) //0x01200000 选择 DDR3 搬出地址为 0x1200000#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) //0x01400000 选择 DDR3 搬回的起始地址为 0x1400000#define RESET_TIMEOUT_COUNTER 10000 //复位时间 DMA传输出错 DMA重新复位的时间#define TEST_START_VALUE 0x0 //测试起始值#define MAX_PKT_LEN 0x100 //发送包长度 搬运的字节长度此处选择 256个字节/************************** Function Prototypes ******************************/static int check_data(int length, u8 start_value);static void tx_intr_handler(void *callback);static void rx_intr_handler(void *callback);static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,u16 tx_intr_id, u16 rx_intr_id);static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,u16 rx_intr_id);/************************** Variable Definitions *****************************/static XAxiDma axidma; //XAxiDma实例static XScuGic intc; //中断控制器的实例volatile int tx_done; //发送完成标志volatile int rx_done; //接收完成标志volatile int error; //传输出错标志/************************** Function Definitions *****************************/int main(void){int i;int status;u8 value;u8 *tx_buffer_ptr;u8 *rx_buffer_ptr;XAxiDma_Config *config;tx_buffer_ptr = (u8 *) TX_BUFFER_BASE;//tx_buffer_ptr指针按照字节顺序逐个处理这些数据rx_buffer_ptr = (u8 *) RX_BUFFER_BASE;xil_printf("\r\n--- Entering main(1) --- \r\n");config = XAxiDma_LookupConfig(DMA_DEV_ID);//通过DMA 设备ID 查找DMA硬件配置信息if (!config) {xil_printf("No config found for %d\r\n", DMA_DEV_ID);return XST_FAILURE;}//初始化DMA引擎status = XAxiDma_CfgInitialize(&axidma, config);//DMA初始化配置if (status != XST_SUCCESS) {xil_printf("Initialization failed %d\r\n", status);return XST_FAILURE;}if (XAxiDma_HasSg(&axidma)) {//判读是否使能SG模式 如果使能就会报错xil_printf("Device configured as SG mode \r\n");return XST_FAILURE;}//建立中断系统status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);if (status != XST_SUCCESS) {xil_printf("Failed intr setup\r\n");return XST_FAILURE;}//初始化标志信号tx_done = 0;rx_done = 0;error = 0;//准备需要发送的值value = TEST_START_VALUE;for (i = 0; i < MAX_PKT_LEN; i++) {tx_buffer_ptr[i] = value;value = (value + 1) & 0xFF;//0、1、2、3.。。。255}Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache//传送数据status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr,MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);//tx_buffer_ptr目的地址 MAX_PKT_LEN需要传输的数据长度 XAXIDMA_DMA_TO_DEVICE传输方向if (status != XST_SUCCESS) {return XST_FAILURE;}while (!tx_done && !error) //等待AXI DMA搬运完从DDR3到AXI Stream Data FIFO的数据;//如果PS向FIFO传输出错if (error) {xil_printf("Failed test transmit%s done\r\n", tx_done ? "" : " not");goto Done;}status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr,MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);if (status != XST_SUCCESS) {return XST_FAILURE;}while (!rx_done && !error) //等待AXI DMA搬运完从AXI Stream Data FIFO到DDR3的数据;// 如果PS接收FIFO传过来的数据出错if (error) {xil_printf("Failed test receive%s done\r\n",rx_done ? "" : " not");goto Done;}Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache//传输完成,检查数据是否正确status = check_data(MAX_PKT_LEN, TEST_START_VALUE);if (status != XST_SUCCESS) {xil_printf("Data check failed\r\n");goto Done;}xil_printf("Successfully ran AXI DMA Loop\r\n");disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);Done: xil_printf("--- Exiting main() --- \r\n");return XST_SUCCESS;}//检查数据缓冲区
static int check_data(int length, u8 start_value)
{u8 value;u8 *rx_packet;int i = 0;value = start_value;rx_packet = (u8 *) RX_BUFFER_BASE;for (i = 0; i < length; i++) {if (rx_packet[i] != value) {xil_printf("Data error %d: %x/%x\r\n", i, rx_packet[i], value);return XST_FAILURE;}value = (value + 1) & 0xFF;}return XST_SUCCESS;
}//DMA TX中断处理函数
static void tx_intr_handler(void *callback)
{int timeout;u32 irq_status;XAxiDma *axidma_inst = (XAxiDma *) callback;//读取待处理的中断状态irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);//确认待处理的中断状态XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);//Tx出错if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {error = 1;XAxiDma_Reset(axidma_inst);//如果出错会对DMA进行复位timeout = RESET_TIMEOUT_COUNTER;while (timeout) {if (XAxiDma_ResetIsDone(axidma_inst))//判断复位是否完成break;timeout -= 1;}return;}//Tx完成if ((irq_status & XAXIDMA_IRQ_IOC_MASK))tx_done = 1;
}//DMA RX中断处理函数
static void rx_intr_handler(void *callback)
{u32 irq_status;int timeout;XAxiDma *axidma_inst = (XAxiDma *) callback;irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);//Rx出错if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {error = 1;XAxiDma_Reset(axidma_inst);timeout = RESET_TIMEOUT_COUNTER;while (timeout) {if (XAxiDma_ResetIsDone(axidma_inst))break;timeout -= 1;}return;}//Rx完成if ((irq_status & XAXIDMA_IRQ_IOC_MASK))rx_done = 1;
}//建立DMA中断系统
// @param int_ins_ptr是指向XScuGic实例的指针
// @param AxiDmaPtr是指向DMA引擎实例的指针
// @param tx_intr_id是TX通道中断ID
// @param rx_intr_id是RX通道中断ID
// @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,u16 tx_intr_id, u16 rx_intr_id)
{int status;XScuGic_Config *intc_config;//初始化中断控制器驱动intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);//通过中断设备ID 查找中断配置if (NULL == intc_config) {return XST_FAILURE;}status = XScuGic_CfgInitialize(int_ins_ptr, intc_config,intc_config->CpuBaseAddress);//初始化中断if (status != XST_SUCCESS) {return XST_FAILURE;}//设置优先级和触发类型//int_ins_ptr指向中断控制器实例的指针 tx_intr_id和rx_intr_id:分别代表发送中断 ID 和接收中断 ID 0xA0:代表设置的优先级值 0x3:代表触发类型XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);//tx_intr_id为中断设置中断处理函数tx_intr_handlerstatus = XScuGic_Connect(int_ins_ptr, tx_intr_id,(Xil_InterruptHandler) tx_intr_handler, axidma_ptr);if (status != XST_SUCCESS) {return status;}//rx_intr_id为中断设置中断处理函数rx_intr_handlerstatus = XScuGic_Connect(int_ins_ptr, rx_intr_id,(Xil_InterruptHandler) rx_intr_handler, axidma_ptr);if (status != XST_SUCCESS) {return status;}//使能XScuGic_Enable(int_ins_ptr, tx_intr_id);XScuGic_Enable(int_ins_ptr, rx_intr_id);//启用来自硬件的中断 设置一些异常信息Xil_ExceptionInit();Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler) XScuGic_InterruptHandler,(void *) int_ins_ptr);Xil_ExceptionEnable();//使能DMA中断XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);return XST_SUCCESS;
}//此函数禁用DMA引擎的中断
static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,u16 rx_intr_id)
{XScuGic_Disconnect(int_ins_ptr, tx_intr_id);XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
}