一.前言
好久没有认真的写一些技术博客了,工作半年了,最近两个月好像才慢慢的恢复过来了,不能摸鱼了,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的配置,然后开始发送数据。
最后,贴上自己测速的结果。