天天看點

ZYNQ基礎---AXI DMA使用前言1. AXI DMA的基本接口2 Block design搭建3. vitis測試結果

前言

  在ZYNQ中進行PL-PS資料互動的時候,經常會使用到DMA,其實在前面的ZYNQ學習當中,也有學習過DMA的使用,那就是通過使用自定義的IP,完成HP接口向記憶體寫入和讀取資料的方式。同樣Xilinx官方也提供有一些DMA的IP,通過調用API函數能夠更加靈活地使用DMA。

1. AXI DMA的基本接口

  axi dma IP的基本結構如下,主要分為三個部分,分别是控制axi dma寄存器通道,從ddr讀出資料通道和向ddr寫入資料通道。其IP結構的兩邊分别對應着用于通路記憶體的AXI總線和用于使用者簡單操作的axis stream總線。axi stream總線相較于axi總線來說要簡單很多,它沒有位址,靠主機和從機之間進行握手來傳遞資料。

ZYNQ基礎---AXI DMA使用前言1. AXI DMA的基本接口2 Block design搭建3. vitis測試結果
ZYNQ基礎---AXI DMA使用前言1. AXI DMA的基本接口2 Block design搭建3. vitis測試結果

2 Block design搭建

  做一個簡單的例子來測試一下axi dma。先自定義一個IP,用于緩存從zynq通過axi dma發來的資料,一次突發傳輸結束之後,将接收到的資料寫回到記憶體中。

2.1 自定義一個IP

  時序設計如下,将接收到的資料緩存到FIFO中,當zynq一次axi stream 傳輸結束的時候,開始将資料從FIFO中讀出,并将資料寫入到記憶體中。

ZYNQ基礎---AXI DMA使用前言1. AXI DMA的基本接口2 Block design搭建3. vitis測試結果
module dma_loop(
    //====================================================
    //clock and reset
    //====================================================
    input   wire            axis_clk            ,
    input   wire            rst_n               ,
    //====================================================
    //input axis port
    //====================================================
    input   wire    [7:0]   axis_in_tdata       ,
    input   wire            axis_in_tvalid      ,
    output  wire            axis_in_tready      ,
    input   wire            axis_in_tlast       ,

    //====================================================
    //output axis port
    //====================================================
    output  wire    [7:0]   axis_out_tdata      ,
    output  reg             axis_out_tvalid     ,
    input   wire            axis_out_tready     ,
    output  wire            axis_out_tlast      
);

//====================================================
//input axis port
//====================================================
wire            wr_fifo_en      ;
wire            rd_fifo_en      ;
wire            full,empty      ;
reg             rd_start        ;
reg     [9:0]   cnt_data_in     ;
wire            add_cnt_data_in ;
wire            end_cnt_data_in ;
reg     [9:0]   data_len        ;

reg     [9:0]   cnt_data_out    ;
wire            add_cnt_data_out;
wire            end_cnt_data_out;

assign wr_fifo_en = axis_in_tvalid & axis_in_tready;
assign axis_in_tready = ~full;

always @(posedge axis_clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        rd_start <= 1'b0;
    end
    else if (axis_in_tvalid & axis_in_tready & axis_in_tlast) begin
        rd_start <= 1'b1;
    end
    else begin
        rd_start <= 1'b0;
    end
end
//----------------cnt_data------------------
always @(posedge axis_clk or negedge rst_n) begin
    if (rst_n == 1'b0) begin
        cnt_data_in <= 'd0;
    end
    else if (add_cnt_data_in) begin
        if(end_cnt_data_in)
            cnt_data_in <= 'd0;
        else
            cnt_data_in <= cnt_data_in + 1'b1;
    end

end

assign add_cnt_data_in = axis_in_tvalid & axis_in_tready;
assign end_cnt_data_in = add_cnt_data_in && axis_in_tlast;

//----------------data_len------------------
always @(posedge axis_clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        data_len <= 'd0;
    end
    else if (end_cnt_data_in) begin
        data_len <= cnt_data_in + 1'b1;
    end
end

sfifo_wr1024x8_rd1024x8 inst_sfifo (
    .clk(axis_clk),         // input wire clk
    .din(axis_in_tdata),    // input wire [7 : 0] din
    .wr_en(wr_fifo_en),     // input wire wr_en
    .rd_en(rd_fifo_en),     // input wire rd_en
    .dout(axis_out_tdata),  // output wire [7 : 0] dout
    .full(full),            // output wire full
    .empty(empty)           // output wire empty
);

//----------------axis_out_tvalid------------------
always @(posedge axis_clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        axis_out_tvalid <= 1'b0;
    end
    else if (end_cnt_data_out) begin
        axis_out_tvalid <= 1'b0;
    end
    else if (rd_start == 1'b1) begin
        axis_out_tvalid <= 1'b1;
    end
end

//----------------cnt_data_out------------------
always @(posedge axis_clk or negedge rst_n) begin
    if (rst_n == 1'b0) begin
        cnt_data_out <= 'd0;
    end
    else if (add_cnt_data_out) begin
        if(end_cnt_data_out)
            cnt_data_out <= 'd0;
        else
            cnt_data_out <= cnt_data_out + 1'b1;
    end
end

assign add_cnt_data_out = axis_out_tvalid & axis_out_tready;
assign end_cnt_data_out = add_cnt_data_out && cnt_data_out == data_len - 1;

assign axis_out_tlast =  end_cnt_data_out;
assign rd_fifo_en = axis_out_tvalid & axis_out_tready;


wire [63:0] probe0;

assign probe0 = {

    cnt_data_in         ,
    cnt_data_out        ,
    rd_start            ,
    data_len            ,
    axis_in_tdata       ,
    axis_in_tvalid      ,
    axis_in_tready      ,
    axis_in_tlast       ,

    axis_out_tdata      ,
    axis_out_tvalid     ,
    axis_out_tready     ,
    axis_out_tlast        
};

ila_0 inst_ila (
	.clk(axis_clk), // input wire clk


	.probe0(probe0) // input wire [63:0] probe0
);

endmodule  
           

搭建block design

ZYNQ基礎---AXI DMA使用前言1. AXI DMA的基本接口2 Block design搭建3. vitis測試結果

3. vitis

   xilinx的東西就有這點優點,基本上fpga裡面有的資源,都有一個示例工程,把它導入進來就可以參考一下具體這個外設改怎麼來使用了。這個demo裡面使用到了序列槽。序列槽在前面的序列槽中斷中已經使用到,本次實驗需要使用到序列槽的中斷和DMA的中斷。

  PC的序列槽向FPGA發送資料,一幀資料發送完成之後,會觸發序列槽的中斷。序列槽中斷函數會接收資料,并且記錄資料長度。然後将通過DMA将資料寫入到自定義的IP當中,自定義IP接收完資料之後,将把資料寫回到記憶體中。

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

#define GIC_DEVICE_ID	XPAR_PS7_SCUGIC_0_DEVICE_ID
#define UART_DEV_ID 	XPAR_PS7_UART_1_DEVICE_ID
#define DMA_DEVICE_ID 	XPAR_AXI_DMA_0_DEVICE_ID

#define UART_INTR_ID 	XPAR_PS7_UART_1_INTR
#define MM2S_INTR_ID 	XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
#define S2MM_INTR_ID 	XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID

XUartPs UartInst;
XScuGic GicInst;
XAxiDma DmaInst;

volatile int TxDone;
volatile int RxDone;
volatile int Error;

u32 UartRxLen = 0;
int UartRxFlag = 0;
u8 * UartRxBuf = (u8 *)(0x2000000);
u8 * UartTxBuf = (u8 *)(0x4000000);
/***********************************
 * Uart Init function
 **********************************/
int Uart_Init();
/********************************
 * DMA initialize function
 ********************************/
int Dma_Init();

int Setup_Interrupt_System();

void Uart_Intr_Handler(void *CallBackRef, u32 Event, unsigned int EventData);

void Mm2s_Intr_Handler();
void S2mm_Intr_Handler();

int main()
{
    init_platform();
    Uart_Init();
    Dma_Init();
    Setup_Interrupt_System();
    while(1)
    {
    	/*****************************************************************************
		* Uart has receive one frame
		*****************************************************************************/
    	if (UartRxFlag == 1) {
    		// clear the flag
			UartRxFlag = 0;

			/*****************************************************************************
			* Transfer data from axidma to device
			*****************************************************************************/
			Xil_DCacheFlushRange((INTPTR)UartRxBuf, UartRxLen);//flush data into ddr
			usleep(2);
			// transfer data from axi dma to device
			XAxiDma_SimpleTransfer(&DmaInst, (UINTPTR)UartRxBuf, UartRxLen, XAXIDMA_DMA_TO_DEVICE);
			while(!TxDone);
			TxDone=0;//reset txdone flag; complete  txtransfer

			/*****************************************************************************
			* Transfer data from device to dma
			*****************************************************************************/
			Xil_DCacheInvalidateRange((INTPTR)UartTxBuf, UartRxLen);
			usleep(2);
			XAxiDma_SimpleTransfer(&DmaInst, (UINTPTR)UartTxBuf, UartRxLen, XAXIDMA_DEVICE_TO_DMA);
			while(!RxDone);
			RxDone = 0;
			XUartPs_Send(&UartInst, UartTxBuf, UartRxLen);

			XUartPs_Recv(&UartInst, UartRxBuf, 4096);//reset conter and start recv from uart
		}
    }

    cleanup_platform();
    return 0;
}

/*****************************************************************************
 * @ function : init uart and set the callback fuction
*****************************************************************************/
int Uart_Init()
{
	int Status;
	u32 IntrMask;

	XUartPs_Config *UartCfgPtr;
	UartCfgPtr = XUartPs_LookupConfig(UART_DEV_ID);
	Status = XUartPs_CfgInitialize(&UartInst, UartCfgPtr, UartCfgPtr->BaseAddress);
	if(Status != XST_SUCCESS)
	{
		printf("initialize UART failed\n");
		return XST_FAILURE;
	}
	/****************************************
	 * Set uart interrput mask
	 ****************************************/
	IntrMask =
		XUARTPS_IXR_TOUT | XUARTPS_IXR_PARITY | XUARTPS_IXR_FRAMING |
		XUARTPS_IXR_OVER | XUARTPS_IXR_TXEMPTY | XUARTPS_IXR_RXFULL |
		XUARTPS_IXR_RXOVR;
	XUartPs_SetInterruptMask(&UartInst, IntrMask);

	/*****************************************************************************
	* Set Uart interrput callback function
	*****************************************************************************/
	XUartPs_SetHandler(&UartInst, (XUartPs_Handler)Uart_Intr_Handler, &UartInst);

	/*****************************************************************************
	* Set Uart baud rate
	*****************************************************************************/
	XUartPs_SetBaudRate(&UartInst, 115200);

	/*****************************************************************************
	* Set Uart opertion mode
	*****************************************************************************/
	XUartPs_SetOperMode(&UartInst, XUARTPS_OPER_MODE_NORMAL);

	/*****************************************************************************
	* Set Uart Receive timeout
	*****************************************************************************/
	XUartPs_SetRecvTimeout(&UartInst, 8);

	/*****************************************************************************
	* Start to listen
	*****************************************************************************/
	XUartPs_Recv(&UartInst, UartRxBuf, 4096);
	return Status;
}

void Uart_Intr_Handler(void *CallBackRef, u32 Event, unsigned int EventData)
{
	if (Event == XUARTPS_EVENT_RECV_TOUT) {
		if(EventData == 0)
		{
			XUartPs_Recv(&UartInst, UartRxBuf, 4096);
		}
		else if(EventData > 0) {
			UartRxLen = EventData;
			UartRxFlag = 1;
		}
	}
}

/*****************************************************************************
 * @ function : init Axi DMA
*****************************************************************************/
int Dma_Init()
{
	int Status;
	XAxiDma_Config * DmaCfgPtr;
	DmaCfgPtr = XAxiDma_LookupConfig(DMA_DEVICE_ID);
	Status = XAxiDma_CfgInitialize(&DmaInst, DmaCfgPtr);
	if(Status != XST_SUCCESS)
	{
		printf("initialize AXI DMA failed\n");
		return XST_FAILURE;
	}
	/*****************************************************************************
	* Disable all the interrupt before setup
	*****************************************************************************/
	XAxiDma_IntrDisable(&DmaInst, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
	XAxiDma_IntrDisable(&DmaInst, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);

	/*****************************************************************************
	*Enable all the interrput
	*****************************************************************************/
	XAxiDma_IntrEnable(&DmaInst, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
	XAxiDma_IntrEnable(&DmaInst, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);

	return Status;
}
/*****************************************************************************/
/*
*
* This is the DMA TX Interrupt handler function.
*
* It gets the interrupt status from the hardware, acknowledges it, and if any
* error happens, it resets the hardware. Otherwise, if a completion interrupt
* is present, then sets the TxDone.flag
*
* @param	Callback is a pointer to TX channel of the DMA engine.
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void Mm2s_Intr_Handler(void *Callback)
{

	u32 IrqStatus;
	int TimeOut;
	XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

	/* Read pending interrupts */
	IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);

	/* Acknowledge pending interrupts */


	XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);

	/*
	 * If no interrupt is asserted, we do not do anything
	 */
	if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {

		return;
	}

	/*
	 * 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)) {

		Error = 1;

		/*
		 * Reset should never fail for transmit channel
		 */
		XAxiDma_Reset(AxiDmaInst);

		TimeOut = 10000;

		while (TimeOut) {
			if (XAxiDma_ResetIsDone(AxiDmaInst)) {
				break;
			}

			TimeOut -= 1;
		}

		return;
	}

	/*
	 * If Completion interrupt is asserted, then set the TxDone flag
	 */
	if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

		TxDone = 1;
	}
}

/*****************************************************************************/
/*
*
* This is the DMA RX interrupt handler function
*
* It gets the interrupt status from the hardware, acknowledges it, and if any
* error happens, it resets the hardware. Otherwise, if a completion interrupt
* is present, then it sets the RxDone flag.
*
* @param	Callback is a pointer to RX channel of the DMA engine.
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void S2mm_Intr_Handler(void *Callback)
{
	u32 IrqStatus;
	int TimeOut;
	XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

	/* Read pending interrupts */
	IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);

	/* Acknowledge pending interrupts */
	XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);

	/*
	 * If no interrupt is asserted, we do not do anything
	 */
	if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
		return;
	}

	/*
	 * 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)) {

		Error = 1;

		/* Reset could fail and hang
		 * NEED a way to handle this or do not call it??
		 */
		XAxiDma_Reset(AxiDmaInst);

		TimeOut = 10000;

		while (TimeOut) {
			if(XAxiDma_ResetIsDone(AxiDmaInst)) {
				break;
			}

			TimeOut -= 1;
		}

		return;
	}

	/*
	 * If completion interrupt is asserted, then set RxDone flag
	 */
	if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

		RxDone = 1;
	}
}


/*****************************************************************************
 * @ function : Set up the interrupt system
*****************************************************************************/
int Setup_Interrupt_System()
{
	int Status;
	XScuGic_Config * GicCfgPtr;
	GicCfgPtr = XScuGic_LookupConfig(GIC_DEVICE_ID);
	Status = XScuGic_CfgInitialize(&GicInst, GicCfgPtr, GicCfgPtr->CpuBaseAddress);
	if(Status != XST_SUCCESS)
	{
		printf("initialize GIC failed\n");
		return XST_FAILURE;
	}
	/*****************************************************************************
	* initialize exception system
	*****************************************************************************/
	Xil_ExceptionInit();

	/*****************************************************************************
	* register interrput type exception
	*****************************************************************************/
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler, &GicInst);

	/*****************************************************************************
	* connect interrput to scugic controller
	*****************************************************************************/
	Status = XScuGic_Connect(&GicInst, UART_INTR_ID, (Xil_ExceptionHandler) XUartPs_InterruptHandler, &UartInst);
	if(Status != XST_SUCCESS)
	{
		printf("Connect Uart interrput to GIC failed\n");
		return XST_FAILURE;
	}
	Status = XScuGic_Connect(&GicInst, MM2S_INTR_ID, (Xil_ExceptionHandler) Mm2s_Intr_Handler, &DmaInst);
	if(Status != XST_SUCCESS)
	{
		printf("Connect DMA tx interrput to GIC failed\n");
		return XST_FAILURE;
	}

	Status = XScuGic_Connect(&GicInst, S2MM_INTR_ID, (Xil_ExceptionHandler) S2mm_Intr_Handler, &DmaInst);
	if(Status != XST_SUCCESS)
	{
		printf("Connect DMA tx interrput to GIC failed\n");
		return XST_FAILURE;
	}

	/*****************************************************************************
	* Enable the interrput
	*****************************************************************************/
	XScuGic_Enable(&GicInst, UART_INTR_ID);
	XScuGic_Enable(&GicInst, S2MM_INTR_ID);
	XScuGic_Enable(&GicInst, MM2S_INTR_ID);

	/*****************************************************************************
	* Enable the exception system
	*****************************************************************************/
	Xil_ExceptionEnable();
	return Status;
}
           

測試結果

ZYNQ基礎---AXI DMA使用前言1. AXI DMA的基本接口2 Block design搭建3. vitis測試結果

  從序列槽發出的資料被成功的接收。再看看ila抓取到的波形。

ZYNQ基礎---AXI DMA使用前言1. AXI DMA的基本接口2 Block design搭建3. vitis測試結果

  輸入到自定義IP的資料:

ZYNQ基礎---AXI DMA使用前言1. AXI DMA的基本接口2 Block design搭建3. vitis測試結果

   從自定義IP輸出的資料。一次傳輸之間隔了較長的時間。

ZYNQ基礎---AXI DMA使用前言1. AXI DMA的基本接口2 Block design搭建3. vitis測試結果