4. 内存控制器参数:SDRAM时序参数与DDR初始化
内存子系统,说白了就是BootROM的粮仓。粮仓运转不顺,CPU就得饿肚子。我见过太多项目,CPU频率跑得飞起,结果内存时序配错,系统启动直接卡死在DDR初始化阶段。嗯,这一章咱们就把内存控制器那几个关键参数掰开揉碎了讲。
4.1 SDRAM核心时序参数
不管你是SDRAM还是DDR,底层都绕不开三个基本时序:CAS延迟、RAS到CAS延迟、预充电时间。这三个参数直接决定了内存读写的效率。
| 参数 | 全称 | 单位 | 典型值(DDR3-1600) |
|---|---|---|---|
| CL | CAS Latency | 时钟周期 | 9~11 |
| tRCD | RAS to CAS Delay | 时钟周期 | 9~11 |
| tRP | Row Precharge Time | 时钟周期 | 9~11 |
4.1.1 CAS Latency(CL)
CL是内存控制器发出读命令到数据出现在数据总线上的延迟。说白了,就是你喊了一声“把数据给我”,到数据真的递到你手里,中间要等几个时钟周期。
我个人习惯,在BootROM阶段先把CL设成芯片手册里最保守的值。为什么?因为这时候内存还没完全稳定,温度、电压都可能波动。我曾经在一个项目里,为了追求那点性能提升,把CL从11压到9,结果低温环境下启动时随机出现数据错乱。排查了整整两天,最后发现是CL太激进。
4.1.2 tRCD(RAS to CAS Delay)
tRCD是行地址选通到列地址选通之间的延迟。你想想看,内存颗粒内部先要打开一行(RAS),然后才能在这一行里选一列(CAS)。这个打开行的动作需要时间,tRCD就是给这个动作预留的缓冲。
我遇到过一块板子,tRCD设成8能跑,设成7就偶尔死机。查了颗粒手册才发现,那个批次的内存颗粒tRCD最小值就是8。所以,别光看频率,时序参数一定要跟颗粒手册对齐。
4.1.3 tRP(Row Precharge Time)
tRP是预充电时间。当你读完一行数据,想切换到另一行时,需要先把当前行关闭(预充电),然后才能打开新行。这个关闭动作需要的时间就是tRP。
这里有个小技巧:如果你的BootROM代码是顺序执行的(比如从Flash拷贝数据到内存),那tRP的影响其实不大。但如果你要做随机访问(比如查表、跳转),tRP就很重要了。我一般建议,在BootROM阶段把tRP设成跟tRCD一样,省事又稳妥。
4.2 DDR初始化序列
DDR的初始化比SDRAM复杂得多。它有一套严格的“上电-复位-训练”流程,顺序错了,内存根本没法用。
4.2.1 标准初始化流程
- 上电与复位:VDD和VDDQ稳定后,保持CKE为低,等待至少200μs。
- 时钟稳定:启动DDR时钟,等待至少10个时钟周期。
- NOP命令:发送至少400个NOP周期。
- 预充电所有Bank:发送PALL命令。
- 刷新:发送至少2次自动刷新命令。
- 模式寄存器配置:配置MR0~MR3(包括CL、突发长度、DLL使能等)。
- ZQ校准:发送ZQCL命令,等待校准完成。
- DLL锁定:等待DLL锁定,通常需要200个时钟周期。
- 训练与校准:进行读写平衡、DQS门控训练等。
我记得第一次调DDR3的时候,照着参考代码写,结果板子就是起不来。后来发现是ZQ校准的等待时间不够。芯片手册说“至少512个时钟周期”,我设了500,就差那12个周期,死活不行。从那以后,我所有时序等待都加20%余量。
4.2.2 模式寄存器配置示例
/* DDR3模式寄存器配置示例 */
void ddr_mr_config(void)
{
/* MR0: 突发长度8, CL=9, DLL复位 */
write_mr(0, 0x00000472);
/* MR1: DLL使能, ODT=40Ω, 输出驱动强度=34Ω */
write_mr(1, 0x00000006);
/* MR2: CWL=6, 自动刷新周期 */
write_mr(2, 0x00000008);
/* MR3: 温度补偿刷新使能 */
write_mr(3, 0x00000000);
}
4.3 内存映射配置
内存映射,就是告诉CPU:哪段地址对应哪块内存芯片。BootROM阶段,内存映射通常比较简单,但有几个坑要注意。
4.3.1 地址映射方式
| 映射方式 | 描述 | 适用场景 |
|---|---|---|
| 线性映射 | 物理地址连续映射到内存空间 | 单颗内存芯片,或简单系统 |
| 交错映射 | 多颗芯片地址交错,提高带宽 | 多通道DDR,追求性能 |
| Bank交错 | 同一芯片内Bank地址交错 | 减少Bank冲突,提升随机访问性能 |
我个人建议,BootROM阶段用线性映射就够了。为什么?因为BootROM代码量小,访问模式简单,没必要搞复杂的交错。而且,交错映射一旦配错,地址就全乱了,调试起来非常痛苦。
4.3.2 地址线映射的坑
这里有个常见的坑:芯片的地址线A0~A15,跟CPU的地址线不一定是一一对应的。有些芯片会把Bank地址、行地址、列地址复用在同一组引脚上。配置的时候,一定要搞清楚内存控制器的地址映射寄存器。
我曾经接手过一个项目,前任工程师把行地址和列地址的位宽配反了。结果呢?内存能初始化成功,但读写数据全是乱的。查了三天,最后用逻辑分析仪抓地址线才发现问题。
4.4 本章知识体系
下面这张图,把内存控制器参数的核心逻辑串起来了。你可以把它当成一个检查清单,调内存的时候对着看,不容易漏。
4.5 避坑指南与经验总结
- 时序参数别贪快:BootROM阶段,稳定第一。用芯片手册里最保守的值,等系统跑稳了再优化。
- 初始化顺序不能乱:JEDEC标准流程是铁律。我见过有人把MR配置放在刷新之前,结果DLL死活锁不住。
- 地址映射要核对:行地址、列地址、Bank地址的位数,必须跟芯片规格书一致。配错了,调试起来非常痛苦。
- 等待时间加余量:所有时序等待,我习惯加20%的余量。芯片手册说“至少512”,我就设成640。多花几个时钟周期,换来的是稳定性。