ZYNQ的AXI_DMA的使用
AXIDMA:官方解释是为内存与AXI4-Stream外设之间提供高带宽的直接存储访问,其可选的scatter/gather功能可以将CPU从数据搬移任务中解放出来,在ZYNQ中,AXIDMA就是FPGA访问DDR3的桥梁,不过该过程受ARM的监控和管理。使用其他的IP(也是AXI4-Stream转AXI4-MM)可以不需要ARM管理。AXIDMA IP有6个接口,S_AXI_LITE是ARM配置DMA寄存器的接口,M_AXI_SG是从存储器加载buffer descriptor的接口,剩下4个接口构成两对,S2MM和MM2S表示数据的方向,AXI存储器一侧的接口,AXIS是FPGA一侧的接口。AXIDMA IP和ARM自带的DMA是很像的,只不过不具备从存储器到存储器的功能,当然也可以将S2MM和MM2S接口与AXIS接口直接相连,其结构如图1所示。
图1,AXI_DMA的IP结构
AXIDMA工作模式可以分为两种,Direct Register Mode和Scatter/Gather Mode。
Direct Register Mode具备DMA的基本功能,除了控制寄存器和状态寄存器之外,给出目的地址和传输长度之后就可以开启一次传输了。但是Direct Register Mode模式配置完一次寄存器之后只能完成存储器连续地址空间的读写,如果有需求往不同空间搬运数据的话,那就需要重新配置寄存器开启一次新的传输。
Scatter/Gather Mode配置灵活,其工作模式复杂很多。Scatter/Gather Mode把关于传输的基本参数(起始地址,传输长度,包信息等)存储在存储器中,一套参数称之为Buffer Descriptor(BD),在工作过程中通过上面提到的SG接口来加载BD且更新BD中的状态。Scatter/Gather Mode下的寄存器没有Address、Length相关寄存器了,取而代之的是CURDESC、TAILDESC。非多通道模式下的BD,主要有四部分内容:NXTDESC、BUFFER_ADDRESS、CONTROL、STATUS。NXTDESC指定下一个BD的地址,由此可以构成一个BD链条,AXIDMA可以顺着该链条依次fetch BD,BUFFER_ADDRESS指定传输的源地址或目的地址,CONTROL主要是length和包信息,STATUS反映该BD完成后的状态。AXIDMA启动后,首先从CURDESC指定的位置加载BD,完成当前BD的传输任务后根据BD链条找到下一个BD,依次完成BD指定的传输,知道遇到TALDESC指定的BD才停止。
Multichannel模式:在Scatter/Gather Mode下S2MM和MM2S都支持多个通道,Direct Register Mode不支持多通道,多通道相比非多通道,BD中增加了TID和TDEST,用来区分不同的通道。多通道支持2D-Transfer,从buffer address开始,读写HSIZE后跳过剩余的Stride – HSIZE个地址单元,下一次从buffer address + Stride位置开始,此过程迭代VSIZE此后结束该BD指定的传输。在Multichannel模式下S2MM有16个通道,每个通道都有独立的CURDESC和TAILDESC寄存器,而CR和SR则是共用的。而MM2S的多个通道共用一个CURDESC和TAILDESC寄存器,MM2S端只能等当前包传输完成才能开始下一次的传输,可能这与CPU不太容易同时操纵多个通道的数据包发送有关系。所以在实际使用时只能先执行一个通道的发送任务再执行另一个通道的发送任务。
Cyclic DMA:循环模式是在Scatter/Gather Mode模式下的一种独特工作方式,在Multichannel Mode下不可用。正常情况下的Scatter/Gather Mode模式在遇到Tail BD就应该结束当前的传输,但是如果使能了Cyclic模式的话,在遇到Tail BD时会忽略completed位,并且回到First BD,这一过程会一直持续直到遇到错误或者人为终止。Cyclic模式只需要在开启传输前设置好BD链条,工作之后就再不需要管了。
Data Cache:在zynq内部AMR CPU和DDR3之间存在两级缓存区,分别是L1 I/D Cache和L2 Cache,它们都是32-byte line size。Data Cache的使用带来了一个问题,DMA和CPU都与DDR3有数据往来,可CPU的Cache是不知道DMA对DDR3的数据读写过程的,也就是说CPU得到的数据很可能是“假的”,这就是Cache一致性问题。解决该问题的办法是在程序中使用flush函数(invalid函数)及时将Cache的数据写入到DDR3(从DDR3读取数据到Cache),也就是说要避免该问题就需要注意编码时加上flush函数。
AXI_DMA实验,设计一个最基本的DMA环路,实现DMA的环路测试,在通过PS端发送数据到DMA然后DMA在把数据发回到DDR里面,PS读取内存地址里面的数据,对比接收的数据是否和发送出去的一致。DMA的接口部分使用了data_fifo的IP链接,其数据流如图2所示。
程序设计:
PL端设计:
建立好工程后,创建一个block,然后在其中添加zynq的IP,AXI4-Stream Data FIFO的IP,AXI Direct Memory Acceess的IP,以及Concat的IP。如图3所示。然后根据需求配置好各个IP的参数。
zynq的IP的参数配置如图4所示,在配置了ZYNQ的基本最小系统后,增加一些配置,由于这个系统需要PS提供时钟给AXI_DMA,所以在Clock Configuration选项卡中配置了PL Fabric clocks->FCLK_CLK0为100M,启动1路HP接口,HP接口是ZYNQ个高速数据接口,所以在PS-PL Configuration选项卡选项中配置了一路HP Slave AXI Interface->S AXI HP0 interface,需要用到PL的中断资源,所以在Interrupts选项卡中增加了PL中断,Fabric Interrupts->PL-PS Interrupt Port->IRQ_F2P[15:0]。
图3 添加IP
图4 ZYNQ的配置
AXI Direct Memory Access IP的配置如图5所示,同时勾选读通道和写通道,另外设置,Wideh of buffer length register 为23bit 这个含义是2的23次方8,388,607bytes8M大小,这里设置14bit 就够用了,长度越大需要的资源也就越多
图5 AXI Direct Memory Access的配置
AXI4-Stream Data FIFO的IP的配置如图6所示。
图6 AXI4-Stream Data FIFO的配置
Concat的IP的配置如图7所示。
图7 Concat的IP配置
连接好物理连线,最终生成硬件版图如图8所示。
图8 硬件配置
PS端设计:
由于是一个回环设计,所以在实现DMA传输数据时,需要用到接收中断和发送中断,所以外设初始化配置和测试流程如下:
步骤1函数源码:
int Xaxi_dma_init(void)
{
int Status = 0;
XAxiDma_Config* XaxidmaConfigInit = NULL;
//1,find device
XaxidmaConfigInit = XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID);
//2,config Xaxidma
Status = XAxiDma_CfgInitialize(&XAxi_Dma,XaxidmaConfigInit);
if (Status != XST_SUCCESS)
{
xil_printf("Initialization failed %d\r\n", Status);
return XST_FAILURE;
}
//3,axidma mode is sg
if(XAxiDma_HasSg(&XAxi_Dma))
{
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
return XST_SUCCESS;
}
步骤2函数源码:
void Exception_Init(XScuGic* Xscugic)
{
//1,connect to hardware intc
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,(void *)Xscugic);
//2,enable hardware intc
Xil_ExceptionEnable();
Xil_ExceptionDisableMask(XIL_EXCEPTION_ID_IRQ_INT);
}
步骤3函数源码:
void Axidma_Gic_Init(XScuGic* Xscugic,XAxiDma *AxiDmaPtr,u16 GicID,u16 TxIntrId,u16 RxIntrId)
{
XScuGic_Config *GicConfigInit;
//1,find device
GicConfigInit = XScuGic_LookupConfig(GicID);
//2,config gicconfig
XScuGic_CfgInitialize(Xscugic,GicConfigInit,GicConfigInit->CpuBaseAddress);
//3,gic selftest
XScuGic_SelfTest(Xscugic);
//4,gic connect to axidma
XScuGic_Connect(Xscugic, TxIntrId,(Xil_InterruptHandler)Dma_TxHandler,(void *)AxiDmaPtr);
XScuGic_Connect(Xscugic, RxIntrId,(Xil_InterruptHandler)Dma_RxHandler,(void *)AxiDmaPtr);
//5,set gic interrupt Priority and type
XScuGic_SetPriorityTriggerType(Xscugic,TxIntrId,0xA0, 0x03);
XScuGic_SetPriorityTriggerType(Xscugic,RxIntrId,0xA0, 0x03);
//6,enable gic interrupt
XScuGic_Enable(Xscugic,TxIntrId);
XScuGic_Enable(Xscugic,RxIntrId);
}
步骤4函数源码:
void Dma_RxHandler(void* CallBackRef)
{
u32 irqstatus = 0;
int TimeOut = RESET_TIMEOUT_COUNTER;
XAxiDma *XaxidmaPter = (XAxiDma*)CallBackRef;
//1,Read pending interrupts /XAXIDMA_DEVICE_TO_DMA
irqstatus = XAxiDma_IntrGetIrq(XaxidmaPter,XAXIDMA_DEVICE_TO_DMA);
//2,Acknowledge pending interrupts
XAxiDma_IntrAckIrq(XaxidmaPter,irqstatus,XAXIDMA_DEVICE_TO_DMA);
//3,If no interrupt is asserted, we do not do anything
if (!(irqstatus & XAXIDMA_IRQ_ALL_MASK))
{
return;
}
//4,If error interrupt is asserted, raise error flag, reset the hardware to recover from the error, and return with no further processing.
if(irqstatus & XAXIDMA_IRQ_ERROR_MASK)
{
//1,flag = 1
Error = 1;
//2,Reset should never fail for transmit channel
XAxiDma_Reset(XaxidmaPter);
//3,wait time out
TimeOut = RESET_TIMEOUT_COUNTER;
while(TimeOut)
{
if (XAxiDma_ResetIsDone(XaxidmaPter))
{
break;
}
TimeOut--;
}
return;
}
//5,If Completion interrupt is asserted, then set the TxDone flag
if(irqstatus & XAXIDMA_IRQ_IOC_MASK)
{
RxDone = 1;
}
}
void Dma_TxHandler(void* CallBackRef)
{
u32 irqstatus = 0;
int TimeOut = RESET_TIMEOUT_COUNTER;
XAxiDma *XaxidmaPter = (XAxiDma*)CallBackRef;
//1,Read pending interrupts /XAXIDMA_DMA_TO_DEVICE
irqstatus = XAxiDma_IntrGetIrq(XaxidmaPter,XAXIDMA_DMA_TO_DEVICE);
//2,Acknowledge pending interrupts
XAxiDma_IntrAckIrq(XaxidmaPter,irqstatus,XAXIDMA_DMA_TO_DEVICE);
//3,If no interrupt is asserted, we do not do anything
if (!(irqstatus & XAXIDMA_IRQ_ALL_MASK))
{
return;
}
//4,If error interrupt is asserted, raise error flag, reset the hardware to recover from the error, and return with no further processing.
if(irqstatus & XAXIDMA_IRQ_ERROR_MASK)
{
//1,flag = 1
Error = 1;
//2,Reset should never fail for transmit channel
XAxiDma_Reset(XaxidmaPter);
//3,wait time out
TimeOut = RESET_TIMEOUT_COUNTER;
while(TimeOut)
{
if (XAxiDma_ResetIsDone(XaxidmaPter))
{
break;
}
TimeOut--;
}
return;
}
//5,If Completion interrupt is asserted, then set the TxDone flag
if(irqstatus & XAXIDMA_IRQ_IOC_MASK)
{
TxDone = 1;
}
}
步骤5函数源码:
int axi_dma_test()
{
int Status;
TxDone = 0;
RxDone = 0;
Error = 0;
xil_printf("\r\n----DMA Test----\r\n");
xil_printf("PKT_LEN=%d\r\n",MAX_PKT_LEN);
for(i = 0; i < Tries; i ++)
{
LED2_ON;
Value = TEST_START_VALUE + (i & 0xFF);
for(Index = 0; Index < MAX_PKT_LEN; Index ++)
{
TxBufferPtr[Index] = Value;
Value = (Value + 1) & 0xFF;
}
/* Flush the SrcBuffer before the DMA transfer, in case the Data Cache
* is enabled
*/
Xil_DCacheFlushRange((u32)TxBufferPtr, MAX_PKT_LEN);
Status = XAxiDma_SimpleTransfer(&XAxi_Dma,(u32) RxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Status = XAxiDma_SimpleTransfer(&XAxi_Dma,(u32) TxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
* Wait TX done and RX done
*/
while (!TxDone || !RxDone) {
/* NOP */
}
success++;
TxDone = 0;
RxDone = 0;
if (Error) {
xil_printf("Failed test transmit%s done, "
"receive%s done\r\n", TxDone? "":" not",
RxDone? "":" not");
goto Done;
}
/*
* Test finished, check data
*/
Status = DMA_CheckData(MAX_PKT_LEN, (TEST_START_VALUE + (i & 0xFF)));
if (Status != XST_SUCCESS) {
xil_printf("Data check failed\r\n");
goto Done;
}
LED2_OFF;
}
xil_printf("AXI DMA interrupt example test passed\r\n");
xil_printf("success=%d\r\n",success);
LED1_ON;
/* Disable TX and RX Ring interrupts and return success */
// DMA_DisableIntrSystem(&Xgic, TX_INTC_ID, RX_INTC_ID);
Done:
xil_printf("--- Exiting Test --- \r\n");
return XST_SUCCESS;
}