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对齐,最好做到中心对齐,这样颗粒内部采样的建立/保持时间才够。

  1. 激活行(和读一样)。
  2. 写命令(WRITE)。带上列地址。
  3. 写潜伏期(WL)。WL = 读潜伏期CL – 1(有些协议是减去1,但并非所有DDR都这样)。例如CL=11,WL=10。控制器必须在WL期间把第一个数据放到DQ上。
  4. 提供DQS。写操作开始时,DQS要先发一个preamble(前导码,低电平持续至少一个时钟),然后才开始随数据切换。这个preamble是颗粒用来同步相位用的。
  5. 数据写入。颗粒在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都处于空闲时才能发起,否则会违例。

DDR读写操作时序流程(简化) 命令总线 ACT READ WRITE PRE REF DQ(读) 读数据(CL后) CL等待 DQ(写) 写数据(WL后) WL Bank状态 行打开 预充电 空闲 刷新中 要点:读操作需等待CL;写操作需主动提供DQS;预充电在读写结束后释放Bank;刷新需在所有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)来减轻影响。

好了,这一章的内容就这么多。记住这四个操作的核心时序参数,调试时多看看波形图上的命令和数据之间的关系,问题往往就藏在细节里。