1. 为什么7020的Vitis程序固化值得单独拿出来讲
Xilinx Zynq-7000系列里的XC7Z020,也就是大家常说的7020,是很多工业控制、图像采集、通信设备的主力芯片。它内部集成了双核ARM Cortex-A9处理器和FPGA可编程逻辑,软硬协同的设计让它在嵌入式领域非常吃香。但很多朋友在Vitis里把程序调通之后,一到固化这一步就卡住了——JTAG在线调试跑得好好的,断电重启就什么都没了。
这个问题的本质是:在线调试时,程序是通过JTAG直接加载到DDR或者OCM里运行的,掉电即失。要让7020上电后自动运行你的程序,必须把启动镜像写入非易失性存储介质,通常是QSPI Flash或者SD卡。而Vitis环境下生成BOOT.bin并烧录到Flash的流程,和早期ISE/SDK时代有不少差异,工具链的更新也让很多老教程不再适用。
这篇文章面向的是已经能在Vitis里完成基本开发和在线调试、但对固化流程还不够熟悉的嵌入式工程师。我会把从工程配置、FSBL生成、BOOT.bin打包,到最终烧录进QSPI Flash的完整链路拆开讲清楚,包括每一步背后的原理和实际踩过的坑。不管你是刚接触Zynq的新手,还是从SDK迁移过来的老手,应该都能从中找到有用的东西。
2. 固化前的核心概念与整体思路拆解
2.1 Zynq启动流程到底是怎么走的
要理解固化,先得搞清楚7020上电之后发生了什么。Zynq的启动分三个阶段:
Stage 0是芯片内部的BootROM,这段代码是出厂固化在芯片里的,改不了。上电后BootROM会根据MIO引脚的电平状态判断启动模式——QSPI、SD卡、NAND、JTAG。判断完启动设备后,BootROM会去对应设备里找启动镜像,也就是BOOT.bin。
Stage 1是FSBL(First Stage Boot Loader),这是用户自己生成的。BootROM把FSBL加载到OCM(On-Chip Memory)里执行。FSBL做的事情包括:初始化DDR控制器、配置时钟、加载PL的bitstream、加载SSBL(通常是U-Boot)或者直接加载裸机应用。
Stage 2就是你的应用程序或者U-Boot。如果是裸机程序,FSBL直接把它加载到DDR里跳转执行;如果是Linux系统,FSBL加载U-Boot,U-Boot再加载内核和文件系统。
所以BOOT.bin里面至少包含两样东西:FSBL的ELF文件和你的应用程序ELF文件。如果用到PL逻辑,还要加上bitstream文件。
2.2 为什么BOOT.bin的组成文件顺序不能乱
BOOT.bin本质上是一个按照特定格式打包的镜像文件,BootROM和FSBL会按照分区顺序去解析它。标准的顺序是:
- FSBL.elf——必须放在第一个分区,BootROM第一个找的就是它
- system.bit——PL的配置文件,如果有的话
- 应用程序.elf——裸机程序或者U-Boot
这个顺序不是随便定的。BootROM的代码逻辑就是读取第一个分区作为FSBL来执行,FSBL再去处理后续分区。如果你把应用程序放在第一位,BootROM会把它当FSBL加载,结果就是跑不起来。
注意:在Vitis中创建Boot Image时,工具会自动识别文件类型并排序,但如果你手动指定顺序,一定要确保FSBL在第一位。
2.3 Vitis和旧版SDK在固化流程上的关键差异
很多朋友之前用SDK 2015.4、2016.4做固化很熟练,换到Vitis之后发现界面全变了,找不到原来的Create Boot Image选项。这里梳理一下主要差异:
| 对比项 | 旧版SDK | Vitis |
|---|---|---|
| 工程结构 | 独立Workspace | Workspace+Platform概念 |
| FSBL生成 | 新建FSBL工程 | 通过Platform或模板创建 |
| Boot Image工具 | Xilinx Tools菜单 | Vitis菜单或命令行 |
| Flash烧录 | Program Flash | Program Flash(路径不同) |
| 命令行工具 | bootgen | bootgen(参数兼容) |
Vitis把平台(Platform)的概念引入进来,FSBL的生成方式变成了基于平台创建。这个变化一开始确实让人不太适应,但理解了平台的概念之后,操作反而更清晰。
2.4 整体固化流程的四个阶段
我把整个固化流程分成四个阶段,后面会逐一展开:
- 第一阶段:确认硬件启动模式——检查MIO引脚配置,确保QSPI启动模式正确
- 第二阶段:生成FSBL——在Vitis中创建FSBL工程并编译
- 第三阶段:制作BOOT.bin——用Create Boot Image工具打包
- 第四阶段:烧录到Flash——通过JTAG将BOOT.bin写入QSPI Flash
每个阶段都有各自的注意事项,下面逐个拆解。
3. 核心细节解析与实操要点
3.1 FSBL工程的创建与关键配置
FSBL是整个启动链条的枢纽,它负责初始化DDR、加载bitstream、加载应用程序。在Vitis中创建FSBL的步骤如下:
首先,确保你已经创建了一个Platform工程,并且这个Platform包含了你的硬件设计(.xsa文件)。然后通过File → New → Application Project,在模板选择页面找到Zynq FSBL模板。Vitis会自动生成FSBL的源码工程。
FSBL里有几个关键配置需要关注:
DDR初始化参数。FSBL会根据你的硬件设计自动生成DDR的初始化代码,这些参数来自Vivado中的DDR配置。如果你在Vivado里改了DDR型号或者时序参数,一定要重新导出.xsa并更新Platform,否则FSBL初始化DDR会失败。
调试打印输出。FSBL默认会通过UART输出调试信息,这对于排查启动问题非常有帮助。在fsbl_debug.h中可以调整调试级别:
#define FSBL_DEBUG_INFO // 或者更详细的 #define FSBL_DEBUG_DETAILED看门狗处理。FSBL默认会关闭看门狗,如果你的系统需要在启动阶段保持看门狗开启,需要修改fsbl_hooks.c中的相关代码。
编译FSBL工程后,会在Debug或Release目录下生成fsbl.elf文件,这个文件就是后面打包BOOT.bin要用的。
实操心得:FSBL编译时如果报错找不到
xil_cache.h之类的头文件,通常是因为Board Support Package(BSP)没有正确配置。检查BSP设置中的standalone操作系统是否包含了必要的库。
3.2 应用程序ELF文件的生成注意事项
应用程序就是你实际要跑的业务代码。在Vitis中,应用程序工程会依赖一个BSP工程,BSP里包含了驱动库和硬件相关的配置。
生成应用程序ELF时需要注意几点:
链接地址的确认。裸机程序的链接地址通常是DDR的起始地址(比如0x00100000),这个地址必须和FSBL加载地址一致。在Vitis中,链接脚本(.ld文件)会自动生成,但如果你手动修改过,要确保没有冲突。
优化等级的选择。Release模式下编译器会做更多优化,生成的ELF文件更小,但有时候优化会导致一些时序敏感代码出问题。如果Release模式下程序跑飞了,先试试降到-O1或者-O0。
BSP的匹配。应用程序的BSP必须和FSBL使用同一个硬件平台,否则寄存器地址可能对不上。在Vitis中,同一个Platform下的所有工程共享硬件信息,这一点比SDK时代更清晰。
3.3 bitstream文件的准备与格式要求
如果你的7020设计中用到了PL逻辑,那么BOOT.bin里还需要包含bitstream文件。Vivado生成的bitstream文件是.bit格式,但BOOT.bin需要的是.bin格式。
转换方法有两种:
第一种是在Vivado中直接导出bin格式。在Generate Bitstream之后,通过File → Export → Export Bitstream选择Bin格式。
第二种是用bootgen命令行工具转换:
bootgen -image design.bif -arch zynq -process_bitstream bin其中design.bif是一个描述文件,内容大致如下:
all: { design.bit }执行后会生成design.bit.bin文件。
注意:bitstream文件在BOOT.bin中的位置必须在FSBL之后、应用程序之前。如果顺序放错了,FSBL加载PL配置时会找不到文件。
3.4 BOOT.bin打包的BIF文件编写
BIF(Boot Image Format)文件是bootgen工具的输入描述文件,它定义了BOOT.bin中包含哪些分区、每个分区的类型和属性。
一个典型的BIF文件内容如下:
the_ROM_image: { [bootloader] fsbl.elf system.bit application.elf }[bootloader]标记告诉bootgen这个文件是FSBL,会被放在第一个分区。如果没有这个标记,bootgen会按照文件顺序排列,但不会做FSBL的特殊处理。
如果需要加密或者认证,BIF文件还可以添加更多属性,比如:
the_ROM_image: { [bootloader, authentication=rsa] fsbl.elf [authentication=rsa] system.bit [authentication=rsa] application.elf }对于大多数普通应用场景,不需要加密认证,保持最简单的格式就行。
3.5 QSPI Flash的型号匹配与分区规划
7020开发板上常见的QSPI Flash型号有W25Q256、S25FL256等,容量一般是32MB或者64MB。烧录之前要确认Flash型号和Vitis中配置的是否一致。
在Vitis的Program Flash界面中,需要选择Flash类型。如果列表里没有你的Flash型号,可以选择兼容的通用配置,或者手动添加Flash参数。
Flash的分区规划也值得考虑。BOOT.bin通常放在Flash的起始地址(0x00000000),如果还有文件系统或者其他数据,需要规划好各自的偏移地址。对于裸机应用,一般整个Flash都给BOOT.bin用就够了。
4. 实操过程与核心环节实现
4.1 硬件启动模式的确认与设置
在开始烧录之前,先确认板子上的启动模式跳线或拨码开关设置正确。7020的启动模式由MIO[5:2]引脚决定:
| MIO[5:2] | 启动模式 |
|---|---|
| 0000 | JTAG |
| 0001 | QSPI |
| 0010 | SD卡 |
| 0100 | NAND |
烧录到QSPI Flash时,通常先把模式设为JTAG(方便烧录),烧录完成后再切换到QSPI模式。有些板子设计的是拨码开关,有些是跳线帽,具体看原理图。
实操心得:我遇到过一块板子,拨码开关标注的ON/OFF和实际电平是反的,导致一直启动不了。后来拿万用表量了MIO引脚的电压才确认。所以不要完全信丝印,关键时候用万用表确认一下。
4.2 在Vitis中创建Boot Image的完整步骤
打开Vitis,确保你的Platform和Application工程都已经编译通过。然后按照以下步骤操作:
第一步:菜单栏选择Xilinx → Create Boot Image。
第二步:在Boot Image Format页面,选择Zynq架构。
第三步:添加分区文件。点击Add按钮,依次添加:
- FSBL的ELF文件(
fsbl.elf),Partition Type选择bootloader - bitstream的bin文件(如果有),Partition Type选择
datafile - 应用程序的ELF文件,Partition Type选择
datafile
第四步:确认输出路径和文件名,默认是BOOT.bin。
第五步:点击Create Image,等待工具生成BOOT.bin。
生成成功后,可以在输出目录看到BOOT.bin文件,大小通常在几MB左右(取决于应用程序和bitstream的大小)。
4.3 通过JTAG烧录BOOT.bin到QSPI Flash
烧录这一步是整个流程中最容易出问题的环节。Vitis中烧录Flash的入口在Xilinx → Program Flash。
具体操作:
第一步:选择硬件平台。Vitis会自动检测连接的JTAG下载器,确认芯片型号是XC7Z020。
第二步:选择要烧录的文件,即刚才生成的BOOT.bin。
第三步:配置Flash类型。在Flash Type下拉框中选择你的QSPI Flash型号。如果找不到完全匹配的,选一个容量相同、指令集兼容的型号。
第四步:设置偏移地址。默认是0x00000000,如果BOOT.bin不是从Flash起始位置开始存放,需要修改。
第五步:点击Program,等待烧录完成。
烧录过程中,Vitis会先通过JTAG加载一个Flash编程器(Flash Programmer)到FPGA的OCM中运行,然后由这个编程器去操作QSPI控制器,把数据写入Flash。
注意:烧录时间取决于BOOT.bin的大小和Flash的写入速度。一个8MB的BOOT.bin,烧录时间大概在1到2分钟。如果超过5分钟还没完成,可能是Flash型号选错了或者硬件连接有问题。
4.4 烧录后的验证与启动测试
烧录完成后,不要急着断电。先通过Vitis的串口终端观察FSBL的输出信息。如果FSBL正常启动,你会看到类似下面的打印:
Xilinx First Stage Boot Loader Release 2023.1 Jan 15 2024-10:30:00 Devcfg driver initialized Silicon Version 3.1 Boot mode is QSPI Flash Base Address: 0xFC000000 ...看到这些信息说明FSBL已经成功从Flash加载并运行了。接下来FSBL会加载应用程序,如果应用程序有串口输出,也应该能看到。
确认一切正常后,断电,把启动模式切换到QSPI,重新上电。如果程序自动运行了,说明固化成功。
4.5 命令行方式生成BOOT.bin的备选方案
除了Vitis的图形界面,也可以直接用bootgen命令行工具生成BOOT.bin。这在自动化构建或者CI/CD流程中很有用。
命令格式:
bootgen -image boot.bif -arch zynq -o BOOT.bin -w on参数说明:
-image:指定BIF文件-arch:指定架构,7020是zynq-o:输出文件名-w:覆盖已存在的输出文件
BIF文件的内容和前面说的一样。命令行方式的好处是可以写进Makefile或者脚本里,每次编译完自动生成BOOT.bin。
5. 常见问题与排查技巧实录
5.1 Flash烧录失败的典型原因与解决方法
烧录失败是固化过程中最常见的问题,表现五花八门。我整理了一个速查表:
| 错误现象 | 可能原因 | 解决方法 |
|---|---|---|
| Cannot load flash device description | Flash型号未配置 | 手动添加Flash参数或选择兼容型号 |
| Failed to communicate with flash chip | JTAG连接不稳定 | 检查下载器连接,降低JTAG时钟频率 |
| Flash download failed - target dll cancelled | Flash编程器加载失败 | 重新上电,确保芯片处于JTAG模式 |
| Cannot load flash programming algorithm | 算法文件不匹配 | 确认Vitis版本和芯片型号匹配 |
| 烧录完成但启动无输出 | 启动模式不对 | 检查MIO引脚电平,确认QSPI模式 |
5.2 FSBL启动卡死的排查思路
FSBL跑不起来,串口没有任何输出,这种情况要从几个方面排查:
DDR初始化失败。这是最常见的原因。FSBL在初始化DDR时如果参数不对,会卡在ps7_init函数里。解决方法是用Vivado重新导出.xsa,确保DDR配置和实际硬件一致。
时钟配置错误。如果输入时钟频率和Vivado中配置的不一样,PLL锁定不了,FSBL也会卡死。检查晶振频率和Vivado中的设置是否匹配。
Flash中的BOOT.bin损坏。重新烧录一次,烧录时勾选Verify选项,确保数据写入正确。
串口波特率不对。FSBL默认波特率是115200,确认串口终端设置正确。
5.3 程序在JTAG下能跑但固化后不运行的经典问题
这个问题困扰过很多人。JTAG调试正常说明硬件和程序逻辑没问题,固化后不运行通常是以下几个原因:
启动模式没切换。烧录时是JTAG模式,烧录完忘了切到QSPI模式。这个低级错误我犯过不止一次。
BOOT.bin分区顺序错误。FSBL没有放在第一个分区,BootROM找不到有效的启动镜像。
应用程序链接地址冲突。应用程序的链接地址和FSBL使用的内存区域重叠了,导致FSBL加载应用程序时把自己覆盖了。
PL配置未加载。如果应用程序依赖PL逻辑,但bitstream没有正确打包进BOOT.bin,程序跑起来也会异常。
5.4 QSPI Flash型号不识别时的处理技巧
Vitis的Flash型号列表里不可能包含所有市面上的QSPI Flash。遇到不认识的型号时,可以尝试以下方法:
选择兼容型号。大多数QSPI Flash的指令集是兼容的,比如W25Q256和S25FL256的读写指令基本一致。选一个容量相同的型号通常能正常工作。
手动添加Flash参数。在Vitis的Flash配置目录下可以添加自定义的Flash描述文件,格式参考已有的配置文件。
用通用SPI模式。有些Flash支持标准SPI模式,可以尝试用通用的SPI Flash配置来烧录,虽然速度慢一些但兼容性更好。
实操心得:我手上有一批板子用的是GD25Q256,Vitis列表里没有这个型号。后来发现选W25Q256完全兼容,烧录和启动都正常。所以遇到不认识的Flash,先查数据手册对比指令集,大概率能找到替代型号。
5.5 固化后启动时间过长的优化方向
有些朋友反馈固化后启动要十几秒甚至更久。启动时间主要消耗在以下几个环节:
FSBL的调试打印。如果FSBL开启了详细调试输出,每个打印都要等UART发送完成,会显著增加启动时间。量产版本应该关闭调试输出。
DDR训练。FSBL每次启动都会做DDR初始化,这个过程需要一些时间。如果DDR参数已经稳定,可以考虑跳过部分训练步骤。
bitstream加载。PL配置文件的加载时间取决于bitstream大小和QSPI时钟频率。提高QSPI时钟频率可以加快加载速度,但要确保Flash支持。
应用程序初始化。如果应用程序本身初始化就很慢,那和固化无关,需要优化应用代码。
6. 几个容易被忽略的细节和进阶建议
6.1 Flash寿命与写入策略
QSPI Flash的擦写次数是有限的,通常标称10万次左右。调试阶段频繁烧录问题不大,但量产或者现场升级时要注意写入策略。比如可以先擦除再写入,避免不必要的全片擦除。另外,如果只是更新应用程序部分,可以考虑只烧录应用程序分区,而不是整个BOOT.bin重写。
6.2 多启动镜像的备份机制
对于可靠性要求高的场景,可以在Flash中存放两个BOOT.bin,一个主镜像一个备份镜像。FSBL启动失败时可以跳转到备份镜像。Zynq的BootROM支持多启动镜像机制,通过设置MIO引脚或者使用BootROM的fallback功能来实现。这个机制在工业控制领域特别实用,能有效降低现场设备变砖的风险。
6.3 从SDK迁移到Vitis的注意事项
如果你之前用SDK 2015.4或者更早版本做固化,迁移到Vitis时要注意:
- 工程文件格式变了,不能直接导入,需要重新创建
- FSBL的生成方式从独立工程变成了基于Platform
- BIF文件的语法基本兼容,但bootgen的版本更新了,某些参数可能有变化
- Flash编程器的路径和界面变了,但底层机制一样
我个人的建议是,如果旧项目还在用SDK维护,没必要强行迁移到Vitis。但如果新项目开始,直接用Vitis,熟悉之后效率更高。
6.4 调试阶段快速迭代的技巧
固化调试最烦的就是每次改代码都要重新生成BOOT.bin、重新烧录Flash,一次下来好几分钟。有几个方法可以加速迭代:
先用JTAG调试确认功能。功能没问题了再固化,避免反复烧录。
用SD卡启动做快速验证。把BOOT.bin放到SD卡里,改启动模式为SD卡,这样每次只需要替换SD卡里的文件,不用烧录Flash。
只烧录应用程序分区。如果FSBL和bitstream没变,只更新应用程序ELF对应的分区,烧录时间会短很多。
用Vitis的Program FPGA功能单独加载bitstream。调试PL逻辑时,不需要每次都打包BOOT.bin,直接用JTAG加载bitstream到PL就行。
这些技巧在实际项目中能省下大量时间。我自己在做7020项目时,前期基本都用SD卡启动来验证,等所有功能稳定了,最后才做一次QSPI固化。
6.5 关于7020使用JTAG固化Flash时是否需要DDR的疑问
这个问题在社区里经常被问到。答案是:FSBL在烧录过程中确实会初始化DDR,但Flash编程器本身不一定需要DDR。
具体来说,Vitis的Program Flash流程是:先通过JTAG把Flash编程器加载到OCM中运行,编程器负责擦除和写入Flash。FSBL在这个过程中也会被加载,用于初始化系统时钟和DDR控制器。但如果你的设计中没有使用DDR,或者DDR初始化有问题,可以尝试使用不依赖DDR的Flash编程流程。
不过在实际操作中,大多数7020设计都会用到DDR,所以FSBL初始化DDR是正常流程的一部分。如果DDR初始化失败导致烧录失败,那说明硬件设计或者FSBL配置有问题,需要先解决DDR的问题。
6.6 版本兼容性问题的处理经验
Xilinx工具链的版本兼容性一直是个让人头疼的问题。Vitis 2020.1和2023.1在Flash烧录界面上就有不少差异。我的经验是:
- Vivado和Vitis的版本必须匹配,不能混用
- .xsa文件的格式在不同版本间可能有变化,升级工具后建议重新导出
- bootgen的BIF语法在大版本间基本稳定,但新版本可能增加新属性
- Flash编程器的算法文件随版本更新,旧版算法可能不支持新Flash型号
如果遇到莫名其妙的烧录失败,先检查工具版本是否匹配,这能排除掉一大半问题。