2. DDR基本原理:SDRAM架构、Bank与Row/Column寻址、DDR读写时序、突发传输模式
好,咱们正式开始啃DDR这块硬骨头。说实话,很多工程师做了好几年FPGA,对DDR的理解还停留在“调个IP核,跑个仿真”的阶段。但做彩超系统,这么干可不行。彩超的原始数据流,动辄几十GB/s,DDR接口但凡有一点不稳定,图像上就会出现条纹、噪点,甚至直接黑屏。我当年调试第一台彩超样机时,就被DDR的时序问题折磨了整整两周,最后发现是Bank冲突导致的带宽骤降。所以,这一章咱们把DDR的底裤扒干净。
2.1 SDRAM架构:从核心存储单元说起
DDR的本质,还是SDRAM(同步动态随机存取存储器)。为什么叫“动态”?因为它的存储单元就是个电容加一个晶体管。电容会漏电,所以需要定期刷新。为什么叫“同步”?因为所有操作都跟着时钟沿走。
一个DDR芯片的内部,可以想象成一个巨大的表格。表格的每一行叫一个Row,每一列叫一个Column。但光有行和列还不够,DDR内部还分了多个Bank。为什么要有Bank?说白了,就是为了并行。你想想看,如果只有一个Bank,那每次读写完一行,想换到另一行,都得先“预充电”再“激活”,这中间浪费的时间可不少。多个Bank就能让你在一个Bank读写的同时,提前激活另一个Bank的行。
核心要点:DDR内部结构 = 多个Bank × 每个Bank内的Row × 每个Row内的Column。每个存储单元由1个晶体管+1个电容构成。
2.2 Bank与Row/Column寻址:地址线到底怎么用?
DDR的地址线是分时复用的。什么意思?就是同一组地址线,在不同时间传不同的信息。先传Row地址,再传Column地址。这样做的好处是节省引脚,坏处是增加了寻址延迟。
具体流程是这样的:
- 激活命令(ACTIVATE):先发Bank地址和Row地址。这时候,这一行的所有存储单元都被“打开”,数据从电容读到Sense Amplifier(感测放大器)里。
- 读写命令(READ/WRITE):再发Column地址。这时候,从已经打开的这一行里,选中具体的列进行读写。
我在项目中遇到过一个问题:某个同事为了省事,把连续的图像数据都放在同一个Bank的同一行里。结果每次读写都要等漫长的tRCD(Row到Column的延迟)。后来我建议他把数据分散到不同的Bank里,利用Bank交替访问,带宽直接翻了一倍。嗯,这里要注意,Bank交替是DDR性能优化的第一课。
个人经验:彩超系统中,通常把不同通道的IQ数据分配到不同的Bank。比如通道0用Bank0,通道1用Bank1。这样在波束合成时,可以流水线式地访问,避免Bank冲突。
2.3 DDR读写时序:那些你必须懂的参数
DDR的时序,说白了就是一堆“延迟”的组合。我刚开始学的时候,看到tRCD、tCL、tRP、tRAS这些参数就头大。后来我总结了一个口诀:“激活要等tRCD,读写出数看tCL,预充电要等tRP,行有效时间看tRAS”。
咱们拆开来看:
| 参数 | 全称 | 含义 | 我的理解 |
|---|---|---|---|
| tRCD | RAS to CAS Delay | 行地址到列地址的延迟 | 你喊了一声“开门”,到门真正打开的时间 |
| tCL | CAS Latency | 列地址到数据输出的延迟 | 你伸手去拿东西,到东西拿到手的时间 |
| tRP | Row Precharge Time | 预充电时间 | 你用完一行,准备换下一行,中间需要“清场”的时间 |
| tRAS | Row Active Time | 行有效时间 | 这一行必须保持打开的最短时间,不能刚打开就关 |
读操作的一个典型时序是这样的:
- 发送ACTIVATE命令,带上Bank和Row地址。然后等tRCD个时钟周期。
- 发送READ命令,带上Column地址。然后等tCL个时钟周期。
- 数据从DQ引脚上冒出来。
- 读完后,发送PRECHARGE命令,等tRP个时钟周期,关闭这一行。
写操作类似,但写数据是和写命令一起发出的,不需要等tCL。不过写完后要等tWR(Write Recovery Time),确保数据真正写进去了。
避坑指南:我曾经在调试DDR3时,发现写数据总是丢包。查了三天,最后发现是tWR设置得太短。DDR控制器以为数据写完了,实际上电容还没充好电。彩超的数据量很大,这种丢包会导致图像出现“雪花点”,非常隐蔽。
2.4 突发传输模式:为什么DDR这么快?
DDR之所以快,除了双倍速率(上下沿都传数据),还有一个杀手锏——突发传输(Burst)。
什么叫突发?就是你发一次读命令,DDR会连续吐出多个数据。比如DDR3的突发长度(Burst Length,BL)通常是8。你发一次读命令,它会连续输出8个64位的数据。为什么能这样?因为DDR内部有个“预取”机制。DDR3内部一次从存储阵列中读出8倍的数据,放到一个缓冲区里,然后一个时钟周期传一个。
我个人习惯把突发传输比作“批发”。你想想看,如果每次只买一个鸡蛋,那得跑很多趟。但如果一次买一箱(8个),效率就高多了。彩超系统里,图像数据在内存中通常是连续存放的,所以突发传输特别适合。
突发传输的规则:
- 顺序突发(Sequential):地址连续递增。比如起始地址是0,BL=8,那就读地址0、1、2、3、4、5、6、7。
- 交错突发(Interleaved):地址按特定模式跳变。这个在彩超里用得少,主要用于某些特殊的数据处理。
这里有个关键点:突发传输的起始地址必须对齐到突发长度。比如BL=8,起始地址必须是8的倍数。如果不对齐,DDR控制器会自动拆分,性能会下降。我在做彩超的DMA控制器时,就特意加了一个地址对齐模块,确保每次DDR访问都是对齐的。
性能要点:DDR的理论带宽 = 时钟频率 × 数据位宽 × 2(双倍速率)。但实际可用带宽通常只有理论值的60%-80%。原因就是Bank冲突、地址不对齐、刷新操作等开销。彩超系统里,我们一般按70%来估算。
2.5 彩超场景下的DDR应用思考
好了,理论讲完了,咱们回到彩超。彩超的DDR主要存什么?
- 原始射频数据:每个通道的ADC采样数据,数据量大,要求连续写入。
- 波束合成后的数据:经过处理后的IQ数据,需要随机访问。
- 图像帧缓存:用于显示和回放,需要高带宽。
针对这些场景,我的建议是:
- 原始数据用连续地址写入:利用DDR的突发传输,一次写一整行。
- 波束合成数据用Bank交替:把不同通道的数据分到不同Bank,减少冲突。
- 图像帧缓存用Page模式:尽量让读写操作落在同一行内,避免频繁的预充电。
嗯,这一章的内容就到这里。DDR的基本原理,说白了就是“电容+时序+突发”。下一章咱们会深入DDR控制器的设计,包括状态机、命令调度和PHY层的接口。到时候我会分享一些具体的Verilog代码,咱们手把手把DDR控制器搭起来。