尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA程序固化实战:从Vivado到QSPI Flash的完整烧录指南

FPGA程序固化实战:从Vivado到QSPI Flash的完整烧录指南 板子上电JTAG下载的程序跑得飞快LED闪得赏心悦目一切看起来都很完美。然后你拔掉下载线断电再上电板子一片死寂。如果这一幕你经历过那说明你正在面对FPGA开发里绕不开的一关——程序固化。我是做嵌入式开发的老兵Vivado从14.x用到2022.2Zynq、Artix-7、Kintex都折腾过固化这个问题几乎每个新人都要卡一次。这篇教程就把我从SDK配置到Flash烧录的完整流程拆开讲清楚包括那些你在官方文档里找不到、只能靠踩坑换来的细节目标就是让第一次接触Vivado固化的朋友照着操作就能顺利跑通。1. 被掉电丢失支配的恐惧先从启动模式认识程序固化1.1 为什么JTAG能跑一断电就白屏很多人第一次遇到这个问题都会怀疑硬件坏了其实不是。FPGA内部的逻辑配置数据存储在SRAM里SRAM的特点是快但它需要持续供电才能保住数据。JTAG下载的本质是把bitstream从电脑通过下载器灌进FPGA的SRAM这个过程只存在于当下断电瞬间数据就没了。所以固化这个词你可以理解成把可执行的文件从易失的临时内存搬到掉电不丢的永久存储里。对FPGA开发板来说这个永久存储通常就是板载的SPI Flash或者QSPI Flash芯片。固化之后每次上电芯片内部的BootROM会主动去读Flash把配置数据搬进FPGA完成自启动。这个原理听起来简单但实际工程里涉及的东西不少启动模式选择、镜像格式、地址映射、Flash器件参数匹配任何一个环节出错都会在烧录阶段给你颜色看。1.2 常见启动模式QSPI、SPI、SD卡到底选哪个7系列和Zynq系列的启动模式由芯片上的mode引脚决定不同板卡的配置方式不同有的用拨码开关有的用跳线帽有的要靠电阻配置。主流就这几种启动模式存储介质适用场景特点JTAG无调试阶段直接下载到SRAM掉电丢失QSPI/SPI Flash板载Flash芯片单板量产速度快容量适中最常用SD卡外置SD卡需要频繁更新灵活但可靠性略低NAND Flash板载NAND大容量需求工艺复杂使用较少开发板默认的调试模式基本都是JTAG你要固化第一件事就是确认板子有没有把启动模式切换到QSPI的通道。以常见的Zynq开发板为例板上会有一个标着MIO[4:0]的拨码开关对应关系就是启动模式的二进制编码。这个细节我见过太多人忽略烧录完成后拨码开关没拨回去上电依然白屏然后就到处怀疑烧录失败了。1.3 固化之前的预期管理固化不是把bitstream丢进Flash就完事尤其是Zynq这类带ARM核的芯片完整的固化产物是一个启动镜像里面包含三样东西FSBLFirst Stage Boot Loader、FPGA bitstream、应用程序elf文件。三个文件按顺序打包成BOOT.binBootROM上电后先加载FSBLFSBL负责初始化DDR、配置PL端再把bitstream和应用程序逐级拉起。纯FPGA芯片比如Artix-7、Kintex-7就简单一些不需要FSBL直接把bitstream写进Flash就行。但如果你在FPGA里跑了MicroBlaze软核情况又不一样了——需要把bitstream和启动程序组合处理。这篇文章后面会分开讲但在动手之前先搞清楚你手里的芯片是哪种类型的活能少走很多弯路。2. 硬件工程侧的准备管脚、时钟与Bitstream导出的三个检查点2.1 检查点一Flash管脚约束有没有对很多人忽略一个关键点Flash烧录走的是FPGA内部的硬核控制器但Flash芯片和FPGA之间的管脚连接必须正确约束。在Vivado工程里打开你的约束文件XDC找到QSPI或SPI Flash相关的管脚定义。以Zynq为例QSPI Flash连接在MIO的1~6号引脚上具体看板卡原理图这部分在Zynq处理系统里已经固化了不需要额外约束。但如果你用的是纯FPGA搭配外部Flash的方案Flash的几个关键信号——CS片选、SCK时钟、MOSI、MISO、WP写保护、HOLD——都必须在XDC里正确分配并且I/O电平要与Flash芯片的工作电压匹配常见的是3.3V或1.8V。我遇到过一种情况约束文件里管脚没写错但I/O标准IO Standard写成了LVCMOS33而实际Flash是1.8V供电结果烧录时芯片怎么也识别不了。这种问题报错信息往往很含蓄会在Hardware Manager里显示unable to read device ID排查了半天最后发现是电平不匹配。2.2 检查点二时钟和复位别埋雷固化之后系统独立运行Flash里的bitstream对时钟的要求比JTAG调试时更严格。JTAG的时候时钟可以由下载器或者板上的调试时钟提供但固化后必须靠板载晶振和FPGA内部的MMCM/PLL完成时钟分频倍频。这里要检查你的设计中是否有硬依赖调试时钟的逻辑。比如有些人的代码里不严谨地用了JTAG相关的时钟信号作为主时钟或者复位信号依赖JTAG链路上的信号这种设计在调试模式下能跑固化后必然出问题。最稳妥的做法是所有逻辑的主时钟都来自板上晶振输入管脚复位信号用一个简单的上电延时电路或者看门狗模块生成不要依赖调试链路。另外如果你在Vivado里开了Bitstream Compression压缩位流某些老型号Flash在烧录大容量bitstream时可能不稳定建议默认不勾选等固化验证通过了再考虑压缩以节省Flash空间。2.3 检查点三导出Hardware时勾选Include bitstream这一步看似简单但绕的人特别多。在Vivado里完成综合、实现、生成比特流之后菜单栏选择File - Export Hardware弹出的对话框里务必勾选Include bitstream。如果你不勾导出的硬件描述文件里就不带bitstream后面SDK里生成的启动镜像就缺了FPGA配置数据烧进Flash后ARM能启动但PL端全废。导出完成后再选择Tools - Launch Vitis IDE老版本是Launch SDK。这里用Xilinx SDK 2019.1之前的版本是Launch SDK之后的版本整合成了Vitis。不管叫什么都不重要核心是这一步导出的hdf/hsa文件里包含了bitstream这个关键内容。导出前后的工程目录里会生成一个.hdf文件Hardware Definition File它像一张硬件名片把FPGA的管脚分配、外设基地址、时钟频率、bitstream全打包在里面。SDK就是靠读这个文件知道你的硬件长什么样的。3. SDK侧的固化三件套FSBL、App工程与启动镜像生成3.1 创建FSBLZynq固化的第一块积木如果你用的是Zynq系列打开SDK后第一步不是急着写App代码而是先建FSBL工程。选择File - New - Application Project在弹出的窗口里给工程起名比如fsbl硬件平台选你刚导出的那个platform然后在模板列表里找到Zynq FSBL或者First Stage Boot Loader。FSBL是Xilinx提供的现成模板里面的代码逻辑是定死的不需要你自己写。它做的事情主要有三件初始化PS端的时钟和DDR、把bitstream从Flash或者其他介质搬进PL端、跳转到应用程序入口。对于大多数应用FSBL不需要任何修改直接编译通过就行。这里有个经验之谈FSBL的编译选项里默认的优化级别是-O2如果之后你在SDK里调试时发现某些诡异的启动问题可以尝试把优化级别调低或者关闭排查是否是FSBL优化导致的初始化时序问题。虽然这种情况不常见但遇到玄学问题的时候值得试试。3.2 创建App工程Hello World只是起点FSBL建好后继续创建你的应用程序工程。同样的路径New - Application Project这次起一个你的业务工程名模板选择Hello World或者Empty Application。Hello World模板虽然简单但它是验证固化流程是否跑通的最短路径——如果连Hello World固化后都打印不出来那就别急着上自己的业务代码。App工程的BSP板级支持包设置里有两个地方我之前吃过亏一个是stdin/stdout的重定向。串口打印依赖BSP里的uart driver你要确认BSP启动时正确配置了对应的UART作为标准输出。另一个是堆栈大小默认值可能偏小如果你的App里有较大的局部数组或者递归调用跑飞了很难查建议直接调大一级。App工程编译通过后右键点工程名选择Build Project确保生成最终的elf文件。到这一步你手里应该有三样东西FSBL编译出的fsbl.elf、FPGA的bitstream文件、App的xxx.elf。3.3 生成BOOT.bin把三块积木拼起来三样东西准备好之后选择菜单Xilinx - Create Boot Image弹出的对话框里会让你选BIF文件路径和分区配置。界面分上下两部分上半部分是Boot Image Partitions启动镜像分区表下半部分是日志和输出信息。在分区表里逐个添加先用Add添加FSBL的elf文件Partition type选择bootloader或者datafile然后添加bitstream类型选datafile这一步是PL端配置最后添加App的elf类型选datafile或者bootloader取决于你的App是否是独立可启动的。顺序不能乱FSBL永远在最前面bitstream在中间App在最后。点击Create Image之后工程目录下会出现一个BOOT.bin文件。这就是你最终要烧进Flash的东西。顺带说一句BOOT.bin这个名字是约定俗成的Zynq的BootROM只认这个名字别改成别的不然上电启动时找不到文件。纯FPGA芯片的流程到这一步会简单很多不需要生成BOOT.bin直接把bitstream文件通过Vivado Hardware Manager烧进Flash即可。如果你有MicroBlaze则需要把它编译出的elf和bitstream合并成一个Download.bit或者MCS文件再烧录。这部分细节比较多后面单独展开。4. 烧录实操从Program Flash Memory到拨码开关切换4.1 连接硬件下载器与板子的一对一连线烧录前先把硬件连接确认一遍。用JTAG下载器连接开发板注意JTAG端的电平要和板子匹配很多下载器是1.8V~5V自适应但高速烧录时建议强制设置成和板子IO电压一致避免通信不稳定。在SDK里打开Xilinx - Program Flash弹出的界面第一项会让你选择硬件目标Hardware Platform。这一步如果发现找不到设备先别急着怀疑下载器坏了检查以下几个常见点下载器驱动是否装了Vivado安装目录下有安装脚本、JTAG线序是否正确、板上有没有JTAG使能跳线帽。我调试的时候碰到过最呆的情况是JTAG下载器的线序接反了一根信号指示灯正常亮但设备枚举不出来检查了一圈最后发现是杜邦线的问题。4.2 填充烧录参数Flash类型、地址与镜像文件Program Flash的对话框看起来选项多实际核心就三个Image File镜像文件选BOOT.bin、Offset偏移地址一般从0x0开始、Flash TypeFlash器件型号。Flash Type这里有一个下拉框里面是SDK内置的Flash器件列表选错型号的后果就是烧录到一半报错退出。如果你用的板卡Flash型号不在列表里需要自己手动添加。Xilinx官方支持的Flash列表可以通过Vivado安装目录下的配置文件查看也可以去板卡厂商的官网找对应的flash part文件。添加的方法是在Program Flash界面点旁边的按钮加载BSP或者Flash描述文件具体名称因版本而异但思路都一样——让SDK知道这块Flash的扇区大小、擦除命令集、最大容量。地址偏移的问题我要多说一句。Zynq的QSPI Flash如果配置成双Quad模式起始地址可以从0x0开始没问题但如果你的设计里Flash分区域使用前面有一部分放了别的数据那BOOT.bin的偏移地址就要对应调整同时上电启动时BootROM读取的地址也要匹配。大多数人用不到这个功能但知道有这个东西真遇到时就不会慌。4.3 烧录过程擦除、写入、校验三连点击Program之后软件会自动执行三步先是全片擦除Erase这个阶段耗时取决于Flash容量128Mb的Flash大约需要一到两分钟接着是写入Program把BOOT.bin按页写入最后是校验Verify回读Flash里的数据与原始文件比对。这个过程中千万不要断电或者拔下载器。Flash擦写过程断电非常容易把芯片搞成砖虽然多数Flash支持重新擦除恢复但那种手贱一次折腾半天的教训我希望你不要亲身体验。烧录完成之后日志窗口会显示Flash Operation Successful之类的提示。到这一步先别急着庆祝把下载器从板子上拔掉或者保持JTAG连接但把SDK的默认启动模式切换掉然后拨动板上的启动模式开关到QSPI/Flash启动档位重新上电。4.4 拨码开关最后一步的仪式感启动模式拨码开关在不同板卡上的位置和含义不同看原理图是最可靠的。Zynq常见的配置是MIO[4:0]拨到对应QSPI的二进制编码比如某块板子是00110代表QSPI启动。拨完之后上电观察串口输出。如果之前串口助手开着波特率下文会说你应该能看到Hello World的打印信息预示着实机验证成功。如果什么都没打印第一反应不要是固化失败了先检查串口接线和串口助手的波特率设置。我本人就犯过这个错——SDK默认的BSP串口波特率是9600而我自己写的串口上位机配的是115200打印出来全是乱码我还以为程序没跑起来。后来才发现是波特率不匹配。这个坑看起来低级但每个初学者都会踩。5. 烧录失败排查专题target dll、flash通信与设备描述加载5.1 高频报错error: flash download failed - target dll has been cancelled到底在说什么这个报错在各大论坛上被问得最多字面意思是目标DLL已取消非常误导人。它实际上是SDK的Flash下载器在通信过程中异常中断时的统一报错导致取消的原因通常不是DLL本身。我统计了一下自己遇到和帮助别人排查的案例触发这个报错的主要原因有三个可能原因现象特征处理方式Flash型号选择错误烧录刚开始几秒就报错核对原理图上的Flash型号重新选择JTAG链路不稳报错前日志有大量Unexpected ID降低JTAG频率SDK里有设置项换短一点的杜邦线Flash写保护生效擦除阶段报错检查Flash的WP引脚是否被拉高某些Flash还有软件保护寄存器降低JTAG频率的方法在Vivado Hardware Manager里可以设置SDK的Program Flash界面也有Frequency选项默认可能是15MHz烧录不稳时改成3MHz甚至1MHz很多玄学报错都能被这个土办法解决。频率降低无非是烧录时间变长换来的是稳定性量产阶段这是标准的保守策略。5.2 warning: failed to communicate with the flash chip与cannot load flash device description这两个报错本质是SDK读不到Flash芯片的响应。遇到failed to communicate时先用示波器或者逻辑分析仪看Flash的CS和SCK引脚有没有动作。如果没有波形问题大概率在FPGA到Flash的通路上——可能是管脚约束错了也可能是FSBL没有正确配置QSPI控制器。cannot load flash device description则更直接就是SDK的Flash器件库里没有你这颗Flash的型号。我的处理方式是去Flash厂商官网下载对应型号的xml描述文件放到Vivado的安装目录对应文件夹里重启SDK后就能识别了。不同版本的Vivado放置路径有差异常用做法是在SDK的Window - Preferences里找到Flash目录配置直接把目录指到描述文件所在位置。5.3 烧录失败后的现场救援先别急着判死刑烧录失败后最怕的是连续重试每次都失败然后心态爆炸。我的建议是按照下面这个顺序做现场排查重新插拔下载器的USB端和JTAG端确保接触良好。关闭SDK的Program Flash界面重新打开硬件连接。用Vivado Hardware Manager不是SDK手动读一次Flash的Device ID确认JTAG链路和Flash通信都正常。这个可以独立验证底层链路是否健康。如果Device ID能读到说明链路没问题问题出在镜像文件或者配置参数上回头检查BOOT.bin内容和Flash选择。如果Device ID读不到那就是硬件通路问题用万用表量Flash供电、量JTAG信号线逐级排查。按照这个顺序90%的问题都能定位到具体的层级不会再像无头苍蝇一样瞎试了。5.4 关于Vivado implement design变红的联想很多朋友在固化过程中的报错其实误打误撞了。实现阶段如果implement design变红也就是报告显示初始化失败或者布局布线失败这个问题跟固化没有直接关系是工程本身的时序约束或者逻辑问题。有几次我在帮同事排查时发现他们的实现失败原因是在顶层模块里漏写了一根跨时钟域的同步寄存器看起来和Flash毫无关系却卡住了整个固化流程的前置步骤。遇到implement design变红正确姿势是打开Implementation的log文件搜索error关键词逐条看。最常见的两种一种是时序收敛不了Timing failure看看是哪条路径违规去约束文件里加set_multicycle_path或者重新分配时钟另一种是管脚冲突IO placement error看看是不是有两组信号被分配到了同一个管脚。先解决了实现问题再往下走固化流程别在烧录阶段里找原因。6. 固化完成后的验证与批量复制经验6.1 断电重启测试至少三轮是底线固化完成后做的第一件事不是接着写代码而是反复做断电上电测试。我一般会连着做三轮第一轮上电后等待10秒确认启动稳定第二轮断电后快速重新上电模拟意外断电的恢复场景第三轮运行一段时间后再次断电重启用来验证长时间运行有没有发热导致的问题。每一轮测试都要记录串口的启动日志是否和第一轮一致。如果某一次上电后启动异常但重新上电后又正常了这类偶发启动失败是最难查的通常指向电源上电时序或者Flash读取时序问题。排查手段是用示波器抓上电瞬间Flash的CS信号和电源电压波形看看是不是电压爬升斜率太陡或者太缓导致的启动临界状态。6.2 批量烧录一板一烧的笨办法与多板并行的优化到了小批量产阶段一台电脑一个JTAG下载器一块板子地烧效率实在太低。我个人的量产土办法是这样先把一块板子固化成功并且验证了BOOT.bin在这个Flash型号上没有任何问题然后把BOOT.bin存到一个固定目录。烧录时每块板子的流程是上电、打开SDK Program Flash、选好镜像和参数、点Program、等待成功、断电换板子。如果板子数量大可以考虑用USB Hub接多个下载器但要注意SDK同时只支持一个下载器会话多板并行得靠多开SDK进程或者用脚本自动化这个因人而异。更省事的方案是如果你的板卡支持从SD卡启动量产时先用SD卡刷好镜像再配合板上的拷贝脚本批量固化Flash不过这套方案需要板卡厂商的软件配合不是每款板子都有。6.3 备份Flash原始数据给自己留条退路新板子第一次烧录前如果Flash里原本有出厂程序比如评估板自带的功能测试程序强烈建议先把原Flash内容读出来备份。SDK的Program Flash界面有Readback功能可以指定地址区间读回并保存成文件。为什么要备份因为你在调试固化过程中有可能把Flash的操作方式摸错导致出厂程序被覆盖掉到时候想恢复出厂状态却没了原始数据就只能找厂商要镜像那效率就低了。我手里囤着好几块板子的出厂Flash备份虽然是当时好奇心驱使下存的但后来真的有同事把板子Flash刷坏了靠这些备份救回来了。别嫌多此一举这成本几乎为零收益关键时刻极大。6.4 形成自己的固化检查清单流程跑通了之后我建议你把整个固化过程沉淀成一份清单每次做固化都按清单走。我的清单长这样硬件连接JTAG线序、下载器驱动、板上电Vivado侧综合实现无错误、bitstream生成成功、Export Hardware勾选Include bitstreamSDK侧FSBL编译通过、App编译通过、BOOT.bin生成无告警Flash型号与原理图核对、描述文件已加载烧录参数镜像文件路径正确、起始地址0x0烧录后验证拨码开关切换到Flash启动、串口输出正常、三轮断电重启测试这份清单看着繁琐但每一次执行全流程大概只需要20分钟。比起出了问题上论坛翻帖子、在群里问人的时间成本这20分钟花得值。7. 一次完整的失败排查记录从报错到修复的全链路思考最后分享一个我印象深刻的真实案例也许能帮你理解固化排查的思路。有个项目用的Artix-7开发板外挂一颗Winbond W25Q128 Flash第一次烧录时SDK报的正是error: flash download failed - target dll has been cancelled。我当时没有急着换Flash型号或者改设置而是按部就班地排查先用Vivado Hardware Manager单独连接JTAG能识别到FPGA的Device ID说明JTAG链路正常。然后在Hardware Manager里尝试读Flash的ID结果也正常。那问题就锁定在SDK的烧录环节了。我重新打开Program Flash界面仔细核对Flash type下拉框里的选项发现我虽然选了W25Q128但界面里的具体子型号和板子上的尾缀对不上。板子是W25Q128JVSIQ而SDK默认的W25Q128条目可能对应的是更早的W25Q128FV。两个型号虽然容量一样但指令集和状态寄存器定义有差别擦除芯片的时候软件用老型号的命令去操作新型号芯片芯片不响应就报通了DLL取消。解决办法是在SDK里手动添加正确的Flash描述文件或者直接选兼容模式。那次之后我就意识到看起来相同的Flash型号尾缀不同往往代表着不同的命令定义烧录软件挑得不对擦除和写入都会出问题。这个问题在论坛上的标准答案是降低JTAG频率或者检查供电但实际根因却是Flash子型号不匹配。所以说排查问题一定要回到现场证据上而不是机械地套用别人的解法。固化流程本身不难难的是每一步的细节都不出错。希望这篇教程能帮你在调试固化的路上少走几个弯路把时间花在更有意思的功能开发上。
返回列表