您的位置 首页 > 娱乐休闲

「正点原子FPGA连载」第十六章AXI DMA环路测试

1)摘自【正点原子】领航者 ZYNQ 之嵌入式开发指南

2)实验平台:正点原子领航者ZYNQ开发板
3)平台购买地址:;id=606160108761
4)全套实验源码+手册+视频下载:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
5)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
6)关注正点原子公众号,获取最新资料

第十六章AXI DMA环路测试


DMA(Direct Memory Access,直接存储器访问)是计算机科学中的一种内存访问技术。它允许某些计算机内部的硬件子系统可以独立地直接读写系统内存,而不需中央处理器(CPU)介入处理。DMA是一种快速的数据传送方式,通常用来传送数据量较多的数据块,很多硬件系统会使用DMA,包括硬盘控制器、绘图显卡、网卡和声卡,在使用高速AD/DA时使用DMA也是不错的选择。
本章我们使用PL的AXI DMA IP核实现DMA环路功能,了解DMA的使用。本章包括以下几个部分:
1616.1简介
16.2实验任务
16.3硬件设计
16.4软件设计
16.5下载验证
16.1简介
DMA是所有现代计算机的重要特色,它允许不同速度的硬件设备进行沟通,而不需要依于中央处理器的大量中断负载。否则,中央处理器需要从来源把每一片段的数据复制到寄存器,然后把它们再次写回到新的地方。在这个时间里,中央处理器就无法执行其它的任务。
DMA是用硬件实现存储器与存储器之间或存储器与I/O设备之间直接进行高速数据传输。使用DMA时,CPU向DMA控制器发出一个存储传输请求,这样当DMA控制器在传输的时候,CPU执行其它操作,传输操作完成时DMA以中断的方式通知CPU。
为了发起传输事务,DMA控制器必须得到以下数据:
• 源地址 — 数据被读出的地址
• 目的地址 — 数据被写入的地址
• 传输长度 — 应被传输的字节数


图 16.1.1 DMA存储传输过程


DMA存储传输的过程如下:
1. 为了配置用DMA传输数据到存储器,处理器发出一条DMA命令
2. DMA控制器把数据从外设传输到存储器或从存储器到存储器,而让CPU腾出手来做其它操作。
3. 数据传输完成后,向CPU发出一个中断来通知它DMA传输可以关闭了。
ZYNQ提供了两种DMA,一种是集成在PS中的硬核DMA,另一种是PL中使用的软核AXI DMA IP。
在ARM CPU设计的过程中,已经考虑到了大量数据搬移的情况,因此在CPU中自带了一个DMA控制器DAMC,这个DAMC驻留在PS内,而且必须通过驻留在内存中的DMA指令编程,这些程序往往由CPU准备,因此需要部分的CPU参与。DMAC支持高达8个通道,所以多个DMA结构的核可以挂在单个DMAC上。DAMC与PL的连接是通过AXI_GP接口,这个接口最高支持到32位宽度,这也限制了这种模式下的传输速率,理论最高速率为600MB/s。这种模式不占用PL资源,但需要对DMA指令编程,会增加软件的复杂性。
为了获取更高的传输速率,可以以空间换时间,在PL中添加AXI DMA IP核,并利用AXI_HP接口完成高速的数据传输。各种接口方式的比较如下表所示:
表 16.1.1 各种接口方式比较
方式 优点 缺点 建议用途 估计吞吐率
CPU控制的IO 软件简单
最少的逻辑资源
逻辑接口简单 吞吐率最低 控制功能 <25MB/s
PS 的DMAC 最少的逻辑资源
吞吐率中等
多个通道
逻辑接口简单 DMAC配置有一定难度 当PL的DMA不够时 600 MB/s
PL的DMA和AXI_HP 吞吐率最高
多个接口
有FIFO缓存 只能访问OCM和DDR
逻辑设计复杂 大块数据高性能传输 1200 MB/s(每个接口)
PL的DMA和AXI_ACP 吞吐率最高
延时最低
可选的Cache一致性 大块数据传输引起Cache问题
共享了CPU的互联带宽
更复杂的逻辑设计 小块又与Cache直接相关的高速传输 1200 MB/s
PL的DMA和AXI_GP 吞吐率中等 更复杂的逻辑设计 PL到PS的控制功能
PS I/O外设访问 600 MB/s
可见通过PL的DMA和AXI_HP接口的传输适用于大块数据的高性能传输,带宽高。该种传输方式的拓扑图如下(灰色填充的框图或红色边框圈出的框图):



图 16.1.2 PL的DMA和AXI_HP接口拓扑图


可以看到DMA的数据传输经S_AXI_HP接口(以下简称HP接口)。ZYNQ拥有4个HP接口,提供了ZYNQ内最大的总带宽。每一个HP接口都包含控制和数据FIFO。这些FIFO为大数据量突发传输提供缓冲,让HP接口成为理想的高速数据传输接口。对DMA的控制或配置通过M_AXI_GP接口,传输状态通过中断传达到PS的中断控制器。下面我们简单的介绍下PL的DMA,即AXI DMA IP核。
AXI Direct Memory Access(AXI DMA)IP内核在AXI4内存映射和AXI4-Stream IP接口之间提供高带宽直接储存访问。其可选的scatter gather功能还可以从基于处理器的系统中的中央处理单元(CPU)卸载数据移动任务。初始化、状态和管理寄存器通过AXI4-Lite从接口访问。核心的功能组成如下图所示:


图 16.1.3 AXI DMA框图


AXI DMA用到了三种总线,AXI4-Lite用于对寄存器进行配置,AXI4 Memory Map用于与内存交互,又分为AXI4 Memory Map Read和AXI4 Memory Map Write两个接口,一个是读一个是写。AXI4 Stream 接口用于对外设的读写,其中AXI4 Stream Master(MM2S,Memory Map to Stream)用于对外设写,AXI4-Stream Slave(S2MM,Stream to Memory Map)用于对外设读。总之,在以后的使用中需要知道AXI_MM2S和AXI_S2MM是存储器端映射的AXI4总线,提供对存储器(DDR3)的访问。AXIS_MM2S和AXIS_S2MM是AXI4-streaming总线,可以发送和接收连续的数据流,无需地址。
AXI DMA提供3种模式,分别是Direct Register模式、Scatter/Gather模式和Cyclic DMA模式,这里我们简单的介绍下常用的Direct Register模式和Scatter/Gather模式。
Direct Register DMA模式也就是Simple DMA。Direct Register模式提供了一种配置,用于在MM2S和S2MM通道上执行简单的DMA传输,这需要更少的FPGA资源。Simple DMA允许应用程序在DMA和Device之间定义单个事务。它有两个通道:一个从DMA到Device,另一个从Device到DMA。应用程序必须设置缓冲区地址和长度字段以启动相应通道中的传输。
Scatter/Gather DMA模式允许在单个DMA事务中将数据传输到多个存储区域或从多个存储区域传输数据。它相当于将多个Simple DMA请求链接在一起。SGDMA允许应用程序在内存中定义事务列表,硬件将在应用程序没有进一步干预的情况下处理这些事务。在此期间,应用程序可以继续添加更多工作以保持硬件工作。用户可以通过轮询或中断来检查事务是否完成。SGDMA处理整个数据包(被定义为表示消息的一系列数据字节)并允许将数据包分解为一个或多个事务。例如,采用以太网IP数据包,该数据包由14字节的报头后跟1个或多个字节的有效负载组成。使用SGDMA,应用程序可以将BD(Buffer Descriptor,用于描述事务的对象)指向报头,将另一个BD指向有效负载,然后将它们作为单个消息传输。这种策略可以使TCP / IP堆栈更有效,它允许将数据包标头和数据保存在不同的内存区域,而不是将数据包组装成连续的内存块。
在本设计中,不需要使用scatter gather DMA模式,因为可以使用DMA的更简单的寄存器直接模式充分实现系统,从而避免实现scatter gather功能带来的面积成本。在系统需要对DMA进行相对复杂的软件控制时,可以使用scatter gather模式。
16.2实验任务
本章的实验任务是在领航者ZYNQ开发板上使用PL的AXI DMA IP核从DDR3中读取数据,并将数据写回到DDR3中。
16.3硬件设计
在实际应用中,DMA一般与产生数据或需求数据的IP核相连接,该IP核可以是带有Stream接口的高速的AD(模拟转数字)或DA(数字转模拟) IP核。不失一般性,在本次实验中,我们使用AXI4 Stream Data FIFO IP核来充当这类IP进行DMA环回实验。大致的系统框图如下(具体的可以看图 16.1.2):



图 16.3.1 AXI DAM环路测试系统框图


PS开启HP0和GP0接口。AXI DMA和AXI4 Stream Data FIFO在PL中实现。处理器通过M_AXI_GP0接口与AXI DMA通信,以设置、启动和监控数据传输。数据传输通过S_AXI_HP0接口。AXI DMA通过S_AXI_HP0接口从DDR3中读取数据后发送给AXI4 Stream Data FIFO,这种情况下AXI4 Stream Data FIFO可以相当于带有Stream接口的高速DA。AXI DMA读取AXI4 Stream Data FIFO中的数据后通过S_AXI_HP0接口写入DDR3的情形,AXI4 Stream Data FIFO相当于带有Stream接口的高速AD。
step1:创建Vivado工程
打开Vivado,创建一个名为“axi_dma_loop”的空白工程,工程路径为F:\zynq\zynq7020文件夹。注意,工程名和路径只能由英文字母、数字和下划线组成,不能包含中文、空格以及特殊字符!
step2:使用IP Integrator创建Processing System
2-1 在左侧导航栏(Flow Navigator)中,单击IP Integrator下的Create Block Design。然后在弹出的对话框中指定所创建的Block Design的名称,在Design name栏中输入“system”。如下图所示:


图 16.3.2 创建 Block Design


2-2点击“OK”按钮。 添加ZYNQ7 Processing System模块。接下来按照《“Hello World”实验》中的步骤2-7、2-8分别配置PS的UART和DDR3控制器。需要特别注意的是,我们在《“Hello World”实验》的步骤2-10中,移除了PS中与PL端交互的接口信号,这些接口在我们本次实验中需要予以保留。
2-3 配置时钟。
点击左侧的Clock Configuration页面,展开PL-Fabric Clocks,可以看到默认勾选FCLK_CLK0,且时钟频率为50MHz,这里我们将其修改为100MHz,如下图所示:


图 16.3.3 配置FCLK_CLK0


2-4 开启HP接口。
点击左侧的PS-PL Configuration页面,然后在右侧展开General下的HP Slave AXI Interface,可以看到有4个HP接口,这里我们只用其中的S AXI HP0 interface,DATA WIDTH保持默认即可,如下图所示:


图 16.3.4 启用HP0接口


2-5 因为DMA在传输完成后通过发送中断通知CPU,所以我们需要开启PL到PS的中断。
点击左侧的Interrupts页面,勾选右侧的Fabric interrupts并展开,勾选PL-PS Interrupt Ports下的IRQ_F2P[15:0],如下图所示:


图 16.3.5 开启PL到PS的中断


2-6 配置ZYNQ7 Processing System完成,点击“OK”,配置完成后的ZYNQ7 Processing System IP模块如下图所示:


图 16.3.6 配置完成后的ZYNQ7 Processing System IP


2-7 添加DMA IP。
添加DMA IP,如同添加ZYNQ7 Processing System IP,只不过搜索词由zynq变为dma,如下图所示:


图 16.3.7 添加DMA模块



图 16.3.8 配置DMA模块


我们双击axi_dma_0,打开配置界面,如图 16.3.8所示,此处我们只需要取消勾选Enable Scatter Gather Engine即可。不过还是介绍下与配置相关的选项。
Enable Scatter Gather Engine
选中此选项可启用Scatter Gather模式操作,并在AXI DMA中包含Scatter Gather Engine。取消选中此选项可启用Direct Register模式操作,但不包括AXI DMA中的Scatter Gather Engine。禁用Scatter Gather Engine会使Scatter/Gather Engine的所有输出端口都绑定为零,并且所有输入端口都将保持打开状态。此处我们取消勾选Enable Scatter Gather Engine。
Enable Micro DMA
选中此选项会生成高度优化的DMA,资源数量较少。此设置可用于传输极少量数据的应用程序。此处我们不勾选。
Width of Buffer Length Register
此整数值指定用于控制字段缓冲区长度的有效位数和在Scatter/Gather描述符中传输的Status字段的字节数。字节数等于。因此,长度为26时,字节数为字节。对于多通道模式,此值应设置为23。此处我们保持默认设置14。
Address Width (32 - 64)
指定地址空间的宽度,可以是32到64之间的任何值。此处保持默认值32。
Enable Read Channel
开启AXI DMA的读通道MM2S,相关选项如下:
Number of Channels:指定通道数。保持默认值1。
Memory Map Data Width:AXI MM2S存储映射读取数据总线的数据位宽。有效值为32,64,128,256,512和1024。此处保持默认值32。
Stream Data Width:AXI MM2S AXI4-Stream数据总线的数据位宽。该值必须小于或等于Memory Map Data Width。有效值为8、16、32、64、128、512和1024。此处保持默认值32。
Max Burst Size:突发分区粒度设置。此设置指定MM2S的AXI4-Memory Map侧的突发周期的最大大小。有效值为2,4,8,16,32,64,128和256。此处保持默认值16。
Allow Unaligned Transfers:启用或禁用MM2S数据重新排列引擎(Data Realignment Engine,DRE)。选中时,DRE被使能并允许在MM2S存储映射数据路径上数据重新对齐到8位的字节水平。对于MM2S通道,从内存中读取数据。如果DRE被使能,则数据读取可以从任何缓冲区地址字节偏移开始,并且读取数据被对齐,使得第一个字节读取是AXI4-Stream上的第一个有效字节输出。
注意:如果为相应通道禁用DRE,则不支持未对齐的缓冲区、源或目标地址。在禁用DRE的情况下使用未对齐的地址会产生未定义的结果。DRE支持仅适用于512位及以下的AXI4-Stream数据宽度设置。
Enable Write Channel
开启AXI DMA的写通道S2MM,相关选项可参考读通道。
2-8 添加axis_data_fifo IP


图 16.3.9 添加axis_data_fifo IP


该IP保持默认设置即可。添加该IP核的重点不是了解该IP核如何使用,而是知道该IP核带有AXI Stream接口。在以后的实际使用中,需要封装自定义IP核时要注意这一点。
2-9 添加Concat IP。
Concate IP 实现了单个分散的信号,整合成总线信号。这里 2 个独立的中断信号,可以合并在一起接入到 ZYNQ IP 的中断信号上。


图 16.3.10 添加Concat IP


2-10 模块连接。
在Diagram窗口中,点击“Run connection Automation”,进行自动连接,在弹出的界面中勾选“All Automation”,然后点击OK,如下图所示:


图 16.3.11 Run connection Automation


点击“Run Block Automation”,在弹出的界面中勾选“All Automation”,然后点击OK,如下图所示:


图 16.3.12 Run Block Automation


2-11 自动连接完成后,发现Concat IP未连接,我们手动进行连接,如下图所示:


图 16.3.13 手动连接未连接的IP


另外添加的axis_data_fifo也未连接,我们同样手动连接。首先将DMA的M_AXIS_MM2S端口与axis_data_fifo的S_AXIS进行连接,如下图所示:


图 16.3.14 连接axis_data_fifo的S_AXIS


然后将axis_data_fifo上的M_AXIS端口连接到DMA的S_AXIS_S2MM端口,如下图所示:


图 16.3.15 连接axis_data_fifo上的M_AXIS


现在我们连接axis_data_fifo的时钟和复位。单击axis_data_fifo的s_axis_aresetn端口并将其连接到DMA的axi_resetn端口,单击axis_data_fifo的s_axis_aclk端口并将其连接到DMA的m_axi_mm2s_aclk端口,如下图所示:


图 16.3.16 axis_data_fifo的时钟和复位


2-12 为了方便截图显示,我们对axis_data_fifo模块进行了左右翻转。最终的IP模块连接图如下图所示:


图 16.3.17 模块连接图


2-13 到这里我们的Block Design就设计完成了,按“F6”键进行“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
step3:生成顶层HDL模块
3-1 在Sources窗口中,选中Design Sources下的, 这就是我们刚刚完成的Block Design设计。右键点击,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
step4:生成Bitstream文件并导出到SDK
4-1 在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”。在连续弹出的对话框中依次点击“YES”、“OK”。然后 Vivado 工具开始依次对设计进行综合、实现、并生成 Bitstream 文件。
4-2 导出硬件。
在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择File > Launch SDK,启动SDK软件。
16.4软件设计
step5:在SDK中创建应用工程
5-1 在SDK软件中新建一个名为“axi_dma_loop”的空白应用工程。
5-2 为应用工程新建一个名为“main.c”源文件,我们在新建的main.c文件中输入本次实验的代码。代码的主体部分如下所示:

  1. 1 /***************************** Include Files *********************************/
  2. 2
  3. 3 #include "xaxidma.h"
  4. 4 #include "x;
  5. 5 #include "xil_exce;
  6. 6 #include "x;
  7. 7
  8. 8 /************************** Constant Definitions *****************************/
  9. 9
  10. 10 #define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID
  11. 11 #define RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
  12. 12 #define TX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
  13. 13 #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
  14. 14 #define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR //0x00100000
  15. 15 #define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1000000) //0x01100000
  16. 16 #define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) //0x01200000
  17. 17 #define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) //0x01400000
  18. 18 #define RESET_TIMEOUT_COUNTER 10000 //复位时间
  19. 19 #define TEST_START_VALUE 0x0 //测试起始值
  20. 20 #define MAX_PKT_LEN 0x100 //发送包长度
  21. 21
  22. 22 /************************** Function Prototypes ******************************/
  23. 23
  24. 24 static int check_data(int length, u8 start_value);
  25. 25 static void tx_intr_handler(void *callback);
  26. 26 static void rx_intr_handler(void *callback);
  27. 27 static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
  28. 28 u16 tx_intr_id, u16 rx_intr_id);
  29. 29 static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
  30. 30 u16 rx_intr_id);
  31. 31
  32. 32 /************************** Variable Definitions *****************************/
  33. 33
  34. 34 static XAxiDma axidma; //XAxiDma实例
  35. 35 static XScuGic intc; //中断控制器的实例
  36. 36 volatile int tx_done; //发送完成标志
  37. 37 volatile int rx_done; //接收完成标志
  38. 38 volatile int error; //传输出错标志
  39. 39
  40. 40 /************************** Function Definitions *****************************/
  41. 41
  42. 42 int main(void)
  43. 43 {
  44. 44 int i;
  45. 45 int status;
  46. 46 u8 value;
  47. 47 u8 *tx_buffer_ptr;
  48. 48 u8 *rx_buffer_ptr;
  49. 49 XAxiDma_Config *config;
  50. 50
  51. 51 tx_buffer_ptr = (u8 *) TX_BUFFER_BASE;
  52. 52 rx_buffer_ptr = (u8 *) RX_BUFFER_BASE;
  53. 53
  54. 54 xil_printf("\r\n--- Entering main() --- \r\n");
  55. 55
  56. 56 config = XAxiDma_LookupConfig(DMA_DEV_ID);
  57. 57 if (!config) {
  58. 58 xil_printf("No config found for %d\r\n", DMA_DEV_ID);
  59. 59 return XST_FAILURE;
  60. 60 }
  61. 61
  62. 62 //初始化DMA引擎
  63. 63 status = XAxiDma_CfgInitialize(&axidma, config);
  64. 64 if (status != XST_SUCCESS) {
  65. 65 xil_printf("Initialization failed %d\r\n", status);
  66. 66 return XST_FAILURE;
  67. 67 }
  68. 68
  69. 69 if (XAxiDma_HasSg(&axidma)) {
  70. 70 xil_printf("Device configured as SG mode \r\n");
  71. 71 return XST_FAILURE;
  72. 72 }
  73. 73
  74. 74 //建立中断系统
  75. 75 status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
  76. 76 if (status != XST_SUCCESS) {
  77. 77 xil_printf("Failed intr setup\r\n");
  78. 78 return XST_FAILURE;
  79. 79 }
  80. 80
  81. 81 //初始化标志信号
  82. 82 tx_done = 0;
  83. 83 rx_done = 0;
  84. 84 error = 0;
  85. 85
  86. 86 value = TEST_START_VALUE;
  87. 87 for (i = 0; i < MAX_PKT_LEN; i++) {
  88. 88 tx_buffer_ptr = value;
  89. 89 value = (value + 1) & 0xFF;
  90. 90 }
  91. 91
  92. 92 Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache
  93. 93
  94. 94 //传送数据
  95. 95 status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr,
  96. 96 MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
  97. 97 if (status != XST_SUCCESS) {
  98. 98 return XST_FAILURE;
  99. 99 }
  100. 100
  101. 101 status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr,
  102. 102 MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
  103. 103 if (status != XST_SUCCESS) {
  104. 104 return XST_FAILURE;
  105. 105 }
  106. 106
  107. 107 Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache
  108. 108 while (!tx_done && !rx_done && !error)
  109. 109 ;
  110. 110 //传输出错
  111. 111 if (error) {
  112. 112 xil_printf("Failed test transmit%s done, "
  113. 113 "receive%s done\r\n", tx_done ? "" : " not",
  114. 114 rx_done ? "" : " not");
  115. 115 goto Done;
  116. 116 }
  117. 117
  118. 118 //传输完成,检查数据是否正确
  119. 119 status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
  120. 120 if (status != XST_SUCCESS) {
  121. 121 xil_printf("Data check failed\r\n");
  122. 122 goto Done;
  123. 123 }
  124. 124
  125. 125 xil_printf("Successfully ran AXI DMA Loop\r\n");
  126. 126 disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);
  127. 127
  128. 128 Done: xil_printf("--- Exiting main() --- \r\n");
  129. 129 return XST_SUCCESS;
  130. 130 }

复制代码


在代码的第14行,我们重新宏定义了XPAR_PS7_DDR_0_S_AXI_BASEADDR,即DDR3的基址,打开XPAR_PS7_DDR_0_S_AXI_BASEADDR的定义处,我们可以看到DDR3的基址为0x00100000,如下图所示:


图 16.4.1 DDR3的地址映射


从而DMA读取数据的起始地址TX_BUFFER_BASE为0x01200000,写入到DDR3中的起始地址RX_BUFFER_BASE为0x01400000。代码第19行TEST_START_VALUE为测试起始值,此处我们将其设为0x0,也可以改为其它任意值。第20行的MAX_PKT_LEN是DMA传输的数据包的长度,此处为0x100,即256。
代码第42行的main函数是程序的主体。第63行的XAxiDma_CfgInitialize函数初始化DMA引擎,第75行的setup_intr_system函数建立DMA中断系统。第87~90行向DDR3的指定地址写入数据,写入的第一个地址为TX_BUFFER_BASE即0x01200000,值为TEST_START_VALUE即0x0,写入的地址长度为MAX_PKT_LEN,即0x100。DMA从TX_BUFFER_BASE读取数据长度为MAX_PKT_LE的数据,然后写入到地址RX_BUFFER_BAS处。第92行的Xil_DcacheFlushRange函数刷新Data Cache,以防Data Cache缓存数据。从第95行到第105行配置并开启DMA传输数据。第107行再次刷新Data Cache,由于DDR3中的数据已经更新,但Cache中的数据并没有更新,CPU如果想从DDR3中读取数据需要刷新Data Cache。此处使用Xil_DcacheFlushRange函数,也可以使用Xil_DcacheInvalidateRange函数,使Data Cache指定范围的数据无效,函数调用方法相同。第119行的check_data函数检查当DMA传输完成后,写入的数据是否正确。第126行的disable_intr_system函数取消DMA中断。
主函数main中调用的自定义函数实现如下:

  1. 132 //检查数据缓冲区
  2. 133 static int check_data(int length, u8 start_value)
  3. 134 {
  4. 135 u8 value;
  5. 136 u8 *rx_packet;
  6. 137 int i = 0;
  7. 138
  8. 139 value = start_value;
  9. 140 rx_packet = (u8 *) RX_BUFFER_BASE;
  10. 141 for (i = 0; i < length; i++) {
  11. <span style="font-style: italic;"><span style="font-style: normal;">142 if (rx_packet != value) {
  12. 143 xil_printf("Data error %d: %x/%x\r\n", i, rx_packet</span><span style="font-style: normal;">, value);
  13. 144 return XST_FAILURE;
  14. 145 }
  15. 146 value = (value + 1) & 0xFF;
  16. 147 }
  17. 148
  18. 149 return XST_SUCCESS;
  19. 150 }
  20. 151
  21. 152 //DMA TX中断处理函数
  22. 153 static void tx_intr_handler(void *callback)
  23. 154 {
  24. 155 int timeout;
  25. 156 u32 irq_status;
  26. 157 XAxiDma *axidma_inst = (XAxiDma *) callback;
  27. 158
  28. 159 //读取待处理的中断
  29. 160 irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);
  30. 161 //确认待处理的中断
  31. 162 XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);
  32. 163
  33. 164 //Tx出错
  34. 165 if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
  35. 166 error = 1;
  36. 167 XAxiDma_Reset(axidma_inst);
  37. 168 timeout = RESET_TIMEOUT_COUNTER;
  38. 169 while (timeout) {
  39. 170 if (XAxiDma_ResetIsDone(axidma_inst))
  40. 171 break;
  41. 172 timeout -= 1;
  42. 173 }
  43. 174 return;
  44. 175 }
  45. 176
  46. 177 //Tx完成
  47. 178 if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
  48. 179 tx_done = 1;
  49. 180 }
  50. 181
  51. 182 //DMA RX中断处理函数
  52. 183 static void rx_intr_handler(void *callback)
  53. 184 {
  54. 185 u32 irq_status;
  55. 186 int timeout;
  56. 187 XAxiDma *axidma_inst = (XAxiDma *) callback;
  57. 188
  58. 189 irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);
  59. 190 XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);
  60. 191
  61. 192 //Rx出错
  62. 193 if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
  63. 194 error = 1;
  64. 195 XAxiDma_Reset(axidma_inst);
  65. 196 timeout = RESET_TIMEOUT_COUNTER;
  66. 197 while (timeout) {
  67. 198 if (XAxiDma_ResetIsDone(axidma_inst))
  68. 199 break;
  69. 200 timeout -= 1;
  70. 201 }
  71. 202 return;
  72. 203 }
  73. 204
  74. 205 //Rx完成
  75. 206 if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
  76. 207 rx_done = 1;
  77. 208 }
  78. 209
  79. 210 //建立DMA中断系统
  80. 211 // @param int_ins_ptr是指向XScuGic实例的指针
  81. 212 // @param AxiDmaPtr是指向DMA引擎实例的指针
  82. 213 // @param tx_intr_id是TX通道中断ID
  83. 214 // @param rx_intr_id是RX通道中断ID
  84. 215 // @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
  85. 216 static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
  86. 217 u16 tx_intr_id, u16 rx_intr_id)
  87. 218 {
  88. 219 int status;
  89. 220 XScuGic_Config *intc_config;
  90. 221
  91. 222 //初始化中断控制器驱动
  92. 223 intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
  93. 224 if (NULL == intc_config) {
  94. 225 return XST_FAILURE;
  95. 226 }
  96. 227 status = XScuGic_CfgInitialize(int_ins_ptr, intc_config,
  97. 228 intc_config->CpuBaseAddress);
  98. 229 if (status != XST_SUCCESS) {
  99. 230 return XST_FAILURE;
  100. 231 }
  101. 232
  102. 233 //设置优先级和触发类型
  103. 234 XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
  104. 235 XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);
  105. 236
  106. 237 //为中断设置中断处理函数
  107. 238 status = XScuGic_Connect(int_ins_ptr, tx_intr_id,
  108. 239 (Xil_InterruptHandler) tx_intr_handler, axidma_ptr);
  109. 240 if (status != XST_SUCCESS) {
  110. 241 return status;
  111. 242 }
  112. 243
  113. 244 status = XScuGic_Connect(int_ins_ptr, rx_intr_id,
  114. 245 (Xil_InterruptHandler) rx_intr_handler, axidma_ptr);
  115. 246 if (status != XST_SUCCESS) {
  116. 247 return status;
  117. 248 }
  118. 249
  119. 250 XScuGic_Enable(int_ins_ptr, tx_intr_id);
  120. 251 XScuGic_Enable(int_ins_ptr, rx_intr_id);
  121. 252
  122. 253 //启用来自硬件的中断
  123. 254 Xil_ExceptionInit();
  124. 255 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
  125. 256 (Xil_ExceptionHandler) XScuGic_InterruptHandler,
  126. 257 (void *) int_ins_ptr);
  127. 258 Xil_ExceptionEnable();
  128. 259
  129. 260 //使能DMA中断
  130. 261 XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
  131. 262 XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
  132. 263
  133. 264 return XST_SUCCESS;
  134. 265 }
  135. 266
  136. 267 //此函数禁用DMA引擎的中断
  137. 268 static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
  138. 269 u16 rx_intr_id)
  139. 270 {
  140. 271 XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
  141. 272 XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
  142. 273 }</span></span>

复制代码


代码第133行起的check_data函数用于检查写入到DDR3中的数据是否正确。
代码第153行起的DMA TX中断处理函数tx_intr_handler用于处理DMA发送中断。首先通过XAxiDma_IntrGetIrq函数读取待处理的中断,然后使用XAxiDma_IntrAckIrq函数确认待处理的中断。如果发现是接收出现错误的中断,则使用XAxiDma_Reset函数复位DMA,并使用XAxiDma_ResetIsDone函数判断是否复位完成。如果是发送完成的中断,则置位发送完成标志tx_done。代码第183行起的DMA RX中断处理函数与此类似。
代码第216行起的建立DMA中断系统函数setup_intr_system首先初始化中断控制器驱动,然后使用XScuGic_SetPriorityTriggerType函数设置DMA的优先级和触发类型。XScuGic_Connect函数为中断设置中断处理函数,因为有发送中断和接收中断,所以需要分别设置。XScuGic_Enable函数用于使能DMA发送中断和DMA接收中断源。最后启用来自硬件的中断和使用XAxiDma_IntrEnable函数使能DMA中断。
5-3 我们按快捷键Ctrl+S保存 main.c 文件,工具会自动进行编译,编译过程可以在SDK下方的控制台(Console)中看到。编译完成后 Console 中会出现提示信息“Build Finished”,同时生成elf文件。
16.5下载验证
首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用Mini USB连接线将开发板左侧的USB_UART接口与电脑连接,用于串口通信。最后连接开发板的电源,并打开电源开关。
step6:板级验证
6-1 在SDK软件下方的SDK Terminal窗口中点击右上角的加号并连接串口,并在弹出的窗口中对串口进行设置。
6-2 下载程序。因为本次实验使用了PL内的资源,因此我们在下载软件编译生成的elf文件之前,需要先下载硬件设计过程中生成的bitstream文件,对PL部分进行配置。
在菜单栏中点击“Xilinx”,然后选择“Program FPGA”。在弹出的对话框中Bitstream一栏,确认已经加载了本次实验硬件设计过程中所生成的 BIT 文件——“”。如 果 没 有 加 载 该 文 件 , 则 需 要 通 过 点 击 右 侧 的Browse按 钮 ,在 工 程 目 录 下 的axi_dma_loop\axi_dma_loop.runs\impl_1 文件夹中选择 文件。
最后点击右下角的“Program”,如下图所示:


图 16.5.1 配置 PL


配置PL完成后,接下来我们下载软件程序。在应用工程axi_dma_loop上右击,选择“Debug As”,然后选择第一项“1 Launch on Hardware (System Debugger)”,进入调试界面。
6-3 设置Memory Monitors。
在调试界面的右下角,打开Memory窗口,添加需要监视的储存器地址。首先我们添加地址0x1200000,也就是DMA从DDR3中读取数据的起始地址TX_BUFFER_BASE,添加方式如下图所示:


图 16.5.2 添加监视存储地址0x1200000


如果出现下图所示选项,选择“Hex Integer”,即16进制整数,便于观看,然后点击右边的“Add Rendering(s)”按钮。


图 16.5.3 选择数据显示类型


从下图可以看到,刚下载完程序后,DDR3的0x1200000地址处的值为FF。


图 16.5.4 0x1200000地址处的值


由于此种格式不方便查看具体地址的数据,我们将其设置成每个地址显示一个数据。鼠标右键点击Memory窗口右边的任意位置,在弹出的菜单中选择“Format…”,


图 16.5.5 选择“Format…”


在弹出的界面中,选择Column Size为1,然后点击“OK”按钮,如下图所示:


图 16.5.6 修改列大小


地址数据显示变成如下图所示,更方便观看。


图 16.5.7 0x1200000地址处的值


现在我们按照同样的方式添加并设置地址0x1400000,也就是DMA将数据写回DDR3中的起始地址RX_BUFFER_BASE。从下图可以看到,刚下载完程序后,DDR3的0x1400000地址处的值也为FF。


图 16.5.8 0x1400000地址处的值


6-4 设置运行断点。
我们在程序的以下几个地方设置断点,


图 16.5.9 设置断点


6-5 我们先将程序运行到断点1处,此时从Memory Monitors中可以看到0x1200000地址处的值变为00,紧随其后的地址的数据值逐次递增1。由于CPU与DDR3之间是通过Cache 交互的,数据暂存在Cache中,没有刷新Data Cache数据到DDR3,显示的数据是Data Cache中的。


图 16.5.10 0x1200000地址数据变化


接着运行到断点2处, Data Cache中的数据已经刷新到DDR3中。此时我们将Memory Monitors窗口中的监视内容切换到0x1400000,看看地址0x1400000处的数据是什么时候更新的,点击左侧的0x1400000即可切换。运行到断点3处,执行完第115行的DMA发送函数,完成从内存中读取数据传输给外设,即DMA从地址0x1200000处读取数据传输给外设,此时地址0x1400000处的数据未更新。运行到断点4处,执行完第121行的DMA接收函数,完成从外设读取数据写入到内存,即将刚才写入到外设的数据读取出来并从DDR3的地址0x1400000处开始写入,不过此时我们从下图发现地址0x1400000处的数据还是未更新。其实此时DDR3中的数据已经更新,只不过我们Data Cache中的数据未更新,而Memory Monitors窗口显示的正是Data Cache中的数据,所以需要刷新Data Cache。


图 16.5.11 0x1400000地址数据未变化


接着运行到断点5处,刷新Data Cache后,此时我们发现地址0x1400000处的值变为00,紧随其后的地址处的数据都变成预期的值。


图 16.5.12 刷新Data Cache后0x1400000地址数据变化


到此,使用DMA从DDR3中读取数据,并将数据写回到DDR3中的实验任务就完成了。继续往下执行,到程序结束,在下方的 SDK Terminal 中可以看到应用程序打印的信息,如下图所示:


图 16.5.13 串口终端中打印的信息

责任编辑: 鲁达

1.内容基于多重复合算法人工智能语言模型创作,旨在以深度学习研究为目的传播信息知识,内容观点与本网站无关,反馈举报请
2.仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证;
3.本站属于非营利性站点无毒无广告,请读者放心使用!

“ps7.0如何绘古典风格的字画边框”边界阅读