Zynq PS_PL间通信学习(一) AXI_DMA_LOOP测试

参考资料:
Xilinx官方参考文档:PG021_axi_dma、UG585_zynq_7000_TRM等
AXI DMA开发 http://www.fpgadeveloper.com/2014/08/using-the-axi-dma-in-vivado.html
黑金教程 course_s2_ALINX ZYNQ开发平台SDK应用教程V2.02.pdf
米联客教程 S03_CH01_AXI_DMA_LOOP(视频) https://v.qq.com/x/page/e0349oqwa27.html

本文介绍在进行AXI_DMA_LOOP测试时遇到的问题和走的弯路,记录下。

  • 前言
    之前一直做FPGA开发,在网络方向的,主要是二层、三层交换方向,采用的硬件架构也是分离式的CPU+FPGA的架构。想想zynq也出了好久了,一直想研究的,无奈工作太忙加上自己太懒一直耽搁了,既然有时间,说搞就搞。想想之前项目的应用环境,FPGA与CPU间是通过以太网通信。ZYNQ里集成了ARM,于是便先搞清楚FPGA与ARM间的通信方式。
    PS(ARM)与PL(FPGA逻辑)间采用比较多的通信方式就是通过AXI总线方式进行通信,具体AXI协议不做介绍,网上资料很多。
    在这里插入图片描述

  • AXI4-Lite有轻量级,结构简单的特点,适合小批量数据、简单控制场合。不支持批量传输,读写时一次只能读写一个字(32bit),主要用于访问一些低速外设和外设的控制。

  • AXI4接口和AXI-Lite差不多,只是增加了一项功能就是批量传输,可以连续对一片地址进行一次性读写。也就是说具有数据读写的burst 功能。

  • AXI4-Stream是一种连续流接口,不需要地址线(类似FIFO),对于这类IP,ARM不能通过上面的内存映射方式控制(FIFO没有地址线),必须有一个转换装置,例如AXI-DMA模块来实现内存映射到流式接口的转换。AXI4-Stream本质都是针对数据流构建的数据通路。

AXI4-Lite和AXI4均采用内存映射控制方式,即ARM将用户自定义IP编入某一地址空间进行访问,读写时就像在读写自己的片内RAM,编程也方便,开发难度较低。代价就是资源占用过多,需要额外的读写地址线、读写数据线以及应答信号等。

AXI4-Stream是一种连续流接口,不需要地址线(类似FIFO),对于这类IP,ARM不能通过上面的内存映射方式控制(FIFO没有地址线),必须有一个转换装置,例如AXI-DMA模块来实现内存映射到流式接口的转换。AXI4-Stream本质都是针对数据流构建的数据通路。

因为以前做的二层、三层交换也是数据流驱动架构,并且原来做以太网的接口方式也是AXI4-Stream方式,对AXI4-Stream比较熟悉,因此先研究AXI4-Stream接口。

几个常用的AXI_stream接口的IP介绍:

  • AXI-DMA:实现从PS内存到PL高速传输高速通道AXI-HP<---->AXI-Stream的转换
  • AXI-FIFO-MM2S:实现从PS内存到PL通用传输通道AXI-GP<----->AXI-Stream的转换
  • AXI-Datamover:实现从PS内存到PL高速传输高速通道AXI-HP<---->AXI-Stream的转换,只不过这次是完全由PL控制的,PS是完全被动的。
  • AXI-VDMA:实现从PS内存到PL高速传输高速通道AXI-HP<---->AXI-Stream的转换,只不过是专门针对视频、图像等二维数据的。

做嵌入式驱动的经常会用到DMA,先从DMA入手吧。

简单介绍zynq的AXI_DMA模块,该模块用到了三种总线,AXI4_Lite用于对寄存器进行配置(这个地方之前没了解清楚,吃了亏,后面介绍弯路),AXI4 Memory Map用于与内存交互,在此模块又分立出了AXI4 Memory Map Read和AXI4 Memory Map Write两个接口,又分别叫做M_AXI_MM2S和M_AXI_S2MM。AXI4_Stream接口用于对用户逻辑进行通信,其中AXI4 Stream Master(MM2S)是PS to PL方向,AMI4 Stream Slave(S2MM)是PL to PS方向。
在这里插入图片描述

下面实例学习:开发环境 vivado 2017.4 SDK 2017.4
AXI_DMA_LOOP的硬件框图:
在这里插入图片描述
这里为了与以前的项目对应,FIFO的宽度定义为8bit(vivado 2017.4版本AXI DMA有个BUG后面介绍),采取中断触发方式。在vivado中建立系统block(方法网上自查)

  1. 错误案例一
    认为DMA只用AXI-HP接口,擅自去掉了zynq的AXI-GP接口,导致生成系统的AXI_DMA 的S_AXI_Lite接口没有连接,最后生成bit文件后导入SDK后peripheral里没有axidma的驱动示例,只有dmaps的驱动示例;以及生成案例的BSP没有axidma的相关.h和.c。

Zynq定制出现错误
在这里插入图片描述

结果生成硬件系统如下:
在这里插入图片描述

导入SDK后发现(只有dmaps驱动示例,没有axidma的驱动示例):
在这里插入图片描述

而正确的工程导入SDK应该是:
在这里插入图片描述

错误案例一总结:AXI DMA的数据传输虽然是用AXI-HP的接口,但是AXI DMA的配置接口S_AXI_Lite用的是AXI-GP接口,所以AXI-GP接口不能擅自去掉。

  1. 错误案例二
    vivado2017.4软件有个BUG,AXI DMA的Wrire Channl通道Stream Data Width无法自适应修改。

AXI DMA定制Wrire Channl默认32位,但是在进行连接时可以自适应修改宽度
在这里插入图片描述
官方手册里说明
在这里插入图片描述
AXI4_Stream Data fifo定制fifo的位宽为8位
在这里插入图片描述

结果生成硬件系统如下:
在这里插入图片描述

正常版本validate后位宽就该改为一样,但vivado2017.4不能自适应修改并报警告:
在这里插入图片描述

而正常版本validate后:

在这里插入图片描述
修改方式:黑金教程有说明
在这里插入图片描述

  1. 正确案例:
    流程与前面一样,不重复。

定制zynq时PL时钟选择两个,一个低速时钟给DMA和FIFO使用,另一个高速时钟作为debug的采样时钟,用来分析axi4_stream接口时序。
在这里插入图片描述
系统硬件图,在AXI4_stream相关地方加了debug信号:
在这里插入图片描述

生成bit文件导入SDK,并建立hello world实例代码:
在这里插入图片描述

驱动部分编写fd_dma_intr.c用来处理中断和dma的初始化,中断处理等,在helloworld.c中编写测试部分程序,测试方式是通过arm发送16组数据通过DMA发送给PL部分,在从PL部分读回这16组数据,在串口上打印进行比对,确认PS与PL通过DMA数据交互正常。源码如下(大量参考官方驱动axidma,非常有用):

先了解系统地址分配(其中DDR为 0x00100000—0x3fffffff)
在这里插入图片描述
helloworld.c

/*
 * helloworld.c: simple test application
 */

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "fd_dma_intr.h"

//相关宏定义在官方驱动里有说明
#define DMA_DEV_ID			XPAR_AXIDMA_0_DEVICE_ID

#define INTC_DEVICE_ID		XPAR_SCUGIC_SINGLE_DEVICE_ID

#define RX_INTR_ID		XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define TX_INTR_ID		XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID

//要在系统DDR地址范围内
#define MEM_BASE_ADDR		0x01000000

//TX:PS TO DMA TO PL
//RX:PL TO DMA TO PS

//定义收发缓存地址
#define TX_BUFFER_BASE		(MEM_BASE_ADDR + 0x00100000)
#define RX_BUFFER_BASE		(MEM_BASE_ADDR + 0x00300000)
#define RX_BUFFER_HIGH		(MEM_BASE_ADDR + 0x004FFFFF)

//每组数据长度为16个字节
#define MAX_PKT_LEN			0x10

/* Device instance definitions */

static XAxiDma AxiDma;		/* Instance of the XAxiDma */

static XScuGic Intc;	/* Instance of the Interrupt Controller */

/* Flags interrupt handlers use to notify the application context the events. */

int axi_dam_test();

int main()
{
	int Status;

    init_platform();

    //DMA 初始化
    Status = DMA_Init(&AxiDma, DMA_DEV_ID);
    if(Status != XST_SUCCESS){
    	print("DMA Initial Failed!\n\r");
    }

    //配置系统中断
    Status = SetupIntrSystem(&Intc, &AxiDma, TX_INTR_ID, RX_INTR_ID);
    if(Status != XST_SUCCESS){
		print("Interrupt Config Failed!\n\r");
	}

	//使能DMA中断
    DMA_Intr_Enable(&AxiDma);

	//测试函数
    axi_dam_test();

	//关闭DMA中断
    DMA_Intr_Disable(&AxiDma);

    cleanup_platform();
    return 0;
}

int axi_dam_test()
{
	u8 *TxBufferPtr;
	u8 *RxBufferPtr;
	u8 Value;
	int i;
	int Status;
	u8 index;
	int delay;

	TxBufferPtr = (u8 *)TX_BUFFER_BASE;		//PS TO DMA TO PL
	RxBufferPtr = (u8 *)RX_BUFFER_BASE;		//PL TO DMA TO PS

	/* Initialize flags before start transfer test  */
	TxDone = 0;
	RxDone = 0;
	Error = 0;

	printf("Start DMA test!\n\n\r");

	//测试开始前内存相关地方清零
	for(i=0; i<MAX_PKT_LEN*16; i++){
		*(TxBufferPtr+i)= 0x00;
		*(RxBufferPtr+i)= 0x00;
		Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN*16);
		Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN*16);
	}

	//产生16组数据
	for(index=0; index<MAX_PKT_LEN; index++){
		//每组数据为0x#0-0x#F  #为0-F
		for(i=0; i<MAX_PKT_LEN; i++){
			Value = (index & 0x0f) << 4;
			*(TxBufferPtr+i) = Value + (i & 0x0f);
			*(RxBufferPtr+i) = 0x00;
		}


  		/* Flush the SrcBuffer before the DMA transfer, in case the Data Cache is enabled */
		Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN);
		Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);

		//调相关系统函数进行发送
		Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr, MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);

		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		//打印发送数据
		printf("======== send packet ======\n\r");

		for(i=0; i<MAX_PKT_LEN; i++){
			printf("0x%02x ", *TxBufferPtr++);
		}

		printf("\n\n\r");
		
		//调相关系统函数进行接收
		Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) RxBufferPtr, MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);

		//做个延时
		for(delay=0; delay<100000000; delay++);

		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		while (!TxDone && !RxDone) {
			/* NOP */
		}

		//发送和接收完成后(在PL内回环后)打印接收数据
		printf("======== recv packet ======\n\r");

		for(i=0; i<MAX_PKT_LEN; i++){
			printf("0x%02x ", *RxBufferPtr++);
		}

		printf("\n\n\r");

		//for(delay=0; delay<100000; delay++);
	}

	printf("finsh DMA test!\n\r");

	return XST_SUCCESS;
}

fd_dma_intr.c(按照官方驱动一步步来即可)

/***************************** Include Files *********************************/
#include "fd_dma_intr.h"

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

/*****************************************************************************/
/*
*
* 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.
*
******************************************************************************/
static void TxIntrHandler(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 = RESET_TIMEOUT_COUNTER;

		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.
*
******************************************************************************/
static void RxIntrHandler(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 = RESET_TIMEOUT_COUNTER;

		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;
	}
}

//系统中断配置
int SetupIntrSystem(XScuGic * IntcPtr, XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId)
{

	int Status; 

	XScuGic_Config *IntcConfig;    /* The configuration parameters of the controller */


 	 //Initialize the interrupt controller driver so that it is ready to use.
	IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	if (NULL == IntcConfig) {
		return XST_FAILURE;
	}

	Status = XScuGic_CfgInitialize(IntcPtr, IntcConfig,
					IntcConfig->CpuBaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	XScuGic_SetPriorityTriggerType(IntcPtr, TxIntrId, 0xA0, 0x3);

	XScuGic_SetPriorityTriggerType(IntcPtr, RxIntrId, 0xA0, 0x3);

	/*
	 * 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(IntcPtr, TxIntrId,
				(Xil_InterruptHandler)TxIntrHandler,
				AxiDmaPtr);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	Status = XScuGic_Connect(IntcPtr, RxIntrId,
				(Xil_InterruptHandler)RxIntrHandler,
				AxiDmaPtr);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	XScuGic_Enable(IntcPtr, TxIntrId);
	XScuGic_Enable(IntcPtr, RxIntrId);



	/* Enable interrupts from the hardware */

	Xil_ExceptionInit();
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
			(Xil_ExceptionHandler)XScuGic_InterruptHandler,
			(void *)IntcPtr);

	Xil_ExceptionEnable();

	return XST_SUCCESS;

}

int DMA_Intr_Enable(XAxiDma * AxiDmaPtr)
{
	/* Disable all interrupts before setup */

		XAxiDma_IntrDisable(AxiDmaPtr, XAXIDMA_IRQ_ALL_MASK,
							XAXIDMA_DMA_TO_DEVICE);

		XAxiDma_IntrDisable(AxiDmaPtr, XAXIDMA_IRQ_ALL_MASK,
					XAXIDMA_DEVICE_TO_DMA);

		/* Enable all interrupts */
		XAxiDma_IntrEnable(AxiDmaPtr, XAXIDMA_IRQ_ALL_MASK,
								XAXIDMA_DMA_TO_DEVICE);


		XAxiDma_IntrEnable(AxiDmaPtr, XAXIDMA_IRQ_ALL_MASK,
								XAXIDMA_DEVICE_TO_DMA);
}

int DMA_Intr_Disable(XAxiDma * AxiDmaPtr)
{
	/* Disable all interrupts before setup */
	XAxiDma_IntrDisable(AxiDmaPtr, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);

	XAxiDma_IntrDisable(AxiDmaPtr, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
}

//DMA 设备初始化
int DMA_Init(XAxiDma *DMAPtr,u32 DeviceId)
{
	int Status;
	XAxiDma_Config *Config;
	
	Config = XAxiDma_LookupConfig(DeviceId);
	if (!Config) {
		xil_printf("No config found for %d\r\n", DeviceId);
		return XST_FAILURE;
	}

	/* Initialize DMA engine */
	Status = XAxiDma_CfgInitialize(DMAPtr, Config);

	if (Status != XST_SUCCESS) {
		xil_printf("Initialization failed %d\r\n", Status);
		return XST_FAILURE;
	}

	if(XAxiDma_HasSg(DMAPtr)){
		xil_printf("Device configured as SG mode \r\n");
		return XST_FAILURE;
	}

	return XST_SUCCESS;

}

运行结果如下:
在这里插入图片描述
通过打印比对发现收发数据一致,PS与PL间通过DMA交互数据正常。
此外可以单步DEBUG调试,实时查看DDR内数据情况
在这里插入图片描述

另外我们看下AXI4_stream的时序,根据官方手册自行研究
在这里插入图片描述

此外如果测试程序没完成就关闭的话在测试的时候会出现如下结果,这是由于虽然测试程序重启了,但是PL的AXI_DMA和FIFO的状态还处于上次测试没完成的状态,导致后面的测试出现于预期结果不匹配的问题,此时需要重新下载bit文件。在以后的实际应用中也要注意ARM的复位与PL复位同步问题。
在这里插入图片描述
总结:
文章主要介绍了PS与PL通过AXI DMA通信的相关基本原理和测试过程中遇到的一些问题,供以后回顾。
后面会继续介绍zynq系统与用户逻辑结合的问题,有问题大家可以一起交流。

  • 23
    点赞
  • 214
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
### 回答1: Zynq是一款Xilinx公司的片上系统(SoC),它将传统的处理系统(PS,即Processing System)和可编程逻辑(PL,即Programmable Logic)集成在一起。PSPL之间的通信是通过DMA(Direct Memory Access)实现的。 DMA是一种高效的数据传输方式,它可以在不经过处理器的情况下直接将数据从一个设备传输到另一个设备的内存中。在Zynq中,DMA控制器可以在PSPL之间进行数据传输,以实现高速的数据交换。 在使用ZynqPSPL之间进行通信时,首先需要在PL中实例化一个DMA控制器,并将其配置为与PS内存进行交互。然后,在PS中通过相应的软件驱动程序或API接口配置和控制DMA控制器。通过设置合适的寄存器和缓冲区,可以实现从PSPL的数据传输或从PLPS的数据传输,以及在传输过程中的中断处理。 PSPL之间的DMA通信可以实现快速的数据交换,因为数据可以直接在PL中进行处理,无需经过PS的干预。这对于需要高速数据处理的应用非常有用,比如图像处理、信号处理等。 需要注意的是,使用DMA进行PSPL之间的通信需要合理地配置和管理DMA控制器的缓冲区和寄存器,以及在PSPL之间的数据传输过程中进行正确的同步和互斥操作,以避免数据冲突和错误。 总之,Zynq PSPL之间的通信使用DMA可以实现高速的数据传输和处理,为嵌入式应用带来了更大的灵活性和高效性。 ### 回答2: Zynq系统中的PS(Processing System)与PL(Programmable Logic)之间的通信可以通过DMA(Direct Memory Access)实现。 DMA是一种特殊的数据传输机制,它可以在不需要CPU的干预下,在内存和外设之间直接传输数据。在Zynq系统中,PSPL之间的DMA通信可以通过AXI(Advanced eXtensible Interface)总线实现。 首先,在Zynq系统中,PS可以使用AXI DMA控制器来设置数据传输的源地址、目的地址、传输长度等参数。这些参数可以通过PS的程序来配置。 然后,在PL中,我们可以使用AXI接口来设计自定义的IP(Intellectual Property)核,该IP核可以与AXI DMA进行通信。这个IP核可以通过PL的开发工具进行开发和配置。 接下来,通过适当的设计和配置,我们可以在PL中连接AXI DMA核和其他IP核,以实现PSPL之间的数据传输。例如,我们可以将一个读取数据的IP核连接到AXI DMA的输入端口,将一个写入数据的IP核连接到AXI DMA的输出端口。这样,当AXI DMA启动数据传输时,读取数据的IP核将从PL的某个存储器中读取数据,然后通过DMA传输到PS的某个存储器中,而写入数据的IP核将从PS的某个存储器中读取数据,然后通过DMA传输到PL的某个存储器中。 总之,通过使用AXI DMA和适当的设计和配置,Zynq系统中的PSPL之间可以进行高效的数据传输和通信。这种方式可以提高系统的性能和可扩展性,并且降低了CPU的负载,提供了更好的系统整合能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值