3、DDR读写时序:ACT、READ、WRITE命令时序、Bank管理与刷新机制

DDR的时序,说白了就是一套严格的「握手协议」。你发命令,它给数据,谁先谁后,差一个时钟周期都不行。我刚开始调DDR的时候,总觉得时序图看着像天书,后来发现,只要抓住几个关键命令,剩下的都是套路。

今天咱们就聊聊DDR读写中最核心的三个命令:ACT(激活)、READ(读)、WRITE(写)。再加上Bank管理和刷新机制。嗯,这些搞懂了,DDR控制器的设计思路就清晰了一大半。

3.1 先说说Bank管理

DDR内部不是一块完整的大内存,而是分成了多个Bank。你可以把每个Bank想象成一个独立的「小仓库」。每个小仓库里又有很多行(Row)和列(Column)。

为什么要分Bank?说白了就是为了并行。你在操作Bank0的时候,Bank1可以提前准备好数据。这样就能隐藏掉一些延迟。

关键点:DDR3通常有8个Bank,DDR4有16个Bank。Bank越多,并行度越高,但控制逻辑也越复杂。

我在项目中遇到过一个问题:连续读写同一个Bank的不同行,性能惨不忍睹。后来才发现,每次切换行都要先Precharge(预充电),再ACT,白白浪费了好多时钟周期。解决办法?把数据分散到不同的Bank里。

3.2 ACT命令:打开行

你要读写某个地址的数据,第一步就是发ACT命令。ACT的作用是打开指定Bank中的某一行。行打开了,数据才能被访问。

ACT命令的时序要求很严格。发ACT之后,要等tRCD(Row to Column Delay)时间,才能发READ或WRITE命令。tRCD一般是几个时钟周期,具体看DDR的速率等级。

// 伪代码示例:发ACT命令
// bank_addr = 3'b001, row_addr = 14'h1234
ddr_cmd = ACT;
ddr_bank = 3'b001;
ddr_row  = 14'h1234;
// 等待 tRCD 个时钟周期
wait(tRCD);
// 然后才能发READ或WRITE

个人经验:我习惯在状态机里单独做一个「行打开检查」。每次发ACT之前,先看看目标Bank的当前行是不是已经打开了。如果是同一行,就不用再发ACT了,直接读写就行。这个小优化,能省不少时间。

3.3 READ命令:读数据

行打开之后,就可以发READ命令了。READ命令需要指定Bank地址和列地址。数据不会立刻出来,要等CL(CAS Latency)个时钟周期。

CL是DDR的一个重要参数。DDR3常见的CL值有9、10、11。DDR4常见的有15、16、17。CL越小,延迟越低,但DDR的频率也受限制。

DDR类型 常见CL值 tRCD(时钟周期) tRP(时钟周期)
DDR3-1333 9 9 9
DDR3-1600 11 11 11
DDR4-2400 15 15 15
DDR4-3200 22 22 22

读数据的时候,还有一个细节:Burst Length(突发长度)。DDR3默认是8,也就是说一次READ命令会连续读出8个数据。你想想看,如果只想要1个数据,那剩下的7个也得读出来,浪费带宽。所以,设计的时候要尽量凑够8个数据再发一次读命令。

3.4 WRITE命令:写数据

写操作和读操作类似,也是先ACT打开行,然后发WRITE命令。但写操作有个额外的要求:写数据要和WRITE命令对齐。

具体来说,发WRITE命令的同时(或者延迟一点点),就要把数据送到DQ总线上。DDR控制器会内部产生DQS(数据选通信号),用来采样数据。

// 伪代码示例:发WRITE命令
// 先ACT打开行,等待tRCD
ddr_cmd = WRITE;
ddr_bank = 3'b001;
ddr_col  = 10'h0F;
// 同时把数据放到DQ上
ddr_dq = write_data;
// 等待tWR(Write Recovery Time)
wait(tWR);
// 然后发Precharge命令关闭行

注意:写操作之后,不能立刻发Precharge。要等tWR(Write Recovery Time)时间,确保数据已经正确写入存储单元。我曾经因为tWR没等够,导致数据写错,排查了好几天才发现是时序问题。

3.5 刷新机制:DDR的「充电」

DDR的存储单元是电容,电容会漏电。所以每隔一段时间,就要给所有行「充电」一次,这就是刷新(Refresh)。

DDR3和DDR4的刷新周期是64ms。也就是说,64ms内,所有行必须至少被刷新一次。DDR控制器会自动处理刷新,但你要给它留出时间。

刷新命令是REF。发REF的时候,DDR不能做任何读写操作。所以,刷新会占用带宽。我记得有一次做视频采集,DDR带宽利用率到了95%,结果刷新一来,数据就丢了。后来加了FIFO缓冲,才解决。

刷新策略:

  • 分散刷新:每隔一段时间发一次REF,把刷新均匀分布。适合带宽要求不高的场景。
  • 集中刷新:攒够一批刷新命令,一次性发完。适合有固定空闲窗口的场景。
  • 自动刷新:DDR颗粒内部自己处理,外部只需要定期发REF命令即可。

3.6 完整的读写时序流程

咱们把整个流程串起来,看看一次完整的读操作是什么样的:

  1. Precharge(可选):如果当前Bank有其他行打开,先发Precharge关闭它。等tRP时间。
  2. ACT:打开目标行。等tRCD时间。
  3. READ:发读命令,指定列地址。等CL时间。
  4. 数据输出:数据从DQ总线上出来。如果是Burst Length=8,连续输出8个数据。
  5. Precharge:如果不再需要这行数据,发Precharge关闭行。等tRP时间。

写操作类似,只是把READ换成WRITE,并且数据要提前准备好。

3.7 避坑指南

嗯,这里我要多说几句。DDR时序的坑,我踩过不少。

  • Bank冲突:连续访问同一个Bank的不同行,每次都要Precharge+ACT,性能极差。我曾经在卷积加速器里遇到过,后来把输入特征图分散到4个Bank里,性能提升了30%。
  • 刷新饥饿:如果读写请求太密集,刷新可能被无限推迟。DDR颗粒会丢数据。解决办法是给刷新一个高优先级,或者预留刷新时间窗口。
  • 时序参数配错:DDR的时序参数(tRCD、CL、tRP等)必须和DDR颗粒的规格一致。配错了,轻则性能下降,重则数据错误。我建议直接从DDR颗粒的数据手册里抄参数,别自己猜。

小技巧:调试DDR的时候,先写一个简单的读写测试。写一个固定模式(比如0xA5A5A5A5),然后读回来对比。如果数据不对,大概率是时序参数配错了。

3.8 知识体系图

下面这张图,把DDR读写时序的核心逻辑串起来了。你看一眼,应该能有个整体印象。

DDR读写时序核心知识体系 Bank管理 命令时序 刷新机制 8/16个Bank并行 行打开/关闭管理 Bank冲突避免 ACT → tRCD → READ/WRITE CL/CWL延迟等待 Burst Length=8 64ms刷新周期 分散/集中刷新 刷新优先级管理 核心原则 先ACT打开行 → 等待tRCD → 读写数据 → 等待CL/tWR → Precharge关闭行 刷新穿插其中,确保数据不丢失

这张图把Bank管理、命令时序、刷新机制三个模块串在了一起。你设计DDR控制器的时候,脑子里要有这张图。每个模块的细节,咱们后面会慢慢展开。

好了,DDR读写时序的核心内容就这些。ACT、READ、WRITE三个命令,加上Bank管理和刷新,基本覆盖了DDR控制器的所有关键点。嗯,下一节咱们聊聊具体的状态机设计,到时候我会给出完整的Verilog代码示例。

总结一下:

  • ACT打开行,等tRCD后才能读写
  • READ等CL个时钟出数据,WRITE要数据对齐
  • Bank管理要避免行冲突,尽量分散访问
  • 刷新是必须的,64ms内所有行至少刷新一次

可可精品资料库