3、NAND Flash接口协议:异步/同步时序,ONFI与Toggle标准,命令/地址/数据总线复用
好,咱们今天聊聊NAND Flash的接口协议。说实话,这部分内容在芯片验证和驱动开发中,是绕不开的核心。你想想看,CPU要跟Flash打交道,总得有个规矩吧?这个规矩就是接口协议。
我个人习惯把接口协议分成两大部分来看:一是时序(异步还是同步),二是标准(ONFI还是Toggle)。另外还有个很关键的设计——总线复用。咱们一个一个说。
3.1 异步时序 vs 同步时序
先讲异步时序。这是最基础的玩法,也是我最早接触的NAND接口。
异步时序,说白了就是靠信号的电平跳变来传递信息。没有时钟线,全靠控制信号的先后顺序。常用的控制信号有:
- CE#(Chip Enable):片选,选中哪个芯片干活
- CLE(Command Latch Enable):命令锁存使能
- ALE(Address Latch Enable):地址锁存使能
- WE#(Write Enable):写使能,下降沿锁存数据
- RE#(Read Enable):读使能,下降沿输出数据
- WP#(Write Protect):写保护
- R/B#(Ready/Busy):忙指示
异步时序的典型操作流程是这样的:
// 发送命令 0x80(页编程)
CE# = 0; // 选中芯片
CLE = 1; // 命令锁存模式
ALE = 0; // 地址不锁存
WE# = 0; // 拉低WE#
// 此时数据总线上放 0x80
WE# = 1; // 上升沿,命令被锁存
CLE = 0; // 结束命令阶段
嗯,这里要注意:异步时序的时序参数都是绝对时间。比如 tWC(写周期时间)最小要多少纳秒,tRC(读周期时间)最小多少纳秒。我在项目中遇到过,有些新手工程师把时序参数设得太紧,结果高温下就跑挂了。所以异步时序的验证,一定要做PVT(工艺-电压-温度)全覆盖。
同步时序就高级一些了。它引入了时钟信号(CLK),所有操作都跟时钟沿对齐。同步模式下,数据可以双沿采样(DDR),也就是时钟的上升沿和下降沿都传数据。这样一来,吞吐量直接翻倍。
同步时序的关键信号多了个CLK和DQS(数据选通)。DQS在读取时由Flash驱动,写入时由控制器驱动。这个设计其实挺巧妙的——用DQS来保证数据采样的窗口足够宽。
我建议你在做同步时序验证时,重点关注DQS与CLK的相位关系。ONFI标准要求DQS与CLK有固定的相位偏移,Toggle标准则要求DQS与数据边沿对齐。搞混了的话,数据采样会出大问题。
3.2 ONFI标准 vs Toggle标准
这两个标准,说白了就是两大阵营的玩法。ONFI(Open NAND Flash Interface)是Intel、Micron他们推的,Toggle是Samsung、Toshiba他们推的。虽然目标都是让Flash跑得更快,但实现方式有差异。
| 特性 | ONFI标准 | Toggle标准 |
|---|---|---|
| 时钟来源 | 控制器提供CLK | 控制器提供CLK,DQS由Flash驱动 |
| 数据采样 | CLK边沿采样 | DQS边沿采样 |
| DQS相位 | 与CLK有固定偏移 | 与数据边沿对齐 |
| 最大速率 | ONFI 5.0可达3600 MT/s | Toggle 5.0可达3600 MT/s |
| 命令集 | 标准化命令集 | 兼容ONFI命令集,有扩展 |
我个人觉得,ONFI的标准化程度更高,命令集定义得很清楚。Toggle则更灵活,但兼容性验证要更仔细。我在一个项目中同时用了ONFI和Toggle的Flash,结果发现Toggle的读时序参数跟ONFI不完全一样,驱动里得做条件编译。
3.3 命令/地址/数据总线复用
这个设计很有意思。NAND Flash的引脚有限,所以把命令、地址、数据都走同一组总线(通常是8位或16位)。靠CLE和ALE信号来区分当前总线上是什么内容。
具体来说:
- CLE=1, ALE=0:总线上是命令
- CLE=0, ALE=1:总线上是地址
- CLE=0, ALE=0:总线上是数据
你想想看,一个页编程操作,要发命令、发5个地址周期、再发数据。整个过程都是在这组总线上完成的。所以驱动里必须严格按顺序来:
// 页编程示例(以8位总线为例)
// 1. 发命令 0x80
set_CLE(1); set_ALE(0);
write_bus(0x80);
toggle_WE();
// 2. 发地址(5个周期)
set_CLE(0); set_ALE(1);
write_bus(column_addr & 0xFF); // 列地址低8位
toggle_WE();
write_bus((column_addr >> 8) & 0xFF); // 列地址高8位
toggle_WE();
write_bus(row_addr & 0xFF); // 行地址低8位
toggle_WE();
write_bus((row_addr >> 8) & 0xFF); // 行地址中8位
toggle_WE();
write_bus((row_addr >> 16) & 0xFF); // 行地址高8位
toggle_WE();
// 3. 发数据
set_CLE(0); set_ALE(0);
for(i=0; i<page_size; i++) {
write_bus(data[i]);
toggle_WE();
}
// 4. 发确认命令 0x10
set_CLE(1); set_ALE(0);
write_bus(0x10);
toggle_WE();
这里有个坑:地址周期数取决于Flash的容量和总线宽度。8位总线通常要5个地址周期(2列+3行),16位总线可能只要4个。我曾经因为地址周期数搞错,导致读写数据错位,查了半天才发现是地址映射问题。
嗯,最后说一句。接口协议这部分,光看文档是不够的。我建议你拿一块开发板,用逻辑分析仪抓一下实际的波形。看看CLE、ALE、WE#、RE#这些信号是不是真的按协议在跳。我当年就是这么干的,抓完波形后,很多疑惑就解开了。