SPI NOR Flash 启动与 XIP 原地执行:命令序列、Quad 读与控制器设计
更新时间:2026-09-19。前置阅读:SPI 主机 CPOL/CPHA、AHB/APB 与桥接。本文讲「代码放在外挂 NOR 里怎么跑起来」,属于典型的接口微架构问题。
本文要回答的问题
- NOR Flash 上电后第一条命令是什么,BootROM 怎么把它读进来?
- 什么是 XIP(原地执行),它和「先拷到 SRAM 再跑」有何区别?
- Quad/Octal 读为什么要 dummy cycle,怎么把慢速 SPI 喂给取指总线?
- 写 Flash(页编程/擦除)为什么慢,为什么不能像 RAM 直接写?
一、两种代码装载方式
| 方式 | 过程 | 优点 | 代价 |
|---|---|---|---|
| BootROM 搬运 | 上电先跑片内 ROM,把 Flash 内容拷到 SRAM,再跳转 | SRAM 里全速执行、无总线等待 | 启动慢、占 SRAM |
| XIP 原地执行 | CPU 直接把 NOR 映射到地址空间,取指经 SPI 控制器 | 省 SRAM、立即可取 | 取指慢,需缓存/预取 |
大多数 SoC 实际是混合:BootROM 极小、负责初始化 Flash 与时钟;随后在 XIP 窗口跑主程序,热点可被搬入 ICache/TCM。NOR 被映射到一段固定地址(如 0x6000_0000),CPU 看到的是只读内存,背后却是串行命令——这层转换就是 SPI Flash 控制器(常称 XIP/QSPI controller)。
二、标准 SPI NOR 命令序列(模式 0/3)
经典单线读(03h Read Data):
bash
CS# 拉低
MOSI: 03h // 命令:低速读,无 dummy
MOSI: A23..A0 // 24 位地址(3 字节)
MISO: D0 D1 D2... // 之后持续吐出,地址自动递增,直到 CS# 拉高
CS# 拉高- 快读
0Bh:地址后多 8 个 dummy bit,换更高 SCLK(给 Flash 内部寻址留时间)。 - Quad 输出快读
6Bh:命令/地址仍走 IO0,数据阶段用 4 根线并行吐出,dummy 后每拍 4 bit,吞吐近 4 倍。 - Quad IO 快读
EBh:命令/地址/模式/数据都走 4 线,支持连续读模式(省去重复命令)。 - 页编程
02h:写使能(06hWREN)→ 命令+地址+≤256 字节 → 等待 WIP 位清零。 - 扇区擦除
20h/块擦除D8h/整片擦除C7h:NOR 只能把 1→0,擦除把整块恢复成全 1。
状态寄存器轮询:
verilog
// 发 05h(RDSR),读状态字节,bit0=WIP
assign busy = status[0]; // 1=正在编程/擦除,控制器必须等待三、XIP 控制器微架构
bash
AXI/AHB 取指/取数
│
┌─────▼──────┐ hit ──► 立即返回(1 拍)
│ Line Cache │(缓存若干 cache line,如 8×32B)
└─────┬──────┘ miss
▼
┌─────────────┐ 串行化:命令+地址+dummy+突发读整 line
│ QSPI FSM │──► IO0..3 SCLK CS#
│ (命令生成/ │
│ 位拼/预取) │
└─────────────┘关键点:
- 整行预取(prefetch wrapping):一次 miss 不只取 4 字节,而是连续读满一条 16/32/64 字节 line,后续顺序取指全命中,摊薄命令/地址/dummy 开销。
- 地址自增 + 跨页处理:Flash 内部按 256 字节页,跨页/跨 16MB(24 位地址边界)必须重新发命令。
- AXI burst 到串行流的位宽转换:这正是 AXI-Stream 位宽适配器 的反向应用。
- 写路径互斥:执行/读与「擦写」不能并发;擦写时该地址窗口不可取指,通常把擦写代码放进 SRAM 执行(
__attribute__((section(".ramfunc"))))。
四、启动时序
bash
POR ─► BootROM(片内MaskROM)
├─ 配置 GPIO/时钟到安全低速
├─ 发 03h 读 Flash 头部(向量表/校验/入口)
├─ 校验通过?─否─► 回退 ISP/UART 升级
└─ 是─► 初始化 QSPI 为 Quad/缓存
└─► 跳转到 XIP 窗口入口 (0x6000_0000)五、常见的坑
- 没发 WREN 就编程:写操作静默失败,状态寄存器 WEL=0;每次编程/擦除后 WEL 自动清零,必须重发
06h。 - 不轮询 WIP:编程/擦除需数 ms,立刻读会拿到旧数据或下一条命令被忽略。
- 跨页写:一次页编程超 256 字节会回卷到页首覆盖,需按页拆分。
- dummy cycle 数配错:Quad/高速读 dummy 与频率、厂商型号相关,少一个就读错位,多一个丢首字节。
- 模式 0/3 与时钟极性:NOR 普遍支持 mode0/mode3,但控制器两端必须一致,见 SPI 四种 mode。
- XIP 窗口里执行擦写:命令总线被占、取指饿死;擦写代码必须放 RAM。
- 24/32 位地址模式:≥16MB 器件需进 4 字节地址模式(
B7h),否则高位地址被截断读到镜像低端。 - 上电电压/复位时序:Flash 未 t_PU 稳定就读,首字节错误;控制器需等 ready 或加重试。
- Quad 引脚复用:WP#/HOLD# 在 Quad 模式变 IO2/IO3,PCB 上拉和引脚 mux 未改会读全 FF。
- 缓存与擦写一致性:擦写后未 invalidate line cache,CPU 继续执行旧指令。
相关与延伸
总线侧见 AXI4 burst 与边界、AHB-Lite 流水线;低速串行对照 I2C EEPROM;启动映射与地址解码见 寄存器表与地址解码。
总结
SPI NOR 启动是「片内 BootROM 用低速命令读出头部并初始化控制器,再把 Flash 映射成只读 XIP 窗口」;XIP 的性能靠 Line Cache + 整行预取把慢速串行命令摊薄,Quad/Octal 用多线和 dummy cycle 换吞吐;而写 Flash 必须 WREN→页编程/擦除→轮询 WIP,且擦写代码要放 RAM、之后刷缓存——dummy、跨页、Quad 复用和缓存一致性是四大高发坑。