赞
踩
好久没有认真的写一些技术博客了,工作半年了,最近两个月好像才慢慢的恢复过来了,不能摸鱼了,2020年,自己的生活中会有很多可见的变化,要快速成长啊,具备与之匹配的技术和能力啊。
由于项目的需要,利用一周的时间测试了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程序也写的挺简单的,直接上代码吧。
#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操作DDR3,CPU是不知道的,要用dcacheflush及时刷新。这个刷新速度很慢的,要注意控制刷新的长度。
关于中断,硬件工程师还是有点没完全弄懂啊,在这里有个提醒吧,可千万别配置完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的配置,然后开始发送数据。
(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等寄存器位,后面都有说明。
判断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了。
最后,贴上自己测速的结果。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。