4. DDR读写操作时序:读、写、预充电与刷新
各位好,我是老周。做DDR接口这么多年,最怕的就是时序出问题。读写操作这部分,说白了就是和DDR颗粒“握手”。握不好,数据就丢了。今天咱们把这四个关键流程掰开揉碎讲清楚。
4.1 读操作流程:数据从颗粒里“拽”出来
读操作的核心是:命令发送 → 列选通 → 数据返回。DDR颗粒不会立刻把数据扔出来,它有自己的潜伏期。
- 步骤1:激活行(ACTIVATE)。先打开Bank和行地址,这步通常提前做。
- 步骤2:读命令(READ)。带上列地址,从数据总线上发出去。
- 步骤3:等待CL(CAS Latency)。CL是固定的等待周期。比如DDR3-1600的CL=11,意味着读命令发出后11个时钟周期,数据才从DQ引脚冒出来。
- 步骤4:数据选通(DQS)对齐。DQS是伴随时钟,颗粒会在读数据之前先输出DQS边沿。我们在控制器侧要用DQS来采样DQ数据。
关键时序参数:
| 参数 | 含义 | 单位 |
|---|---|---|
| tRCD | 行激活到列选通的最小间隔 | 时钟周期 |
| CL | 读命令到第一个数据输出的延迟 | 时钟周期 |
| tAC | 数据输出相对于DQS边沿的偏斜 | ps |
我个人习惯把读操作的时序画成一条时间线。命令发出去后,CL期间颗粒内部在做数据准备。如果在CL还没结束时就去看DQ,你看到的只会是高阻态。我记得刚入行那会儿,把CL配错了,结果读回来的数据全是FF。查了好几天,差点怀疑是颗粒坏了。
避坑指南:我曾经在一个项目中,DDR控制器的读命令长度设成4,但颗粒只支持Burst Length=8。结果读出来的数据只有一半是对的。所以读命令的Burst Length必须和颗粒配置一致,别想当然。
4.2 写操作时序:数据如何“塞”进颗粒
写操作和读操作很像,但有个关键区别:控制器要主动提供DQS。DQS在写时要和DQ对齐,最好做到中心对齐,这样颗粒内部采样的建立/保持时间才够。
- 激活行(和读一样)。
- 写命令(WRITE)。带上列地址。
- 写潜伏期(WL)。WL = 读潜伏期CL – 1(有些协议是减去1,但并非所有DDR都这样)。例如CL=11,WL=10。控制器必须在WL期间把第一个数据放到DQ上。
- 提供DQS。写操作开始时,DQS要先发一个preamble(前导码,低电平持续至少一个时钟),然后才开始随数据切换。这个preamble是颗粒用来同步相位用的。
- 数据写入。颗粒在DQS的上升沿和下降沿都采样DQ(DDR就是双倍速率嘛)。
写时序里最让人头疼的是DQ和DQS的相位关系。如果你让DQS比DQ提前了那么一点点,或者落后了,颗粒可能采错数据。我在调试时喜欢在仿真里把DQ和DQS的边沿对上眼,看看是否满足颗粒的tDS和tDH。
注意:写操作结束后,颗粒会把DQ总线拉回高阻态。这中间有一个写恢复时间(tWR)。如果你紧接着发读命令,DQ还没释放,就会产生总线冲突。所以读写之间的时间间隔必须严格遵守tWTR。
4.3 预充电:关闭行,释放Bank
预充电说白了就是把当前打开的行关掉,让Bank准备好接受下一次行激活。为什么需要?因为DDR内部的行地址线是共享的,不关掉当前行,下一步操作就不能选别的行。
- 自动预充电(AP):在读/写命令时带上Auto Precharge标志。这样在Burst传输完成后,颗粒自己执行预充电。适合连续访问同一Bank的情况。
- 显式预充电(PRE):单独发一条预充电命令。可以同时关闭所有Bank(PREA),也可以只关闭一个。
- 预充电延迟(tRP):从预充电命令发出到Bank关闭完成的时间。这期间不能对该Bank做任何行激活。
你想想看,在随机读写的场景下,每次读/写后都自动预充电,看起来方便,但会增加tRP开销。反之,如果连续访问同一行,就不用预充电。所以时序收敛的关键之一,就是根据访问模式选择是否用自动预充电。我曾经遇到过DDR带宽上不去,查了半天发现每笔读写都带了AP,导致Bank一直被关掉又打开,效率极低。
预充电常见误区:
| 误区 | 后果 | 正确做法 |
|---|---|---|
| 读命令后立即预充电,不考虑tRAS | 行激活时间不足,数据可能损坏 | 确保行打开时间 ≥ tRAS |
| 预充电后紧接着激活同一Bank | 违反tRP,时序违例 | 等待tRP结束后再发ACT |
| 全Bank预充电(PREA)使用过于频繁 | 其他Bank无辜被关,延迟增加 | 尽量用单Bank预充电 |
4.4 刷新:不刷新,数据就没了
DDR颗粒是电容存储电荷,电荷会慢慢漏掉。所以必须定期给每一行“充电”,这就是刷新(REF)。刷新操作本质上就是先关闭所有Bank,然后对某个行地址执行一次内部激活+预充电,其实并没有真正的读写,只是把存储电容的电量补满。
- 刷新周期(tREFI):两次刷新命令之间的最大间隔。典型值:7.8 μs(DDR3, DDR4)。
- 刷新时间(tRFC):一条刷新命令占用的时间。DDR3约160 ns(8192个刷新周期),DDR4可能更长。
- 所有行刷新一遍的时间:tREFI × 行数。颗粒内部的刷新计数器会自动遍历所有行。
刷新操作会打断正常的读写。如果你不预留刷新时间,等到DDR开始自动刷新时,读写命令会排队等待,导致延迟抖动。在调试时序时,我建议你用周期性的刷新请求插入到DDR命令调度队列中,而不是等到最后时刻才刷。我曾经在一个原型验证板上没处理好刷新时机,结果连续32次读写都被刷新命令占用了超过100个周期,带宽损失惨不忍睹。
实践技巧:对于低速DDR(如DDR3-800),tRFC比较短,可以把刷新放到IDLE间隙。但对于DDR4-3200这样的高速颗粒,tRFC可能超过400 ns,你必须提前调度,甚至考虑使用刷新后置或Burst Refresh模式来减少功耗开销。
4.5 四个流程的时序关系图
下面的SVG图把读、写、预充电、刷新的时序关系串起来了。注意看,刷新命令需要在所有Bank都处于空闲时才能发起,否则会违例。
4.6 四个流程的避坑清单
最后,我把这些年遇到的血泪经验总结成一张表。你对照着检查,基本能避免90%的时序问题。
| 流程 | 常见坑 | 我的建议 |
|---|---|---|
| 读操作 | CL配置与颗粒不匹配 | 一定从颗粒的数据手册中查到CL值,并在控制器寄存器中写对。我见过有人把CL=10写成了0xA,实际上0xA=10,但有些控制器是按二进制编码的,别搞混。 |
| 写操作 | DQS preamble长度不够 | 写前preamble长度至少1个时钟周期。遇到DDR4可能要求2个时钟的preamble。我曾忽略这点,结果颗粒在采样第一个DQS边沿时失败。 |
| 预充电 | 违反tRAS | 行激活后至少保持tRAS时间再预充电。计算时注意时钟周期换算。 |
| 刷新 | 刷新请求太密集,挤占读写带宽 | 用Burst Refresh或者分散刷新(Per-Bank Refresh)来减轻影响。 |
好了,这一章的内容就这么多。记住这四个操作的核心时序参数,调试时多看看波形图上的命令和数据之间的关系,问题往往就藏在细节里。