ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MicroBlaze软核固化:ELF与BIT合并烧写SPI Flash完整流程

MicroBlaze软核固化:ELF与BIT合并烧写SPI Flash完整流程 最近在调一块Artix-7板卡上的MicroBlaze软核遇到一个绕不过去的问题程序在JTAG调试模式下跑得好好的但一断电就回到解放前。要把ELF和BIT合并后烧进SPI FlashFPGA上电后才能自动加载程序。这个过程涉及Vivado里的MMI文件、updatemem、write_cfgmem和Hardware Manager烧录我踩了一圈坑把完整流程和注意事项整理出来给同样在做MicroBlaze固化的朋友参考。这篇内容以Vivado 2018.3版本为例讲解其他版本在界面上略有差异但核心命令和思路完全通用。1. 为什么要合并ELF和BITMicroBlaze的启动链路与三种加载方式1.1 BIT管硬件、ELF管程序两者本来不在一个文件里很多刚开始接触软核的朋友会困惑FPGA用的BIT文件和SDK编译出的ELF文件明明是两个独立的东西为什么非要合并答案要从FPGA和软核的分工说起。BIT文件是FPGA的配置文件它描述的是整个FPGA内部逻辑怎么布线、LUT和FF怎么配置、Block RAM的初始内容是什么。换句话说BIT管的是硬件长什么样。ELF文件是MicroBlaze上跑的程序它包含指令、数据、调试符号、段表信息编译完成后默认存储在开发环境的文件系统里。上电之后这个ELF还静静地躺在硬盘上FPGA内部根本没有它。MicroBlaze是软核它的指令存储靠FPGA片内的Block RAMBRAM或者外部存储器。如果你的程序放在BRAM里那MicroBlaze上电后必须要从BRAM里取出第一条指令。但BRAM是易失性的断电就清零程序从哪里来答案就藏在BIT文件里BIT文件本身就是通过配置链路写入FPGA的它里面包含Block RAM的初始化数据。如果你把ELF里的机器码转换成BRAM初始化数据塞进BIT文件对应的BRAM位置那么FPGA配置完成后BRAM里就已经躺着应用程序了MicroBlaze一复位就能开跑。这就是合并烧录的本质。1.2 MicroBlaze上电后从哪里取指令理解合并烧录必须理解MicroBlaze的启动地址。MicroBlaze处理器在硬件上有一个基地址C_BASE_VECTORS的概念默认情况下复位向量的地址是0x00000000。在Vivado的Block Design里这个地址恰好被映射到本地存储器Local Memory Bus简称LMB上的Block RAM。配置好DDR的场合复杂一些后面我会单独讲。正常情况下MicroBlaze的第一个取指地址就是0x0也就是BRAM的首地址。所以你的程序链接地址必须从0x0开始链接脚本必须使用LMB对应的内存段。这就解释了为什么你在SDK工程里一般的默认链接脚本就能跑因为SDK知道你的MicroBlaze挂在LMB上默认链接起始地址就是0x0。当FPGA上电配置完成MicroBlaze解除复位PC指针跳到0x0读取指令这个行为是硬件逻辑固化的不需要软件干预。我们要做的就是确保0x0地址上确实有可执行代码。1.3 三种加载方式对比JTAG、SPI Flash、外部RAM把程序放到MicroBlaze的BRAM里业界有三种常见办法加载方式操作方式掉电保持应用场景缺点JTAG调试加载通过SDK或Xilinx System Debugger把ELF下载到BRAM掉电丢失开发调试阶段每次上电都要重新下载SPI Flash固化合并ELF到BIT一次性烧入SPI Flash掉电保持产品发布、现场部署需要额外烧录步骤外部RAM运行先烧Bootloader启动后再搬运程序视RAM类型程序很大、BRAM装不下流程更复杂JTAG加载的方式开发阶段用得很爽但产品不可能带着下载器跑。SPI Flash固化是绝大多数MicroBlaze项目最终需要的方案因为Artix-7、Spartan-7这些器件都支持从SPI Flash启动配置。只要把合并好的BIT文件烧进FlashFPGA上电后自身的配置模块自动从Flash读取配置数据连同BRAM初始化数据一起加载整个系统就活了。有点类似给单片机烧写固件一个HEX文件既包含配置也包含程序。只不过FPGA上你是先有硬件配置再有软件程序必须理解它们是怎么打包到一起的。2. 烧录前必须确认的三件事MMI文件、BRAM地址和Flash型号2.1 MMI文件怎么找Vivado工程和XSA里的位置合并ELF和BIT的核心工具是updatemem它依赖一个关键的中间文件——MMI文件MicroBlaze Memory Map Interface。MMI文件记录了MicroBlaze处理器的实例名、各内存段的地址范围、以及BRAM在FPGA内部的具体位置信息。updatemem拿着MMI才知道ELF里的代码该往哪个BRAM初始数据里塞。MMI文件在Vivado工程里自动生成路径大致如下工程目录/工程名.gen/sources_1/bd/BD名/ip/BD名_microblaze_0_0/data/microblaze_0.mmi如果你在Vivado里用File Export Hardware导出硬件时勾选了Include bitstream那么导出的XSA文件里也会包含一个system_wrapper.mmi。XSA本质是个zip压缩包解压后在子目录里能找到。还有更简单的办法直接在Vivado菜单栏Tools Tcl Console里运行find . -name *.mmi或者更习惯一点把文件浏览器直接切到工程目录搜索.mmi后缀文件通常结果不止一个选和你BD实例名对得上的那一个。可以打开MMI文件看一眼内容大致是这个样子?xml version1.0 encodingUTF-8? Project Version1 Level1 Processor C_BASE_VECTORS0x00000000 HW_INSTANCEmicroblaze_0 AddressRange TypeMEMORY Namemicroblaze_0_dlmb BaseAddress Value0x0/ Size Value0x8000/ RemapAddress Value0x0/ /AddressRange AddressRange TypeMEMORY Namemicroblaze_0_ilmb BaseAddress Value0x0/ Size Value0x8000/ RemapAddress Value0x0/ /AddressRange /Processor /Project注意里面的HW_INSTANCE它就是updatemem里的-proc参数要填的名字两者必须一致。2.2 确认MicroBlaze程序在BRAM里而不是DDR这是最容易翻车的一个点。updatemem能把ELF合并进BIT前提是你的代码在BRAM地址范围里。如果你的MicroBlaze工程把程序放在了DDR3、DDR4这类外部存储器里那情况完全不同。为什么因为BIT文件在FPGA上电配置时是无法完成对DDR控制器的初始化的。DDR控制器本身也需要配置完成、时钟稳定、训练完成之后才能访问。MicroBlaze一上电就要执行DDR里的代码可DDR还没准备好怎么执行所以DDR运行程序的场景通常需要一个Bootloader先放在BRAM里运行等DDR初始化完成后再从SPI Flash把真正的应用搬到DDR里执行。判断你的程序到底在哪个段里最直接的办法是查看SDK的链接脚本lscript.ld。打开这个文件找到类似下面的内容MEMORY { microblaze_0_dlmb_bram_if_cntlr_Mem : ORIGIN 0x00000000, LENGTH 0x00008000 microblaze_0_ilmb_bram_if_cntlr_Mem : ORIGIN 0x00000000, LENGTH 0x00008000 ... }如果ORIGIN是0x0、长度对应BRAM尺寸就在BRAM里。如果出现DDR地址比如0x80000000开头那你就得先解决启动引导问题不能直接合并。2.3 核对SPI Flash型号、启动模式和容量烧录Flash之前把这三样信息记牢并写到项目备注里。第一Flash的具体型号和厂商。不同厂商的SPI Flash在Hardware Manager里显示的型号名完全不同Micron的叫n25q128-3.3v-spi-x1_x2_x4Spansion/Cypress的叫s25fl128s-3.3v-spi-x1_x2_x4Winbond的叫w25q128-3.3v-spi-x1_x2_x4。选错型号烧录时Flash ID校验直接失败。第二Flash容量。容量决定write_cfgmem命令里的-size参数单位是Mb。2MB的Flash写-size 16这个我一开始没注意把16Mb写成了16MB导出MCS时报了格式错误。第三板子的启动模式引脚。Artix-7可以通过MODE引脚设置启动源必须设置为SPI模式一般是M[2:0]001。很多开发板有拨码开关或者跳线烧录前就要拨好否则烧完了也不会从SPI启动。这部分我放在后面排错章节细说。3. 核心操作用updatemem把ELF合并进BIT3.1 updatemem命令拆解updatemem是Xilinx提供的一个命令行工具位置在Vivado安装目录的bin文件夹下通常你打开Vivado的Tcl Console就能直接调用不需要额外配置环境变量。它做的事情概括起来就是打开原始BIT文件根据MMI里描述的内存布局把ELF文件中的代码和数据转换成BRAM初始化数据覆盖写入BIT对应位置输出一个新的BIT文件。命令的标准格式如下updatemem -meminfo MMI文件 -data ELF文件 -bit 原始BIT文件 -proc 处理器实例名 -out 输出BIT文件各参数的含义参数作用示例-meminfo指定MMI文件路径system_wrapper.mmi-data要合并的ELF文件路径hello_world.elf-bit原始BIT文件路径system_wrapper.bit-proc处理器实例名要和MMI里的HW_INSTANCE一致microblaze_0-out输出的合并后BIT文件download.bit注意你的SDK工程里默认生成的BIT文件是没有包含ELF数据的它是纯硬件配置。用updatemem处理过之后输出的download.bit才是我们要的东西。3.2 实测命令与输出解读以我的工程为例我在Vivado的Tcl Console里执行了这条命令updatemem -meminfo C:/work/proj/system_wrapper.mmi \ -data C:/work/proj/sdk/hello_world/Debug/hello_world.elf \ -bit C:/work/proj/system_wrapper.bit \ -proc microblaze_0 \ -out C:/work/proj/download.bit终端里会出现类似下面的输出UpdataMem: Info: Loading bit file C:/work/proj/system_wrapper.bit UpdataMem: Info: Loading mem info file C:/work/proj/system_wrapper.mmi UpdataMem: Info: For processor microblaze_0, loading data file C:/work/proj/sdk/hello_world/Debug/hello_world.elf UpdataMem: Info: Updating the bitstream... UpdataMem: Info: Creating output file C:/work/proj/download.bit UpdataMem: Info: Done如果看到Updating the bitstream...基本就成功了大半。这条命令跑完后download.bit的大小和原始system_wrapper.bit差不多但里面BRAM初始化段已经更新了。关键一点updatemem只是修改BIT里的BRAM初始化数据不会改变FPGA逻辑布线所以不影响硬件功能。你可以拿这个download.bit去JTAG下载验证如果程序行为和SDK里调试时一致说明合并正确。3.3 合并成功怎么验证合并完别急着烧Flash先做一个低成本验证在Vivado Hardware Manager里Open Target把download.bit下载到FPGA。此时不用启动SDK调试器程序会自动运行。如果这个文件下载后程序正常工作至少说明updatemem合并没问题问题只可能出在后续Flash烧录环节。另外一个快速检查方法是看download.bit里的内容。用文本编辑器打开如果里面能找到你的ELF文件名或者工程名相关字符串说明合并进去了。虽然原始BIT文件里也会有一些路径信息但对比两个文件的大小和修改时间也能判断。更严谨的做法是检查BRAM初始化段的变化不过日常开发中靠下载验证就够了。3.4 避坑MMI路径错误、proc名字不匹配、工具链架构不匹配我在这里踩过的坑基本可以写成一份负面清单。第一个坑是MMI文件路径错误。Vivado工程里.mmi文件通常嵌套在深层目录下如果从SDK里打开Tcl Console当前工作目录可能不在Vivado工程目录直接写相对路径很容易找不到文件。解决办法是用绝对路径命令就不容易出岔子。updatemem找不到MMI会报类似UpdataMem: Error: Cannot open file的错误看到这个先检查路径。第二个坑是-proc参数和MMI里的HW_INSTANCE不一致。MMI里的HW_INSTANCE是microblaze_0你在-proc里写microblaze_0是正确的。但如果你在Block Design里给MicroBlaze改过实例名比如改成cpu那-proc必须写cpu否则updatemem会说Unable to find processor。打开MMI文件看第一段Processor标签里的HW_INSTANCE照着写准没错。第三个坑比较隐蔽使用了错误架构的工具链。某些时候你从网上下载了一个第三方产物或者工程迁移时编译器版本变了SDK可能生成一个标准ELF但它不是MicroBlaze架构的。updatemem在解析ELF时可能会报出一个看起来不太相关的错误比如relocations in generic elf。这个报错的含义往往是拿MicroBlaze的updatemem处理了非MicroBlaze架构的ELF文件或者ELF文件损坏、架构字段不对。检查方法很简单在SDK的终端里运行readelf -h hello_world.elf看Machine字段有没有出现MicroBlaze相关标识。如果显示的是ARM或者其它架构赶紧检查SDK工程里的编译工具链选没选对。4. 生成MCS并在Hardware Manager中烧写SPI Flash4.1 write_cfgmem生成MCS文件download.bit拿到了接下来要把它转换成Flash能识别的格式。Artix-7的SPI Flash烧录最稳妥的方式是生成Intel HEX格式的MCS文件写入时Flash编程器能从MCS里按地址解析数据。生成MCS还是用Vivado Tcl Console命令是write_cfgmemwrite_cfgmem -format mcs -interface spix1 -size 16 -loadbit up 0x0 C:/work/proj/download.bit -file C:/work/proj/system.mcs拆开看每个参数到底在干什么-format mcs输出MCS格式这是Intel HEX的一种扩展变体。如果要给第三方烧录器用也可以选bin格式BIN是不带地址信息的纯二进制流。-interface spix1指定SPI Flash的接口模式。x1就是单线SPI。如果你的板上Flash接到了FPGA的四线SPI引脚且BIT工程里使能了x4模式写spix4。这个必须和你在Vivado IP配置里给SPI控制器设置的位宽一致同时也可以看Flash数据手册里的最高支持模式。-size 16Flash总容量单位Mb。16Mb就是2MB。这里千万注意别写成2那是2Mb正好少一位导出会报容量不够。-loadbit up 0x0 download.bitup是update的意思0x0是烧录起始偏移。如果需要把程序烧到Flash的特定偏移地址比如0x100000就写up 0x100000 ...。注意引号不能漏。-file输出的MCS文件名。命令执行后会在指定目录生成system.mcs文件。生成过程如果报错说bit文件太大装不进Flash要么-size写小了要么Flash容量真的不够。有些工程里FPGA配置bit加上BRAM初始化数据后超过1MB小容量Flash确实放不下。4.2 用Add Configuration Memory Device烧写MCSMCS文件有了烧录界面操作其实不难难在没经验的人容易漏掉关键步骤。第一步连接开发板Vivado右上角Open Target选择本地连接的hw_server打开Hardware Manager。此时你会看到设备列表里的FPGA型号比如xc7a35t。右键点击设备选择Add Configuration Memory Device。这一步其实Vivado是在帮你在BITSTREAM配置里添加一个SPI Flash器件的描述菜单翻译过来就是添加配置存储器设备。第二步在弹出的对话框里搜索你板上Flash的型号。这里输入型号关键词比如在搜索框输入n25q128会列出该系列下多种配置模式。选择与Flash实际型号完全一致的那一项比如n25q128-3.3v-spi-x1_x2_x4。型号选错后面烧写时Flash ID校验会失败。第三步选定Flash后Vivado会弹出一个烧录配置窗口里面让你选择烧录文件。默认加载的是当前工程的BIT文件你要手动改成刚才生成的system.mcs。注意这里也可以直接选择download.bitVivado会自动转换不过我还是建议用事先生成的MCS因为地址、偏移这些你自己可控排查问题也方便。第四步勾选ProgramVivado会询问是否擦除整片Flash默认即可。点击OK开始烧录等待进度条走完。烧录完成后系统会有提示如果你勾选了Verify工具会回读Flash内容和源数据比对这个强烈建议保留。校验通过后断开JTAG把板子启动模式切到SPI断电再上电看程序是否自动运行。4.3 烧写失败排查Flash ID不匹配、校验失败、启动模式未设置烧录环节最常见的几个报错我挨个说清楚。Flash ID不匹配是最常见的。Hardware Manager会提示检测到的Flash ID和选择的不一致。原因无非两种一是选错了Flash型号照着实物的丝印重新选二是Flash本身不兼容有些板卡上丝印标注和Vivado数据库里的Part Name对应不上可以查这个Flash的Manufacturer ID和Device ID在Vivado的Flash列表里按ID找。校验失败大概率出在SPI线序或电压上。SPI Flash的MISO/MOSI接反读写数据会全错Flash供电电压如果与配置引脚bank电压不一致也可能出现写的进去但读出来不对。这种情况先用示波器抓一下SPI时钟和数据线波形确认通信正常。还有一次遇到烧录中途卡住进度条纹丝不动最后发现是JTAG下载线太差10pin的杜邦线加长线信号质量完全不行。换一根原装短下载线后秒过。烧录SPI Flash对时序稳定性要求比JTAG下载BIT高很多这方面别吝啬。启动模式没设置好则容易被忽略。烧录完成后上电发现程序没跑很多人第一反应是烧录过程出了问题但往往忽略了板卡上FPGA配置模式引脚。Artix-7的M[2:0]引脚电平必须设置成SPI模式常见是001。有些开发板上是一个拨码开关写着ON/OFF有些板子是用跳线帽短接。不设置的话FPGA上电后会尝试从其它模式启动根本不去读SPI Flash。这一步排查起来特别容易忽略。5. 备选方案SDK直接烧ELF以及无BD工程的手工MMI5.1 SDK的Program Flash Memory到底能不能用很多教程会提到在SDK里直接使用Xilinx Program Flash Memory烧写一个镜像文件。我对这个功能的态度是可以用但别把它当作合并烧录的捷径。SDK的Program Flash Memory工具在设计上主要面向Zynq-7000系列它会要求你先有一个FSBLFirst Stage Bootloader或者Bootloop。对纯MicroBlaze裸机工程这个操作流程在不同版本的SDK里表现不一致我甚至在2018.3版本上遇到过烧写后无提示成功但实际上啥也没写进去的诡异情况。如果你非要用SDK烧写可以试试新建或使用默认Bootloop然后把你的application.elf作为Image添加进去。老版本EDK里这个过程很成熟叫Program Flash Memory配合Bootloop把ELF搬进Flash。但到了Vivado SDK时代建议还是回归updatemem加write_cfgmem的流程思路清晰兼容性也更好。我还见过有人想在SDK里直接用program_flash命令行烧ELF这在某些版本确实可行但前提是工具链完美支持你的Flash型号。相比之下Hardware Manager的Add Configuration Memory Device会维护一个庞大的Flash芯片数据库ID校验、擦除、编程、回读都是现成的比命令行裸写可靠得多。5.2 没有BD工程时手工构造MMI有的朋友用的是HDL工程没有图形化的Block DesignMicroBlaze是在代码里例化的。这种方式下Vivado不会自动生成MMI文件但你仍然可以用updatemem合并ELF。做法是手工写一个MMI文件让updatemem知道你的MicroBlaze实例名和BRAM地址映射。一个最小可用的MMI文件格式如下?xml version1.0 encodingUTF-8? Project Version1 Level1 Processor C_BASE_VECTORS0x00000000 HW_INSTANCEmicroblaze_0 AddressRange TypeMEMORY Namemicroblaze_0_dlmb BaseAddress Value0x0/ Size Value0x8000/ RemapAddress Value0x0/ /AddressRange AddressRange TypeMEMORY Namemicroblaze_0_ilmb BaseAddress Value0x0/ Size Value0x8000/ RemapAddress Value0x0/ /AddressRange /Processor /Project这个XML看起来简单但里面有几个字段很容易出错。HW_INSTANCE必须和代码中例化MicroBlaze时的实例路径一致否则updatemem找不到处理器。AddressRange的Size必须和BD里LMB BRAM实际容量一致不是越大越好填大了updatemem可能尝试写入超出BRAM真实范围的数据生成的BIT下载后程序无法正常工作。RemapAddress通常保持和BaseAddress一致除非你的电路设计里做了地址重映射。手工构造MMI最大的坑在于如果BRAM是用多个Block RAM拼接起来的MMI里还需要正确描述各片BRAM在FPGA中的物理位置。这个信息最好通过Vivado的report或者布局数据获取手工猜很容易出错。所以只要能用BD工程建MicroBlaze就别给自己找麻烦去手写MMI。5.3 两种固化方案怎么选说到底MicroBlaze固化有两条主路方案核心命令优点局限先合并再烧MCSupdatemem write_cfgmem流程清晰、工具链兼容性最好、烧录时可校验每次改软件都要重新合并BITSDK直接烧ELFSDK Program Flash / program_flash只更新软件时不用碰BIT需要Bootloop支持、版本差异大我的建议是项目初期做一次完整固化用第一种方案走通整体流程后续频繁修改软件调试时如果SDK的Program Flash在你当前版本上表现稳定可以用它只更新Flash里的程序区域省去重新合并和完整擦写时间。但产品交付前一定要再用第一种方案完整烧录并上电验证。6. 上电验证与排错经验6.1 断电重启后的完整验证流程烧录成功不等于固化成功。正确验证流程应该是这样的一次都不要省。第一步断开所有JTAG下载线保证FPGA不是被电脑通过JTAG配置的只有板卡自己上电。第二步检查启动模式拨码开关确认为SPI启动。第三步断电并重新上电观察程序运行状态。如果你的代码里设计了一个LED闪烁或者通过UART周期性打印字符这个现象应在上电1到2秒内出现。第四步连续断电上电至少3次以上。因为SPI Flash在不同温度、不同电压下读取时序可能略有飘移如果连续启动都稳定基本说明时序裕量足够。如果在验证中发现上电后程序不跑JTAG连接上去又能跑优先怀疑启动模式和Flash配置数据异常而不是代码问题。6.2 常见启动失败问题排查表把我在项目里遇到过的问题整理成一张表按频率从高到低排列现象可能原因怎么处理上电后FPGA的DONE灯不亮启动模式没设为SPI、Flash里配置数据损坏、MCS格式错误检查MODE引脚重新擦除再烧录核对write_cfgmem参数DONE灯亮但程序不跑ELF没合并进BIT、启动地址不匹配、BRAM容量不够用download.bit做JTAG下载验证检查链接脚本ORIGIN部分功能正常部分异常BRAM初始化数据错位、MMI里多段BRAM映射错误核对MMI的AddressRange和ELF实际段分配上电后偶尔跑偶尔不跑Flash读写时序紧、SPI模式配置不当、电源不稳定换x1模式试试检查Flash供电和去耦电容烧录时校验失败Flash型号选错、SPI接线问题、速度过高核对Flash ID降低SPI时钟频率检查硬件连接遇到DONE灯亮但程序不跑是我见过最多的求助帖的问题。多数情况下问题出在这条命令上updatemem -meminfo ... -data ... -bit ... -proc microblaze_0 -out download.bit如果你直接拿SDK导出的原始BIT烧进了Flash那Flash里根本没有程序数据FPGA只完成了硬件配置MicroBlaze从0x0取出的是全F或全0自然跑不起来。所以烧录前一定要先确认Flash里放的确实是合并后的download.bit而不是原始BIT。6.3 经验之谈几个值得养成的习惯把这些经验收个尾我觉得有几个习惯特别值得养成。第一个习惯是把updatemem和write_cfgmem这两条命令写成一个批处理脚本或者Tcl脚本固定在每次改完软件后自动执行一遍。Vivado的Tcl Console支持source脚本我通常把命令写到一个txt文件里每次替换路径参数即可。脚本化之后既不会漏掉中间步骤也方便同事接手。第二个习惯是烧Flash前先用JTAG下载合并后的download.bit验证。这个步骤虽然有重复但能帮你把程序问题和Flash烧录问题隔离开省去很多排查时间。就算程序每次都一样这个验证也值得做。第三个习惯是针对大工程的保留原始BIT文件不要把updatemem的输出直接覆盖到原始BIT上。这样每次改动都可以重新合并避免污染原始配置。另外养成烧录前先擦除整个Flash但只擦除程序区域的习惯如果同一片Flash上还存了校准数据、以太网MAC地址等其它信息全片擦除会让这些数据丢失。最后一个建议和版本相关Vivado 2019.2之后SDK改名为Vitis界面和操作路径有不小的变化但updatemem和write_cfgmem这两个Tcl命令在Vivado里一直保留。如果你用的新版Vitis只要打开Vitis工具里的Xilinx Tcl Console同样可以执行这套流程。学会命令行以后其实不用太在意SDK还是Vitis的界面差异命令行的稳定性和可移植性反而更好。
返回列表