本篇文章包含的内容
前言
最近(2024.11.18)AMD官方推出了Vivado/Vitis 2024.2新版本,正好时间充裕,手头有空闲的开发板,所以想使用新板子和新版本的开发环境娱乐一下。所以就有了这一篇Vivado/Vitis 2024.2初体验的文章。
Vitis新版本将HLS和SDK融合在一起,并且以VSCode界面开发,使用clang编译器和cmake组织工程,相较于老版本的界面代码提示更快,界面逻辑性和美观性更强,目前给笔者的开发体验是比较好的。本文章相当于实现了之前SDK的功能。
- Vivado:2024.2
- Vitis:2024.2
- ZYNQ芯片:xc7z020clg400-2
- 开发板:Microphase Z7-Lite 7020版
1 ZYNQ MIO结构
ZYNQ 芯片的GPIO 分为三种,分别是MIO、EMIO和AXI_GPIO,其中MIO是PS的专用I/O,作为普通输入输出或者外设驱动I/O来使用,EMIO是扩展的MIO,当MIO不够时,可以使用部分PL的I/O做为MIO来使用,而AXI_GPIO是一个可以实现GPIO功能的IP核,PS端可以通过AXI总线对其进行操作。在需要众多I/O口的应用场合,AXI_GPIO可以将所有可用引脚作为I/O口使用。
ZYNQ的GPIO分为4个Bank,在官方手册中记为Bank0到Bank3。其中MIO连接Bank0和Bank1,在原理图中常常对应Bank500和Bank501。其余的Bank2和Bank3连接EMIO,他们本身是PL端的引脚,当PS端的引脚不够用,或者PS端必须通过PL端的引脚输出时,就可以将外设的端口连接到PL的EMIO上(也就是说,如果一个设备连接在PL的端口上,我们可以通过EMIO使用PS的CPU来控制这个端口)。
MIO是PS内核和PS外设沟通的桥梁。对于外设,相信熟悉STM32的开发者并不会陌生。UART0是最常用的PS外设之一,除此之外,PS端还拥有SPI、I2C、CAN、GPIO、SD、USB、ENET等众多外设。我们可以使用库函数的方式直接调用和使用这些外设,并且如果我们仅仅使用ZYNQ的PS端,那么开发流程和开发单片机裸机不会有太大区别。
相较于STM32,MIO的端口映射自由了很多,但它并不能完全做到像FPGA那样想映射哪个端口就映射在哪个端口。我们可以在多个可选的组合中进行配置,具体可选的方案在Vivado的Block Design界面就可以找到。
2 Vitis 设置
在这里记录一些新版Vitis使用的经验。
2.1 Vitis 串口助手使能
网络上关于Vitis设置的教程极少,很多设置和功能目前笔者也只能根据零星的资源和教程以及自己的经验摸索。甚至对比Vitis 2024.1和Vitis 2024.2,后者似乎删除了自带的串口助手,好在无伤大雅(也可能是AMD官方放在别的地方我没有找到),使用自己的串口助手(笔者使用vofa+)。
如果有朋友找到Vitis 2024.2自带的串口助手打开方式,敬请在评论区告知,感激不尽。通过在AMD官方论坛发帖提问,笔者得到了想要的答案。Vitis 2024.2自带的串口助手需要手动使能后开启。具体问题贴:Vitis 2024.2 may be missing serial monitor?。使用后感觉还不如自己的串口助手好用(貌似只能接收不能发送),不过AMD官方确实也声明了该功能还在开发中。
2.2 切换编程字体和大小
windows 11 更新了系统自带的Cascadia Code字体,笔者非常喜欢。如果你不是windows 11 系统,也可以自行安装字体后在这里更改。
更改字体大小同理,直接搜索即可。
2.3 查看库函数源码
使用.xsa
文件创建平台后,可以在打开平台中使能的外设的库函数代码。在这里以使能GPIO为例,在下面的路径查看库函数相关的源码。
2.4 查看例程
AMD官方为这些外设提供了详尽的手册说明和样例,非常有助于我们学习。可以在下面的位置找到每一个外设的手册链接和样例App。
3 MIO(GPIO)的常用库函数
GPIO的使用较为简单,编程思路可以大体分为以下三步:
- 初始化GPIO(创建XGpioPs对象和XGpioPs_Config指针,初始化GPIO),通过原理图查找GPIO对应的MIO编号;
- 设置GPIO的输入和输出方向。对于输出方向需要额外使能;
- 依靠程序逻辑,读取端口和写入端口。
3.1 初始化GPIO
XGpioPs_LookupConfig
:该函数仅接受一个参数,但是在xgpios.h
中却有两个声明。如果没有定义SDT
,则函数此时接受的参数就可能形如XPAR_XGPIOPS_0_DEVICE_ID
,如果是定义了,函数的参数就可能形如XPAR_XGPIOPS_0_BASEADDR
。具体使用时可以在工程中全局搜索(点击左侧搜索图标即可)查看该工程使用哪种定义,但是新版Vitis可以智能提示形参的名称。该函数返回一个XGpioPs_Config
结构体指针。
#ifndef SDT
XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId);
#else
XGpioPs_Config *XGpioPs_LookupConfig(u32 BaseAddress);
#endif
XGpioPs_CfgInitialize
:接受三个参数,分别是XGpioPs
结构体对象的地址,XGpioPs_Config
结构体指针和该结构体的BaseAddr
属性。可以用一个int
型变量检查返回值是否正常。
3.2 设置输出方向
-
XGpioPs_SetDirectionPin
:设置GPIO的输入输出方向。接受三个参数,分别是XGpioPs
结构体对象的地址,端口号,输出方向(0代表输入,1代表输出)。 -
XGpioPs_SetOutputEnablePin
:使能输出端口。输入端口不需要使能。接受三个参数,分别是XGpioPs
结构体对象的地址,端口号,是否使能(1代表使能)。
具体哪个端口号对应哪个GPIO,可以查看原理图:
#define MIOLED1 0 // LED 1 链接到 MIO0
#define MIOKEY1 9 // KEY 1 连接到 MIO0
#define INPUT 0
#define OUTPUT 1
3.3 读写端口
XGpioPs_ReadPin
:读取端口值,高电平返回1,低电平返回0。接受两个参数,分别是XGpioPs
结构体对象的地址,端口号。XGpioPs_WritePin
:输出对应电平,1输出高电平,0输出低电平。接受三个参数,分别是XGpioPs
结构体对象的地址,端口号,输出的值。
4 工程建立和代码编写
4.1 Vivado工程创建
点击创建新项目:
为项目命名,指定路径。注意项目路径不能包含中文和空格。关于工程的结构安排,笔者建议采用如下的结构,即将vitis的工作空间和vivado的项目文件夹放在同一目录下,这样同一个功能只有一个目录,其中包含两个工程文件夹,条理清晰,便于管理。
选择工程类型,保持默认(也可以在这里勾选Do not specify sources at this time,勾选后添加.v
源文件和约束文件的界面就不会出现)。
添加.v
源文件,也可以不添加,后续在工程中添加。
添加约束文件,也可以不添加,后续在工程中添加,或者通过界面化的操作配置。
选择器件型号。
总结页面。
4.2 创建Block Design
点击Create Block Design来创建Block Design。每个工程只有一个Block Design,所以这里Block Design的名称建议和工程名字相同,也可以填写更多信息。
点击加号添加IP,输入zynq找到Processing System IP核,双击添加到Block Design。
双击IP核打开配置界面。
该界面支持多种方式配置外设。这里提供常用的一种。点击Peripheral I/O Pins窗口 ,由于本次实验需要使用MIO连接到GPIO控制外部引脚,并且我们通常使用串口来调试程序或者方便地观察实验现象,所以在这里勾选UART0和GPIO MIO。UART0点击Bank 0 14 15号端口的小方块,小方块变绿即可完成使能。
在PS-PL Configuration中设置UART0的波特率,默认115200。
在MIO Configuration中可以找到UART0的端口设置,速度设置,电平标准,是否默认上拉等选项(直接勾选这里的复选框也可以直接使能外设)。
本次实验不需要PS和PL进行数据交互,所以我们可以将默认添加的PL Fabric Clock和Reset(FCLK_RESET0_N)信号都取消掉,把默认添加的AXI GP0端口也取消掉,如下面三张图所示。
根据开发板上的DDR3型号和片数选择对应的信号的位宽。例如Microphase Z7-Lite使用的DDR3芯片是美光的MT41J256M16 RE-125,只焊接了一片,所以尾款选择16bit。其他设置保持默认即可。
如果你厌倦了每次都配置ZYNQ的PS IP,可以选择上面的Presets中的Save Configuration保存当前预设为
.tcl
文件。需要读取时选择Apply Configuration读取预设。
完成ZYNQ PS IP配置。点击OK确定。点击Block Design上方的Run Block Automation进行自动端口约束定义。ZYNQ PS端的DDR接在芯片上的哪个引脚是不能确定的,其余的引脚也会根据你在IP核内的配置自动完成。
点击Validate Design验证当前模块的连线。
在Source板块,右键Block Design,点击Generate Output Products生成当前Block的底层部件。
在这个界面,需要注意的是Synthesis Options。参考文章初用vitis2024.1之Hello world的描述。
- Global:任何工程文件的更改都会重新综合Block Deisgn,这个选择会减慢综合速度,但是据笔者的经验,当工程体量过大时,这个选择或许可以避免一些奇怪的Bug。
- Out of Context per IP:为Block Design中的每一个 IP 生成输出结果,并且为每个 IP 生成 DCP(design checkpoint)文件。如果勾选此选项,可以显著地减少综合的运行时间。这是因为 OOC 综合方式只运行一次,在特定的IP 没有改变的情况下,可以防止 Vivado 在综合时重复的生成特定 IP 的输出结果。
- Out of context per Block Design:此选项可以独立于顶层设计而单独地综合完整的 BD,并且可以为 BD 生成 DCP 文件,通常是在第三方综合时才会勾选此选项。
为Block Design生成顶层RTL“外套”。
4.3 Vitis工程创建
本次实验不涉及任何PL逻辑,所以不需要生成bit流文件,完成上面的任务即可直接生成.xsa
文件。
我们没有生成bitstream文件,所以选择第一项。
指定.xsa
文件的名称,这个名称默认会和顶层模块保持一致。默认存放路径是vivado工程的根目录。
打开Vitis IDE。
进入Vitis IDE,首先选择工作区。之前以及建立好了工作区文件夹,这里可以直接选择。
对于Vitis Embedded Development,有多种方法创建工程:可以在左侧的菜单创建,也可以在右侧的欢迎界面创建,但是点击哪个按钮在这里不重要。读者需要知道的是,Vitis的工程逻辑是:由.xsa
文件生成一个平台部件(Platform Component),这个平台部件包含了我们在Vivado配置的所有硬件信息,包括Block Design中的所有IP,如果你设计了RTL代码,那么它也包含你写的PL逻辑。之后,在这个平台部件上,我们可以创建多个应用部件(Application Component),应用部件完全由软件逻辑组成,也就是由CMake组织的C/C++代码,它们应该运行在PS的ARM核CPU上。每个应用部件彼此分离,可以独立编译烧录。事实上,如果你阅读了本文章第二部分的内容,你会发现平台部件中还包含我们在ZYNQ PS IP配置中配置的所有外设的库函数,怎么查看这些库函数和它们的应用示例,在这里不再赘述。
所以,我们应该先创建一个平台部件,再创建应用部件。笔者建议,一个工作区只存放一个平台部件,但是可以存放多个应用部件。点击左侧菜单的对应位置可以依次创建,点击右侧欢迎页面中的按钮可以一次操作创建完成。可以创建一个空的应用部件,也可以导入一个简单的例子来减少我们的代码书写量。在这里,笔者为了文章的逻辑清晰,选择左边的按钮依次创建。首先点击Create Platform Component。
定义平台部件的名称。笔者习惯将这个部件的特点表示在名称中,例如这里,这个硬件平台只能用咋Z7Lite开发板上,使能了MIO和UART,所以采用这样的命名。采用什么命名是你的自由,但是一定要和其他工程文件和开发版有所区分,因为随着你技术的提高,你大概率不会只创建一个平台部件,也不会只使用一个开发板。
点击Next,点击Hardware Design,导入之前创建的.xsa
文件。之后一路next保持默认。
在这里可以看到当前操作系统(标准,可以理解为裸机)和处理器的信息。
平台部件创建完成。
接下来创建应用部件。点击左侧的Examples按钮,选择Hello World样例,输入应用部件的名称,点击Next。
选择刚才创建的平台部件。后面的页面展示一些信息,一路保持默认,然后Next。
应用部件创建完成。打开main函数存在的helloworld.c
文件,发现存在一个报错?不要慌,点击左下角FLOW面板的Build编译当前应用部件,之后切换到别的 .c
文件再切换回来(或者重新打开文件),报错即刻消失,此时代码拥有了智能提醒的功能。
连接开发板,打开串口助手连接对应的端口,点击FLOW面板的Run,不久即可看到接受窗口正确地收到了信息。
4.4 GPIO操作
AMD官方已经将开发流程作了足够的简化。如果你仔细观察helloworld.c
中的main
函数,在程序开始之前你需要init_platform()
,程序结束后你需要cleanup_platform()
。这两个函数究竟干了什么,目前还不需要深究。结合本文章第三部分和第四部分的描述,补充这个文件,相信你可以优雅地完成使用Key操控LED的操作。笔者体验下来,开发过程还是比较怡人的(除了Vitis 2024.2的serial monitor找不到了)。
/******************************************************************************
* Copyright (C) 2023 Advanced Micro Devices, Inc. All Rights Reserved.
* SPDX-License-Identifier: MIT
******************************************************************************/
/*
* helloworld.c: simple test application
*
* This application configures UART 16550 to baud rate 9600.
* PS7 UART (Zynq) is not initialized by this application, since
* bootrom/bsp configures it to baud rate 115200
*
* ------------------------------------------------
* | UART TYPE BAUD RATE |
* ------------------------------------------------
* uartns550 9600
* uartlite Configurable only in HW design
* ps7_uart 115200 (configured by bootrom/bsp)
*/
#include <stdio.h>
#include <xstatus.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpiops.h"
#include "xgpiops_hw.h"
#define MIOLED1 0 // LED 1 链接到 MIO0
#define MIOKEY1 9 // KEY 1 连接到 MIO0
#define INPUT 0
#define OUTPUT 1
XGpioPs Gpios; // “例化”一个GPIO对象
int main()
{
init_platform();
int status;
XGpioPs_Config *ConfigPtr;
print("MIO Test!\n\r");
// 初始化GPIO
ConfigPtr = XGpioPs_LookupConfig(XPAR_XGPIOPS_0_BASEADDR);
status = XGpioPs_CfgInitialize(&Gpios, ConfigPtr, ConfigPtr->BaseAddr);
if (status != XST_SUCCESS) {
return XST_FAILURE;
}
// 设置MIO方向
XGpioPs_SetDirectionPin(&Gpios, MIOLED1, OUTPUT);
XGpioPs_SetOutputEnablePin(&Gpios, MIOLED1, 1); // 使能输出
XGpioPs_SetDirectionPin(&Gpios, MIOKEY1, INPUT);
while(1) {
if (XGpioPs_ReadPin(&Gpios, MIOKEY1)) {
XGpioPs_WritePin(&Gpios, MIOLED1, 1);
} else {
XGpioPs_WritePin(&Gpios, MIOLED1, 0);
}
}
cleanup_platform();
return XST_SUCCESS;
}
原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。