zynq的软硬协同测试
🧤

zynq的软硬协同测试

Tags
IC设计
FPGA
Projects
Published
August 17, 2024
Author
内容摘要
标签
是否记得

效果预览

notion image
💡
现在可以拨动按键实现对led的控制。
这个工程的目的在于学习zynq的PS端对PL端的操作,与之前《安路HDMI软硬测试》那篇文章类似,是为了充分理解soc原理,以及实现嵌入式编程。
zynq比安路开发板的编译环境更加完善,但同时也意味着操作更加复杂,需要仔细学习其中的每一个环节,才能最终保证嵌入式系统的稳定正常运行。
 

硬件操作

和安路开发板的使用一样,我们的工程同样分为硬件部分和软件部分,硬件部分实现底层逻辑,本次实验很简单,就是key对led的控制。软件部分则在vitis中编写,用C语言实现功能的开启和禁用。
在我目前的学习经历中,PS对PL的交互可以使用GPIO接口和AXI总线,GPIO的MIO接口连接着PS端的外设,EMIO接口可以用来拓展连接PL端的外设,这种连接比较简单,只能用来进行简单的操作逻辑;AXI总线是zynq内部重要通信管道,从AXI4-Lite到AXI_HP,AXI协议可以担负起zynq内部绝大部分高通量的信息传递需求。但使用AXI更加复杂,需要理解总线传输协议,并且对soc底层运转逻辑要有一个清晰的认知。本实验基于AXI总线进行交互。
为了要让zynq实现嵌入式编程,我们需要在vivado套件中引用processing_system处理器IP核,对IP核的操作可以在block design中进行,为此,我们要编写基于AXI总线的底层硬件逻辑,也需要自己封装一个带有AXI接口的IP核。

创建硬件IP核

创建IP核的过程基本上就参考正点原子的文件就好,我这里只记录怎么在官方提供的模版上修改verilog代码。
notion image
进入IP核的编辑界面,右半块的东西我们先不管,先看中间source部分的内容👉
我们把自己的IP核命名好名字后,系统会自动生成两级的.v文件结构,我们之前有编写AHBLite总线的经验,这个顶层.v文件就包含了AXI各种信号的声明,其中例化了二级.v文件,我们主要可以在这个二级.v文件(这里就是key2led_v1_0_S0_AXI.v)中实现我们想要的功能。
notion image
 
放在vivado中我们不方便看,可以打开vscode来编辑,我截图了一部分代码,大体就是这个样子。那我们能改哪里呢?先来想象我们的目的,就是想让key来控制led,这个在verilog中实现起来再简单不过了,但首先我们得声明两个信号,一个是led一个key,必须得有这两个引脚,才能实现操作。
notion image
 
在第一级.v文件中,我加入了这两句话:
input wire key, output wire led
同时,在下面紧接着有一个例化声明,我们把key和led也例化进去,像这样:
notion image
这一级文件就可以了,下面进入第二级.v文件
notion image
🐬
回过头来看,key2led_v1_0_S0_AXI.v程序中的大部分地方都不需要改,像AXI协议的控制部分我们甚至完全都不需要动,这也就是vivado的方便之处。我们只要把寄存器的功能写好,将自己的代码插入到add user logic here部分中就可以了。
 
还是常规操作,先声明两个端口,就可以了。
下面来编写硬件逻辑。
我的想法是这样的:我在软件中执行开关指令,当需要开时,我就把key和led连起来,实现key对led的直接操作;当需要关时,key就和led断开,他们之间便没有了连接。怎么实现呢?或者换句话说,怎么能够把这个软件指令发送到这个硬件中去呢?这时我们就要使用一个很重要的东西——寄存器!仔细浏览key2led_v1_0_S0_AXI.v 这个文件可以发现,程序中定义了四个寄存器,他们分别是:
slv_reg0 slv_reg1 slv_reg2 slv_reg3
他们的位宽可以自定义,本次定义是parameter integer C_S_AXI_DATA_WIDTH = 32 。这个寄存器就是我们本次操作的中间媒介。在soc中,这四个寄存器用来存储从ps发来的数据。在soc中系统会为这些寄存器分配地址,我们只要知道基地址的基础上定位到寄存器的地址,就可以向其发送数据。四个寄存器我们不需要全都用,我们这里只用slv_reg0的第一位,即slv_reg0[0]。硬件逻辑这么写:
// Add user logic here reg key_reg; reg led_reg; // 输入同步 always @(posedge S_AXI_ACLK) begin key_reg <= key; // 使用非阻塞赋值进行同步 end // LED 控制逻辑 always @(posedge S_AXI_ACLK) begin if (S_AXI_ARESETN == 1'b0) begin led_reg <= 1'b0; // 复位时 LED 关闭 end else begin if (slv_reg0[0] == 1'b1) begin led_reg <= key_reg; // 当使能时,LED 跟随按键状态 end // 注意:这里没有else语句,意味着当 slv_reg0[0] 为 0 时,led_reg 保持原值 end end // 输出赋值 assign led = led_reg; // User logic ends
硬件逻辑太简单了,一看就明白,if (slv_reg0[0] == 1'b1) begin led_reg <= key_reg; // 当使能时,LED 跟随按键状态 end这里实现了寄存器对硬件的控制。
写到这,硬件部分最难的地方就差不多完成了,之后就是什么封装IP核,并在vivado中将自己定义的IP核和processing_system连接起来,Generate Output Products以及Create HDL Wrapper的操作,并且约束好led与key的引脚,导出比特流,这些实现细节就去看正点原子的文档吧,这里不再过多赘述。

软件操作

软件部分在vitis中进行,这里就没有什么特别多的操作需要我们注意了,重心来到如何编程上面。先看一下完整的程序:
#include <stdio.h> #include "xparameters.h" #include "xil_io.h" // 控制开关:1 为开启LED控制,0 为关闭LED控制 #define ENABLE_LED_CONTROL 1 // 修改这里的值来开启(1)或关闭(0)LED控制 // 使用xparameters.h中定义的基地址 #define KEY2LED_BASEADDR XPAR_KEY2LED_0_S0_AXI_BASEADDR // 控制寄存器偏移量(假设为0,需要根据您的设计进行调整) #define CONTROL_REG_OFFSET 0 void setLEDControl(int enable) { u32 regValue; // 读取当前寄存器值 regValue = Xil_In32(KEY2LED_BASEADDR + CONTROL_REG_OFFSET); if (enable) { // 设置最低位为1,启用LED控制 regValue |= 0x00000001; } else { // 清除最低位,禁用LED控制 regValue &= ~0x00000001; } // 写回寄存器 Xil_Out32(KEY2LED_BASEADDR + CONTROL_REG_OFFSET, regValue); } int main() { printf("Key2LED Control Program\n"); // 根据ENABLE_LED_CONTROL的值来设置LED控制 setLEDControl(ENABLE_LED_CONTROL); if (ENABLE_LED_CONTROL) { printf("LED control enabled. LED should now follow the key state.\n"); } else { printf("LED control disabled. LED should now be off.\n"); } return 0; }
有时候感觉C语言写起来比verilog都要繁琐🤣。
注意,#define KEY2LED_BASEADDR XPAR_KEY2LED_0_S0_AXI_BASEADDR这一句很关键,它需要我们定义好soc的基地址,这一长串去哪找呢?没错,点开xparameters.h文件,我们去那里面看。
notion image
在这里,系统为我们的IP核分配了地址,我们可以直接使用它。
前面我们说过,我们希望通过slv_reg0[0]来控制key与led的连接,现在机会来了:
void setLEDControl(int enable) { u32 regValue; // 读取当前寄存器值 regValue = Xil_In32(KEY2LED_BASEADDR + CONTROL_REG_OFFSET); if (enable) { // 设置最低位为1,启用LED控制 regValue |= 0x00000001; } else { // 清除最低位,禁用LED控制 regValue &= ~0x00000001; } // 写回寄存器 Xil_Out32(KEY2LED_BASEADDR + CONTROL_REG_OFFSET, regValue); }
我们通过对这个寄存器进行赋值,改变它的最低位,就可以来影响硬件的连接关系了。

下载执行

硬件部分与软件部分都写完后,下面就来到了激动人心的时刻。我们把C语言编译好,下载到开发板里!铛铛!程序跑通了,功能实现了!

总结

历经一个下午的折腾,这个想法终于照进了现实,现在应该能大概明白软件是怎么操控硬件的了。未来可以进一步拓宽应用范围,争取实现更多功能的集成。