
正文在Vivado 2020.1里用Vitis给Zynq生成QSPI启动文件我相信不少人都被下面这行红字卡过ERROR: Failed to copy boot image to ...中文界面下直接翻译成“尝试复制启动文件失败”。我第一次遇到时第一反应是重新点一遍Xilinx → Create Boot Image结果连点三次三次挂在同一个地方。后来冷静下来看日志才发现这个报错的关键词是“复制”而不是“生成”也就是说bootgen其实已经把BOOT.BIN拼出来了卡住的是Vitis把镜像从临时目录挪到工程目录这最后一步。这篇文章就把2020.1这条链路上所有可能出问题的点以及我当时怎么一步步排查、最后怎么绕过去的过程完整写出来希望对卡在这个地方的朋友有帮助。1. QSPI启动镜像的组装原理BOOT.BIN里到底装了什么1.1 Flash并非整包烧录BootROM、FSBL与分区表的接力关系很多人第一次做QSPI启动时会以为把生成的某个文件整包烧进Flash上电后芯片就能跑起来。实际上Zynq上电后的启动过程是分段接力完成的理解这一点后面排查报错会轻松很多。上电瞬间片内BootROM会先接管它做的事情非常有限初始化引脚检测启动模式是QSPI、SD还是JTAG然后去读取外部存储设备开头的一段数据。这段数据里包含一个启动头里面有关键的跳转信息。BootROM会根据启动头的信息把存放在Flash里的FSBLFirst Stage Boot Loader的第一部分拷贝到片内OCM运行。FSBL紧接着接手它要干的事就多了初始化DDR、PLL、MIO等硬件资源把bitstream从Flash读出来配置到PL端再把应用程序或者U-Boot从Flash读到DDR里最后跳转过去执行。而BOOT.BIN这个文件本质就是把FSBL、bitstream、应用程序这几个二进制体按照一定格式拼接起来的容器。每个部分称为一个“分区”每个分区前面都有头部描述符注明分区类型、加载地址、大小、校验方式等。BootROM和FSBL正是靠这些头部信息才能按顺序把每个分区加载到目标位置。顺带说一句QSPI和普通SPI的差别也在这里体现。普通SPI只有4根线CS、CLK、MOSI、MISO只能单线读写而QSPI增加了IO2、IO3支持Dual/Quad模式也就是一次可以并行走2根或4根线读速度提升明显。DSPI其实就是Dual SPI这几种叫法在Xilinx文档里很常见。项目普通SPIQSPI(QSPI)数据线数量1收1发4根IO线传输模式SingleSingle / Dual / Quad典型用途传感器、低速配置启动镜像、配置存储XIP支持较弱支持可片内直接执行所以Flash里最终存的不是“一个文件”而是一张有组织结构的镜像表BOOT.BIN就是这张表的物理载体。1.2 Vitis 2020.1里生成镜像的完整链路与断点整个生成过程在Vitis里是一条明确的链路Vivado里完成硬件工程综合实现生成bitstream。File → Export Hardware导出XSA文件硬件描述bitstream的打包体。打开Vitis用XSA新建Platform工程工具会自动生成BSP和FSBL。在Platform上新建Application工程写应用代码并编译出elf。菜单Xilinx → Create Boot Image在对话框里把FSBL、bitstream、应用程序elf都加进去点生成。第五步就是报错“尝试复制启动文件失败”的地方。Vitis内部实际上是调用了Vivado安装目录下的bootgen工具这个工具会按照一个BIFBoot Image Format描述文件把几个输入文件拼成BOOT.BIN。Vitis的Create Boot Image对话框只是帮你在后台写好BIF再调用bootgen执行等bootgen返回成功之后它再把生成的BOOT.BIN复制到应用工程的_ide/images目录。问题就出在“等bootgen返回成功之后”的复制环节。如果你仔细看日志会发现在红字报错之前bootgen相关的log往往是正常的也就是说镜像本体已经生成完毕。那Vitis为什么连自己刚生成的文件都拷不动这就是接下来要讲的排查重点。2. “尝试复制启动文件失败”这个报错的定位过程2.1 报错不是bootgen给的先分清生成失败和复制失败遇到报错别急着重试先把完整日志看一遍。在Vitis 2020.1里Console窗口会把bootgen的输出和最后复制失败的ERROR都打出来。正常布局下你会看到类似这样的东西INFO: [Bootgen 55-133] Using BIF file: C:/work/zynq_qspi/app/_ide/bootimage/boot.bif INFO: [Bootgen 55-130] Output BOOT.BIN file name: C:/work/zynq_qspi/app/Debug/BOOT.BIN INFO: [Bootgen 55-160] Generating image... ... ERROR: Failed to copy boot image to C:/work/zynq_qspi/app/_ide/images/BOOT.BIN注意最后一行是Failed to copy不是bootgen的ERROR: [Bootgen 55-xxx]。这说明bootgen已经把BOOT.BIN输出到了app/Debug目录甚至在那个位置你已经能看到一个正常的BOOT.BIN文件但Vitis去复制它的时候失败了。所以排查方向应该锁定在“文件为什么拷不进去”而不是去怀疑FSBL、bitstream或应用elf有问题。我当时还干过一件蠢事——反复调整Create Boot Image对话框里三个文件的顺序以为分区顺序不对。其实那个顺序只影响BootROM能不能正确跳转不影响复制动作本身。2.2 第一嫌疑工程路径里的中文、空格和长路径我在多个项目里验证过这个报错最常见的元凶就是路径。Vitis 2020.1对路径的容忍度远没有你想象的那么高尤其是Windows环境下下面几种情况都会让复制阶段莫名其妙失败工程路径含中文比如D:\开发板\基于Zynq的项目\...路径含空格比如D:\work\my project\...路径过长Vitis在拼接内部路径时超过Windows默认的260字符限制工程放在网络盘或映射盘上Vitis内部脚本在处理这些路径时有些地方引号没加全有些地方按字符截断结果就是bootgen能通过命令行参数找到输入文件、生成BOOT.BIN但后续文件系统操作直接挂掉。我当时的情况是工程建在C:\Users\用户名\Vitis-workspace\...下用户名本身带中文Vitis生成的临时路径里有中文复制就失败了。排查验证的方法很简单在Vitis里把workspace切到一个纯英文短路径下例如D:\fpga\zynq\ws重新新建或导入工程再生成一次Boot Image如果你不想推翻重来可以先手工复制一下日志里那个BOOT.BIN路径用资源管理器打开看看文件在不在。如果在手动把它复制到_ide/images目录Vitis后续烧写一样能认——这算是一种应急办法但不推荐长期用因为工程路径问题后面还会以别的方式出现。2.3 第二嫌疑workspace目录的写权限与磁盘占用如果路径干净还是报复制失败那就看权限。Vitis 2020.1默认workspace可能在用户目录下一般情况下问题不大。但如果你把workspace放在C:\Program Files、C:\根目录、或者系统目录下UAC会把写操作拦截掉。Vitis本身是以普通权限运行的bootgen往系统保护目录写文件时往往没反应但后续复制动作会直接报权限不足。还有种隐蔽情况用于放workspace的磁盘分区满了。bootgen生成BOOT.BIN需要临时空间复制到_ide/images也需要空间。BOOT.BIN一般只有几MB到几十MB但如果磁盘只剩几百MB同时Windows还在写页面文件Vitis复制文件就可能触发磁盘满错误。可以顺手检查一下磁盘剩余空间。排查步骤右键workspace目录属性 → 安全确认当前用户有“完全控制”权限。把workspace从系统目录挪到独立数据盘。清理磁盘保证有至少1GB可用空间。2.4 第三嫌疑杀毒软件、双开Vitis与文件占用锁这个原因比较隐蔽特别是在Windows 10/11自带Defender的环境下。Vitis的Create Boot Image流程是bootgen生成BOOT.BIN → 立刻被Defender实时防护扫描 → 扫描未结束前Vitis就去复制文件 → 复制操作因为文件被占用而失败。杀毒软件导致的“复制启动文件失败”有个特征报错不稳定有时能成功有时失败而且重试时偶尔又好了。另外如果你的机器上同时开了两个Vitis实例或者上次Vitis异常退出后有残留的java/进程没清干净它们可能会锁住workspace里的文件或目录。遇到这种情况直接任务管理器结束所有与Xilinx/Vitis相关的进程再重新打开。我当时最后定位到的原因比较奇怪C盘临时目录被人清理软件设置成了C:\Temp而Vitis脚本默认还在往%TEMP%里写文件两个路径不一致导致生成流程的临时文件和工程目录位于不同盘符。跨盘复制偶尔也会因为Vitis脚本的路径拼接出错而失败。这个也算在内遇到跨盘的情况优先统一到同一盘符。为了帮你快速定位我做了个排查表现象优先排查项验证方法报错且BOOT.BIN不存在bootgen本身失败看bootgen日志的具体ERROR报错但Debug目录里有BOOT.BIN复制环节失败检查路径、权限、杀软报错有时成功有时失败Defender或杀软扫描临时关闭实时防护重试报错且workspace路径含中文路径兼容问题换纯英文短路径3. 绕过GUI的手工打包方案手动bootgen一张BOOT.BIN3.1 bootgen工具所在位置与调用方式既然Vitis的GUI在复制环节容易出错一个很直接的思路就是绕开它手动调用bootgen自己打包。这也是我后来项目中大量采用的方式因为它不仅绕开了报错还让镜像生成过程变得可脚本化、可复现。bootgen是Vivado安装目录下的一个独立可执行工具。以2020.1为例Windows下路径通常为C:\Xilinx\Vivado\2020.1\bin\bootgen.bat打开一个CMD窗口先切到你的工作目录然后直接全路径调用。如果提示找不到动态库之类的环境变量问题就先在CMD里执行一遍Vivado的环境配置脚本C:\Xilinx\Vivado\2020.1\settings64.bat执行后bootgen命令就能直接用了。Linux下同理source settings64.sh后在Shell里执行bootgen。3.2 手写一套可用的BIF文件bootgen的核心输入是一个BIF文件。我们在Vitis里Create Boot Image时Vitis会在后台生成一份boot.bif你可以在工程目录的_ide/bootimage下看到它。但现在我们要自己写。拿最常见的Zynq-7000平台举例新建一个文本文件boot.bif内容如下the_ROM_image: { [bootloader] .\zynq_fsbl.elf .\system.bit .\app.elf }三个文件按顺序对应FSBL、bitstream、应用elf。注意[bootloader]标记必须加在FSBL那一行原因后面第4节会展开讲。如果你用的是Zynq UltraScale MPSoCBIF会稍微复杂一点因为需要加入PMUFW如果启动Linux还需要ATFthe_ROM_image: { [bootloader] .\fsbl.elf .\pmufw.elf .\bl31.elf .\system.bit .\app.elf }裸机应用不跑Linux时ATF可以省略但PMUFW是必须的。写好BIF后把FSBL、bitstream、应用elf这三个文件复制到同一个目录在CMD里执行bootgen -image boot.bif -o BOOT.BIN -w on -arch zynq对于ZynqMP把最后改成-arch zynqmp。-w on表示覆盖已有输出文件。如果一切顺利当前目录下会出现一个BOOT.BIN。3.3 手工打包验证通过后如何让工程恢复正常手工生成BOOT.BIN后怎么验证它确实可用先用十六进制工具打开文件看开头的字节。Zynq启动镜像头的魔术字是0xAA995566如果你能在文件头部看到AA 99 55 66说明头已经正确写入。再用dir看文件大小是否合理——如果FSBL有几MBbitstream有几十MBBOOT.BIN应该和各输入文件大小之和接近。验证OK之后你可以把它手动复制到Vitis工程原来的_ide/images目录。后面如果再通过Vitis的Program Flash Memory烧写直接选这个文件就行。不过我更建议的是不要恢复GUI流程直接把手工bootgen的命令写成一个批处理或Makefile脚本把BIF文件纳入版本管理。这样每次改完代码只需要跑一遍脚本镜像生成就完成了。一来规避了2020.1这个复制bug二来以后切到CI流水线也方便。4. 别再只看最后一步XSA、FSBL和旧工程迁移的连环雷4.1 导出的XSA里根本没有bitstream手工bootgen解决了复制失败但有些人的问题根本没有到复制那一步——bootgen本身就会报错。这种情况往往是源头就埋了雷最常见的就是导出的XSA里没有bitstream。很多初学者在Vivado里完成综合实现后直接File → Export Hardware弹出的对话框里有个复选框“Include bitstream”默认是不勾选的。如果不勾导出的XSA里只有硬件描述没有PL端的配置文件。到Vitis里你会发现问题不是立即出现的。创建Platform、编译应用都正常因为软件工程不依赖bitstream。直到Create Boot Image时你发现列表里没有可以添加的.bit文件或者你想添加bit文件但工具提示找不到。解决办法回到Vivado重新Export Hardware时勾上Include bitstream覆盖导出XSA然后在Vitis里右键Platform工程 → Update Hardware Specification重新指向新XSA重新编译平台工程。之后再执行Boot Image生成分区里才会有bit。4.2 平台工程没生成FSBL“找不到bootloader分区”的连锁反应FSBL在整个启动链中的作用相当于“二传手”。如果你在创建Platform工程时没有勾选“Generate boot components”Vitis不会生成FSBL而你后续Create Boot Image时才想着加FSBL自然找不到。这种情况在Vitis 2020.1里很典型。有些老工程师习惯了SDK时代自动生成FSBL的流程换到Vitis后新建Platform时习惯性把不必要的选项取消掉结果就踩坑。FSBL缺失时bootgen会报类似这样的错误ERROR: [Bootgen 55-152] Need at least one BOOTLOADER partition这不是“复制失败”的问题而是BIF里根本没标出哪个分区是bootloader。手工写BIF时如果忘了在FSBL那行加[bootloader]标记一样会报这个错。解决方法是回到Platform工程右键 → 打开平台配置对话框确认“Generate boot components”已勾选然后重新Build Platform。生成后你会在Platform工程的bsp目录下看到fsbl工程编译它得到zynq_fsbl.elf。另一种情况是FSBL明明存在但Create Boot Image对话框把它识别成了普通应用分区。手动检查对话框里的分区列表FSBL对应的Partition Type必须显式选为“bootloader”。4.3 从SDK老工程迁移到Vitis时的工程结构异变热词里有个“vitis打开已有工程”这确实是2020.1的重灾区。很多手里有老SDK工程的人图省事直接把SDK的workspace用Vitis打开结果Platform工程结构完全对不上。SDK工程和Vitis平台的工程模型差异不小。老SDK里硬件平台、BSP、应用是相对扁平的目录关系Vitis里则明确拆成Platform Project和Application Project两层。直接打开旧workspaceVitis会尝试转换但经常出现Platform里没有FSBL、应用工程找不到BSP、生成Boot Image时输入文件路径指向旧SDK目录等怪问题。这些迁移引发的问题在最后一步生成启动镜像是往往会一起爆发。我的建议是别走导入的老路。手工新建一个Platform工程指向已有的XSA勾选Generate boot components再把旧的应用源码文件复制到新Application工程下重新编译。整个过程大概多花半小时但能省下后面无数个玄学报错。5. 镜像生成不算完QSPI烧录与上电启动实测的后续坑5.1 烧录之前确认flash类型与密度BOOT.BIN生成成功只是第一步把它正确烧进Flash并启动成功才算完成。这一步的坑一点也不少。Vitis的Program Flash Memory功能需要你明确Flash类型。常见选项有qspi-x1-single、qspi-x4-single、qspi-x4-dual等。这个参数必须和你板卡上实际使用的Flash及连接方式一致。很多开发板为了兼容不同芯片会按固定模式连接QSPI Flash比如只连了IO0和IO1那就是dual模式但你如果选qspi-x4-single烧写器对Flash的ID识别都可能失败。还有dual stacked的情况——板子上放了两片Flash组合成更大的容量这时的选型和单片的完全不同。拿不准的话最直接的办法是查原理图看CS、CLK、IO0-IO3连到FPGA/SOC的哪些引脚以及Flash型号手册支持的指令集。5.2 program_flash命令的参数与烧录偏移Vitis 2020.1里可以在菜单Xilinx → Program Flash Memory里操作。对话框里需要选择Image File之前生成的BOOT.BINFSBL File手动选择FSBL烧写驱动要用它来初始化FlashFlash Type按上一步确认的类型选择Offset默认0x0也就是从Flash起始地址开始烧如果一切正常烧写完成后Vitis会提示successful。命令行下也可以执行program_flashprogram_flash -f BOOT.BIN -offset 0x00000000 -flash_type qspi-x4-single关于偏移地址需要多说一句。Zynq BootROM默认从QSPI Flash地址0x0开始查找启动头。如果你的烧写偏移不是0BootROM就找不到镜像。只有当你做多镜像启动比如一个放生产固件一个放恢复固件时才需要把第二份镜像放到特定偏移并通过用户寄存器或引脚选择启动哪一份。新手不要随意改偏移。5.3 上电后启动失败的三板斧查法烧录完成后上电看到串口没输出或者固件没跑起来先别急着怀疑镜像。按这三步查第一步看启动模式引脚。Zynq的BootROM会读取MIO引脚电平判断从哪个设备启动。以常见的Zynq-7000为例QSPI启动对应MIO[5:2]的电平组合是0010MIO50、MIO40、MIO31、MIO20。查你板卡手册上Boot Mode DIP开关的说明确认拨到了QSPI/Flash档而不是SD或JTAG档。第二步看串口输出。FSBL默认不打印太多信息但哪怕是最初级的BootROM输出也应该能在串口上看到Xilinx Zynq Boot这类字样。如果连这个都没有基本可以断定BootROM没找到镜像先查上一步的启动引脚和Flash焊接、供电。第三步读回Flash验证。把开发板切到JTAG模式用Vitis的Program Flash Memory或Xilinx的硬件管理器从Flash地址0x0读回前面几百字节检查魔术字0xAA995566是否完好。如果读回来全是FF或者乱码说明烧写过程实际没成功重新烧一次并注意观察烧写时长和校验信息。如果BootROM有输出但卡在FSBL阶段可以在FSBL工程中开启FSBL_DEBUG_INFO编译宏重新编译再把新的FSBL打包成BOOT.BIN烧进去。这样FSBL运行时会通过串口打印每个分区的加载地址和跳转情况哪个分区加载失败一目了然。最后再说一个我自己用下来的体会Vivado 2020.1这条QSPI启动链路最大的痛点其实不是性能也不是功能缺失而是工具链自身在文件处理和路径处理上的不严谨。把BIF文件和bootgen命令做成一个固定脚本纳入版本控制是最值得早点做的工程化改进。后面不管换电脑、换版本、还是迁移到Linux服务器上编译一键出BOOT.BIN都能省下大量折腾时间。希望这篇文章能帮你把卡住的那一步迈过去。