ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA远程升级实战:AXI Quad SPI与N25Q128 Flash方案详解

FPGA远程升级实战:AXI Quad SPI与N25Q128 Flash方案详解 我在这块摸爬滚打了五六年远程升级这个需求几乎每个FPGA项目跑到后期都会冒出来——不管是现场设备固件迭代、算法参数在线调整还是产品出货后悄悄修几个不那么光彩的Bug。但说实话FPGA远程升级比MCU那套要别扭得多尤其是当你以为“不就是往Flash里写几个数嘛”的时候设计本身已经在埋雷了。这篇就拿Xilinx 7系列Vivado环境配合AXI Quad SPI IP核操作N25Q128这块再常见不过的128Mb SPI NOR Flash把整个流程从架构、寄存器、时序踩坑到回滚策略完整捋一遍。这篇文章适合谁正在做FPGA在线升级方案选型的、被ICAP和MultiBoot文档绕晕的、写完Flash读写却发现上电起不来的还有那些准备把远程升级当“最后一公里”交付给现场的工程师。我们直接开工。1. 远程升级的整体架构先把“能回滚”想明白再动手远程升级最怕的不是升级失败而是失败之后设备变砖人还得跑一趟现场。所以整个架构设计的核心不是“怎么把新固件写进去”而是“写坏了怎么回来”。N25Q128有16MB空间FPGA的bit文件通常在几MB到十几MB不等合理划分这16MB让系统同时具备正常启动区和备份区是第一步。1.1 存储布局Golden区、Update区与临时缓存区我常用的分区方式是这样的最前面的1MB放Golden镜像出厂固化、永不擦除的“保命”版本接下来8MB放Update区日常远程升级的落地区再往后的4MB作为Download Buffer接收新固件的临时暂存区域校验通过后才搬移到Update区最后留一些空间存版本信息和升级日志。这样即使升级过程中途断电最多丢一个未完成的临时文件Golden镜像永远在。地址分配并不是固定的要根据实际bit文件大小调整但核心准则是Golden区必须独立、写保护、所有用户逻辑都无法修改它。有些设计会把Golden区放在Flash末尾用跳线控制启动地址但用AXI Quad SPI和MultiBoot机制的话Goldern放低地址段是默认且最简单可靠的。1.2 为什么用AXI Quad SPI而不是普通SPI或者BPIN25Q128支持标准SPI、Dual SPI和Quad SPI模式。远程升级最看重的就是写入速度和总线打通效率。标准SPI一次只有一根数据线16MB写满得跑很久而Quad SPI模式下四根IO并行同样的时钟频率下理论传输速度提升四倍。AXI Quad SPI IP把AXI4协议转成Flash时序让PS嵌入式ARM或者用户逻辑可以通过内存映射的方式直接读写Flash。在7系列纯FPGA场景下用户逻辑通过AXI4-Lite或AXI4接口挂Master在PS场景下Zynq的MIO/EMIO可以连接到IP。双场景统一用AXI Quad SPI的好处是驱动逻辑可以复用不需要为PS和PL写两套Flash驱动。1.3 启动流程与远程升级的关系7系列的上电加载流程是芯片先按配置模式引脚M[2:0]找到SPI Flash读取起始地址0x000000的镜像如果该镜像头部的CRC校验失败或者你通过ICAP主动发起了IPROG命令则跳到WBSTAR寄存器指定的地址继续加载。这就给了我们很大的设计空间正常启动时读Golden区需要升级时先把新固件写到Update区然后用WBSTAR把启动地址指向Update区最后触发ICAP重配置——启动流程就变成立即用新固件运行。这个机制拆解下来会发现所谓“远程升级”的本质就是改一个启动地址 触发一次重配置。这比以往听到的各种云里雾里的说法朴素得多。2. AXI Quad SPI IP配置详解最容易翻车的几个寄存器Vivado里添加AXI Quad SPI IP并不难双击打开配置界面才是噩梦的开始。那些选项组合起来有几十种可能选错一个后面写Flash写到你怀疑人生。2.1 模式选择Standard vs Dual vs QuadIP提供了三种模式Standard单线、Dual双线、Quad四线。我强烈建议直接用Quad模式因为后续所有读写指令都统一走四线协议速度和兼容性都有了。Dual是过渡方案如果是第一次接这个IP为了稳妥想先用单线打通也不是不行但后面再改IP配置、改约束、改驱动工作量翻倍。一个容易忽视的点是Quad模式下读Flash用的是0x6B指令Fast Read Quad Output而写Flash用的是0x32指令Quad Page Program。也就是说读和写都会走四线但指令码完全不同驱动里要把这两个指令分开定义清楚。2.2 时钟比例与FIFO深度IP内部的SCK时钟来自AXI时钟分频配置界面有几个档位2、4、8。N25Q128最高支持108MHz的读时钟但实际布线、PCB信号完整性以及温度漂移都会限制这个值不建议直接顶到上限。我一般ACLK跑100MHzSCK分频到50MHz左右写Flash时页面编程Page Program本身就受限于Flash内部写时间SCK再高也无法突破读的时候50MHz也是比较稳的区间。FIFO深度则取决于你的AXI总线位宽和突发长度。选32或者64就够用除非PS侧DMA搬运数据才会考虑更深的FIFO。过大的FIFO只会浪费Block RAM资源。2.3 EZ模式与DIY模式的取舍这是IP最隐蔽、也最坑人的一个选项。INCR模式叫DTR其中有个“FIFO mode Legacy mode”的选择EZ模式IP内部把Flash的状态机封装好了你只需要做AXI读写它会自动处理写使能、轮询忙标志、页写入边界等底层细节。看起来美好但它假定你对地址空间的操作是连续的、自然对齐的不够灵活。DIY模式IP只负责最基本的指令收发写使能、状态寄存器轮询、擦除、页编程这些要你自己通过SPI指令序列完成。远程升级这种场景需要精细控制配置寄存器比如状态寄存器的保护位而且跳转启动地址前要读Flash状态确认EZ模式的封装反而碍事DIY模式才是能自由操控的方案。记住这个结论远程升级用DIY模式纯XAxi接口的流水数据搬运场景才考虑EZ模式。3. N25Q128关键指令与时序从擦除到编程每个步骤的Wait都省不得N25Q128是一款非常标准的SPI NOR Flash指令集在Micron/Numonyx的datasheet里都有但时序的严谨程度直接决定了你的Flash是健康工作十年还是三个月就坏块。3.1 写使能WREN 0x06的前置角色NOR Flash写入前必须拉高状态寄存器的WEL位Write Enable Latch就像你改系统关键配置前要输入sudo密码一样。WREN指令只有一条0x06没有地址阶段。每执行一次Page Program或Sector Erase都会自动把WEL位清0所以每次编程/擦除操作前必须先发WREN。很多新手的第一个坑就在这里写了WREN之后立刻发Page Program然后发现数据根本没写进去大概率是WREN之后没有轮询状态寄存器确认WEL已经拉高。正确的顺序是发WREN → 读状态寄存器 → 确认WEL1 → 再发Page Program。3.2 状态寄存器轮询Busy Bit的等待逻辑N25Q128的Sector Erase典型时间是3秒Page Program典型时间是0.65ms。这些时间里Flash内部在忙外部任何指令除了RDSR0x05都被忽略。驱动必须循环读状态寄存器直到Bit0WIP位Write In Progress变成0才算完。这里有个时间预算的问题。Sector Erase一次要3秒如果你的升级固件有4MB按64KB一个Sector算就是64个扇区光擦除时间就192秒。这个时间用户从APP层看就是“升级中”的等待体验上必须给足提示。更关键的是如果你的驱动在上次擦除还没结束时就直接发下一个指令Flash会忽略当前异常指令并导致状态错乱后续操作全部扑街。所以轮询一定不能省而且要保证超时判断足够宽裕。我用的轮询函数大致长这样int flash_wait_ready(uint32_t timeout_ms) { uint32_t elapsed 0; uint8_t status 0; uint32_t command 0x05000000; // RDSR via AXI Quad SPI while (elapsed timeout_ms) { // 通过IP发送0x05指令并读取状态寄存器 status axi_quad_spi_transfer(command); if ((status 0x01) 0) { // WIP 0不忙了 return 0; } delay_ms(1); elapsed; } return -1; // 超时 }3.3 扇区擦除与数据保护位N25Q128的Sector Erase指令是0xD8按64KB一个扇区操作。官方还支持4KB的子扇区擦除0x20但升级固件动辄几MB用4KB擦除会极其耗时只有更新配置参数时才划算。所以升级场景默认用0xD8做整扇区擦除。N25Q128有BP0~BP3四个保护位位于状态寄存器的高位。默认情况下保护区域是空的你可以自由擦写。但一旦你开启保护擦除和编程指令在保护地址范围内会被硬生生拒绝。我见过产线上一批板子Flash写不进去查半天发现是之前某次升级代码里多了一句写状态寄存器保护位把整个Flash锁死了。解锁的方法是先发WREN然后往状态寄存器写入0x00清除所有保护位。这条逻辑要放在每一次擦除前做到“不管上次状态如何这次都要强制解锁”。3.4 Page Program与跨页写入N25Q128的Page Program指令Quad模式0x32一次最多写256字节且这256字节必须落在同一个256字节对齐的Page内。如果你要写的缓冲区跨越了Page边界必须拆分成两次甚至多次Page Program。这块逻辑我通常在驱动层封装好提供一个缓冲区任意长度写入的接口内部自动拆页int flash_write_buffer(uint32_t addr, const uint8_t *buf, uint32_t len) { uint32_t offset 0; uint32_t page_offset addr 0xFF; // 当前页内偏移 uint32_t remaining_in_page 256 - page_offset; uint32_t chunk; while (len 0) { chunk (len remaining_in_page) ? len : remaining_in_page; flash_write_enable(); // 发送0x32指令写入当前块 flash_quad_page_program(addr offset, buf offset, chunk); flash_wait_ready(1000); // 等待页编程完成 offset chunk; len - chunk; remaining_in_page 256; // 后续都从页首开始 } return 0; }这个封装的要点是首先生效了页内偏移之后每次写入都以256为基准不用担心跨页数据被拆乱。4. 双镜像启动与MultiBoot细节WBSTAR和IPROG的正确用法4.1 Golden与Update的启动地址设定回到分区设计。假设Golden镜像在0x000000Update镜像在0x1000001MB处。7系列的上电默认读0x000000没有问题。但要切换到Update启动需要做两件事把WBSTAR寄存器设为0x100000然后触发IPROG命令。IPROG是ICAP接口提供的一条虚拟指令值通常是0x0000000F和0x0000AABB的组合具体取决于芯片族。通过ICAPE2原语发送IPROG后FPGA会立刻重新加载配置读取WBSTAR指向的地址。这个“立即”很关键——它不经过PLD复位、不经过DDR复位相当于整个芯片热重启。4.2 ICAPE2原语的驱动实现ICAPE2在Vivado里是一个原语直接例化即可。7系列驱动ICAP发送IPROG的代码框架如下module icap_reboot ( input wire clk, input wire trigger, input wire [31:0] wbstar_addr, output reg done ); // WBSTAR 寄存器设置 (0x2000??) // 需要先从ICAP写入WBSTAR再写入IPROG localparam WBSTAR_CMD 32h2000_0000; // 地址对齐后写入 localparam IPROG_CMD 32h0000_000F; reg [31:0] icap_data; reg [7:0] icap_count; reg icap_csib; reg icap_rdwrb; always (posedge clk) begin if (trigger) begin // 先写WBSTAR icap_csib 0; icap_rdwrb 0; // 写模式 icap_data {8h00, wbstar_addr}; // 按ICAP时序组合 // ... 实际需要按ICAP字对齐累加 end end // 之后再发送IPROG指令并断言CS_B等最终触发配置加载 endmoduleIPROG的设计有一个很重要的细节WBSTAR的地址必须按32位字对齐即bit[1:0]00原因在SPI Flash启动时地址是字地址而不是字节地址。如果你的Update区地址设计成0x100000那么写字到WBSTAR的值是0x040000。这个换算很多人第一次都栽了启动总是不对往往是多了一个左移两位。4.3 回滚策略一种主动放弃的方式双镜像的好处是Update区固件如果因为自身Bug起不来Golden区仍然在Flash起始处上电默认还是从Golden启动。但这里有个前提你的Update区Bootloader必须能判断“自身不能正常工作”的条件。最终极的兜底方案是外置看门狗——FPGA重配置后如果软件一段时间内没有喂狗看门狗强制硬件复位复位后的启动流程又回到Golden。所以在一个完整的远程升级系统里FPGA内部的状态机会这样运转处于Golden镜像时收到升级请求把新固件下载到Download Buffer校验CRC和镜像头写Update区回读校验设置WBSTAR指向Update区并触发IPROG。系统在Update镜像中运行若运行期间检测到致命错误、喂狗超时、或收到回滚命令则直接把WBSTAR改为0x000000并IPROG回到Golden。上电或复位时硬件默认从0x000000的Golden启动不受任何远程状态干扰。这套策略哪怕远端网络中断、固件下载一半断电都不会导致不可恢复的变砖。设备最差也就是回到出厂版本这是远程升级系统的保命底线。5. 远程升级执行流程从固件接收到ICAP重启5.1 固件包的协议设计远程升级不是把原始.bit文件裸着扔过去。现场网络环境千差万别丢包重传、断点续传都是家常便饭。我建议自定义一个升级包格式至少包含以下几个字段魔数Magic Word用于快速识别有效镜像版本号主版本次版本构建号镜像长度CRC32校验值时间戳镜像数据接收到固件包后先做完整性校验再做启动头校验Xilinx bit文件有固定的同步字32位0xFFFFFFFF的同步序列然后是0xAA995566确认这个包确实能当配置镜像用才允许进入Flash编程阶段。5.2 下载到Download Buffer的通道选择如果你的FPGA系统有以太网口固件数据通常从网口进来通过DMA或者CPU搬运到DDR再从DDR读到AXI Quad SPI写进Flash。这一路带宽瓶颈其实不在FPGA逻辑而在Flash的擦写速度。实测下来N25Q128在Quad模式下写完16MB需要大约40-60秒含擦除这个数据可以作为你给上层应用设计升级进度条的参考。如果是纯PL设计比如没有PS可以通过UART、SPI Slave或者PCIe接收固件写入一个Block RAM或DDR作为缓存。DDRAXI DMA的方式在大固件面前优势很大否则就要用状态机慢慢磨对协议栈超时控制压力大得多。5.3 编程流程的完整状态机整个升级写Flash的流程我习惯用状态机严格管理每个状态都必须有超时跳转和处理分支typedef enum { ST_IDLE, ST_UNLOCK, ST_ERASE, ST_PROGRAM, ST_VERIFY, ST_SET_BOOT_ADDR, ST_REBOOT, ST_ERROR } upgrade_state_t;各状态的转移条件和关键细节ST_UNLOCK解除Flash保护位。发WREN写状态寄存器0x00读回确认WIP0、保护位清零成功后再往下走。ST_ERASE按扇区擦除Update区整片区域。每个扇区擦除前都要发WREN、等待WIP擦除完成后建议巡检整个区域读回来应该是全0xFF如果有残留说明Flash有坏块或擦除失败直接进ST_ERROR并回滚。ST_PROGRAM逐块写入数据。这里要注意AXI Quad SPI IP的FIFO溢出问题——你的Master写入速率不能超过IP到Flash的实际吞吐否则FIFO满了之后IP会拉低AXI的WREADY此时你必须等待不可超时放弃。ST_VERIFY写完后回读整个Update区逐字节和源数据比较。这一步很多人偷懒但恰恰是它能在现场升级失败前拦住99%的坏块和电压波动问题。ST_SET_BOOT_ADDR把WBSTAR设置为Update区地址写完后读回确认然后才进入最后的ST_REBOOT状态。ST_REBOOT发送IPROG命令让FPGA从新地址加载。这个状态机里每个超时都要有“回滚到Golden”的出口。比如说擦除超时不要试图反复重试直接退出升级流程、保持Golden启动并把错误码上报。5.4 验证阶段的附加保险启动头回读除了逐字节比较我还会额外验证Update区开头的64字节确认同步字0xAA995566和头部寄存器值正确。这是因为SPI Flash偶尔会出现一种极其隐蔽的错误——页面编程时数据线毛刺导致个别位写错但读取时因为电压和时序的变化又读出正确的值。这属于写后读不一致的偶发失效单独靠读回校验可能漏过去。加一个“启动头校验”再配合CRC双保险才能把现场返修率压到最低。6. 实测阶段最容易踩的坑给我打工资级别的血泪经验这部分是全文的“付费内容”。以下每个坑我都真金白银付过代价有的甚至是几个通宵排查出来的。每一类问题的表象、根因、解决方式我都列在下面供你对照。6.1 坑一擦除期间IP的FIFO卡死与AXI死锁表象写Flash到一半CPU或状态机跑飞AXI总线的WREADY一直拉低整个系统像被点穴一样一动不动。根因AXI Quad SPI IP的发送FIFO深度有限当你连续写入大量数据时如果Flash配置了比较慢的等待时间比如页面编程本身需要时间IP内部的发送通道会阻塞而你没有监控AXI的WREADY信号傻乎乎等固定延迟结果产生总线上锁。解决用户逻辑每一个AXI写操作都要使用正确的握手时序即等WREADY变高后才认为写完成开启IP的寄存器中断Interrupt Enable Register使能TX FIFO Half Empty和TX FIFO Full中断用于驱动层感知利用IP的SRRSoftware Reset Register作为最后杀招检测到异常后软复位IP让总线脱离死锁。6.2 坑二SCK频率过高导致的数据毛刺表象读取时每个扇区偶尔有一两个字节是0xFF整个文件CRC怎么都对不上擦除、编程偶尔失败但重试一次又好了间歇性发作。根因我按datasheet的极限值把SCK设到了108MHz但板级走线过长、EMI干扰、Flash芯片批次稍差的情况下高速时钟下的数据采样窗口不够临界时序导致毛刺。解决把SCK降到50MHz左右把FPGA内部IO约束加上IOBUF用于改善信号完整性同时在PCB上保证Flash的VCC旁路电容尽量靠近芯片引脚。这些“小事”能解决很大概率的偶发故障。我的习惯是量产前的验证板用108MHz跑100小时不间断读写老化测试如果没问题再放开到高速实验室环境直接降频到50MHz能省掉80%的疑难杂症。6.3 坑三IPROG后系统没有从Update启动表象IPROG触发了但设备起来后发现运行的还是老版本固件。根因三个原因按概率排序WBSTAR写入的地址没有按32位字对齐跳到错误地址加载失败后7系列会自动fallback到SPI的0x000000也就是Golden区所以看起来是“还运行老版本”ICAP的CE和WRITE信号时序不对IPROG命令根本没有被识别Update区起始地址其实写错了和下载固件时用的地址不一致跳转到了空白区域。排查用Vivado Hardware Manager连上JTAG检查配置地址寄存器。如果你用的7系列UG570里面有个命令xil_rdcfg和xil_wrcfg可以用直接读WBSTAR的值和预期比对。我印象最深的一次是花了很长时间检查ICAP例化最后发现是WBSTAR地址左移两位的问题这种低级错误往往最隐蔽。6.4 坑四断电导致Update区损坏但擦除策略不合理表象升级过程中有时会出“部分扇区擦除了写了一半另外一半还是旧数据”的现场。根因大固件升级时擦除和编程的时间窗口拉得很长。如果在这个窗口中发生掉电Update区的数据是一团浆糊——有些扇区是旧的、有些是擦除后的0xFF、有些是半写状态。解决设计上升级流程必须支持“基于Sector的双缓冲升级”。比如你的Update区分为A/B两个子区先在一个子区完成整包的擦除写入校验校验成功后再切断另一个子区的配置启动路径。如果中途掉电最坏情况就是当前子区坏了另一个子区还能启动不会触发IPROG。这在两个子区都可用的情况下把掉电风险窗口缩短到只有一次切换动作的几十毫秒。6.5 坑五驱动函数没有做“至少一次”的解锁保护表象设备在某些批次上工作正常另一些批次升级必失败重新上电后又恢复。根因Flash的状态寄存器保护位可能被上电时外部下拉或上拉电阻设置成了保护状态不同批次的Flash上电默认值有差异。你把解锁逻辑省略了直接把一切初始化建立在“保护位关闭”的假设上。解决每次写操作前无条件执行“WREN→写状态寄存器0x00→读回确认”三步曲宁可多花几毫秒也不能赌默认状态。后续在加电初始化阶段同步读回状态寄存器并断言发现保护位异常就亮告警灯把问题在升级前暴露。这五个坑覆盖了我在远程升级项目上遇到的80%问题。剩下的20%基本都是网络协议层和上层应用的问题不在本文讨论范围内但内核里的逻辑思想是通用的永远假设最坏情况会发生然后为最坏情况留好后路。7. 写在最后一个建议和一点体会如果只让我留一条建议那就是在N25Q128这种SPI Flash上做远程升级把驱动的每一条指令都当成“可能被下一次上电打断”的原子操作来设计。写使能、状态轮询、擦除、编程、读回校验每一步都要有军规一样的时序纪律缺失任何一环都可能把一个简单的写Flash任务变成拉锯战。实际项目里远程升级的代码量并不大难的是把各种异常路径想清楚、测彻底。建议在实验室做一个掉电矩阵测试在升级流程的每个状态随机断电然后上电确认系统一定回到Golden区并能正常启动。这个测试如果能连续跑上一百次不出问题我对现场的信心就很足了。好了这一路的坑和东XC细节都在这了。祝各位现场升级顺利永不翻车。
返回列表