SPI_NOR XIP 与执行性能 技术探讨
从 XIP 三条路线到有效带宽天花板 —— 一份给电路设计指南「选型章节」用的性能论证材料 | 编制日期 2026-10-14 | 版本号 Rev 1.0
《SPI_NOR_Flash_电路设计指南》里「选型章节」最容易被写成一句口号:「支持 XIP,最高 200 MHz」。口号无法支撑评审,因为评审真正会问的是三件事:XIP 到底是怎么实现的、要付出什么代价、我的板子上能不能达到宣传带宽。
本文把这三件事拆开讲透:先把 XIP 的三条实现路线摆在一起做取舍,再把「一次读指令」拆到 SCK 级别算清固定开销,然后逐层叠加连续读、DTR/Octal、器件 cache,最后落到一个可以自己复算的有效带宽公式与一张瓶颈判别树上。读完你应该能独立写出:我们的启动镜像多大、走哪条 XIP 路线、总线配到多少、实测应该落在哪个区间、达不到时先查哪一级。
结论: XIP 不是器件特性,而是主控内存映射 + 器件随机取指延迟 + 一条足够快的总线三者的合成结果。 决定启动性能的从来不是峰值带宽,而是固定事务开销——指令帧、器件首字节延迟、主控发起一次读事务的软件成本。 连续读、DTR、器件 cache 这三项技术,本质上都在摊薄同一个固定开销; 而当总线快到固定开销占比超过三成时,再往上加频率只会让曲线变平,此时该做的是换总线宽度,而不是换主控频率。
读者路径
- 写电路设计指南的选型章节:直接看第 1、6、7 章(路线取舍 + 带宽公式 + 启动拆解),这三章是性能论证的全部骨架;再配第 8 章做跨介质对照。
- 做硬件 / SI 评审:第 4、5、9 章,重点是 DQS 相位裕度、串阻拓扑与电源跌落对读时序置信度的影响。
- 做软件与 BSP:第 2、3 章最有用——dummy 周期为什么各家不同、连续读什么时候必须退出、模式位切换的顺序约束。
- 遇到测出来的带宽不对:第 6.4 节的瓶颈判别树,按图索骥逐级排除,不要凭感觉换料号。
- 被现象卡住:第 11 章的现象排查表与误区表,按现象反查。
- 需要量级数字:附录 B(代际带宽与时序)与附录 C(模式位组合与 dummy 周期)是两张速查大表。
定位说明:本文是《SPI_NOR_Flash_电路设计指南》与《SPI_NOR_Flash_软件设计规范》的技术支撑篇,只讲性能论证,不复述那两份文档的接口定义、寄存器职责与软件流程。
1. XIP 是什么:三条实现路线的取舍
XIP(eXecute In Place executing in Place)经常被当成「器件支持内存映射」这一句话来用。实际上它是三段能力的合成:主控把一段地址空间映射到串行总线、器件能在极短时间内吐出任意地址的数据、取指链路不需要软件搬运。缺任何一段,都不是真 XIP。
1.1 XIP 的定义与三个必要条件
先把它拆成可验证的三个条件,后面所有性能讨论都挂在这三个条件上:
| 必要条件 | 具体含义 | 由谁提供 | 不满足时的表现 |
|---|---|---|---|
| 随机取指 | CPU 可以在任意顺序、任意地址上取到指令字节,且这个顺序由程序的跳转决定 | 器件阵列结构(NOR 的优势) | 表现为「只能顺序读」,代码必须线性排列并整段搬走 |
| 地址连续映射 | Flash 被映射进主控地址空间的一段,主控的取指访问被硬件翻译成总线事务 | 主控的存储控制器 / MMU | 表现为每次取指要走中断或函数指针,软件复杂度爆炸 |
| 无需搬运 | 没有「先把 Flash 内容拷进 SRAM」这一步,CPU 直接对映射地址取指 | 软件 + 硬件共同保证 | 表现为启动时间被搬运耗时线性支配 |
这个拆法的工程价值:当项目抱怨 XIP 性能差时,可以先定位是哪一条没满足。第三条不满足(其实在做搬运),瓶颈在总线带宽;第一条不满足(其实在做顺序读),瓶颈在器件访问模型;第二条不满足(每次取指要走软件),瓶颈在主控配置而不是 Flash 本身。这三种情况的处置方向完全不同,混在一起谈「换颗快一点的 Flash」是最常见的误判。
还要先说清一件事:NOR 与 NAND 的差别不在「谁更快」,而在「访问模型不同」。NOR 的阵列结构允许任意地址独立读出,取指代价与地址是否连续无关;NAND 的读取以页为单位,页内可随机、页间必须按序把页内容搬进片内缓存。这条差别直接决定了 XIP 只有 NOR 能做。
| 维度 | 路线一 直接内存映射 | 路线二 搬运到 SRAM | 路线三 边搬边执行 |
|---|---|---|---|
| 取指是否来自 Flash | 是,CPU 直接对映射地址取指 | 否,取指来自 SRAM | 部分来自 SRAM,按块切换 |
| 对主控的硬件要求 | 必须有支持串行总线取指的存储控制器 | 只需普通 SPI/QSPI 从机控制器 | 只需普通控制器,但要能并行做 DMA 搬运 |
| 对 Flash 容量与速度的敏感度 | 极高:带宽不够直接表现为取指停顿 | 低:只影响搬运时间 | 中:影响搬运窗口内的执行供给 |
| SRAM 用量 | 可以极小(仅栈与少量数据) | 必须大于等于镜像大小 | 只需一个块大小(常见 8 ~ 32 KB) |
| 代码是否必须重定位 | 必须做绝对地址编译或链接脚本重定向 | 可以按固定 SRAM 地址编译 | 必须可重定位,但可按块链接 |
| 启动时间量级 | 最低,受总线带宽支配 | 最高,等于搬运时间 + 执行时间 | 介于两者之间,接近搬运时间 |
| 代码加密 / 保密性 | Flash 明文暴露在总线上,可被读出 | Flash 可加密,加密后执行的是明文 SRAM | Flash 可加密,暴露窗口小 |
| 典型适用场景 | 有 QSPI/OPI 取指能力的主控 + 中小镜像 | 所有 MCU(最低门槛的通用做法) | 镜像较大但 SRAM 又不够的中等场景 |
1.2 路线一:控制器直接内存映射
这条路线的硬件特征很明确:主控的存储控制器知道「这段地址对应一颗串行 Flash」,CPU 的取指与数据访问会被硬件翻译成串行总线事务,软件完全看不见这条总线。它的性能上限由三件事决定:总线宽度与时钟、器件的首字节延迟、主控发起一次读事务的固定开销。
| 映射要素 | 工程含义 | 设计时必须确认的点 |
|---|---|---|
| 窗口起点与大小 | Flash 在主控地址空间里的位置与长度 | 是否与 SRAM、 peripherals 冲突;镜像变大后是否需要重划窗口 |
| 地址对齐粒度 | CPU 一次访问被翻译成多大粒度的总线事务 | 过小则事务数爆炸,过大则浪费带宽;常见粒度为 16 ~ 64 B |
| 读事务的发出方式 | 每次取指单独发起,还是由硬件预取合并 | 有无预取缓冲决定随机小读与顺序读的差距有多大 |
| cache 属性 | 映射区是否可缓存、是否写回 | 可缓存则随机取指几乎无感;不可缓存则每个 cache line 都走总线 |
| 时序寄存器组的配置顺序 | 模式位、dummy 周期、连续读开关的生效次序 | 顺序错误会表现为「偶发首字节错」,见第 3.4 节与第 11 章 |
| 不可读区间的行为 | 未映射或超范围地址的响应 | 是返回全 1、返回总线错误,还是挂死——决定故障可诊断性 |
在设计指南与料号文档里,凡是写「支持 XIP」,都必须同时写清三件事:① 映射窗口的对齐粒度与 cache 属性;② 初始化必须写入的模式位与 dummy 周期(写「按 datasheet 建议值配置」等于没写);③ 在该主控上实测的首次取指延迟与顺序读有效带宽。
只写频率不写这三项,读者无法判断自己的系统能不能用;评审时也拿不出可复算的依据。
1.3 路线二:bootloader 搬运到 SRAM 再跳转
这条路线的唯一优势是硬件门槛为零:任何 MCU 都能做,代价是启动时间被完整搬运一遍支配。它的性能公式非常直白:
搬运路线启动时间 约等于 固定开销(初始化 + 校验) + 镜像字节数 / 有效读带宽 + 镜像字节数 / SRAM 写带宽
| 约束项 | 典型量级 | 工程含义 |
|---|---|---|
| SRAM 容量门槛 | 必须 ≥ 镜像大小 + 栈 + 数据 + 缓冲 | 常见 512 KB 镜像需要 512 KB 以上 SRAM,嵌入式 MCU 常常给不起 |
| 分块搬运的块大小 | 8 ~ 64 KB(取决于 SRAM 余量与中断延迟要求) | 块越小则中断/搬运切换越频繁,块越大则首字节等待越长 |
| 校验开销 | CRC32 约 1 ~ 3 GB/s,SHA-256 约 0.1 ~ 0.5 GB/s | 需要安全启动时,哈希校验的耗时可能与搬运本身同量级 |
| 解密开销 | AES-CTR 约 0.1 ~ 1 GB/s(依主控是否有硬件加速) | 无硬件加速时,解密会成为新的第一瓶颈 |
| 首字节就绪时间 | 首个块搬完即可跳转,典型 50 ~ 300 µs | 这条路线的「快速启动」能力只体现在首块上 |
三种情况下继续用全量搬运是明智的:① 主控没有串行总线取指能力,方案里本来就没有第二条路;② 镜像很小(几十 KB)且 SRAM 充裕,搬运时间被固定开销主导,差距不明显;③ 需要在执行前完整校验并解密整段镜像——安全启动通常要求「验证通过才执行」,这天然要求整段落地。
反过来,如果镜像已经涨到几百 KB 而 SRAM 只有一两百 KB,路线二就变成了必须分块的分时搬运,此时应当认真评估路线三。
1.4 路线三:bootloader 边搬边执行(XIP-like)
路线三的核心思想是:用一块小窗口 SRAM 做「当前执行区」,搬完一块就跳过去执行,同时在后台搬下一块。它把路线二的「搬运时间」与路线一的「执行时间」重叠起来,理想情况下总耗时接近两者中的较大值。
| 设计要素 | 取值 / 做法 | 踩坑点 |
|---|---|---|
| 窗口大小 | 一个链接单元的大小,常见 8 ~ 32 KB | 窗口太小则跳转与中断开销占比上升;太大则首字节等待变长 |
| 搬运与执行的互斥手段 | 双 bank 交替、双缓冲、或者单 bank 内做前后指针 | 同一 bank 边写边读会破坏正在执行的代码,必须做区域隔离 |
| 跳转前的地址重定位 | 每个链接单元内相对寻址,单元入口做绝对跳转 | 跨单元的函数调用与中断向量需要特殊处理 |
| 中断处理 | 中断向量表放在常驻区,且不依赖被覆盖的窗口 | 向量表被覆盖会表现为「跑一阵就崩」,且难以复现 |
| 搬运失败的处理 | 校验失败时停在当前单元并给出可诊断状态 | 最常见的现场问题是「不校验、失败就跳」,最后变成偶发死机 |
| 并行度 | 搬运用 DMA / 硬件传输,执行用 CPU | 若主控只有单总线且 DMA 与取指抢同一控制器,并行度会低于预期 |
典型症状是「大部分时候正常,偶发死机,且死机点不固定」。根因几乎总是同一个:搬运覆盖了正在执行的代码区或它依赖的数据。三个必查项:
① 单元边界与链接脚本是否严格一致(链接单元大小必须与窗口步进严格相等);② 中断向量与常驻数据是否真的放在永不覆盖的区域;③ 跳转前是否校验了当前单元、且校验覆盖的是「跳转目标地址」而不是「拷贝源地址」。
1.5 三条路线的启动时间对照
把三条路线放在同一张表上对照,结论列已经足够写进设计指南:
| 对比项 | 路线一 直接 XIP | 路线二 全量搬运 | 路线三 边搬边执行 |
|---|---|---|---|
| 启动时间支配项 | 执行的代码量与总线带宽 | 镜像大小 / 有效带宽 | max(搬运时间, 执行时间) + 窗口切换开销 |
| 512 KB 镜像算例 | 约 4 ms 量级 | 约 13 ms 量级 | 约 12 ms 量级 |
| 镜像增大到 2 MB | 几乎不变(只随执行量略增) | 线性增长到 50 ms 量级 | 线性增长到 45 ms 量级 |
| SRAM 需求 | 几百字节 ~ 数 KB | ≥ 镜像大小 | 一个窗口大小 |
| 硬件门槛 | 高(需总线取指能力) | 无 | 低(需 DMA) |
| 对总线宽度的敏感度 | 极高:x1→x8 接近 8 倍 | 较高:x1→x8 接近 8 倍 | 较高但被窗口切换摊薄 |
| 对 dummy 周期的敏感度 | 高(直接影响每次取指的固定开销) | 中(每次事务的 dummy 被平均到 256 B 数据上) | 中 |
| 代码可重定位要求 | 必须 | 不需要 | 必须(按窗口) |
把三条路线写进设计指南时,建议直接给决策规则而不是给描述:
① 主控有 QSPI/OPI 取指能力且镜像 ≤ 512 KB → 优先路线一,因为此时 SRAM 搬运带来的时间收益远小于实现复杂度收益。② 主控无取指能力或需要先完整校验解密 → 路线二,这是唯一稳妥选择。③ 镜像 > 512 KB 且 SRAM 不足 25% 镜像 → 路线三,并且必须按 1.4 节的三个必查项逐条验证。
无论选哪条,都必须在同一块板子上把「镜像大小 / 有效带宽」这个除法算一遍并实测验证——这是唯一能在评审会上站得住的性能论证。
2. SPI NOR 基础读时序与延迟构成
第 1 章把路线定下来了,接下来要看清「一次读指令到底贵在哪」。本章把一次 Read 命令拆到 SCK 级别,得到的结论会贯穿全文:随机小读慢的原因不在带宽,而在固定开销。
2.1 一次 Read 命令的完整开销拆解
一次普通的 Read 事务由四段组成,缺一不可:指令(1 B)、地址(3 B 或 4 B)、dummy 周期(0 ~ 16 个 SCK,随代际变化)、数据(可编程长度)。前三段与数据量无关,在带宽账里必须单独扣出来。
把这三段折算成时间(x4 @ 104 MHz 算例,见 6.3 节完整代入过程):
| 分段 | 占多少 SCK | 折算时间 | 随数据量变化吗 | 谁能优化它 |
|---|---|---|---|---|
| 指令 | 8 个 SCK / 1 B | 约 77 ns | 否 | 不能改,是协议固定成本 |
| 地址 | 32 个 SCK / 4 B | 约 308 ns | 否(只随地址位数变) | 地址由 3 B 改 4 B 只在 >128 Mb 才必要 |
| dummy 周期 | 4 ~ 16 个 SCK(随代际/模式) | 约 38 ~ 154 ns | 否 | 换代际器件、或在器件允许范围内取最小值 |
| 器件首字节延迟 | 不含 SCK | 约 0.4 ~ 1.0 µs | 否 | 换器件(阵列访问与输出启动更快) |
| 主控发起一次读事务 | 不含 SCK | 约 1 ~ 5 µs | 否 | 换主控 / 改用连续读 / 用 DMA 链式搬运 |
| 数据传输 | N × 8 SCK | N / 有效线速 | 是(正比于 N) | 唯一能靠加带宽改善的一段 |
在一次事务只搬几十字节的场合,前五项合计 1 ~ 5 µs,而搬 64 字节本身只要 64 × 8 / (104e6 × 4) 约等于 1.2 µs。也就是说一半以上的时间花在了「什么都不传」的开销上。
这解释了第 2.3 节要回答的问题,也解释了为什么连续读、DTR、器件 cache 三项技术全部指向同一个目标:把固定开销摊到更多字节上。
2.2 各类延迟的量级表
把启动与运行期间会遇到的延迟归成六类。设计指南里的性能章节,建议直接引用这张表的「量级」列。
| 延迟类别 | 典型量级(经验区间) | 出现在哪个阶段 | 对启动的影响 |
|---|---|---|---|
| CE# 建立到 SCK 启动 | 数十 ns ~ 数百 ns | 每次事务 | 可忽略,但频繁翻转会累积 |
| 指令 + 地址 SCK 时间 | 0.1 ~ 1 µs(随频率与模式) | 每次事务 | 频率越高,这一段的绝对时间越短 |
| dummy 周期 | 数十 ns ~ 1 µs | 每次事务 | 同上;DTR 下按双沿计,SCK 数可能翻倍而时间不变 |
| 器件首字节延迟 | 0.4 ~ 1.5 µs | 每次事务 | 随机取指的固有代价,连续读可摊薄 |
| 片内页切换 / bank 切换 | 数十 ns ~ 数百 ns | 跨页或跨 bank 时 | 大容量多 bank 器件上更明显 |
| 主控总线周转 | 数十 ns ~ 数百 ns | 事务之间 | GPIO 复用切换或控制器重新配置时出现 |
| 主控发起一次读事务 | 1 ~ 5 µs(软件/寄存器/DMA 配置) | 每次事务 | 本轮讨论中最大的单项,常被低估 |
| 主控 cache 未命中时的整条取指链 | 2 ~ 10 µs | 每个 cache line | 决定「慢启动」是不是总线问题 |
| 时钟准备(PLL 锁定 + 稳压环) | 100 µs ~ 数 ms | 上电到第一条指令之前 | 常是启动时间的真正大头 |
| 器件上电延时(POR) | 数十 µs ~ 数百 ms(依器件与温度) | 上电 | 低温时显著变长,必须按最大值设计 |
| 模式切换(切回读模式、dummy 重配) | 数十 µs | 写操作之后 | 写后紧跟读的代码段会吃到这段延迟 |
把三类延迟分开对待,是本章的核心方法:
① 与总线频率成正比、可被频率优化掉的:SCK 时间、总线周转;② 与频率无关、只能靠「减少次数」摊薄的:指令与地址、dummy、首字节延迟、主控发起开销;③ 只在上电时出现一次的:时钟准备与器件 POR。
第 3 步的判定尤其重要:如果启动时间的实测值主要落在第三类上,那么再换快的 Flash 也不会有任何改善,这是设计评审上一个非常值得先问的问题。
2.3 为什么「随机小读」比「顺序大读」慢一个量级
现在可以正面回答这个问题了。设一次事务的固定开销为 T_fix,数据量为 N,理论线速为 B_th(单位 B/s),则有效带宽为:
有效带宽 B_eff = N / ( T_fix + N / B_th ) 折算效率 eta = B_eff / B_th = 1 / ( 1 + T_fix · B_th / N )
| 数据量 N | 数据传输时间 | 固定开销占比 | 有效带宽 B_eff | 相对顺序读的效率 |
|---|---|---|---|---|
| 16 B | 0.31 µs | 92% | 约 4.1 MB/s | 8% |
| 64 B | 1.23 µs | 75% | 约 13.2 MB/s | 27% |
| 256 B | 4.92 µs | 42% | 约 30.0 MB/s | 60% |
| 1 KB | 19.7 µs | 15% | 约 44.0 MB/s | 88% |
| 4 KB | 78.8 µs | 4.3% | 约 49.7 MB/s | 99.6% |
| 4 KB 连续读(一次事务) | 78.8 µs | 4.5% | 约 49.5 MB/s | 99.5% |
这张表用同一组参数算出三件事:
- 16 B 的随机读只有 4.1 MB/s,是 4 KB 顺序读的 1/12——这就是「慢一个量级」的准确出处,它不是带宽不够,是事务太小。
- 数据量每翻四倍,效率就上一个台阶;从 256 B 到 4 KB 只差 1.66 倍,但从 16 B 到 4 KB 差 12 倍。所以「提升有效带宽」的第一杠杆是「把事务做大」,第二杠杆才是「把总线做快」。
- 连续读在这一档几乎没有额外收益(49.5 对 49.7 MB/s)。这是常见误解的来源:连续读的收益主要出现在更小的总线配置上,下一节给出它真正起作用的条件。
禁止用「顺序拷贝一个大文件」的结果来评估启动性能。这类测试在 N ≥ 64 KB 时已经进入效率饱和区,测出来的是器件与总线的能力,而启动阶段执行的是零散的小事务,两者可以相差十倍以上。
正确的测法见第 10 章:按「复位 → 第一条有效指令」的口径,用 GPIO 翻转打点,测出三段耗时分别落在哪。
2.4 指令与 dummy 周期的代际对照
dummy 周期是「指令与地址之后、数据之前插入的空 SCK」,用来覆盖器件内部把地址转成行列地址所需的时间。它的数量由器件内部架构决定,不由主控决定,因此同一颗器件在不同模式(x1/x4/x8、SDR/DTR)下 dummy 数可能不同。
| 代际 / 模式 | 典型时钟(经验区间) | 地址字节 | dummy 周期(经验区间) | 说明 |
|---|---|---|---|---|
| 早期 x1(单线读) | 20 ~ 50 MHz | 3 B | 0 ~ 8 SCK | 地址转换简单,dummy 少甚至为 0 |
| x4 快速读(SDR) | 50 ~ 104 MHz | 3 B / 4 B | 4 ~ 8 SCK | 四线并行后 dummy 略增以覆盖内部并行化 |
| x8 DTR 八线双沿 | 133 ~ 200 MHz | 4 B | 8 ~ 16 SCK(双沿) | dummy 数常按双沿计,实际时间与 x4 同量级 |
| x4 DTR 四线双沿 | 80 ~ 133 MHz | 3 B / 4 B | 6 ~ 10 SCK | 带宽介于 x4 SDR 与 x8 SDR 之间 |
| 高性能 x8 DTR(二代) | 166 ~ 250 MHz | 4 B | 10 ~ 20 SCK(双沿) | 内部架构更复杂,dummy 相应增加 |
① 偏少:第一个数据字节(或前几个字节)被丢掉 / 被前一次事务的残留污染,表现为「偶尔头几个字节错」,且与温度、时钟漂移相关,极难复现。
② 偏多:数据整体后移若干字节,表现为「读出来的东西偏移固定字节数」,这个反而容易发现。
③ 模式切换后没同步更新:从 x4 切到 x8 之后仍用旧的 dummy,表现为「同一段代码在两种模式下结果不同」。
规范做法:把 dummy 周期作为器件与模式组合的表项写进配置流程,并在模式切换后强制重写一遍,而不是依赖上电默认值。
最后强调一次本文的数值纪律:上面所有频率、dummy 数、延迟都来自行业经验区间,用于建立量级概念与做快速估算。任何进入原理图 BOM、时序表或软件配置的数值,必须以正式 datasheet 的时序图与实测为准。这条边界在附录 D 有完整说明。
3. 连续读模式
连续读(Continuous Read,也叫 Sequential Read / AutoBoot 家族)是 SPI NOR 里性价比最高的一项技术:它不增加任何引脚、不提高任何频率,只靠「让器件自己递增地址」就把固定开销从每事务一次降到每次传输一次。
3.1 连续读的收益本质:省掉每 256B 的指令与地址开销
普通读命令的语义是「从地址 A 读 N 字节」,命令结束。下一次读必须重新给指令和地址。而连续读把语义改成「从地址 A 开始,一直读下去,直到我打断你」——器件内部有一个地址计数器,读完一段就自己加一段,主控不用再发指令。
| 维度 | 普通读(每次重新给地址) | 连续读(器件自动递增) | 工程影响 |
|---|---|---|---|
| 指令开销 | 每事务一次指令 + 地址 + dummy | 每次连续读序列只付一次 | 事务越多、每事务越短,差距越大 |
| 地址开销 | 每事务一次完整地址 | 只有首地址由主控给出 | 省掉的是 SCK 时间,不是延迟时间 |
| 主控侧开销 | 每事务都要配置一次读事务 | 配置一次,后续只收数据 | 这一项往往是省得最多的 |
| 对事务长度的要求 | 任意长度都可 | 越长的连续序列收益越大 | 长序列对写保护状态切换更敏感 |
| 打断能力 | 随时可以发新指令 | 需要先发「退出连续读」再发别的指令 | 见 3.4 节的终止条件 |
| 适用场景 | 随机取指、随机查表 | 顺序取指、大块数据读取、镜像下载 | 启动阶段两种都要用到 |
这一点非常反直觉,值得单独强调:总线越快,固定开销占比越高,连续读的相对收益越大。
在 x1 @ 50 MHz 上,48 个 SCK 的指令开销约 1 µs,而主控发起一次事务的软件开销可能 2 ~ 5 µs——连续读把后者省掉,收益巨大。到了 x8 DTR @ 200 MHz,48 个 SCK 只剩 0.24 µs,占比可以忽略,连续读能省的只剩下软件侧那一份。
所以从「投入产出」看,连续读是高总线配置下的高性价比项,低总线配置下相对收益有限。但它的实现成本几乎为零,理由充分,所以无论什么配置都应该开。
下图与上表给出同一组数字:搬 4 KB 数据时,逐 256 B 发指令要付 16 次固定开销,合计 16 × 3.6 = 57.6 µs;连续读只付一次 3.6 µs(固定开销取 x1 @ 50 MHz 的保守值,不随位宽与频率折算,各档统一沿用)。数据传输时间两者相同(4096 / 52 = 78.8 µs)。于是有效带宽分别为 30.0 MB/s 与 49.5 MB/s,差 1.65 倍。
| 配置 | 事务次数(搬 4 KB) | 固定开销合计 | 数据传输时间 | 有效带宽 | 相对普通读的提升 |
|---|---|---|---|---|---|
| x1 @ 50 MHz(线速 6.25 MB/s) | 16 次 | 约 57.6 µs | 655.4 µs | 约 5.74 MB/s | 1.10 倍 |
| x4 @ 104 MHz(线速 52 MB/s) | 16 次 | 约 57.6 µs | 78.8 µs | 约 30.0 MB/s | 1.65 倍 |
| x8 DTR @ 200 MHz(线速 400 MB/s) | 16 次 | 约 57.6 µs | 10.2 µs | 约 60.1 MB/s | 约 4.9 倍 |
提升倍数等于连续读带宽除以逐 256 B 普通读带宽。固定开销按 x1 保守取值、不随线速缩短,而数据传输时间随线速成比例缩短——总线越快,固定开销占总时间的比例越高,连续读摊掉的开销份额越大,所以提升倍数从 x1 档的 1.10 倍一路升到 x8 DTR 档的约 4.9 倍。换句话说:总线越快,连续读越重要。
3.2 模式位(Mode Bits)的常见组合
「模式位」这个词在不同厂商文档里指代略有差异,但实质都是一组决定「后续读命令如何解释总线」的寄存器位:总线宽度、单双沿、dummy 档位、是否连续读。本文只讲它作为寄存器组职责的那一层。
| 寄存器组职责 | 决定什么 | 何时必须配置 | 配错的表现 |
|---|---|---|---|
| 模式位(Mode Bits) | 后续读命令使用几根线、单沿还是双沿 | 切换总线宽度或双沿方式之前 | 读回数据全错或全 0xFF,且与模式相关 |
| dummy 周期配置 | 读命令中插入多少空 SCK | 每次模式位变更后必须同步重写 | 首字节错、固定偏移、偶发错误 |
| 连续读控制 | 是否进入隐式寻址的读循环 | 进入顺序读之前;任何其他指令之前先退出 | 指令被吞、后续读错位、偶发死机 |
| 状态寄存器组(含写保护位) | 是否可写、是否处于擦除/编程中、是否异常 | 写操作之前;每次启动时清零 | 写入静默失败、状态永远忙 |
| 器件识别与参数区 | 容量、地址位数、型号特征 | 上电初始化时读一次并校验 | 容量判断错误、地址截断 |
| 配置区(一次性可写) | 保护位、启动方式等出厂配置 | 产测或特殊需求时按需修改 | 不可恢复,需专用解锁序列 |
寄存器位域位置与选项字节默认值属于具体料号的数据手册内容,且不同厂商、不同代际之间没有统一定义。写进一份通用技术文档只会带来两个后果:读者照抄到不同料号上直接出错;文档一升级就过时。
正确做法:文档写清「有哪些寄存器组、各自负责什么、配置顺序如何、配置错了什么表现」,位域与取值指向 IDE 生成的器件头文件与对应 datasheet 的寄存器表。本文全篇遵守这一纪律。
| 模式组合 | 总线宽度 | 传输沿 | 典型线速(@峰值时钟) | dummy 相对量 | 板级约束等级 |
|---|---|---|---|---|---|
| x1 单线 | 1 bit | 单沿 | 50 MHz → 6.25 MB/s | 最少 | 低:普通 SPI 布线即可 |
| x4 四线 | 4 bit | 单沿 | 104 MHz → 52 MB/s | 中等 | 中:注意 IO 串阻与长度匹配 |
| x4 四线 DTR | 4 bit | 双沿 | 133 MHz → 133 MB/s | 中等偏多 | 中:需关注采样点与串阻 |
| x8 八线 | 8 bit | 单沿 | 104 MHz → 104 MB/s | 较多 | 中高:八根等长线,DQS 可选 |
| x8 八线 DTR | 8 bit | 双沿 | 200 MHz → 400 MB/s | 最多 | 高:需 DQS/DQS# 与严格阻抗控制 |
3.3 AutoBoot 与隐式寻址的连续读
「AutoBoot」这一族功能(不同厂商命名不同,含义一致)的思路是:把一段固定区域(通常是 Flash 开头的一小段空间)的读循环在出厂时或配置时就设定好,上电后器件自动进入该循环并持续输出其中的内容。主控甚至不需要发读指令,只需要按时钟收数据。
| 特性 | 隐式寻址连续读(模式位方案) | AutoBoot(自动启动方案) |
|---|---|---|
| 谁发起 | 主控先发一条模式设置或读指令,之后器件自行维持 | 上电即处于该状态,主控可零指令启动 |
| 起始地址 | 由主控在指令里给出 | 出厂固定或配置区指定 |
| 长度 | 由主控控制(发新指令或拉高 CS# 结束) | 通常固定为一个窗口大小 |
| 适合的镜像位置 | 任意,按需设置 | 通常是固定偏移,适合放一段引导代码 |
| 对主控的依赖 | 需要主控支持连续读(有些主控不支持) | 只需要主控能收数据,取指链路要求最低 |
| 主要风险 | 退出顺序写错导致后续指令被吞 | 配置区被误改后难以恢复,量产需管控 |
| 软件侧配合 | 需要连续读的驱动与退出序列 | 读取到窗口末尾后的行为依赖器件约定 |
适合:只需要在复位后第一时间喂给 CPU 一小段引导代码(比如一个「模式设置 + 复制到 SRAM」的小 loader),主控的取指链路还没有准备好时的场景。此时它能把「第一条取指」的成本压到只有 SCK 时间。
不适合:需要灵活切换镜像、需要多个镜像并存、需要热更新启动链路的场景。因为它的起点与长度在出厂或配置期就固定了,灵活性为零,一旦配置区写坏往往需要专用解锁序列才能恢复。
3.4 连续读的终止条件与注意事项
进入连续读之后,器件处于「只认数据、不认指令」的状态。这是它最大的价值,也是它最容易出事的地方。退出顺序必须严格按下表执行。
| 终止方式 | 操作序列 | 适用场景 | 失败时的表现 |
|---|---|---|---|
| 拉高 CS# | 在 SCK 的下一个空闲沿拉高片选,器件退出读循环 | 最通用;主控打算发新指令前 | 下次发指令无响应,需重新初始化 |
| 发显式退出指令 | 在数据流中插入约定的退出码(无效指令) | 要求 CS# 保持低、需连续传输多个区域时 | 退出码被判为数据,位置错位 |
| 写入模式位 | 先解除写保护,再写模式寄存器把连续读关掉 | 要在不拉高 CS# 的情况下切换到写操作时 | 写操作被忽略,状态位一直不对 |
| 复位 | 硬件复位或软件复位器件 | 异常兜底,不适合正常流程 | 启动时间显著变长 |
| 软件约定长度 | 主控收满约定字节数后自行停止 | 长度固定的场景(如一段镜像) | 多收或少收,取决于约定是否与器件一致 |
① 任何非读指令之前必须先退出连续读,包括写状态寄存器、写模式位、擦除、编程、以及读 ID 这类「看起来也是读」的指令——很多器件要求显式退出。
② 退出后建议空读一次(发一条无效指令),把总线上残留的字节冲掉,避免下一条真指令的头几个 bit 被污染。这一条在高频下尤其重要。
③ 不要在中断里发新指令。若连续读期间允许中断且中断服务程序要访问 Flash,必须在中断入口显式退出、出口重新进入,代价很高;更好的做法是关中断期间完全不访问 Flash。
④ CS# 拉高的时机要对齐 SCK 空闲沿。在 SCK 高电平中间拉高会触发器件内部状态机的边界情况,表现为「偶尔一次退出不干净」。
⑤ 模式位与 dummy 必须成对更新。连续读的 dummy 与普通读的 dummy 常常不同档,切换时要一起改。
最后补一条器件层面的注意事项:连续读期间器件内部输出驱动器持续驱动总线。如果此时主控的总线上还有其他设备(多片并挂、或者有别的从设备共用),必须确保片选划分清晰,避免总线争用导致电平不确定。多片并挂时,连续读要么每片独立片选,要么严格设计成链式传递(前一片的输出接到后一片的输入,不并联)。
4. DTR 与 Octal:双沿传输
前三章讲的是「怎么把固定开销摊薄」,从本章开始讲另一条路径:把总线本身变宽、变快。这条路线的收益上限很高,但代价全部落在板级约束上——本章与第 9 章必须连着读。
4.1 单沿 SDR 到双沿 DDR 的本质区别
SDR(Single Data Rate)每个时钟周期只在一个边沿采样数据;DDR(Double Data Rate)在上升沿和下降沿各采样一次。就这一个改动,带宽立刻翻倍,但对时序的要求也立刻翻倍。
| 对比项 | 单沿 SDR | 双沿 DDR | 工程含义 |
|---|---|---|---|
| 每周期传输位数 | 总线宽度 × 1 | 总线宽度 × 2 | DDR 是带宽翻倍的直接原因 |
| 采样时刻 | 每个周期只有一个稳定采样点 | 每周期两个采样点,均需满足建立/保持 | 采样窗口减半,相位容限约减半 |
| 数据有效时间 | 接近半个周期 | 约四分之一个周期 | 走线长度偏差与抖动容限显著变紧 |
| 对时钟抖动的要求 | 相对宽松 | 严格(抖动直接吃掉建立/保持余量) | PLL 与时钟源的品质成为关键 |
| 是否需要 DQS | 通常不需要 | 高速 DTR 通常需要 | 多一根差分对与一个额外引脚 |
| 板级仿真要求 | 常规 SI 仿真可覆盖 | 需做时序通道仿真 | 见第 9 章 |
| 典型适用 | x1 / x4 SDR 中低速配置 | x4 / x8 DTR 高速配置 | 按线速需求与板级能力取舍 |
4.2 DTR(Dual Transfer Rate)如何把带宽翻倍
带宽公式里的「每周期传输位数」在 DTR 下直接乘 2。这个倍数是严格的、无条件的——不依赖任何数据压缩、缓存技巧或协议优化,是物理层面的收益。这也是它比连续读更「硬」的地方。
理论带宽 B_th = 总线宽度(bit) × 时钟频率(Hz) × 每周期沿数 / 8 (单位 MB/s)
| 配置 | 总线宽度 | 时钟(经验区间) | 每周期沿数 | 理论带宽(区间) | 器件侧约束 |
|---|---|---|---|---|---|
| x1 SDR | 1 | 20 ~ 50 MHz | 1 | 2.5 ~ 6.3 MB/s | 最少;dummy 也最少 |
| x4 SDR | 4 | 50 ~ 104 MHz | 1 | 25 ~ 52 MB/s | 四线等长;中等 dummy |
| x4 DTR | 4 | 80 ~ 133 MHz | 2 | 80 ~ 133 MB/s | 需要相位对齐;dummy 增加 |
| x8 SDR | 8 | 50 ~ 104 MHz | 1 | 50 ~ 104 MB/s | 八线;引脚压力大 |
| x8 DTR | 8 | 166 ~ 250 MHz | 2 | 332 ~ 500 MB/s | 需 DQS;SI 要求最高 |
datasheet 上的时钟上限是在特定条件下测得的:指定电压范围、指定温度、指定的走线长度与负载、指定的时序余量。板子做出来之后,能不能跑到这个上限取决于你的板级 SI 能不能满足同一组条件。
所以在设计指南里,「最高频率」这一栏的正确写法是一个条件化表述:例如「x8 DTR 下推荐 200 MHz,条件为 VCC 在 2.7 ~ 3.6 V、温度 -40 ~ 85 °C、DQS 与 DO 走线长度差 ≤ 5 mm、单端串阻 33 欧(容差 5%)」。这类表述能让读者判断自己的板子是否落在条件范围内。
4.3 Octal(八线)DDR 的 IO 组织与引脚代价
Octal / OPI(Octal Parallel Interface)把数据线从 4 根扩到 8 根,再加上 DQS/DQS#,引脚数量与布线复杂度都是一次跃升。这不是为了「带宽」而带宽——它是为了在不继续提高时钟频率的前提下把带宽做上去。
| 信号组 | x1 方案 | x4 方案 | x8 DTR 方案 | 说明 |
|---|---|---|---|---|
| 数据线 | 1 根(DO/DI 复用) | 4 根(D0~D3 双向) | 8 根(DQ0~DQ7 双向) | 双向意味着每个方向都要单独做阻抗与串阻设计 |
| 时钟 | 1 根 SCK | 1 根 SCK | 1 根 CK + DQS + DQS# | DQS 是给数据用的采样参考,与 CK 不同源或同源视设计而定 |
| 片选 | 1 根 CS# | 1 根 CS# | 1 根 CS#(有时另有扩展脚) | 片选数量往往不够,需要软件或硬件扩展 |
| 其它必需脚 | VCC / GND | VCC / GND | VCC / VCC / GND | 八线方案的电流与去耦要求明显更高 |
| 封装引脚量级 | 8 ~ 16 | 16 ~ 24 | 24 ~ 40+ | 大封装会挤占 PCB 面积并增加焊接与返修难度 |
| PCB 层数与布线 | 2 层可应付 | 2 ~ 4 层 | 建议 4 ~ 6 层 | 八根等长线在 2 层板上极难做 |
引脚数只是账面。真正的代价有三条:
① 电流与去耦:八根数据线同时翻转时的峰值电流比四线大一倍多,电源跌落直接抬高读时序的不确定性(详见 9.4 节);② 布线约束:八根 DQ 加一对 DQS 的长度匹配与阻抗一致性,是本系列文档里最严苛的一组要求;③ 主控支持度:很多 MCU 的 QSPI 控制器只支持四线,Octal 需要额外的控制器或专门的引脚复用配置,选型阶段就要确认。
4.4 x1 / x2 / x4 / x8 模式与理论带宽表
把四种总线宽度与两种沿数组合成一张完整的理论带宽表。这张表是第 6 章算例的基础,建议直接引用。
| 模式 | 线/周期 | 沿/周期 | 位/周期 | 50 MHz | 104 MHz | 133 MHz | 200 MHz |
|---|---|---|---|---|---|---|---|
| x1 SDR | 1 | 1 | 1 | 6.3 MB/s | 13.0 MB/s | 16.6 MB/s | 25.0 MB/s |
| x2 SDR | 2 | 1 | 2 | 12.5 MB/s | 26.0 MB/s | 33.3 MB/s | 50.0 MB/s |
| x4 SDR | 4 | 1 | 4 | 25.0 MB/s | 52.0 MB/s | 66.5 MB/s | 100.0 MB/s |
| x4 DTR | 4 | 2 | 8 | 50.0 MB/s | 104.0 MB/s | 133.0 MB/s | 200.0 MB/s |
| x8 SDR | 8 | 1 | 8 | 50.0 MB/s | 104.0 MB/s | 133.0 MB/s | 200.0 MB/s |
| x8 DTR | 8 | 2 | 16 | 100.0 MB/s | 208.0 MB/s | 266.0 MB/s | 400.0 MB/s |
典型的反推流程:① 先从启动时间需求算出需要的线速(例如「4 KB 镜像必须在 1 ms 内取完」→ 需要 4 MB/s 以上);② 再扣除固定开销,得到实际需要的理论带宽(除以约 0.8 的折算效率);③ 查表选模式与时钟,并且优先用「加宽总线」而不是「提时钟」来补足带宽——因为提时钟会同时抬高板级约束,而加宽总线只是多几根线与更多引脚。
只有当主控与板级条件都支持、且确实需要 200 MB/s 以上线速时,才值得上 x8 DTR。