zcu104 AXI DMA速度测试总结

一.前言

好久没有认真的写一些技术博客了,工作半年了,最近两个月好像才慢慢的恢复过来了,不能摸鱼了,2020年,自己的生活中会有很多可见的变化,要快速成长啊,具备与之匹配的技术和能力啊。

二.PL侧工程设置

由于项目的需要,利用一周的时间测试了zcu104开发板DMA的实际带宽。之前就用过AXI DMA做过图像处理方面的东西,还以为这次两天驾轻就熟,两天就能做好呢,结果细细的研究了一下,才发现还是有很多的坑的,学无止境啊,自己也记录一下。
工程block design的整体设计如下:
在这里插入图片描述 AXI DMA的配置如下:
在这里插入图片描述
DMA没有开启Scatter/Gather模式,只使用了DMA的简单模式,width of buffer length register 选择了24bits,因为本次测试,最大传输的数据是634*1600个,传输的数据数不能大于2^24。
zcu104使用的是zynq Ultrascale MPsoc的芯片,不同于zynq7000系列AXI_HP,AXI_GP,AXI_ACP的接口类型,AXI接口更加细化。选中与DMA相连的接口。
在这里插入图片描述
综合,布局布线,generate bitstream。

三.PS应用程序

PS程序也写的挺简单的,直接上代码吧。

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xaxidma.h"
#include "xparameters.h"
 #include "xscugic.h"
#include "Xtime_l.h"
#include "sleep.h"


#define DMA_DEV_ID		        XPAR_AXIDMA_0_DEVICE_ID
#define wt_INTR_ID		        XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define rd_INTR_ID		        XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
#define INTC                    XScuGic
#define INTC_DEVICE_ID          XPAR_SCUGIC_SINGLE_DEVICE_ID
#define DMA_0_BASE_ADDR         0x1000000
#define Trans_Len               (634*1600)*4  //这里要乘以4,因为一个32位的数据占用4个地址空间
#define CPU_COUNTS_PER_SECOND    COUNTS_PER_SECOND



static int SetupIntcSystem(INTC * IntcInstancePtr,
			   XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId);
static int TxIntrHandler(void *param);//read
static int RxIntrHandler(void *param);//write

XTime Tsetup_wr,Tend_wr,Tsetup_rd,Tend_rd;
static XAxiDma AxiDma;
u32 *wr_dma_ptr;
u32 *rd_dma_ptr;


int main()
{
//====================================
	XAxiDma_Config *config = NULL;
	int status;
	static INTC intc;

	wr_dma_ptr = (u32 *)DMA_0_BASE_ADDR;
	rd_dma_ptr = (u32 *)DMA_0_BASE_ADDR;

    printf("zcu104 DMA test begin!!!\n\r");

	config = XAxiDma_LookupConfig(DMA_DEV_ID);
    if(!config){
    	printf("ther is no DMA Dev found!!!\n\r");
    	return XST_FAILURE;
    }
    status = XAxiDma_CfgInitialize(&AxiDma,config);//config->AxiDma
    if(status != XST_SUCCESS){
    	printf("Initialization failed \n");
    	return XST_FAILURE;
    }
    if(XAxiDma_HasSg(&AxiDma)== TRUE){
    	printf("Device configured as SG mode \n");
    }
    else{
    	printf("Device configured is NOT SG mode \n");

    }
//设置 DMA的中断
   status = SetupIntcSystem(&intc,&AxiDma,rd_INTR_ID,wt_INTR_ID);
   if(status != XST_SUCCESS){
	   printf("DMA intc failed!!!\n");
	   return XST_FAILURE;
   }
   //=====================================================================test time
   XTime T1,T2;
   u32 T3;
	XTime_GetTime(&T1);
	usleep(1314);
	XTime_GetTime(&T2);
	T3 = ((T2-T1)*1000000)/CPU_COUNTS_PER_SECOND;
	 printf("elapsed time is %d us  \n\r",T3);
   //=======================================================================

	/* Disable all interrupts before setup */
   XAxiDma_IntrDisable(&AxiDma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DMA_TO_DEVICE);
   XAxiDma_IntrDisable(&AxiDma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
   /* Enable all interrupts */
   XAxiDma_IntrEnable(&AxiDma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DMA_TO_DEVICE);
   XAxiDma_IntrEnable(&AxiDma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
//----------------------------flush cathe before setup DMA trans
   XTime_GetTime(&Tsetup_wr);
   Xil_DCacheFlushRange((INTPTR)wr_dma_ptr,Trans_Len);
   status = XAxiDma_SimpleTransfer(&AxiDma,(INTPTR)wr_dma_ptr,Trans_Len,XAXIDMA_DEVICE_TO_DMA);
   if(status != XST_SUCCESS){
	   printf("setup data write to DMA failed!!!\n");
	   return XST_FAILURE;
   }
   else{
	   printf("setup data write to DMA success !!!\n");
   }

sleep(1);
//after trans data to DDR,start reading from DDR
  XTime_GetTime(&Tsetup_rd);
  status = XAxiDma_SimpleTransfer(&AxiDma,(INTPTR)rd_dma_ptr,Trans_Len,XAXIDMA_DMA_TO_DEVICE);
  if(status != XST_SUCCESS){
	   printf("setup read data from DMA failed!!!\n");
	   return XST_FAILURE;
  }
  else{
	   printf("setup read data from DMA success!!!\n");
  }
while(1){


};

    return 0;
}

static int SetupIntcSystem(INTC * IntcInstancePtr,
			   XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId){
	int Status;
	XScuGic_Config *IntcConfig;

		/*
		 * Initialize the interrupt controller driver so that it is ready to
		 * use.
		 */
		IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
		if (IntcConfig == NULL) {
			print("INTC device ID is wrong!!!\n");
			return XST_FAILURE;
		}

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


		XScuGic_SetPriorityTriggerType(IntcInstancePtr, RxIntrId, 0xA0, 0x3);
		XScuGic_SetPriorityTriggerType(IntcInstancePtr, TxIntrId, 0xA0, 0x3);//MM2S 03为上升沿触发中断
		/*
		 * Connect the device driver handler that will be called when an
		 * interrupt for the device occurs, the handler defined above performs
		 * the specific interrupt processing for the device.
		 */

		Status = XScuGic_Connect(IntcInstancePtr, RxIntrId,
					(Xil_InterruptHandler)RxIntrHandler,
					AxiDmaPtr);
		if (Status != XST_SUCCESS) {
			return Status;
		}
		Status = XScuGic_Connect(IntcInstancePtr, TxIntrId,
					(Xil_InterruptHandler)TxIntrHandler,
					AxiDmaPtr);
		if (Status != XST_SUCCESS) {
			return Status;
		}


		XScuGic_Enable(IntcInstancePtr, RxIntrId);
		XScuGic_Enable(IntcInstancePtr, TxIntrId);

		/* Enable interrupts from the hardware */

		Xil_ExceptionInit();
		Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
				(Xil_ExceptionHandler)XScuGic_InterruptHandler,
				(void *)IntcInstancePtr);

		Xil_ExceptionEnable();
		//====================================

		return XST_SUCCESS;
	}




static int RxIntrHandler(void *param){
	u32 Tuse;
	int Speed_wr_DMA;
	printf("into RxIntr process!!!\n");
	XTime_GetTime(&Tend_wr);
	Tuse = ((Tend_wr-Tsetup_wr)*1000000)/CPU_COUNTS_PER_SECOND;
    printf("wr data elapsed time is %d us \n",Tuse);
    Speed_wr_DMA = (634*1600*32/8/1024/1024*1000*1000)/Tuse;
    printf("data translate to DMA DDR is %d MB/s \n\r",Speed_wr_DMA);

//      XTime_GetTime(&Tsetup_rd);

    return XST_SUCCESS;
}

static int TxIntrHandler(void *param){
	u32 Tuse;
	int Speed_rd_DMA;
	printf("into TXIntr process!!!\n");
	XTime_GetTime(&Tend_rd);
	Tuse = ((Tend_rd-Tsetup_rd)*1000000)/CPU_COUNTS_PER_SECOND;
    printf("read data elapsed time is %d us \n",Tuse);
    Speed_rd_DMA = (634*1600*32/8/1024/1024*1000*1000)/Tuse;
    printf("read data from DMA DDR is %d MB/s \n\r",Speed_rd_DMA);


    //    printf("read data from DMA DDR is %d  \n\r",Tsetup_rd);
//    printf("read data from DMA DDR is %d  \n\r",Tend_rd);
    return XST_SUCCESS;
}

关于cache一致性,CPU在访问内存时,首先判断所要访问的内容是否在Cache中,如果在,就称为“命中(hit)”,此时CPU直接从Cache中调用该内容;否则,就 称为“ 不命中”,CPU只好去内存中调用所需的子程序或指令了。CPU不但可以直接从Cache中读出内容,也可以直接往其中写入内容。由于Cache的存取速 率相当快,使得CPU的利用率大大提高,进而使整个系统的性能得以提升。
Cache的一致性就是直Cache中的数据,与对应的内存中的数据是一致的。DMA是直接操作总线地址的,这里先当作物理地址来看待吧(系统总线地址和物理地址只是观察内存的角度不同)。如果cache缓存的内存区域不包括DMA分配到的区域,那么就没有一致性的问题。但是如果cache缓存包括了DMA目的地址的话,会出现什么什么问题呢?
问题出在,经过DMA操作,cache缓存对应的内存数据已经被修改了,而CPU本身不知道(DMA传输是不通过CPU的),它仍然认为cache中的数 据就是内存中的数据,以后访问Cache映射的内存时,它仍然使用旧的Cache数据。这样就发生Cache与内存的数据“不一致性”错误。所以,在使用DMA写入内存中的一段区域的时候,应该告诉CPU这段区域不能的cache无效了,也就是把cache中的数据送还到内存中,使用的函数是
:

   Xil_DCacheFlushRange((INTPTR)wr_dma_ptr,Trans_Len);

关于中断,硬件工程师还是有点没完全弄懂啊,在这里有个提醒吧,可千万别配置完DMA传输就直接return 0了,return 0之后就再也不会进去中断了TT

四. 注意
配置完成AXI DMA开始进行测试的时候,发现DMA的时序和我想象中的有些不一样。
(1)关于DMA的tready信号
之前一直脑补的是,在PS配置完DMA IP核的相关寄存器后,tready信号才拉高,数据传输开始进行。但是用逻辑分析仪抓取信号之后,发现不是这样的。
在这里插入图片描述
上图是上电后,PS进行debug,烧写bit文件之后的相关时序。注意,还没有开始执行main.cpp程序。刚烧写进去,还没有DMA进行配置,由上面数据的计数可知,tready已经有了4个clock的高电平了。但是这4个数据并没有被读入DDR中,而是在配置DMA传输中丢失了。个人分析原因,是因为PL端的配置速度比PS端快的多,当烧写工程文件进行整个系统的初始化时,PL很快就完成了初始化配置,但是PS较慢。
在这里插入图片描述
在PS对DMA进行配置后,还会有4个clock 的tready高电平信号,此时这4个数据可以被正确地写入到DDR中去。发送数据tlast后,也会有额外的4个clock 的tready高电平信号,也可以被正确的写入,所以理论上应该每次发送时,都会有4个数据被提前写入。
在这里插入图片描述
解决 AXI DMA发送丢失4个数据的方式,可以通过s2mm_introut或者PS驱动GPIO的方式,告诉上一级数据源,PS已经完成对AXI DMA的配置,然后开始发送数据。
在这里插入图片描述
最后,贴上自己测速的结果。

发布了23 篇原创文章 · 获赞 34 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/alangaixiaoxiao/article/details/103958007