4. NAND Flash 时序分析:读、写、擦除与时序优化
时序这东西,说白了就是芯片内部各个操作在时间轴上的“对齐”问题。你驱动写得再花哨,时序对不上,数据就是错的。我早年调一个 ONFI 接口的 NAND,就因为 tREA 差了那么几纳秒,读出来的数据每隔几页就跳一个 bit 的错误,查了整整两天。
这一章我们就把 NAND Flash 最核心的几组时序参数掰开揉碎。读、写、擦除,每个操作都有它的关键路径。理解了这些,你写驱动的时候才知道哪些地方能压,哪些地方必须等。
4.1 读时序参数:tR、tRC、tREA
读操作是 NAND 最频繁的操作。三个参数决定了读性能的上限。
| 参数 | 全称 | 典型值(TLC) | 含义 |
|---|---|---|---|
| tR | Read Time | 45~100 μs | 从发出读命令到数据准备好,送到页缓存的时间 |
| tRC | Read Cycle Time | 20~50 ns | 连续读取时,相邻两个数据输出之间的最小间隔 |
| tREA | Read Enable Access Time | 15~35 ns | RE# 信号有效到数据在 DQ 上稳定的时间 |
tR 是读延迟的大头。你发一个 00h-Addr-30h 的读序列,然后就要等 tR。这期间 NAND 内部在做电荷感测和数据搬移。我见过有人在这个等待期里用轮询 R/B# 引脚,其实更高效的做法是发完读命令后立刻切到下一个 die 去操作——这就是后面要讲的 多平面交错 的基础。
tRC 决定了读带宽。假设 tRC = 25 ns,数据位宽 8 bit,那理论峰值就是 1 / 25 ns × 8 = 320 MB/s。但实际跑不到,因为还有命令开销和 tREA 的抖动。
tREA 是个容易踩坑的参数。它定义了 RE# 下降沿到数据在 DQ 上有效的时间。控制器采样数据的时候,必须等 tREA 过了再采样。我见过一个项目,控制器采样点设得太靠前,高温下 tREA 变大了,数据就采错了。后来我们在驱动里加了 采样窗口调整 的逻辑,根据温度动态偏移采样点。
避坑指南: 我曾经在一个 3D NAND 项目里,发现读同一个 LUN 的不同 plane,tR 居然有 ±15% 的差异。后来查手册才知道,plane 离页缓存的距离不同,内部走线延迟不一样。所以驱动里不要对 tR 做死假设,最好用 R/B# 中断或者状态寄存器来确认操作完成。
4.2 写时序参数:tPROG、tWC、tADL
写操作比读复杂得多。因为要注入电荷,所以时间也长得多。
| 参数 | 全称 | 典型值(TLC) | 含义 |
|---|---|---|---|
| tPROG | Program Time | 500~2000 μs | 从数据写入页缓存到电荷注入完成的时间 |
| tWC | Write Cycle Time | 20~50 ns | 连续写入时,相邻两个数据输入之间的最小间隔 |
| tADL | Address to Data Load Time | 70~200 ns | 地址锁存后,到可以开始加载数据的最小等待时间 |
tPROG 是写操作的瓶颈。一个 page 的编程要 500 μs 到 2 ms。你想想看,如果一次只写一个 page,那带宽就被锁死了。所以现代 NAND 都支持 多页编程(Multi-Plane Program),一次 tPROG 可以写 2 个或 4 个 page。
tWC 决定了写带宽。和 tRC 类似,tWC 越小,数据灌入越快。但要注意,tWC 是控制器侧的限制,不是 NAND 内部的速度。NAND 内部写缓存的速度其实比 tWC 快,瓶颈在 I/O 接口上。
tADL 是个容易被忽略的参数。它规定了地址锁存(ALE 有效)之后,必须等 tADL 才能开始写数据。我见过有人把地址发完立刻就开始发数据,结果数据没写进正确的页缓存。嗯,这里要注意:tADL 不是 NAND 的“处理时间”,而是地址解码和页缓存选通需要的建立时间。
个人经验: 写驱动的时候,我习惯把 tADL 和 tWC 合并成一个约束:地址发完后,至少等 tADL 再开始发数据;数据发完后,至少等 tWC 再发下一个数据。这样既保证时序,又不会过度浪费等待时间。
4.3 擦除时序参数:tBERS
擦除操作是 NAND 里最慢的操作,没有之一。
| 参数 | 全称 | 典型值(TLC) | 含义 |
|---|---|---|---|
| tBERS | Block Erase Time | 3~15 ms | 擦除一个 block 所需的时间 |
tBERS 为什么这么慢?因为擦除是对整个 block 的所有 wordline 施加高压脉冲,把浮栅里的电子拉出来。这个过程需要精确控制电压和时长,快了擦不干净,慢了会过度擦除导致阈值电压漂移。
我遇到过一个问题:某个批次 NAND 的 tBERS 在低温下会翻倍。从 5 ms 变成 10 ms。驱动里如果用了固定超时,低温下就会频繁超时。后来我们在驱动里加了 自适应超时,根据温度查表调整 tBERS 的等待时间。
警告: 擦除操作不可中断。一旦发出擦除命令,必须等 tBERS 完成或者用 R/B# 检测到结束。强行复位或断电可能导致 block 损坏。我曾经在调试时手贱按了复位,结果那个 block 再也没法用了。
4.4 时序优化策略
知道了参数,怎么优化?我总结了几条实战经验。
4.4.1 流水线化:隐藏延迟
tR 和 tPROG 是“等待时间”,tRC 和 tWC 是“传输时间”。优化的核心思路是:让等待时间和传输时间重叠。
- Cache Read / Cache Program:利用 NAND 内部的页缓存,在读下一个 page 的同时,把上一个 page 的数据从缓存搬出去。
- 多 Die 交错:一个 die 在 tPROG 的时候,另一个 die 可以接收数据。我做过一个 4-die 交错的方案,写带宽提升了 3.2 倍。
- 多 Plane 操作:同一个 die 的多个 plane 可以并行编程,tPROG 只花一次。
4.4.2 采样点调优:tREA 的补偿
tREA 会随 PVT(工艺、电压、温度)变化。驱动里可以加一个 训练序列:在初始化时发一段已知数据,然后调整采样点,找到最佳位置。我习惯在驱动初始化时做一次 DQS 训练,把采样点锁定在眼图中央。
4.4.3 命令合并:减少开销
每次发命令都有地址和命令周期开销。如果能合并,就能省下不少时间。
- 多页读:一次发多个 page 的读命令,然后连续读数据。
- 多页写:一次发多个 page 的写命令,然后连续写数据。
- Copy-Back:在 NAND 内部直接搬移数据,不经过控制器。适合 GC(垃圾回收)场景。
4.4.4 时序参数表
下面这张图是我自己整理的时序优化决策树,帮你快速定位瓶颈。
4.4.5 实际案例:读带宽从 180 MB/s 提升到 280 MB/s
我之前优化过一个 UFS 3.1 的读性能。原始驱动是单 die 顺序读,tR 期间 CPU 空等。我做了三件事:
- 启用 Cache Read:让 NAND 在输出数据的同时预读下一个 page。
- 多 Die 交错:4 个 die 轮流读,tR 被完全隐藏。
- DQS 训练:把采样点从默认的 50% 位置调到 65%,消除了高温下的采样错误。
结果读带宽从 180 MB/s 直接跳到 280 MB/s。嗯,这就是时序优化的价值。
小技巧: 调时序的时候,别只看 datasheet 上的典型值。要关注 min 和 max。驱动里用典型值做性能估算,用 max 值做超时保护。这样既快又稳。