本帖最后由 FPGA课程 于 2024-9-29 13:16 编辑
软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述数据在PS和PL之间的交互是FPGA+ARM构架SOC的重要内容,从本文开始,下面的内容大部分都和DMA数据交互有关系。 本课讲解了一个最基本的DMA环路搭建,通过PS端控制DMA对DDR数据的读写和校验,完成环路测试。本课程是DMA设计的基础,读者务必认真阅读和学习。 本文实验目的: 1:PS端ARM将数据发送给DDR。 2:PS控制DMA,使DMA通过数据通道读取DDR中的数据;DMA将读取到的数据传给FIFO。 3:FIFO将数据传输给DMA;PS控制DMA,使DMA通过数据通道将数据写入DDR中。 4:传输校验,对比接收数据与发送数据是否一致。 本课程会详细介绍创建工程的每个步骤,后面的课程将不再详细介绍创建工程的步骤。 2系统框图本系统中首先ZYNQ中的ARM把测试数据写入到PS DDR中,之后启动AXI-DMA通过AXI4总线把PS DDR中的测试数据发送给AXI-DMA IP。AXI-DMA的发送端把数据环路到接收端后通过AXI4总线把数据写入到ZYNQ的PS DDR中,并且产生中断通知ARM读取DDR中的数据。
3搭建SOC系统工程详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。 3.1Zynq IP PS部分设置本文中的PS设置内容是新增加的配置部分,关于DDR、MIO、CPU时钟等设置请参考“3-1-01米联客2024版ZynqSocSDK入门篇”中第一个工程 “01Vitis Soc开发入门”这个实验。 1:PS复位设置
2:设置 PS GT Master 接口和 HP Slave 接口
3:设置PL到PS的中断Interurptsà勾选 Fabric Interrupt,勾选IRQ_F2P[15:0]。
4:设置PL的时钟勾选FCLK_CLK0,设置为100,即PS的PLL提供本系统的时钟100MHZ。
5:ZYNQ IP设置完成后下图中IRQ_F2P[0:0]会根据xconcat IP自动扩展
3.2添加IP单击添加IP按钮“
编辑”,输入如下模块IP名字的关键词,并双击添加。
3.3IP设置1:AXI-DMA IP设置关于更多关于AXI-DMA的描述请阅读“附录2” Enable Scatter Gather Engine 是否使能SG DMA模式,不勾选代表采用Direct DMA方式 Enable Micro DMA 是否使能微DMA ,不勾选代表不使能 Wideh of buffer length register :设置14。(寄存器设置最大为26,即2的26次方64MB大小,这里设置14bit 就够用了,长度越大,需要的资源也就越多)对于S2MM或者MM2S通道,这个值必须大于或者等于一次Stream的数据量。 Address Width:设置32 对于ZYNQ系统设置32bit Enable Read Channel:使能AXI-DMA 读通道 Enable Write Channel:使能AXI-DMA写通道 Number of Channels:默认1不可修改 Memory Map Data Width:设置AXI4总线的数据位宽,越大效率越高 Stream Data Width:设置AXIStream数据接口的数据位宽 Max Burst Size:设置AXI4总线最大支持的Burst数据量,这个值越大效率越高,但是会过多占用AXI4总线带宽。
2:AXI-DATA-FIFO IP设置Data FIFO设置TDATA Width为4。
3:设置xlconcatIpxlconcat IP实现了单个分散的信号,整合成总线信号。这里,将2个独立的中断信号,合并在一起连接到MPSOC IP的中断信号接口上。
3.4PL图形编程点击Run Connection Automation 快速自动连线。
只要软件提示你需要自动连线,一般都需要进行自动连线,除非自己知道如何连线,有特殊需求。
这部完成后,部分连线已经完成,如果还有提示需要自动连线的继续让软件自动连线
直到出下如下。可以看到,还有未连线的模块,这部分就需要手动完成了。
完成后如下:
3.5添加ila在线逻辑分析仪对于图形化的设计,添加ila核比较简便,选中需要被添加的信号右击debug
之后软件会再次提醒需要自动连线
自动添加ila核
由于我们还要观察2个中断信号,双击ILA核,增加对于非总线接口的信号支持,
设置Monitor Type为MIX
之后手动添加中断信号到ila
3.6完成图形编程设计
至此,就完成了工程架构的搭建。后面的操作过程是Validate Design->Generate Out products->Create wrappers-> Generate Bitstream ,产生完成后导出硬件,加载Vitis IDE。 3.7地址空间分配
3.8编译并导出平台文件1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。 2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。 3:生成Bit文件。 4:导出到硬件: FileàExport HardwareàInclude bitstream 5:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
4搭建Vitis-sdk工程创建soc_base sdk platform和APP工程。 4.1创建SDK Platform工程启动Vitis-Sdk
设置好路径
米联客资料中的路径规范如下图: soc_prj里面是基于SOC的硬件工程源码 soc_hw里面是xsa格式文件,soc_prj编译会导出system_wrapper.xsa到这个文件 soc_sdk里面是裸机的sdk工程,sdk工程创建依赖soc_hw中的system_wrapper.xsa
单击Create Platform Project 创建基于开发平台的工程
添加之前导出的system_wrapper.xsa文件
创建完成后
最后,右击soc_base完成编译
4.2创建axi_dma_loop APP工程首选创建一个空的工程
复制soc_prj/uisrc/07_sdk_src路径下已经编写好的源码到src路径下
5程序分析
5.1总流程图如下图所示,本文的程序工作流程如下,包括初始化中断、写测试数据到PS DDR、先启动DMA接收中断、再启动DMA发送中断、等待发送中断和接收中断都完成、读取PS DDR中接收到的数据、完成数据对比校验。
5.2main.c源码的分析
1:init_intr_sys函数功能:对中断资源的初始化,使能中断资源。 说明:这个函数里面调用的函数是米联客封装好的初始化函数,使用起来比较方便。一般只要给出中断对象,中断号,就可以对中断进行初始化。 init_intr_sys函数 - int init_intr_sys(void)
- {
- DMA_Intr_Init(&AxiDma,0);//初始化DMA中断
- Init_Intr_System(&Intc); //初始化系统中断
- Setup_Intr_Exception(&Intc); //设置系统中断
- DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID);//设置DMA中断关联到系统中断
- DMA_Intr_Enable(&Intc,&AxiDma); //使能DMA中断
- return 0;
- }
复制代码
下面对init_intr_sys函数中调用的函数功能进行说明: DMA_Intr_Init(&AxiDma,0): 第一参数是DMA的对象;第二参数是硬件ID。 Init_Intr_System(&Intc): 初始化系统中断系统 DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID) : 初始化并设置DMA的中断系统。 第一参数是一个指向INTC实例指针; 第二个参数是一个指向DMAengine的实例指针; 第三个参数是AXI-DMA TX通道中断ID; 第四个参数是AXI-DMA RX通道中断ID; DMA_Intr_Enable(&Intc,&AxiDma) : DMA中断使能 2:axi_dma_test()函数该函数中的工程流程如下: 1:写测数据到DDR中,这里需要注意,必须用Xil_DCacheFlushRange((u32)TxBufferPtr, MAX_PKT_LEN)确保数据都从cache刷到DDR中,否则DMA发送数据的时候可能会错误。 - Value = TEST_START_VALUE + (i & 0xFF);
- for(Index = 0; Index < MAX_PKT_LEN; Index ++) {
- TxBufferPtr[Index] = Value; //PS 发PL 缓存写入测试数据
- Value = (Value + 1) & 0xFF;
- }
- Xil_DCacheFlushRange((u32)TxBufferPtr, MAX_PKT_LEN) ;//PS写数据到DDR用Xil_DCacheFlushRange函数确保数据都从cache刷如DDR
复制代码2:在启动DMA发送前,先设置并且DMA接收,这样数据还没接收到前可以让DMA接收先做好接收准备 - Status = XAxiDma_SimpleTransfer(&AxiDma,(u32) RxBufferPtr,MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);<img width="15" _height="15" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" border="0" alt="">
复制代码3:启动本次DMA发送,把上面写入到DDR发射缓冲区的数据发送到FPGA的AXI-DMA控制器 - Status = XAxiDma_SimpleTransfer(&AxiDma,(u32) TxBufferPtr,MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
复制代码4:等待发送中断和接收中断都产生 - while (!TxDone || !RxDone)
复制代码5:对比接收到的数据和发送的数据是否一致 - Status = DMA_CheckData(MAX_PKT_LEN, (TEST_START_VALUE + (i & 0xFF)));
复制代码这一步需要注意,当从DMA接收中断接收数据后,读取DDR中的数据前也要采用Xil_DCacheInvalidateRange函数确保读取的数据都是从DDR读到的。 - int DMA_CheckData(int Length, u8 StartValue)
- {
- u8 *RxPacket;
- int Index = 0;
- u8 Value;
- RxPacket = (u8 *) RX_BUFFER_BASE;
- Value = StartValue;
- /* Invalidate the DestBuffer before receiving the data, in case the
- * Data Cache is enabled
- */
- Xil_DCacheInvalidateRange((u32)RxPacket, Length); //PL写数据到DDR用Xil_DCacheInvalidateRange函数确保数据都从cache刷如DDR
- for(Index = 0; Index < Length; Index++) {
- if (RxPacket[Index] != Value) {
- xil_printf("Data error %d: %x/%x\r\n",
- Index, RxPacket[Index], Value);
- return XST_FAILURE;
- }
- Value = (Value + 1) & 0xFF;
- }
- return XST_SUCCESS;
- }
复制代码
5.3dma_intr.c源码分析
1:DMA中断函数的设置和中断的使能DMA_Setup_Intr_System函数 该函数用于设置接收和发送中断函数的回调函数。 首先通过XScuGic_SetPriorityTriggerType函数设置了PL中断的优先级为0Xa0,采用上升沿出发方式。 其次通过XScuGic_Connect完成中断函数的绑定 最后通过XScuGic_Enable函数完成DMA的PL中断绑定到INTC系统中断中,这样系统中断就能根据中断号回调之前绑定的函数。 - int DMA_Setup_Intr_System(XScuGic * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId)
- {
- int Status;
- XScuGic_SetPriorityTriggerType(IntcInstancePtr, TxIntrId, 0xA0, 0x3); //设置中断上升沿触发
- XScuGic_SetPriorityTriggerType(IntcInstancePtr, 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(IntcInstancePtr, TxIntrId, //把中断函数连接到系统中断
- (Xil_InterruptHandler)DMA_TxIntrHandler,
- AxiDmaPtr);
- if (Status != XST_SUCCESS) {
- return Status;
- }
- Status = XScuGic_Connect(IntcInstancePtr, RxIntrId, //把中断函数连接到系统中断
- (Xil_InterruptHandler)DMA_RxIntrHandler,
- AxiDmaPtr);
- if (Status != XST_SUCCESS) {
- return Status;
- }
- XScuGic_Enable(IntcInstancePtr, TxIntrId); //使能DMA发送中断
- XScuGic_Enable(IntcInstancePtr, RxIntrId); //使能DMA接收中断
- return XST_SUCCESS;
- }
复制代码
DMA_Intr_Enable函数: 由于AXI-DMA控制器是FPGA端实现的,因此为了让其产生中断还要使能其中断能能寄存器,通过调用DMA_Intr_Enable使能DMA控制器的发送和接收中断。 - int DMA_Intr_Enable(XScuGic * IntcInstancePtr,XAxiDma *DMAPtr)
- {
- /* Disable all interrupts before setup */
- XAxiDma_IntrDisable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DMA_TO_DEVICE);
- XAxiDma_IntrDisable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
- /* Enable all interrupts */
- XAxiDma_IntrEnable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DMA_TO_DEVICE);
- XAxiDma_IntrEnable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
- return XST_SUCCESS;
- }
复制代码
2:DMA发送中断函数DMA_TxIntrHandler当启动XAxiDma_SimpleTransfer(&AxiDma,(u32) TxBufferPtr,MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE)函数后,数据从DDR通过AXI总线搬运到AXI-DMA控制器后,AXI-DMA控制器会产生一个发送中断通知CPU,发送中断函数就会被回调。DMA_TxIntrHandler函数中的内容分析如下: XAxiDma *AxiDmaInst = (XAxiDma *)Callback;这句代码是为了获取当前中断的对象。void *Callback是一个无符号的指针,传递进来的阐述可以强制转换成其他任何的对象,这里就是强制转换成 XAxiDma 对象了。 IrqStatus =XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE)这个函数获取当前中断号。
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);这个函数是响应当前中断,通知CPU 当前中断已经被接收,并且清除中断标志位。如果中断全部正确,TxDone将被置为1表示发送中断完成。如果有错误,则复位DMA,并且设置超时参数。
函数源码如下:
3:DMA接收中断函数DMA_RxIntrHandler当启动XAxiDma_SimpleTransfer(&AxiDma,(u32) TxBufferPtr,MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE)函数后,数据从DDR通过AXI总线搬运到AXI-DMA控制器,数据通过AXI-DMA 的TX通道环路到AXI-DMA的RX通道.这样数据会被再次写入到DDR.当RX通道数据被AXI-DMA控制器发送完毕,DMA控制器就会产生一个PL的RX中断。XAxiDma_SimpleTransfer(&AxiDma,(u32) RxBufferPtr,MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA)函数已经被设置,所以当AXI-DMA控制器发送了RX中断后,中断系统会回调DMA_RxIntrHandler函数。DMA_TxIntrHandler函数中的内容分析如下: XAxiDma *AxiDmaInst = (XAxiDma *)Callback;这句代码是为了获取当前中断的对象。void *Callback是一个无符号的指针,传递进来的阐述可以强制转换成其他任何的对象,这里就是强制转换成 XAxiDma 对象了。 IrqStatus =XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA)这个函数获取当前中断号。
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);这个函数是响应当前中断,通知CPU 当前中断已经被接收,并且清除中断标志位。如果中断全部正确,TxDone将被置为1表示发送中断完成。如果有错误,则复位DMA,并且设置超时参数。
函数源码如下:
- static void DMA_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;
- }
- }
复制代码
4:数据对比数据对比函数用于对比发送的数据是否和接收的数据一致,以此验证我们的DMA环路实验是否正确。这里需要注意必须使用Xil_DCacheInvalidateRange确保读取的数据都是从DDR中读到 - int DMA_CheckData(int Length, u8 StartValue)
- {
- u8 *RxPacket;
- int Index = 0;
- u8 Value;
- RxPacket = (u8 *) RX_BUFFER_BASE;
- Value = StartValue;
- /* Invalidate the DestBuffer before receiving the data, in case the
- * Data Cache is enabled
- */
- Xil_DCacheInvalidateRange((u32)RxPacket, Length);
- for(Index = 0; Index < Length; Index++) {
- if (RxPacket[Index] != Value) {
- xil_printf("Data error %d: %x/%x\r\n",
- Index, RxPacket[Index], Value);
- return XST_FAILURE;
- }
- Value = (Value + 1) & 0xFF;
- }
- return XST_SUCCESS;
- }
复制代码
6方案演示
6.1硬件准备本实验需要用到 JTAG 下载器、USB 转串口外设,另外需要把核心板上的 2P 模式开关设置到 JTAG 模式,即 ON ON (注意新版本的 MLK-H3-CZ08-7100FC(米联客 7X 系列),支持 JTAG 模式,对于老版本的核心板,JTAG 调试 的时候一定要拔掉 TF 卡,并且设置模式开关为 OFF OFF)
6.2实验结果
Debug程序,单程序停止main函数处,打开VIVADO,扫描芯片,这个时候会自动之前添加的在线逻辑分析仪IP核。如下红框的按键先不要单击
具体步骤如下: 在VIVADO工程中点击Open Target 然后点击Auto Connect
连接成功后入下图
设置触发条件、观察信号。设置波形偏移500。 Settings -->Trigger position in window:500 Trigger Setup --> 添加触发信号axi_dma0_mm2s_introut,设置Value为R。如图所示。
启动波形捕捉
SDK中点击继续运行
当中断触发的时,VIVAIDO中Hardware Manager出现捕捉波形,如下图所示
观察数据,打开Memory:Window->Show View->Memory
点击添加接收内存部分地址用于观察内存中的数据 地址为 0x08300000
为了观察一次收发数据:设置断点,重新让收发程序跑一次。
收发一次,从内存中读取的数据如图:
可以看到第一个数据是0X0C ,后面是依次加1,一次接收的数据量是2047。发送数据也是OX0C后面依次加1,发送量是2047。接收数据一致,测试结束。
|