
简介面向Zynq UltraScale MPSoC开发者的固化工程包聚焦XCZU4EV平台演示如何用VITIS工具链将程序固化到EMMC等非易失存储同时兼容XCZU2CG、XCZU2EG等相近型号。压缩包共2109个文件约62.92MB以C/H源码、makefile、链接脚本、Vitis工程配置以及bit/elf/xsa等构建产物为主另有BD工程、XDC约束、驱动库和日志报告可支撑从源码阅读到工程重建的全过程。已有441人学习下载适合希望借助完整工程熟悉MPSoC固化流程的工程师和高校学生。通过研究工程中的BIF文件、启动镜像生成设置和多级目录结构可以理解ZynqMP的启动分区、地址映射与固化步骤并直接在Vitis中编译、修改和复用相关配置减少从零搭建的时间成本。1. 把XCZU4EV的程序固化做对VITIS下从启动镜像到BOOT.BIN的完整闭环做Zynq UltraScale MPSoC项目时多数人第一次接触“固化”都是在调试器里把程序跑通了然后发现断电重来一切归零。XCZU4EV这类芯片在开发板上通过JTAG加载bitstream和elf很简单但真正要部署到现场必须把FSBL、PMUFW、ATF、U-Boot、bitstream和应用程序全部打包进一个BOOT.BIN配置好启动模式后写入QSPI Flash或SD卡。这个项目的标题里直接点出了VITIS实现说明作者选择了2019.2之后主流的统一软件环境来做这件事而不是退回Vivado SDK的老路。资源包里那份9240_zynqmp_emmc.bin.50Mhz文件是生成BOOT.BIN的中间产物从它可以反向核对整个镜像构建流程是否正确。本文按理论到落地的顺序拆解在VITIS 2019.2及以上版本中完成XCZU4EV程序固化的标准路径包含关键参数设置和常见的坑。固化的本质是让BOOTROM在芯片上电后能从非易失介质加载一段代码这段代码负责初始化DDR、时钟、MIO引脚和PS-PL接口然后引导后续启动阶段。ZCZU4EV内部集成了四核Cortex-A53和双核Cortex-R5PL侧还有大量可编程逻辑资源启动流程比单核FPGA复杂得多但整个过程的枢纽就是VITIS生成的BOOT.BIN。在动手操作前必须先理解ZynqMP启动的几个阶段否则后面配置VITIS工程时容易把启动源搞混。ZCZU4EV上电后片内BootROM先运行它根据MIO[7:2]的电平状态决定从QSPI、SD、eMMC、JTAG还是NAND加载镜像。考虑资源包里的文件名为9240_zynqmp_emmc.bin.50Mhz目标介质是eMMC且工作频率50MHz这说明板卡设计走的是eMMC启动路径。BootROM从eMMC读出的第一份数据是BOOT.BIN这个文件内部由多个分区组成按固定头部格式组织BootROM识别头部并跳转到FSBL。FSBL完成DDR初始化后把bitstream从BOOT.BIN中解析出来写入PL再把U-Boot或其他BL33镜像加载到DDR运行。理解这个阶段链是排查启动失败的前提。2. VITIS工程创建与启动镜像结构不是简单点几下编译2.1 从硬件导出的XSA是一切的基础VITIS平台工程依赖的XSA文件由Vivado导出但导出前必须确认硬件设计里已经正确配置了eMMC控制器、QSPI控制器和UART。针对XCZU4EV在Vivado的Address Editor里能看到DDR地址空间从0x00000000开始这是A53的地址映射而PL侧的BRAM或AXI外设地址通常在0xA0000000之后。如果DDR配置不对FSBL阶段就会卡死在内存测试。有一个容易被忽视的细节是PS端MIO bank电压必须与板卡实际电平匹配MIO[7:2]的模式引脚在Vivado里设置启动模式为eMMC时对应的bank电压要选1.8V否则BootROM可能无法稳定读取eMMC。导出的XSA里包含了这些配置VITIS导入后会自动同步到BSP中。2.2 Platform工程里的BSP裁剪与库依赖新建Platform工程选择Linux或Standalone时要针对A53的standalone BSP做裁剪。资源包里的libxil.a和libxilsecure.a就是编译产物libxilsecure提供了对PMU固件和证书相关功能的支持。在platform.spr文件中能看到BSP的配置选项其中boot_mode默认取XSA里的设定不需要手工改但standalone的stdin和stdout要指向UART0或UART1否则printf输出没有地方显示。有一个常见的操作偏差是把所有库都勾选上编译结果镜像体积膨胀eMMC前4KB的头部区域被撑爆导致BootROM读取失败。实际上对于固化场景xilffs用于读取FAT分区如果没有用到可以去掉xilpm库用于电源管理只在需要与PMUFW交互时保留。编译后生成的libxil.a和libxilsecure.a会自动链入最终的image无需手工管理。2.3 Boot Image分区规划分区顺序不能随意排VCU或DP等PL IP涉及的bitstream要放在FSBL之后PMUFW要放在第一个分区以便BootROM最先加载。这里的顺序对ZynqMP有硬性要求BootROM固定先找PMUFW然后FSBL之后才轮到bitstream和U-Boot。在VITIS中点击Xilinx - Create Boot Image后会自动按依赖关系排序但手动调整分区时容易把顺序打乱。常见做法是第一部分放PMUFW来自p plaform的pmufw.elf第二部分放FSBL来自FSBL工程编译出的fsbl.elf第三部分放bitstream第四部分放U-Boot或应用elf。如果是纯裸机应用不经过U-Boot则第四个分区就是应用程序elf。这里要特别留意PMUFW的编译VITIS在创建platform时会自动生成pmufw.elf路径在platform的psu_pmu_interface目录下不要从旧工程拷贝版本不匹配会出现启动后PMU不停复位。# 在VITIS的Xilinx菜单中选择Create Boot Image后分区顺序如下 # partition0: pmufw.elf # partition1: fsbl.elf # partition2: design.bit # partition3: app.elf # 每个partition的加密和校验选项保持默认除非有安全启动需求分区顺序影响的不仅是启动流程还关系到启动耗时。BootROM对每个分区头部都要做CRC校验分区越多校验时间越长如果分区超过8个启动时间可能从几百毫秒拉长到数秒。对于实时性要求高的应用建议把多个PL bitstream合并在一个分区内或者在FPGA内部用ICAP动态加载避免在BOOT.BIN里放多个比特流。3. QSPI与eMMC固化的双路径操作从BIF文件到烧写命令3.1 BIF文件里的关键字段与memory映射VITIS生成BOOT.BIN时依赖BIF文件BIF包含了镜像的布局逻辑。在BIF文件里id_code字段对应具体芯片型号XCZU4EV的id_code是0x04A12093这个值必须与芯片实际ID一致。如果BIF里的id_code写成了XCZU2CG或XCZU3EG的生成的BOOT.BIN在当前芯片上会在BootROM阶段直接卡死因为头部校验失败。另一个字段是fsbl_config它指定FSBL的配置选项常见的有none和single_boot在VITIS中会自动填充正确值。pmufw_image字段必须给出pmufw.elf的完整路径路径中包含空格时需要加引号。手动编辑BIF文件时要注意每个字段的格式VITIS的Boot Image界面生成的文件格式如下// arch: zynqmp // bha: 4 const boot_image { id_code 0x04A12093; fsbl_config { none; } partition { pmufw_image C:/workspace/platform/zynqmp_pmufw.elf; } partition { fsbl_image C:/workspace/fsbl/Debug/fsbl.elf; } partition { bitstream_image C:/workspace/hw/design_wrapper.bit; load 0x00100000; } partition { uboot_image C:/workspace/u-boot.elf; load 0x00080000; } }BIF里的load字段指定分区在DDR中的加载地址bitstream的加载地址通常由Vivado分配默认在0x00100000附近而U-Boot的加载地址固定为0x00080000这是ZynqMP约定俗成的加载位置。bha字段表示Boot Header Alignment默认4即可不建议修改。手动修改BIF后VITIS会在build时重新读取并生成BOOT.BIN。如果只改BIF不重新build直接使用上一次生成的BIN文件修改不会生效。3.2 从XCZU4EV型号差异看启动模式切换ZCZU2CG、XCZU2EG与XCZU4EV在启动逻辑上没有本质差别都是同一个BootROM流程但PS端DDR控制器支持的速率等级不同导致FSBL初始化DDR时用的时序参数不一样。Cortex-A53核支持的DDR频率上限不同做镜像移植时不能直接把XCZU4EV生成的BOOT.BIN烧到XCZU2EG上跑——即使引脚兼容DDR初始化参数不同也会导致FSBL死在内存校准阶段所以芯片型号切换时要在Vivado里重新生成XSA并重新构建FSBL和BOOT.BIN。3.3 在VITIS Terminal里完成eMMC烧写的完整命令序列VITIS自带的Terminal可以直连JTAG调试器使用xsdb命令行工具烧写镜像是最稳妥的做法不需要额外的第三方软件。先用connect命令连接目标然后执行dow命令下载BOOT.BIN到DDR。整个烧写流程依赖U-Boot在RAM中运行因为eMMC的烧写需要文件系统驱动的支持。# 进入VITIS Terminal后执行xsdb命令行 xsdb% connect xsdb% target # 输出结果中会列出Available Targets # 选择A53-0核心APU核的编号通常在列表靠前位置 xsdb% ta 3 xsdb% rst -processor # 将BOOT.BIN下载到DDR固定地址 xsdb% dow C:/workspace/boot/BOOT.BIN # 让A53从DDR地址跳转执行 xsdb% conU-Boot启动后切换到U-Boot命令行执行eMMC烧写命令。mmc dev 0选择eMMC设备mmc write把内存中的镜像写入eMMC的指定块位置。需要注意eMMC在U-Boot下的设备号可能和硬件设计编号不一致通过mmc list查看当前识别到的设备。写入前用mw.b命令清空目标区域避免旧数据残留导致启动异常。# U-Boot命令行下执行 U-Boot# mmc dev 0 U-Boot# mw.b 0x20000000 0xFF 0x600000 U-Boot# fatload mmc 0:1 0x20000000 BOOT.BIN U-Boot# mmc write 0x20000000 0x0 0x3000 U-Boot# mmc partconf 0 0 1 0mw.b命令先填充内存fatload把BOOT.BIN从FAT分区加载到内存mmc write把镜像从内存写入eMMC用户分区mmc partconf配置eMMC的boot分区和用户分区访问方式这个命令不加的话可能出现上电后BootROM无法读取镜像的问题。mmc write的第三个参数是起始块号这里是0x0指向用户数据区起始块第四个参数是要写入的块数量0x3000块对应0x600000字节即6MB空间足以容纳完整BOOT.BIN。3.4 QSPI Flash路径的差异处理如果板卡不提供eMMC而只有QSPI Flash那启动镜像需写到QSPI里命令上略有不同。QSPI在FSBL阶段由psu_qspi_init完成控制器配置U-Boot里的sf probe会检测Flash型号和容量。当前主流环境里QSPI容量普遍是512Mb即64MB而BOOT.BIN大小往往只有几MB到十几MB直接擦除整个Flash耗时较长按需擦除镜像区域即可。# U-Boot下将BOOT.BIN从SD卡读入DDR再写入QSPI U-Boot# sf probe 0 0 0 U_Boot# fatload mmc 1:1 0x20000000 BOOT.BIN U-Boot# sf erase 0x0 0x600000 U-Boot# sf write 0x20000000 0x0 0x600000QSPI路径下注意sf probe的三个参数分别是SPI控制器编号、工作频率和SPI模式。频率参数填0表示使用驱动默认值模式通常为0表示Mode 0。SPI Flash的写操作前必须擦除QSPI Flash擦除粒度是4KB扇区所以sf erase的首地址和长度要对齐扇区边界否则擦除操作会返回错误镜像写入的位置也会错乱。4. 排错定位利用VITIS调试器和启动日志反向定位固化失败的根源4.1 卡死位置与UBOOT日志的联合判断固化失败的现象一般分为几类上电后UART完全无输出、输出到FSBL阶段停止、能进入U-Boot但app没启动。第一类问题大概率是BOOT.BIN头部损坏、eMMC/QSPI没有选中正确启动源或PMUFW加载失败。第二类问题常见于DDR初始化异常或bitstream配置失败。第三类问题则指向U-Boot环境变量配置不当或应用镜像格式错误。其中最难排查的是第一类因为UART无输出时看不到任何错误信息只能靠JTAG读取寄存器状态来定位。VITIS的Debug视图连接到A53后执行rrd命令读取寄存器组重点观察PC指针停在哪个地址——如果停在0xFFFC0000附近说明BootROM还在运行停在0xFF000000说明已经进入PMUFW或FSBL代码段。FSBL阶段的错误输出是黑盒子因为FSBL打印信息的时机受BSP中FSBL_DEBUG_INFO宏控制默认打开串口输出后能看到XFsbl_PrintInit的打印。资源包里如果包含了修改过的FSBL源码可以在其中XFsbl_GetBootMode函数处打断点判断BootROM是否正确传递了启动模式参数。这个参数在寄存器BOOT_MODE_USER中读取它的值可以直接确认当前启动介质eMMC对应值是0x03。另外eMMC启动时BootROM对前16KB的读取有特殊要求如果PCB上没有把eMMC的BOOT_BUS_WIDTH配置为1-bit模式BootROM可能无法识别设备。遇到这类情况时不要急着改软件先查板级原理图上的eMMC配置电阻。4.2 使用VITIS的波形对比和时间分析的进阶技巧VITIS集成的Vivado逻辑分析器可以用于观察FSBL配置PL时寄存器写入的次序。打开debugger后将A53的ITRACKER寄存器开启记录FSBL运行完成后导出的地址轨迹可以精确还原每一条访存指令的地址和执行周期。这个方法在排查FSBL卡在bitstream配置阶段时非常有效因为bitstream配置过程涉及大量对PCAP或DevC寄存器的读写一旦某个寄存器没有按datasheet要求的顺序写入PCAP会挂死。对比正常板卡和异常板卡在同一寄存器偏移处的写入间隔能快速定位是哪一步GPIO或时钟初始化没有完成。这类问题的根源大多是Vivado工程里的PS-PL时钟配置有误或外部时钟源没起振在Vivado的Clock Configuration页面可以检查PL Fabric Clock是否被启用以及频率值是否符合设计预期。4.3 eMMC初始化时序和dts配置对齐的小结启动镜像很快会进入U-Boot阶段然后跳转到应用的dts配置。eMMC在Linux下挂载时工作模式由dts里的mmc-hs200-1_8v或mmc-ddr-1_8v属性决定如果dts配置的时序比eMMC实际支持的高读操作会偶发CRC错误。这种现象在固化完成后首次启动时出现概率极大容易被误判为镜像损坏或Flash老化。应对方法是先检查内核dmesg中是否有mmc0: Card stuck in programming state或mmc0: error -84这类关键字。另外资源包里的9240_zynqmp_emmc.bin.50Mhz文件名暗示eMMC工作频率为50MHz对应HS模式如果U-Boot传参强制eMMC进入HS200模式导致运行不稳定需要在U-Boot环境变量里固化为mmc_device0的同时检查mmc_boot_frequency参数。对于XCZU4EV这样的MPSoC平台DDR和eMMC的时序容限本身偏紧出现偶发启动失败时优先降低eMMC频率验证。5. 通过启动模式引脚与image版本管理把固化工作做扎实ZynqMP的启动模式引脚MIO[7:2]在PCB上的走线方式决定了实际生效的启动介质。调试时经常发现Vivado里BFM仿真验证全通过但上电后就是不启动根因在于MIO引脚的外部上下拉电阻与BootROM期望的电平不一致。在VITIS环境里虽然无法直接读引脚电平但可以通过Vivado的Hardware Manager连接设备后读取PS的MIO寄存器状态对比BOOT_MODE_RO寄存器里的值。标准接线方案是MIO[5:2]的bit编码对应不同介质例如eMMC启动对应1000QSPI对应0010JTAG对应0000。MXO引脚区域如果是1.8V bank焊盘上的上拉电阻值建议用4.7kΩ到10kΩ取值太小会导致漏电流偏大取值太大会受噪声干扰造成开机瞬间误判。镜像版本管理在项目交付阶段容易被忽略但在现场出现问题需要回滚时会变得非常关键。建议固化前在BIF文件的partition字段里增加name属性来区分版本并且利用VITIS的build配置自动化生成带时间戳的BOOT.BIN。具体做法是在Vitis工程中配置一个pre-build步骤用批处理或Makefile生成带日期的文件名然后修改BIF模板文件对应的输出路径。这样每次构建生成的BOOT.BIN文件名中直接包含构建时间例如BOOT_20240412_1830.BIN复制到现场后根据文件名就能判断镜像版本避免出现固化了旧版本而误以为是新代码的问题。对于需要批量生产的场景要额外关注不同单板间eMMC的出厂CID差异带来的格式化差异固件里不要硬编码某个特定eMMC器件的容量参数。本文还有配套的精品资源点击获取