13 - AXI DMA环路实验

1 实验任务

本实验任务是使用PL端的AXI DMA IP核从从PS端的DDR3中读取数据,并将数
据写回到PS端的DDR3中。

2 系统框图

在这里插入图片描述

3 硬件设计

3.1 Block Design

  1. 添加ZYNQ7 Processing System IP核
    • (1)在PS-PL Configuration页面
      • 1)勾选FCLK_RESET0_N
      • 2)勾选M AXI GP0 interface
      • 3)勾选S AXI HP0 interface
    • (2)在Peripheral I/O页面
      • 1)勾选UART
      • 2)正确选择Bank 0和Bank 1的电压
    • (3)在Clock Configuration页面
      • 1)勾选FCLK_CLK0
      • 2)勾选FCLK_CLK1
    • (4)在DDR Configuration页面
      • 1)DDR Controller Configuration下,合理配置Memory Type、Memory Part和Bus Width等参数
    • (5)在Interrupts页面
      • 勾选IRQ_F2P[15:0]
  2. 添加Clocking Wizard IP核
  3. 添加AXI Direct Memory Access IP核
  4. 添加AXI4-Stream Data FIFO IP核
  5. 添加Concat IP核
  6. 添加System ILA IP核
  7. 自动连线

在这里插入图片描述

3.2 IP核配置

  1. AXI DMA IP配置
    在这里插入图片描述
    • (1)Width of Buffer Length Register选项:指定DMA传输的最大长度
      在这里插入图片描述
    • (2)读通道:作为主接口,其Memory Map Data Width和Stream Data Width都是可设置的
    • (3)写通道:作为从接口,其Memory Map Data Width和Stream Data Width默认是Auto的(当然,用户可以改为Manual,手动指定)
      • 1)Memory Map Data Width基于Stream Data Width
      • 2)Stream Data Width由s_axis_s2mm接口所连接的主接口决定
        在这里插入图片描述
  2. AXI4-Stream Data FIFO IP配置
    • (1)General页面
      • 1)FIFO Depth:选择4096,FIFO容量为4096x4B=16KB,足够容纳一次DMA读写操作的数据量(PS侧设计为8KB);
      • 2)Memory Tyoe:选择Block RAM
      • 3)Independent Clocks:选择Yes,即读写接口的时钟不同
        在这里插入图片描述
    • (2)Flags页面
      • 1)Enable write data count:选择Yes
      • 2)Enable read data count:选择Yes
        在这里插入图片描述

3.3 注意事项

  1. 本设计共有4个时钟,PS和外部晶振各提供两个时钟
    • (1)PS FCLK_CLK0:50MHz,为PS的配置接口M_AXI_GP0提供时钟
    • (2)PS FCLK_CLK1:100MHz,为PS的数据接口S_AXI_GP0提供时钟
    • (3)晶振clk_out1:120MHz,为AXI DMA的MM2S数据流提供时钟(AXI DMA读-下行)
    • (4)晶振clk_out2:150MHz,为AXI DMA的S2MM数据流提供时钟(AXI DMA写-上行)
    • (5)目的:各个接口有各自的时钟,且频率不一样,模拟实际使用场景
  2. 为各个接口自动连线:必须手动指定主从接口和互联模块的时钟
    • (1)为AXI DMA IP的S_AXI_LITE接口连线:主接口是PS的M_AXI_GP0接口
      在这里插入图片描述
    • (2)为PS的S_AXI_HP0接口连线:主接口是AXI DMA IP的M_AXI_MM2S接口
      在这里插入图片描述
    • (3)为AXI DMA IP的M_AXI_S2MM接口连线:从接口是PS的S_AXI_HP0接口
      在这里插入图片描述
    • (4)手动连接AXI DMA IP和AXI4-Stream Data FIFO IP的Stream接口,然后运行Run Connection Automation,工具会自动为AXI4-Stream Data FIFO的读写接口时钟连线
      在这里插入图片描述

4 软件设计

4.1 注意事项

  1. 缓存一致性问题:在进行PS-PL之间的DMA传输时,不可避免会遇到Cache问题
    • (1)产生原因:
      • 1)PS访问DDR时,为了提高访问速度,通常会将数据缓存在Data Cache中
      • 2)好处:加快下一次访问速度
      • 3)坏处:Data Cache中的数据与DDR中的数据不一致
    • (2)解决办法
      • 1)方法1:使用Xil_DCacheDisable函数禁用Cache(简单粗暴)
      • 2)方法2:使用Xil_DCacheFlushRange函数和Xil_DCacheInvalidateRange函数
        • 在AXIDMA发送前,使用Xil_DCacheFlushRange函数,将Data Cache中缓存的数据强制写回到DDR中去
        • 在AXIDMA接收后,使用Xil_DCacheInvalidateRange,使Data Cache中缓存的数据无效,直接从DDR中读取数据
  2. 在Simple模式 下,XAxiDma_SimpleTransfer() 函数每次只完成一次DMA传输,传输完成后DMA会自动停止,不会持续传输。

4.2 工程源码

/***************************** Include Files *********************************/

#include "stdio.h"
#include "xparameters.h"
#include "xscugic.h"
#include "xaxidma.h"

/************************** Constant Definitions *****************************/

#define AXIDMA_DEVICE_ID          	XPAR_AXIDMA_0_DEVICE_ID
#define INTC_DEVICE_ID      		XPAR_SCUGIC_SINGLE_DEVICE_ID

#define AXIDMA_RX_INTR_ID          	XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define AXIDMA_TX_INTR_ID          	XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID

#define DDR_BASE_ADDR       		XPAR_PS7_DDR_0_S_AXI_BASEADDR   //0x00100000
#define MEM_BASE_ADDR       		(DDR_BASE_ADDR + 0x01000000)    //0x01100000
#define TX_BUFFER_BASE      		(MEM_BASE_ADDR + 0x00100000)    //0x01200000
#define RX_BUFFER_BASE      		(MEM_BASE_ADDR + 0x00300000)    //0x01400000

#define RESET_TIMEOUT_COUNTER   	10000    //复位时间
#define TEST_START_VALUE        	0x0      //测试起始值
#define AXIDMA_LENGTH             	0x2000   //DMA长度 = 8192Byte

/************************** Function Prototypes ******************************/

int  AxiDmaInit(XAxiDma* AxiDmaInstPtr);
int  SetupIntrSystem(XScuGic* IntcInstPtr, XAxiDma* AxiDmaInstPtr, u16 AxiDmaTxIntrId, u16 AxiDmaRxIntrId);
void AxiDmaTxIntrHandler(void* Callback);
void AxiDmaRxIntrHandler(void* Callback);
int  CheckData(u32* RxBuffer);

/************************** Variable Definitions *****************************/

XAxiDma AxiDmaInst;     //XAxiDma实例
XScuGic IntcInst;       //中断控制器的实例

int AxiDmaTxDone;      //发送完成标志
int AxiDmaRxDone;      //接收完成标志
int AxiDmaTxError;     //发送出错标志
int AxiDmaRxError;     //接收出错标志

/************************** Function Definitions *****************************/

 int main(void)
 {
	 //
     int  Status;
     u32  TestValue;
     u32* AxiDmaTxBufferPtr;
     u32* AxiDmaRxBufferPtr;

     //
     AxiDmaTxBufferPtr = (u32 *) TX_BUFFER_BASE;
     AxiDmaRxBufferPtr = (u32 *) RX_BUFFER_BASE;

     //
     printf("AXI DMA Read and Write Loop Test.\n");

     // 初始化AXIDMA
     Status = AxiDmaInit(&AxiDmaInst);
	 if (Status != XST_SUCCESS) {
	     printf("Error : axi dma initialization failed.\n");
	     return XST_FAILURE;
	 }

     // 设置中断系统
     Status = SetupIntrSystem(&IntcInst, &AxiDmaInst, AXIDMA_TX_INTR_ID, AXIDMA_RX_INTR_ID);
     if (Status != XST_SUCCESS) {
         printf("Error : axi dma setup interrupt system failed.\n");
         return XST_FAILURE;
     }

     // 初始化标志信号
     AxiDmaTxDone  = 0;
     AxiDmaRxDone  = 0;
     AxiDmaTxError = 0;
     AxiDmaRxError = 0;

     // 注意:在嵌入式系统中,程序直接运行在硬件上,内存地址由硬件设计确定,开发者可以直接访问这些地址,而不需要动态申请内存
     TestValue = TEST_START_VALUE;
     for (int i = 0; i < AXIDMA_LENGTH / sizeof(u32); i++) {
    	 AxiDmaTxBufferPtr[i] = TestValue;
    	 TestValue++;
     }

     // 在启动AXIDMA发送之前,将Data Cache中缓存的数据强制写回到DDR中去
     Xil_DCacheFlushRange((INTPTR)AxiDmaTxBufferPtr, AXIDMA_LENGTH);

     //
     char ch;
     while(1)
     {
    	 //
    	 printf("Enter 'T' to start axi dma tx :>\n");
    	 scanf(" %c", &ch);
    	 if (ch == 'T') {
    		 // AXIDMA读操作:DDR -> FPGA
    	     Status = XAxiDma_SimpleTransfer(&AxiDmaInst, (UINTPTR)AxiDmaTxBufferPtr, AXIDMA_LENGTH, XAXIDMA_DMA_TO_DEVICE);
    	     if (Status != XST_SUCCESS) {
    	    	 printf("Error : axi dma simple transfer tx failed.\n");
    	         return XST_FAILURE;
    	     }

    	     //
    	     while(!AxiDmaTxDone && !AxiDmaTxError);

    	     if (AxiDmaTxDone == 1) {
    	    	 printf("Axi dma tx done.\n");
    	    	 AxiDmaTxDone = 0;
    	     }
    	     else if (AxiDmaTxError == 1) {
    	    	 printf("Error : axi dma tx error.\n");
    	    	 AxiDmaTxError = 0;
    	     }
    	 }
    	 else {
    		 break;
    	 }

    	 //
    	 printf("Enter 'R' to start axi dma rx :>\n");
    	 scanf(" %c", &ch);
    	 if (ch == 'R') {
    		 //
    	     Status = XAxiDma_SimpleTransfer(&AxiDmaInst, (UINTPTR)AxiDmaRxBufferPtr, AXIDMA_LENGTH, XAXIDMA_DEVICE_TO_DMA);
    	     if (Status != XST_SUCCESS) {
    	    	 printf("Error : axi dma simple transfer rx failed.\n");
    	         return XST_FAILURE;
    	     }

    	     //
    	     while(!AxiDmaRxDone && !AxiDmaRxError);

    	     if (AxiDmaRxDone == 1) {
    	    	 printf("Axi dma rx done.\n");
    	    	 AxiDmaRxDone = 0;
    	     }
    	     else if (AxiDmaRxError == 1) {
    	    	 printf("Error : axi dma rx error.\n");
    	    	 AxiDmaRxError = 0;
    	     }

    	     // 在启动AXIDMA接收之后,使Data Cache中缓存的数据无效,直接从DDR中读取数据
    	     Xil_DCacheInvalidateRange((INTPTR)AxiDmaRxBufferPtr, AXIDMA_LENGTH);

    	     // 检查数据是否正确
    	     Status = CheckData(AxiDmaRxBufferPtr);
    	     if (Status != XST_SUCCESS) {
    	         printf("Error : axi dma rx data check failed.\n");
    	         return XST_FAILURE;
    	     }
    	     else {
    	    	 printf("Axi dma rx data check succeeded.\n");
    	     }
    	 }
    	 else {
    		 break;
    	 }
     }

     //
     return XST_SUCCESS;
 }

 /*****************************************************************************/

 int AxiDmaInit(XAxiDma* AxiDmaInstPtr)
 {
	 //
	 int Status;
	 XAxiDma_Config* AxiDmaConfigPtr;

	 // 初始化AXIDMA
	 AxiDmaConfigPtr = XAxiDma_LookupConfig(AXIDMA_DEVICE_ID);
	 if (AxiDmaConfigPtr == NULL) {
		 return XST_FAILURE;
	 }

	 Status = XAxiDma_CfgInitialize(AxiDmaInstPtr, AxiDmaConfigPtr);
	 if (Status != XST_SUCCESS) {
	     return XST_FAILURE;
	 }

     // 判断AXIDMA模式:S/G或Simple DMA
     if (XAxiDma_HasSg(AxiDmaInstPtr)) {
         printf("Axi dma configured as sg mode.\n");
         return XST_FAILURE;
     }

     // 使能AXIDMA中断
     XAxiDma_IntrEnable(AxiDmaInstPtr, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
     XAxiDma_IntrEnable(AxiDmaInstPtr, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);

	 //
	 return XST_SUCCESS;
 }

 /*****************************************************************************/

 int  SetupIntrSystem(XScuGic* IntcInstPtr, XAxiDma* AxiDmaInstPtr, u16 AxiDmaTxIntrId, u16 AxiDmaRxIntrId)
{
	//
    int Status;
    XScuGic_Config* IntcConfigPtr;

    // step1. 初始化中断控制器GIC
    IntcConfigPtr = XScuGic_LookupConfig(INTC_DEVICE_ID);
    if (IntcConfigPtr == NULL) {
    	return XST_FAILURE;
    }

    Status = XScuGic_CfgInitialize(IntcInstPtr, IntcConfigPtr, IntcConfigPtr->CpuBaseAddress);
    if (Status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    // step2. 在处理器中初始化异常处理功能
    Xil_ExceptionInit();

    // step3. 在处理器中为IRQ中断异常注册处理程序
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler,(void *) IntcInstPtr);

    // step4. 在处理器中使能IRQ中断异常
    Xil_ExceptionEnable();

    // step5. 在GIC中为外设注册中断处理程序
    Status = XScuGic_Connect(IntcInstPtr, AxiDmaTxIntrId, (Xil_InterruptHandler) AxiDmaTxIntrHandler, AxiDmaInstPtr);
    if (Status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    // step5. 在GIC中为外设注册中断处理程序
    Status = XScuGic_Connect(IntcInstPtr, AxiDmaRxIntrId, (Xil_InterruptHandler) AxiDmaRxIntrHandler, AxiDmaInstPtr);
    if (Status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    // step6. 在GIC中设置外设中断的优先级和触发类型
    XScuGic_SetPriorityTriggerType(IntcInstPtr, AxiDmaTxIntrId, 0xA0, 0x3); // Rising Edge
    XScuGic_SetPriorityTriggerType(IntcInstPtr, AxiDmaRxIntrId, 0xA0, 0x3); // Rising Edge

    // step7. 在GIC中使能外设中断
    XScuGic_Enable(IntcInstPtr, AxiDmaTxIntrId);
    XScuGic_Enable(IntcInstPtr, AxiDmaRxIntrId);

    //
    return XST_SUCCESS;
}

 /*****************************************************************************/

void AxiDmaTxIntrHandler(void *Callback)
{
	//
	XAxiDma* AxiDmaInstPtr = (XAxiDma*)Callback;
	u32 IsrStatus ;
    int AxiDmaResetTimeOut;

    // 获取中断状态
    IsrStatus = XAxiDma_IntrGetIrq(AxiDmaInstPtr, XAXIDMA_DMA_TO_DEVICE);

    // 清除中断状态
    XAxiDma_IntrAckIrq(AxiDmaInstPtr, IsrStatus, XAXIDMA_DMA_TO_DEVICE);

    // AXIDMA发送出错
    if ((IsrStatus & XAXIDMA_IRQ_ERROR_MASK)) {
    	AxiDmaTxError = 1;
        XAxiDma_Reset(AxiDmaInstPtr);
        AxiDmaResetTimeOut = RESET_TIMEOUT_COUNTER;
        while (AxiDmaResetTimeOut) {
            if (XAxiDma_ResetIsDone(AxiDmaInstPtr)) {
            	break;
            }
            AxiDmaResetTimeOut -= 1;
        }
        return;
    }

    //  AXIDMA发送完成
    if ((IsrStatus & XAXIDMA_IRQ_IOC_MASK)) {
    	AxiDmaTxDone = 1;
    }

    //
    return;
}

/*****************************************************************************/

void AxiDmaRxIntrHandler(void *Callback)
{
	//
	XAxiDma* AxiDmaInstPtr = (XAxiDma*)Callback;
	u32 IsrStatus ;
    int AxiDmaResetTimeOut;

    // 获取中断状态
    IsrStatus = XAxiDma_IntrGetIrq(AxiDmaInstPtr, XAXIDMA_DEVICE_TO_DMA);

    // 清除中断状态
    XAxiDma_IntrAckIrq(AxiDmaInstPtr, IsrStatus, XAXIDMA_DEVICE_TO_DMA);

    // AXIDMA接收出错
    if ((IsrStatus & XAXIDMA_IRQ_ERROR_MASK)) {
    	AxiDmaRxError = 1;
        XAxiDma_Reset(AxiDmaInstPtr);
        AxiDmaResetTimeOut = RESET_TIMEOUT_COUNTER;
        while (AxiDmaResetTimeOut) {
            if (XAxiDma_ResetIsDone(AxiDmaInstPtr)) {
                break;
            }
            AxiDmaResetTimeOut -= 1;
        }
        return;
    }

    // AXIDMA接收完成
    if ((IsrStatus & XAXIDMA_IRQ_IOC_MASK)) {
    	AxiDmaRxDone = 1;
    }

    return;
}

/*****************************************************************************/

int CheckData(u32* RxBuffer)
{
	//
    u32 TestValue;

    //
    TestValue = TEST_START_VALUE;

    //
    for (int i = 0; i < AXIDMA_LENGTH / sizeof(u32); i++) {
        if (RxBuffer[i] != TestValue) {
            return XST_FAILURE;
        }
        TestValue++;
    }

    //
    return XST_SUCCESS;
}

/*****************************************************************************/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值