zcu104 AXI DMA速度测试总结

一.前言

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

二.PL侧工程设置

由于项目的需要,利用一周的时间测试了zcu104开发板DMA的实际带宽。之前就用过AXI DMA做过图像处理方面的东西,还以为这次两天驾轻就熟,两天就能做好呢,结果细细的研究了一下,才发现还是有很多的坑的,学无止境啊,自己也记录一下。
工程block design的整体设计如下:
zcu104 AXI DMA速度测试总结_第1张图片 AXI DMA的配置如下:
zcu104 AXI DMA速度测试总结_第2张图片
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相连的接口。
zcu104 AXI DMA速度测试总结_第3张图片
综合,布局布线,generate bitstream。

三.PS应用程序

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

#include 
#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操作DDR3,CPU是不知道的,要用dcacheflush及时刷新。这个刷新速度很慢的,要注意控制刷新的长度。

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

四. 注意事项

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

(2)读写数据的实际数据长度
在每次DMA IP进行数据传输之前,都需要配置一次DMA相关的寄存器,告诉本次传输的起始地址、数据长度和方向。在MM2S通道,配置要读取的数据长度,很好理解。DMA 需要根据用户配置的数据长度来产生tlast信号。但是在S2MM通道,配置要写入内存的数据长度,在用户看来,其实并不太必要,因为一方面,每一帧传输的数据量可能并不固定,在传输之前未知;另一方面,DMA IP核完全可以根据PL端送来的tlast信号来判断数据传输完成。所以,DMA S2MM通道配置的数据长度,其实并不一定是本次传输的准确值,只需要大于等于本次数据量,DMA根据tlast信号判断本帧数据是否结束,完成本数据传输后,DMA会将本次传输真实的数据量写入数据长度配置寄存器S2MM_LENGTH中。

详细的说明如下:

Xilinx的例程代码向来都是异常复杂,调到最后不免都要去看寄存器,翻阅手册我们看到DMA简单模式如何使用。要注意,MM2S是说 memory map to stream,S2MM是stream to memory map,memory map当然是指DDR3内存了,根据名称我们就知道MM2S是说DMA把数据从DDR3搬移到stream FIFO中。 从下面手册中我们看到了寄存器偏移地址从00h-28h 是mm2s的,30h-58h是s2mm的。30h是CR寄存器,有RS,reset等寄存器位,后面都有说明。


zcu104 AXI DMA速度测试总结_第7张图片 zcu104 AXI DMA速度测试总结_第8张图片 zcu104 AXI DMA速度测试总结_第9张图片 zcu104 AXI DMA速度测试总结_第10张图片 zcu104 AXI DMA速度测试总结_第11张图片

判断DMA发送的SR寄存器idle是否为1,为1则idle,说明发送结束。

while (XAxiDma_Busy(&AxiDma,XAXIDMA_DMA_TO_DEVICE))

a.不使能scatter gather和miro DMA,使用简单模式,前者可以配置多个地址,一次开启多次传输。width of buffer length register这个是指接收或发送的最长长度,14位最大就是16384,实际上在函数中判断是16383,该参数设置过长, XAxiDma_SimpleTransfer函数就会返回一个错误。

b.address width是32位,就是对应到DDR3上,一次操作是32bit。read channel 和write channel很不好记,要理解DMA工作时是独立于CPU的,DMA读写都是指对DDR3的操作,由于我的应用是从底层产生数据存到FIFO中,等DMA来读取,因此只需要开启写DDR3通道即可。

c.max burst size,突发传输长度。突发的意思,就是传送一次地址,取多个数据的长度。这一点不好理解:当CPU通过AXI lite写DMA寄存器开启DMA接收后,DMA的tready拉高,开始传输数据,每接收到突发长度后,拉低一次tready,把数据写到DDR3中,写完之后再拉高再接收数据。

d.allow unaligned transfers。地址对齐,这个很重要,发送和接收通道都有,不开启,你发送和接收都必须要从4byte对齐的位置开始,必须从0x00,0x04,0x08等位置发送或接收,否则DMA不会正常工作。

我们再回去看接收的编程说明。

a.写S2MM_DMACR.RS =1,然后DMASR.Halted = 0 ,DMA表示在运行了。

b.写目的地地址到DA寄存器中。

c.写LENGTH寄存器,必须要最后写。写完这个寄存器后,DMA就开始传输了。传输完毕后,该寄存器的值是实际接收到的字节数。

d.判断DMASR.idle = 1,后表示接收结束。

DMA何时结束?
DMA的读写接口都是axis stream,是强制有tlast的,就是靠tlast信号来判断接收结束的,而不是填写的length值。发送接收值,最后一个tdata会同时伴随一个tlast高电平。接收时,设置接收5000字节,并不是说接收满5000个字节就结束了,而是开启接收后,接收到1个tlast高电平就结束了,也就是说,你可能会只接收到2000个字节,手册中明确说了,当设置的length = 0,或者接收到的数据大于设置的length,就会引起DMAIntErr置1,接收错误。

所以,当设置每次接收5000字节时,tlast就应该每隔<5000字节时插入。我的应用中,DMA前是一个AXIS DATA FIFO,写入是自定义的时序,但接收时还是报错,接收得太多了。为什么呢?因为DMA是自己拉高tready,而fifo的valid也为高就开始计数了,跟FIFO随后的valid 无关(我瞎猜的),DMA读的速度远高于我写入速度,我还没写到tlast,DMA就计数到16383了,DMA就报错,怎么办呢,开启fifo的packet模式。然后DMA接收就ok了。

zcu104 AXI DMA速度测试总结_第12张图片
最后,贴上自己测速的结果。

你可能感兴趣的:(Zynq,SOC)