ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

FPGA远程升级实战:基于AXI Quad SPI与Multiboot的完整避坑指南

2026/10/5 12:52:54 拓冰建站 浏览量
FPGA远程升级实战:基于AXI Quad SPI与Multiboot的完整避坑指南 这两天群里又有人在问FPGA远程升级的事板卡已经发到现场了结果业务逻辑要改总不能还拎着JTAG线飞过去。以前遇到这种情况确实头疼但现在Xilinx FPGA基本都支持multiboot配合外部SPI Flash就能实现“上电自主选择加载哪个镜像”配合ICAPE3还能在运行中主动触发跳转。整套方案的关键器件就是AXI Quad SPI这个IP核和N25Q128这颗Flash踩过的坑不少写篇完整的避坑流程出来希望能帮正在搞远程升级的人少走点弯路。这篇博文的内容我按“原理—配置—读写—升级—排障”这条链路来拆覆盖AXI Quad SPI IP核的参数配置、N25Q128的指令集操作、Golden/Update双镜像规划、ICAPE3触发multiboot以及我在实测中遇到的几类典型问题。适合已经会用Vivado建工程、能看懂AXI总线基本时序的FPGA开发工程师阅读刚入门的朋友建议先补一下SPI协议和FPGA配置流程的基础知识再来看。1. 远程升级的底层逻辑与整体方案设计1.1 为什么是“双镜像 Multiboot”FPGA上电后配置逻辑会从外部Flash的起始地址搬运bitstream。Xilinx在7系列及之后的器件里支持Multiboot功能意思是FPGA启动时可以先从Flash的0地址加载一个“引导镜像”这个引导镜像运行起来之后再通过ICAPE3发起一条IPROG命令让FPGA主动跳转到Flash的另一个地址去加载真正的业务镜像。如果跳转加载失败或者写入的镜像本身是坏的FPGA还能自动回退到Golden镜像继续启动。这套机制的好处是明显的现场不需要任何物理连接只要业务端能收到新的bit文件把它写到Flash的指定区域然后触发热复位整机就在无人干预的情况下完成了逻辑更新。就算新镜像写坏了设备也不会变成砖还会回到出厂版本继续工作。坏处也有就是Flash分区、镜像生成、回退逻辑这些必须在前期设计好不能临时抱佛脚。1.2 AXI Quad SPI在这个方案里扮演什么角色AXI Quad SPI是Xilinx提供的一个软核IP挂在AXI4-Lite总线上通过寄存器操作去控制QSPI控制器最终完成对SPI Flash的擦除、写入和读取。在远程升级方案里它的定位是“运行时的Flash访问通道”。为什么不用普通的GPIO模拟SPI因为QSPI控制器支持x1、x2、x4模式可以跑到较高的时钟频率而且自带命令解析逻辑适配市面上主流的SPI NOR Flash指令集同时还能配置成线性地址模式把Flash直接映射到处理器地址空间读起来非常方便。用GPIO模拟当然也能写但性能差太多写一个16MB的镜像会等到怀疑人生。需要注意的是FPGA上电配置阶段Flash是由FPGA内部的配置逻辑直接控制的AXI Quad SPI在此时还不存在。等Golden镜像加载完成AXI Quad SPI跑起来之后它才接管Flash的访问权。这个“接管”不是硬件切换而是靠时序上的约定AXI Quad SPI发出访问时配置阶段的驱动早已释放总线。1.3 N25Q128芯片特性速览N25Q128是Micron现在归入英飞凌旗下的128Mbit SPI NOR Flash等于16MB。这个容量放2~3个中等规模的FPGA镜像完全够用。它的页大小是256字节扇区大小默认是64KB部分型号支持子扇区擦除擦写寿命大约10万次数据保持能力官方标称20年工业级温度范围-40~85摄氏度非常适合做配置存储。指令集方面我常用的就几个0x06写使能、0x05读状态寄存器、0x20扇区擦除、0x02页编程、0x03/0x0B读取、0x9F读ID、0x01读状态寄存器1、0x50清状态寄存器。后面操作的时候会逐个讲。1.4 我最终采用的架构我在实际项目里用的是XC7K325T N25Q128Flash挂在FPGA的专用配置引脚上AXI Quad SPI配置成StandardQuad模式线性地址空间直接映射Flash的前16MB。片内逻辑挂了一个AXI-Lite Master通过它操作SPI控制器寄存器完成擦写和校验。镜像布局分三块0x000000Golden镜像永远保留只允许在出厂时写入后续升级流程不许擦除它。0x200000Update A区放最新版本业务镜像。0x400000Update B区放上一个版本镜像形成AB双备份避免单区更新失败后无版本可用。实际容量如果镜像较小也可以简化成Golden Single Update两个区关键是必须留一个不会动的兜底镜像。2. AXI Quad SPI IP核配置从参数选择到上电初始化2.1 IP配置里最容易忽略的4个参数AXI Quad SPI IP的配置界面乍看很简单就几项下拉框但真正决定成败的就是几个细节。第一个是SPI Mode。IP提供Standard、Dual、Quad、Dual/Quad等选项。如果Flash支持x4读想让启动速度快一点选择“Quad”并勾选“Use the Quad Mode for Read”这类选项注意x4模式的DO引脚会被复用必须设为inout类型。第二个是Data Width。默认是32位还是8位取决于你的AXI总线设计。用AXI4-Lite的话数据位宽最好统一避免后续地址对齐和读写逻辑混乱。第三个是Instruction Width。N25Q128指令通常是8位选8就行但如果是某些国产Flash或者老型号是16位指令这里不对控制器就会发出错误的命令字。第四个是SPI Clock Divider。这个很多人都忽略了直接用默认的32分频结果在SPI时钟频率较高的时候Flash工作不稳定。我自己习惯先在低频下把所有功能调通比如AHB/AXI时钟100MHzSPI时钟先给10MHz左右稳定后再往上提。后面有专门一节讲速度问题。还有个容易忽略的点IP配置里需要选择是否有FIFOFIFO深度是多少。做大量数据搬运的时候FIFO深一点能减少CPU轮询次数但消耗的BRAM也更多根据实际需要取舍。2.2 STARTUP原语不说清楚这里必踩坑如果你把AXI Quad SPI的时钟直接接到普通逻辑时钟上一开始功能看似正常但等到真正加载大镜像时经常出现偶发错误。原因在于FPGA配置完成后CCLK引脚可能已经停止输出而QSPI IP为了操作Flash需要在运行时自己产生SPI时钟。如果这个时钟没有经过STARTUP原语引到CCLK相关路径上硬件上可能存在时钟拓扑不匹配的问题。正确做法是在AXI Quad SPI IP的配置界面里勾选“Include STARTUP primitive”然后让IP内部产生的时钟通过STARTUP原语连接到配置时钟路径或者手动例化STARTUPE2原语把SPI时钟从原语的CLKOUT引脚引出来。这样Flash的时钟域就与配置阶段保持一致读写时序不容易出幺蛾子。具体到代码用STARTUPE2的时候大概是这样的写法STARTUPE2 #( .PROG_USR(FALSE), .SIM_CCLK_FREQ(100.0) ) STARTUPE2_inst ( .CFGMCLK(), // 输出 .CLK(0), .GSR(0), .GTS(0), .KEYCLEARB(0), .PACK(1), .USRCCLK(spi_clk_internal), // 把IP生成的时钟喂进来 .USRDONEO(1), .USRDONETS(1) );这样写完之后实测SPI时序的稳定度会高很多尤其在高位宽模式下误码率明显下降。2.3 寄存器模型与读写接口AXI Quad SPI的寄存器不多常用的就几个。0x00是控制寄存器0x04是状态寄存器0x08是发送FIFO数据寄存器0x0C是接收FIFO数据寄存器0x10是SPI状态寄存器0x14以上还有一些扩展控制位。单次传输的基本流程是先在控制寄存器里配置SPI传输模式把要发送的指令和数据写入DTR寄存器然后拉起控制寄存器的启动位控制器内部会自动完成MOSI/MISO的移位操作完成后状态寄存器会置位。接收侧从DRR寄存器读回数据即可。需要注意的是控制寄存器里有个“执行”位必须在写入指令数据之后再触发顺序反了会导致指令先发出去、数据跟不上Flash直接解析成错误命令。另外SPI状态寄存器里有忙标志每次传输结束后要等待它清零不要急着发下一条命令。2.4 IP核的“开机动作”AXI Quad SPI上电之后不能直接就去操作Flash。建议先做一次软复位然后清空两个FIFO再读取Flash的ID确认链路正常。这个动作花不了几个时钟周期但能避免上一次复位残留的状态影响后续操作。清FIFO的方法在不同版本IP里略有差异一般是写控制寄存器的FIFO清空位然后读状态寄存器确认FIFO空标志。如果FIFO里残留数据后续读回的数据就全是偏移的那排查起来非常蛋疼。3. N25Q128 Flash读写实操指令模式还是线性模式3.1 指令模式与线性模式的适用场景AXI Quad SPI支持两种操作模式指令模式和线性模式这个概念必须分清。指令模式下CPU通过写寄存器的方式向Flash发出完整的命令序列比如“发送0x06写使能”、“发送0xD8擦除扇区”、“发送0x02写入256字节”。这个模式的优点是灵活可以执行任意指令缺点是每次传输要CPU参与带宽不高。线性模式下AXI地址直接映射到Flash的地址空间CPU像读内存一样读Flash控制器自动生成读指令并搬家数据。这个模式非常高效适合大量回读校验以及直接把配置数据从Flash加载到DDR。我在项目里的用法是升级阶段用指令模式擦写校验阶段用线性模式整片回读比对。两个模式都打开按需切换。3.2 最关键的坑跨页编程N25Q128的一个page是256字节page program指令最多只能写一个page而且不能跨页。这句话看起来简单实际操作中至少有一半的“写入失败”案例是跨页造成的。比如你要从一个非对齐地址开始写300字节0x0200地址写100字节剩下的200字节写到0x0300这实际上是两个page的操作你必须拆成两次page program第一次写0x0200~0x02FF第二次写0x0300~0x0363否则Flash会从当前页起始地址卷绕把数据写错不报错回读才发现对不上。我的经验是封装一个“任意起始地址、任意长度写入”的函数内部自动处理页对齐int flash_write(uint32_t addr, uint8_t *data, uint32_t len) { uint32_t offset addr 0xFF; uint32_t first_chunk 256 - offset; if (first_chunk len) first_chunk len; // 先写不满一页的首块 spi_write_page(addr ~0xFF, data, first_chunk); addr first_chunk; len - first_chunk; // 剩余部分按整页写 while (len 0) { uint32_t chunk (len 256) ? 256 : len; spi_write_page(addr, data (addr - (uint32_t?) ), chunk); addr chunk; len - chunk; } }注意上面的索引只是示意实际要维护好源数据指针别拿地址当偏移去索引数组。3.3 读状态与等待函数Flash写完或者擦除后状态寄存器1的低位WIP会保持1表示器件的内部状态机还在忙。发送0x05指令后读回的那个字节bit0为1就继续等为0才代表操作结束。等待逻辑一定要设计超时机制。N25Q128整片擦除可能要几十秒扇区擦除也要几十到几百毫秒页编程一般在1ms以内。如果程序里只写了个死等一旦Flash异常CPU就hang死在那边了。建议给不同操作设置不同的超时阈值比如页编程100ms、扇区擦除5s、读状态100us轮询一次超时就报错返回。int spi_flash_wait_ready(uint32_t timeout_ms) { uint32_t elapsed 0; while (elapsed timeout_ms) { uint8_t status; spi_flash_read_status(status); if (!(status 0x01)) return 0; delay_ms(1); elapsed; } return -1; }这个函数虽然简单但在远程升级里是生命线。状态没等就发下一条命令Flash会直接忽略超时时间设太短明明还在擦除就会报写入失败。3.4 完整读写函数库的结构我习惯把N25Q128的操作封装成一个独立的C文件对外暴露几个接口初始化、读ID、擦除扇区、页写入、任意长度写入、线性模式读取、CRC校验。这些接口底层都走AXI Quad SPI的寄存器读写上层业务调用时不用关心SPI细节。页写入的底层序列一定要按顺序来先写0x06使能然后立即发0x02页编程命令后面跟上3字节地址再跟256字节数据。命令和数据之间不要有任何多余的SPI访问N25Q128要求这一整条序列是连续的中间被打断就会失败。3.5 线性模式回读校验写完镜像之后我习惯用线性模式把整个Update区读回来算CRC和发送前的CRC对比完全一致才允许跳到新镜像启动。这一步虽然多花几秒钟但能把绝大部分坏块和总线问题挡在升级之前。线性模式的优势在于读回操作不会污染Flash内部状态也不需要擦除可以反复读。如果发现CRC不一致还能再把出错的扇区重新擦写一遍不用全片重来。实测下来大部分闪存错误都是单bit翻转重写一次就能修复。4. 远程升级与回滚实现Multiboot、ICAPE3与WBSTAR4.1 Golden区与Update区的镜像规划双镜像规划的核心是不管你Update区怎么折腾Golden区都不能碰。所以我推荐Golden区在出厂时一次性烧好后面所有升级程序都只操作Update区。地址分配我给出一个实际例子分区起始地址大小用途Golden0x0000002MB出厂引导镜像永不修改Update A0x2000002MB当前业务镜像Update B0x4000002MB备份业务镜像如果你的bit文件超过2MB按实际大小调整但注意扇区擦除是按64KB对齐的分区边界最好对齐64KB否则擦除一个扇区会连累邻居分区。4.2 ICAPE3原语与32位命令序列FPGA运行过程中用户逻辑可以通过ICAPE3原语把命令发送给配置逻辑从而实现重新配置重加载。ICAPE3是一个32位命令接口发送固定序列即可触发multiboot跳转。我常用的跳转命令序列如下localparam [31:0] SYNC_WORD 32hAA995566; localparam [31:0] CMD_WBSTAR 32h20000000; // 写入WBSTAR寄存器之前的前导命令 localparam [31:0] WBSTAR_UPDATE 32h00200000; // Update区起始地址注意低4位为0 localparam [31:0] CMD_IPROG 32h30008001; localparam [31:0] IPROG_CMD 32h0000000F;发送顺序是先同步字0xAA995566再发0x20000000紧接着把目标地址0x00200000写入再发0x30008001最后写0x0000000F触发IPROG。其中WBSTAR的值要左移对齐实际上WBSTAR寄存器只使用高28位所以地址低4位必须为0。有个细节ICAPE3一次只能接收32位不能用AXI直接连续写必须通过状态机把序列一条一条喂进去。喂完之后配置逻辑会自动重新加载Flash此时原来的逻辑停止运行所以这个跳转是不可逆的执行前一定要确认写好的镜像合法。4.3 触发远程升级的三种方式第一种最简单收到远程命令后先校验完整镜像再写WBSTAR触发ICAPE3。适合有外部通信链路的场景比如以太网、UART。第二种是定时触发系统每天凌晨固定检查一次是否有升级包有就自动升级。这种适合无人值守设备但必须加看门狗防止升级过程中主流程卡死。第三种是外部IO触发比如拨码开关或者按钮按下才允许升级适合维修调试时手动升级。我的做法是这三种都保留用寄存器选择触发源灵活性最高。4.4 升级、校验与回滚的完整流程一次真正靠谱的远程升级我总结成六步第一步通过通信接口接收升级文件放入DDR暂存。第二步对文件计算CRC和发送端的CRC比对确认文件完整。第三步擦除Update A区。第四步向Update A区写入新镜像。第五步线性模式回读整区并计算CRC再次比对。第六步写WBSTAR指向Update A区触发ICAPE3重加载。这六步里最容易被忽略的是第二步和第五步。第二步不校验网络传输出错后照样写进Flash回读比对时才发现但此时旧镜像可能已经被擦掉了。第五步校验不能省万一擦写过程中掉电Flash里是半截数据直接重启必挂无疑。4.5 Fallback回退是怎么生效的很多人以为fallback要靠自己的逻辑实现其实Xilinx的配置逻辑自带一个回退机制。当FPGA从Update区加载bitstream时配置逻辑会启动一个超时计数器如果在规定时间内没有完成同步字检测或者加载出错就会自动重新回到Golden区加载。这个超时时间在生成bitstream时的属性里设置一般设5~20ms。时间设大了启动失败后要等很久才能回退设小了正常加载稍微慢一点就会误触发回退。我实测7系列在50MHz SPI时钟下2MB镜像大约几百毫秒能加载完成所以超时时间设置在100ms左右比较稳妥具体要看你自己的Flash时钟频率。需要特别注意的是回退只能跳过损坏的bitstream如果你写进去的镜像能通过配置逻辑的校验但业务逻辑本身跑飞了那fallback不会触发。这种场景需要在应用层加看门狗发现业务异常时主动通过ICAPE3跳回Golden。4.6 生成合并镜像与烧写Vivado里生成bitstream之后勾选生成bin文件然后用Write_cfgmem生成一个适合烧写的bin或者MCS格式文件。如果想把Golden和Update拼成一个文件一次性烧进Flash可以直接用bin文件拼接最简单的方式是dd ifgolden.bin ofcombined.bin bs1M count2 dd ifupdate_a.bin ofcombined.bin bs1M seek2 dd ifupdate_b.bin ofcombined.bin bs1M seek4注意dd的bs和count要和实际分区大小对应。拼完之后用Vivado Hardware Manager加载到Flash或者自己做上位机通过AXI总线写进去都行。如果只更新Update区更快的做法是直接通过运行中的系统把bin文件送到FPGA端由FPGA自己把文件写入Update区完全不需要连接JTAG。这也是“远程升级”的核心价值所在。5. 实测中踩过的坑与排查思路5.1 Flash ID读到0xFF或0x00这个问题大概率不是Flash坏了而是SPI链路压根没通。先查硬件连接确认片选、时钟、MISO/MOSI没有接反再查约束如果Flash挂在专用配置引脚上需要在XDC里加上SPI_BUS_STANDARD、SPI_BUS_READ_WRITE这些属性最后查时钟IP的SPI时钟频率是不是超出了Flash支持上限N25Q128在Quad模式下最高标称108MHz但常规读取模式建议先降到20MHz以下调试。还有一个隐蔽原因SPI控制器被配置成Quad模式但Flash并没有进入Quad Enable状态此时读ID会读到错误值。先发0x01读状态寄存器看bit1Quad Enable是否为1不是的话发0x35或写状态寄存器把它打开不同型号Flash状态位可能不同具体看数据手册。5.2 升级之后设备直接卡死设备卡死要分两种。一种是新镜像本身有问题比如差分时钟没锁住、外设初始化卡住这种是应用层问题fallback救不了需要在业务逻辑里加软狗。另一种是Flash里的镜像被写坏了这种fallback应该能拉到Golden但如果Golden区也空着或者本身没烧好那就只能JTAG重新烧了。排查思路是先用ILA抓ICAPE3的状态确认WBSTAR有没有写进去、IPROG有没有发出来再量一下Flash的CS脚看加载期间有没有正常的跳变最后看PS端的BOOT_MODE引脚确认当前是从SPI启动别是板卡上跳线被改了。5.3 Fallback没生效Fallback没生效最常见的三个原因一是生成bitstream时没勾选fallback属性默认是关的二是WBSTAR写到的地址不是一个合法镜像的起始地址配置逻辑找不到同步字就直接挂住不会再回退三是超时时间设得太短Flash时钟太慢5ms内连同步字都没给全提前触发了回退的“假失败”。实操中最容易疏忽的是第三条因为看起来像是回了Golden实际上只是超时设太小。我调试时把超时时间直接设成500ms保证一帧镜像能完整加载完最后再把时间调回合适值。无论什么原因排查fallback问题最快的办法是拿一个已知能启动的镜像分别放到Golden和Update区然后故意在Update区放一个空的全FF文件触发一次升级看能不能回退。能回退说明机制正常不能回退再逐项查配置。5.4 用了普通IO导致启动失败如果Flash接的是普通IO而不是专用配置引脚上电时FPGA还没加载配置普通IO处于高阻态Flash片选可能悬空导致配置读取失败。解决办法主要有两种一是干脆把启动模式改成从普通IO接的Flash加载前提是这块FPGA支持非专用引脚启动这个要查器件手册二是加上拉电阻让片选在上电时保持无效电平等逻辑跑起来之后再把存储访问权接管过来。更稳的方案是Flash放在专用配置引脚上同时把AXI Quad SPI的引脚约束指向同一组引脚。这样上电时配置逻辑用这些引脚加载运行时的SPI控制器也用这些引脚访问Flash不会冲突。5.5 SPI速率上不去跑高频就出错N25Q128在标准SPI模式下最高可以跑到几十MHz但关键是PCB走线、上拉电阻、寄生电容都会影响信号质量。我调试时发现时钟升到33MHz以上就丢数据降到20MHz一切正常。升速要分步走不要一上来就调到最高。先把分频系数调到SPI时钟10MHz验证所有读写和擦除流程没问题再调到20MHz重复验证最后再往高提。同时检查XDC里有没有对QSPI引脚做时序约束如果时序违例高速模式下数据采样点不准是必然的。5.6 ILA抓不到信号怎么办远程升级逻辑跑在FPGA内部最直接的调试手段就是ILA。抓不到信号先确认ILA例化的数量和采样深度会不会导致综合后资源不够ILA被优化掉了再确认触发条件是不是太严格最好先设一个“任意变化就触发”看波形里有没有信号活动最后确认时钟域ILA的采样时钟和待测信号必须在同一个时钟域。我遇到过一种情况ILA能看到写寄存器的AXI事务但SPI控制器没有任何输出原因是IP的时钟没有使能。后来在IP配置里把SPI时钟的分频系数改成了可配置项代码里先写分频寄存器再打开全局使能问题就没了。5.7 常见问题速查表现象可能原因解决办法读ID全FFSPI链路不通、时钟未起查硬件连接和约束降频重试写入后回读不一致跨页写入封装页对齐写入函数擦除后数据没变Flash被写保护先读状态寄存器清BP位升级后卡死新镜像不合法检查WBSTAR地址改用可回退流程Fallback不动作没勾选fallback属性重新生成bit文件确认属性高频读写误码信号完整性差降频、检查PCB走线、加约束ILA无信号时钟域不对或ILA被优化确认采样时钟和触发条件这里面Flash写保护这个坑我想单独多说一句。N25Q128状态寄存器里有BP0~BP3位出厂时可能是0但有些板厂烧录工具或者旧代码会不小心置1导致后面所有写操作都静默失败。遇到写不进、擦不掉的情况第一步永远是读状态寄存器确认保护位状态必要时发0x50清除状态寄存器。结语还是算了吧说点实在的做了这么多次远程升级我最大的体会是做这套东西七分靠设计三分靠调试。设计阶段把分区、回退、校验、超时这些都想清楚后面几乎不会出大问题反过来如果一上来就疯狂写代码等板子现场挂了才查那真是叫天天不应。再分享一个小技巧在开发环境里我习惯用Vivado的硬件管理器把Flash内容导出来用电脑上的十六进制工具和原始bin文件做对比一眼就能看出来地址偏移或数据错位。这个习惯帮我抓过三次升级文件生成时的打包错误比在板子上盲调高效得多。调试顺序上也有一点建议先把“读写-擦除-校验”这条链路完全跑通再做“WBSTAR跳转”最后才接远程通信。三层都稳定之后再考虑并发、超时、断点续传这些业务细节。远程升级不是功能是保命用的基础设施值得多花几天时间打磨。