PS 与 PL 如何高效交互?一个基于 Zynq 的真实案例

 

 近期,不少小伙伴反馈希望做一期关于PS与PL实际项目交互的应用分享。恰巧,最近有一位粉丝委托我完成一个小型项目,非常符合这一主题需求。

该项目的核心需求是:在PS端接收两路浮点型ADC数据,将数据发送至PL端,经特定算法处理后,再将结果传回PS端。设计采用DMA传输方式实现高效数据搬运,同时部分关键参数与控制指令通过reg寄存器进行传递。虽然看似结构简单,但该案例涵盖了Zynq平台中PS与PL交互的基本要素,具有很好的代表性。

因此,本文将以该项目为例,详细讲解我之前编写的DMA数据传输程序Zynq系统中的实际应用,帮助大家理解相关流程与实现方法。

该系统实现主要涵盖以下关键环节:

  1. PS端对DDR的读写操作:由PS负责完成与DDR存储器的数据交互。

  2. PL通过AXI协议读取DDR:PL经由AXI总线协议实现对DDR中数据的直接访问。

  3. PS与PL之间的寄存器交互:双方通过寄存器(Reg) 进行控制参数与状态信息的传递。

  4. PS与PL的GPIO交互机制:利用GPIO(通用输入输出) 实现简单的信号交互与触发控制。


📌 项目背景

  • 平台:Xilinx Zynq-7000 (zynq 7020)
  • 目的:实现ZYNQ平台PS与PL之间的数据交互,以及指令交互
  • 工具:vivado2018.3、Xilinx SDK 2018.3

✅功能展示

在文章开始之前,首先来看看本篇文章具体做了什么

①  通过PS端写2通道ADC数据到DDR

通过PS端写入2个通道累加数到DDR,此次写入的数据为0.1、1.1 .... 8.1、9.1

image

②  PL端通过读取DDR数据

image

③  然后在PL端实现算法运算

image

④ 然后在运算的结果发送给PS端

image

⑤ PS读取运算结果,并编写一个python脚本实现进行对比

image

 

完整工程下载

完整工程文件下载:https://pan.baidu.com/s/15l7tfuvo0PGK6MAGitcrmg?pwd=47d2(点击蓝色字体获取)

 

一、设计思路

        在读者已掌握AXI总线读写原理的基础上,本文将引导大家深入实战应用。本项目旨在通过PL实现特定的算法加速,并重点阐述PS与PL之间数据交互控制协同的关键环节。

公式:

image

公式说明:

  • aiai​ 和 bibi​ 分别代表通道A和通道B的第 i 个浮点数数据。

  • × 是乘法运算。

  • ∑i(1-n) 表示对从第1个到第n个的所有乘积结果进行求和。

  • Result 是最终计算出的标量结果。

        该设计基于 Zynq SoC 架构,PS 端负责接收两路浮点型 ADC 数据,并通过 DMA 搬运至 PL 端。PL 内部由 AXI 读模块、算法处理模块、AXI 写模块及寄存器组成,完成数据读取、算法计算和结果回写。最终结果存入 DDR3,由 PS 端应用程序获取使用。系统通过 AXI 总线实现高效数据传输,同时通过寄存器支持参数配置与控制指令交互。        

二、整体设计框图

本次设计的总体框图,如下所示

image

本设计基于 Zynq SoC 平台,系统由 PS(Processing System)端PL(Programmable Logic)端协同工作,完成数据接收、算法处理与结果返回的整体流程。

PS 端,ARM Cortex-A9 作为核心处理器,负责接收两路浮点型 ADC 数据,并通过 DMA 搬运方式将数据高效传输至 PL 端。同时,PS 端运行用户应用程序(user APP),与 DDR3 存储器交互,用于数据缓存和结果管理。此外,PS 端还通过 M_AXI GP 接口访问寄存器,实现对算法参数的配置与控制指令下发。

PL 端,系统主要包含 AXI 读模块、算法处理模块、AXI 写模块、AXI SmartConnect 总线互连寄存器模块

  • AXI 读模块:从 DDR3 中读取由 PS 搬运过来的 ADC 数据;

  • 算法处理模块:对输入的浮点型 ADC 数据执行特定算法,实现数据处理与计算;

  • AXI 写模块:将处理后的结果通过 AXI4 总线写回 DDR3;

  • AXI SmartConnect:完成 AXI 总线互联,保证数据在各模块间以及 PL 与 PS 之间的高效传输;

  • 寄存器模块:用于参数配置与控制指令交互,实现灵活可控的算法运行。

整个数据流动过程为:PS 端接收两路浮点型 ADC 数据 → 存入 DDR3 → 通过 AXI 读模块搬运至 PL 端 → 经过算法处理模块计算 → 结果写回 DDR3 → 由 PS 端 APP 获取并进一步使用或输出。

该架构在保证 高效数据传输(DMA+AXI 总线) 的同时,提供了 可配置的算法执行机制(寄存器控制),从而满足“两路浮点 ADC 数据 → PL 算法处理 → PS 结果输出”的设计需求。

三、PL端设计

        PL端作为核心计算单元,主要实现两大功能:一是通过AXI总线完成数据读写,并对ADC的浮点数据进行算法处理;二是完成ZYNQ芯片PS与PL之间硬件接口的配置。

3.1 zynq处理系统配置

配置MIO和电平信息

image

配置GP和HP接口

image

3.2 数据通路

通过HP接口读取两路ADC数据,并进行算法处理

image

将处理好的结果在写入PS

image

同时配置好reg寄存器,实现PL与PS参数传递

image

3.3 总结

        本章节主要讲解PL端的实现配置,其关键依然是我们熟悉的“AXI三剑客”:AXI Write模块AXI Read模块 和 Reg寄存器模块。本项目再次证明了这套模块化设计的强大通用性。它们不仅是ZYNQ PS-PL交互的利器,更是一套通用的AXI接口解决方案。只要深入理解其工作机理,未来面对各种复杂的AXI总线传输场景,都能迎刃而解。

AXI三剑客介绍文章:

https://blog.csdn.net/w18864443115/article/details/142936619

四、PS端设计

PS端主要负责以下三项核心任务:

  • 生成ADC仿真数据,并通过指定通路发送至PL端。

  • 配置并传递GPIO控制信号,用以协调PL端的工作状态。

  • 下发算法参数至PL端的配置寄存器,确保处理逻辑正确配置。

这部分比较简单,我就直接把代码粘贴到下面了,不懂地方可以评论区留言

其部分代码如下所示

while (1){
		xil_printf("程序开始!!!\n");
		xil_printf("你输入操作码:\n");
		xil_printf("(1) PS端写入浮点数据到DDR\t\n");
		xil_printf("(2) PS端读取浮点数据运算结果\t\n");
		xil_printf("(3) 测试读写浮点数据\t\n");
		xil_printf("(4) 测试BRAM内存读取\t\n");
		scanf(" %c", &j);
		switch (j) {
			case '1':
				xil_printf("PS写入数据到DDR\n");

				Xil_Out32(BRAM_BASE_ADDR,DATA_COUNT);
				// A channel
				for (int a=0;a<DATA_COUNT;a++) {
					data_test.f=(float)a + 0.1;
					Xil_Out32(WR_A_ADDR+a*4, data_test.u);
					printf("写入: %.2f (0x%08lX)到地址0x%X\n", data_test.f, data_test.u,WR_A_ADDR+a*4);
				}

				// B channel
				for (int a=0;a<DATA_COUNT;a++) {
					data_test.f=(float)a + 0.1;
					Xil_Out32(WR_B_ADDR+a*4, data_test.u);
					printf("写入: %.2f (0x%08lX)到地址0x%X\n", data_test.f, data_test.u,WR_B_ADDR+a*4);
				}
				Xil_DCacheFlushRange(WR_A_ADDR, DATA_COUNT * 4);  // 刷新 Cache
				Xil_DCacheFlushRange(WR_B_ADDR, DATA_COUNT * 4);  // 刷新 Cache

				xil_printf("触发 EMIO 信号...\n");
				trigger_emio(&Gpio, EMIO_GPIO_PIN + 54);

			break;

			case '2':
				xil_printf("读取DDR数据!!!\n");
				Xil_DCacheInvalidateRange(RD_ADDR, DATA_COUNT*4);	// 刷新地址 从SRC_ADDR 向后1024个字节地址
				data_buffer0.u = Xil_In32(RD_ADDR+(DATA_COUNT-1)*4);
				printf("addr = 0x%X,data= %.4f\n",RD_ADDR+(DATA_COUNT-1)*4,data_buffer0.f);

			break;

			case '3':
				xil_printf("测试浮点数据!!!\n");

				// 写入 DDR
				for (int a=0;a<16;a++) {
					data_test.f=(float)a + 0.1;
					Xil_Out32(RD_ADDR+a*4, data_test.u);
					printf("写入: %.2f (0x%08lX)\n", data_test.f, data_test.u);
				}

				Xil_DCacheFlushRange(RD_ADDR, 32 * 4);  // 刷新 Cache
				Xil_DCacheInvalidateRange(RD_ADDR, 32 * 4);	// 刷新
				// 读取 DDR
				for (int b=0;b<16;b++) {
					read_bits.u = Xil_In32(RD_ADDR+b*4);
					printf("读取: %.2f (0x%08lX)\n", read_bits.f, read_bits.u);
				}
			break;

			case '4':
				xil_printf("测试BRAM内存读取\t\n");

				Xil_Out32(BRAM_BASE_ADDR + 0x00, 0x12345678);
				printf("Wrote 0x12345678 to BRAM\n");
				u32 value = Xil_In32(BRAM_BASE_ADDR+0x00);
				printf("Read value: 0x%08X\n", value);

				u32 value_c = Xil_In32(BRAM_BASE_ADDR+0x00);
				printf("Read value: 0x%08X\n", value_c);
				// 读数据从 BRAM offset 0x00
				for(int c=0;c<10;c++) {
					u32 value_w = Xil_In32(BRAM_BASE_ADDR+c*4);
					printf("Read addr 0x%8X value: 0x%08X\n", BRAM_BASE_ADDR+c*4, value_w);
				}


			break;

			default :
				xil_printf("无效指令!\n");
			break;
		}

通过检索用户输入参数,来执行特定任务。

五、下板验证

        大部分功能演示在文章开头的功能展示部分已经展示过了,这里我将用一个视频的形式,给大家演示本设计的所以功能和介绍。

https://live.csdn.net/v/494510

至此,我们的PL 与 PS 数据交互实际应用demo  全流程就彻底验证完成了!!

下一节讲解更多的实际工程中调用并使用这AXI三个模块!!,大家有想看实现什么功能的也可以发到评论区,我给大家更新!!!!

制作不易,记得三连哦,给我动力,持续更新!!!

posted @ 2025-09-25 17:41  FPGAmaster  阅读(832)  评论(0)    收藏  举报