1. 简介
DDR SDRAM(Double Data Rate Synchronous Dynamic Random Access Memory,双数据率同步动态随机存储器)通常被我们称为DDR,其中的“同步”是指内存工作需要同步时钟,内部命令的发送与数据传输都以它为基准。DDR是一种掉电就丢失数据的存储器件,并且需要定时的刷新来保持数据的完整性。
现在嵌入式系统设计或者计算机设计,考虑到存储性能、存储容量、成本等因素,通常采用存储金字塔式的设计,比如CPU后面紧接着寄存器,寄存器后面跟着cache,cache后面紧接着DDR,然后DDR后面跟着SSD、EMMC等非易失。通过利用程序的时间及空间局部性原理,可以在尽可能少的影响性能的前提下,增加存储容量,降低存储成本。
1.1 存储器分类
存储器一般来说可以分为内部存储器(内存),外部存储器(外存),缓冲存储器(缓存)以及闪存这几个大类。
内存:也称为主存储器,位于系统主机板上,可以同CPU直接进行信息交换。其主要特点是:运行速度快,容量小。
外存:也称为辅助存储器,不能与CPU之间直接进行信息交换。其主要特点是:存取速度相对内存要慢得多,存储容量大。内存与外存本质区别是,一个是内部运行提供缓存和处理的功能,也可以理解为协同处理的通道;而外存主要是针对储存文件、图片、视频、文字等信息的载体,也可以理解为储存空间。
缓存:就是数据交换的缓冲区(称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找。由于缓存的运行速度比内存快得多,故缓存的作用就是帮助硬件更快地运行。
闪存(Flash Memory):是一种长寿命的非易失性(在断电情况下仍能保持所存储的数据信息)的存储器,数据删除不是以单个的字节为单位而是以固定的区块为单位(注意:NOR Flash 为字节存储。),区块大小一般为256KB到20MB。闪存是电子可擦除只读存储器(EEPROM)的变种,闪存与EEPROM不同的是,EEPROM能在字节水平上进行删除和重写而不是整个芯片擦写,而闪存的大部分芯片需要块擦除。由于其断电时仍能保存数据,闪存通常被用来保存设置信息,如在电脑的BIOS(基本程序)、PDA(个人数字助理)、数码相机中保存资料等。
内存有多种不同的形式分别如下:
RAM(Random Access Memory) 随机存储器。存储单元的内容可按需随意取出或存入,且存取的速度与存储单元的位置无关的存储器。这种存储器在断电时将丢失其存储内容,故主要用于存储短时间使用的程序。 按照存储信息的不同,随机存储器又分为静态随机存储器(Static RAM,SRAM)和动态随机存储器(Dynamic RAM,DRAM)。
静态随机存储器SRAM(Static RAM)不需要刷新电路即能保存它内部存储的数据。除此以外,还有一种随机存储器SSRAM(Synchronous SRAM)即同步静态随机存取存储器。同步是指Memory工作需要同步时钟,内部的命令的发送与数据的传输都以它为基准;随机是指数据不是线性依次存储,而是由指定地址进行数据读写。对于SSRAM的所有访问都在时钟的上升/下降沿启动。地址、数据输入和其它控制信号均于时钟信号相关。这一点与异步SRAM不同,异步SRAM的访问独立于时钟,数据输入和输出都由地址的变化控制。
动态随机存储器DRAM(Dynamic RAM)则每隔一段时间,要刷新充电一次,否则内部的数据即会消失。综上所述,SRAM具有较高的性能,但是SRAM也有它的缺点,即它的集成度较低,相同容量的DRAM内存可以设计为较小的体积,但是SRAM却需要很大的体积,且功耗较大。所以在主板上SRAM存储器要占用一部分面积。SRAM的速率高、性能好,它常应用于CPU与主存之间的高速缓存以及CPU内部的L1/L2或外部的L2高速缓存。
1.2 DDR关键指标
随着CPU 发展,内存也发生了巨大的变革,DDR从诞生到现在已经经历了多代,分别是第一代SDR SDRAM(Single Data Rate SDRAM,同步型动态存储器),第二代的DDR SDRAM,第三代的DDR2 SDRAM,第四代的DDR3 SDRAM,现在已经发展到DDR5 SDRAM。为了实现容量增加和传输效能增加,规范的工作电压越来越低,DDR容量越来越大,IO的速度越来越高。
- Voltage(VDDQ):存储芯片(颗粒)的输出缓冲供电电压。
- Device Width:颗粒位宽,常见为4/8/16bit。一个Memory Array中由行地址和列地址的交叉选中一个位,若2个Array叠加在一起,就同时选中了2个Bit,位宽是X2。若4个Array叠加到一起,就能够同时选中4个Bit,位宽则是X4。也就是说,对一个X4位宽的DDR 颗粒,如果给出行地址和列地址,就会同时输出4个Bit到DQ(数据输入、输出:双向数据总线)数据线上。
- Die Density:颗粒密度,也就是容量,随着DDR迭代,容量越来越大。
- Data rates:MT/s指每秒传输多少个数据(Mega-transfer per second),和时钟频率是两个不同的概念。DDR(dual data rate)是双边沿传输数据。因此MT/s是IO时钟频率的两倍。
- Prefetch:在一个时钟周期中,同时将相邻列地址的数据一起取出来,并行取出DRAM数据,再由列地址0/1/2(DDR1使用列0,DDR2使用列0和列1,DDR3/DDR4使用列0,1和2)选择输出。2n/4n/8n。这里的数字指的就是并行取出的位数。这里的n,就是DQ位宽,即上面的device width(x4/x8/x16)。所以DDR3 16bit SDRAM内存颗粒,16bit指的是位宽,其一次读写访问的数据量是8*16=128bit
- Bank:DDR4以前是没有Bank Group的,所以该值就表示整个颗粒中Bank数量。但是在DDR4和DDR5中,就表示每个Bank Group中Bank的数量,整个颗粒Bank数量 = Bank Group * Bank。
- Bank Group:Bank分组数量,该特性只存在于DDR4和DDR5中。
- Burst Length:指突发长度,指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度,在DDR SDRAM中指连续传输的周期数。一般对应预取bit数目。
- Core frequency:颗粒核心频率,即内存cell阵列的工作频率,它读取数据到IO Buffer的频率。它是内存频率的基础,其他频率都是在该频率的基础上得出来的。
- IO clk Frequency:内存的数据传输速率。它和内存的prefetch有关。对于DDR,一个时钟周期的上升沿和下降沿都在传输数据,即一个时钟周期传输2bit的数据,所以DDR的prefetch为2bit。对于DDR2,IO时钟频率是其核心频率的两倍,同时也是双沿传输数据,因此DDR2的prefetch为2×2bit=4bit。对于DDR3,IO时钟频率是其核心频率的四倍,同时也是双沿传输数据,因此DDR3的prefetch为4×2bit=8bit。
DDR SDRAM是由威盛等公司提出的第二代SDRAM标准,主要它允许在时钟脉冲的上升沿和下降沿都能传输数据,这样不需要提高时钟频率就能实现双倍的SDRAM提速。DDR2 SDRAM是由电子设备工程联合委员会开的第三代SDRAM内存技术标准,相比上一代提供了更高运行效能(拥有两倍与上一代的预读取能力,4bit数据prefetch)和更低的电压(1.8v)。DDR3 SDRAM相比上一代,电压更低(1.5v),效能更高(支持8bit prefetch),只需133MHz就能实现1066MHz的总线频率。DDR4相比上一代,工作电压更低(1.2v),效能更高(16bit prefetch),同样的频率下,理论速度是上一代的两倍。
1.3 DDR分类
DDR: 英文全称Double Data Rate Synchronous Dynamic Random Access Memory。它主要应用在普通内存条;
LPDDR: 英文全称Low Power Double Data Rate Synchronous Dynamic Random Access Memory。它主要应用在智能手机,智能手表等对功耗,体积敏感得产品;
GDDR: 英文全称Graphics Double Data Rate Synchronous Dynamic Random Access Memory。主要用于高速图像处理的场合,比如计算机的显卡中,可以简单理解为专门为显卡而做的DDR内存,这种内存与普通DDR相比,拥有更高的时钟频率和更小的发热量。
2. 框架
DDR SDRAM子系统包含DDR controller、DDR PHY和DRAM存储颗粒三部分。DDR IP一般包括DDR Controller和DDR PHY,内部涉及的内容包括但不限于以下几个方面:数据保序、仲裁、最优调度、协议状态机设计、防饿死机制、bypass通路、快速切频、DDR training。我们分别看一下各部分的组成,然后讲述一下数据的读写过程。
2.1 DDR controller
内存控制器负责初始化DRAM,并重排读写命令,以获得最大的DRAM带宽。它通过多端口与其他用户核进行连接,这些端口的类型包含AXI4/AXI3/AHB/CHI。每个端口有可配置的宽度、命令和数据FIFO。
内存控制器接收来自于一个或者多个CPU、DSP、GPU的请求,这些请求使用的地址是逻辑地址,由仲裁器来决定这些请求的优先级,并将其放入内存控制器中。如果一个请求处于高优先级(赢得仲裁),会被映射到一个DRAM的物理地址并被转换为一个DRAM命令序列。这些命令序列被放置在内存控制器中的队列池(Queue pool)中,内存控制器会执行队列池中这些被挂起的命令,并将逻辑地址转化为物理地址,并由状态机输出符合DRAM访问协议的电信号,经由PHY驱动DRAM的物理IO口。
- Arbitration CMD priority:仲裁器,仲裁CMD的优先级。会对来自各端口的请求进行仲裁,并将请求发送给控制器,仲裁其从端口收到的每个事务,每个事务都有一个相对应的优先级。端口仲裁逻辑会根据优先级进行处理,从而确定如何向控制器发出请求。以Cadence Denali内存控制器为例,它有几种仲裁策略:
- Round Robin:每个端口对应一个独立的计数器,当端口上有请求被接受的时候,计数器就会增加,然后仲裁器会针对计数器非0的端口的请求进行轮流仲裁,每仲裁执行一次,相应端口的计数器减一,直到端口接受请求计数器变为0。
- 带宽分配/优先级轮流操作:结合轮流操作、优先级、带宽和端口带宽保持等,根据用户分配的命令优先级,将传入的命令按优先级分组。在每个优先级组内,仲裁器评估请求的端口、命令队列和请求的优先级,从而确定优先级。当控制器繁忙时,超过其带宽分配的端口,可能会接受较低的优先级服务。
- 加权优先级循环:是一种面向服务质量的算法,结合了循环操作、优先级、相对优先级、端口排序的功能。根据命令的优先级或该类型命令的相关端口的优先级,将传入的命令分成优先级组。具有较高权重的端口可能会更频繁的接受仲裁,从而更容易被运行到。
- DDR SDRAM Control:DDR SDRAM的控制。包含了一个命令队列,接受来自仲裁器的命令。该命令队列使用一个重排算法来决定命令的放置顺序。重排逻辑遵循一些规则,通过考虑地址碰撞、源碰撞、数据碰撞、命令类型和优先级,来确定命令插入到命令队列的位置。重排逻辑还通过命令分组和bank分割,来提高控制器的效率。当命令进入命令队列后,选择逻辑扫描命令队列中的命令进行运行。若较高优先级的命令还没有准备好运行,较低优先级的命令不与命令队列中排在前面的命令冲突,那么这个较低优先级的命令,可以先于该没准备好的高优先级命令运行。此外,控制器还包含一个仲裁块,支持软件可编程接口、外部引脚及计数器的低功耗控制。另外,控制器支持调频功能,用户可以通过操作寄存器组,调整ddr的工作频率。
- Transaction Processing:事务处理用于处理命令队列中的命令。该逻辑会重排命令,使DRAM的读写带宽吞吐最大化。
2.2 DDR PHY
DDR PHY是连接DDR颗粒和DDR Controller的桥梁,它负责把DDR Controller发过来的数据转换成符合DDR协议的信号,并发送到DDR颗粒。相反地,它也负责把DRAM发送过来的数据转换成符合DFI(DDR PHY Interface)协议的信号并发送给内存控制器。DDR PHY和内存控制器统称为DDR IP,他们保证了SoC和DRAM之间的数据传输。
目前在DDR IP的市场上,国际厂商占据较高的市场份额,而国内IP企业占比很小,究其原因,主要是由于DDR PHY具有较高的技术门槛,要在这类PHY上实现突破并不容易。DDR PHY是一个系统工程,在如下方面需要着重关注:
- DDR PHY的数据传输采用并行多位、单端突发的传输模式,对电源完整性PI(Power Integrity,电源完整性)和信号完整性SI (Signal Integrity,信号完整性)的要求很高。
- 为了能够补偿不确定的延时,针对不同信号,DDR PHY有个灵活配置的延时电路及对应的辅助逻辑,这些延时电路可能会随着电压及温度变化而变化。因此PHY针对这些电路要有校准(Training),可以说DDR PHY是对Training要求最多的接口。
2.3 DDR DRAM颗粒
从DDR PHY到内存颗粒的层次关系如下:channel->DIMM->rank->chip->bank->row/column组成的memory array。例如,i7 CPU 支持两个Channel(双通道),每个Channel上可以插2个DIMM(dual inline memory module,双列直插式存储模块),每个DIMM由2个rank构成,8个chip组成一个rank。由于现在多数芯片的位宽是8bit,而CPU的位宽是64bit,因此经常是8个芯片可以组成一个rank。
- Channel:简单理解一个通道对应一个DDR控制器,每个通道拥有一组地址线、控制线和数据线。
- DIMM:是主板上的一个内存插槽,一个channel可以包含多个DIMM。
- Rank:内存条上一组可以被一个内存通道同时访问的芯片组合称作一个rank,一个rank中的每个芯片都共用内存通道提供的地址线、控制线和数据线,同时每个芯片都提供一组输出线,这些输出线组合起来就是内存条的输出线。简单来说rank是一组内存芯片集合,当芯片位宽*芯片数=64bit(内存总位宽)时,这些芯片组成一个Rank,存储64bit的数据。一般每个芯片位宽是8bit,然后内存条每面8个芯片,那么每面就构成了一个Rank,这两面的Rank通过一根地址线来区分当前要访问的是哪一面。同一个Rank中所有的芯片协作来读取一个地址(1个Rank,8个芯片*8bit=64bit),这个地址的不同bit,每8个一组分散在这个Rank上的不同芯片上。设计Rank的原因是为了减少每个芯片的位宽(在CPU总位宽确定的前提下,比如64bit),降低复杂度。
- Chip:是内存条上的一个芯片,由多个bank组成,大多数是4bit/8bit/16bit,多个chip做成一个rank,配合完成一次访问的位宽。
- Bank:是一个逻辑上的概念。一个bank可以分散到多个chip上,一个chip也可以包含多个bank。
- Row、Column组成的memory array:可以简单的理解bank为一个二维bit类型的数组。每个bank对应一个bit,8个bank组成8bit的数据。
3. DRAM剖析
3.1 基本结构
3.1.1 DRAM的基本单元
基本的DRAM单元(cell),是一个电容加一个CMOS晶体管组成的电路。通过给晶体管最上面的一端(称作栅极)加上电压或取消电压,就可以控制CMOS晶体管的开、关。一旦打开就可以读出电容上存储的电量,或者向电容写入电量。这样电容上的电荷有无就对应着存储1bit的1或0。
当要读取 cell 的存储值,首先打开电子开关(即晶体管),然后根据电容的充放电信息获得存储值。如果 cell 保存“ 1 ”,即电容存有电荷,那么当打开开关,电容就会放电;如果 cell 保存“ 0 ”,即电容不保存电荷,那么打开开关之后电容不会放电。
当要向 cell 中写入值,仍然先打开电子开关,然后在电子开关的另一侧施加电压。如果要写入“ 1 ”,则施加高电压,此时电流会通过晶体管向电容充电;如果要写“ 0 ”,则让电子开关另一端接地。施加电压一段时间后即可断开开关,此时 cell 已经保存好写入值,因为电容很小,所以施加电压的时间会很短。
为了存储更多的bit,可以用如上的DRAM单元组成存储阵列。可以看到每行 cell 的晶体管的栅极都是连在一起的,即都连在字线上,这意味着给字线施加电压,字线对应的一行cell都会被打开。当一行 cell 被打开,cell 电容就会向位线充放电,一行中的每个 cell 都与一条位线直接相连,读取位线的电压变化,即可知道 cell 的存储信息。由于电容很小,打开字线后产生的电压波动也很小,所以在读取的时候,要经过sense amplifier进行放大。
位线之所以叫位线,是因为在读取信息时,每一根线上的电压波动都代表一位比特信息,一根线代表一位,所以叫做位线;
字线之所以叫字线,是因为给这根线通电,一行 cell 都会被打开,在计算机里八位等于一个字节,多个字节等于一个字,因此多个 cell 组合起来就是多个字,因为这根线可以打开多个字,所以叫字线。
每个位线都接在一个放大器上,由于每个cell的电容太小了,在读某一bit前,先对bit line进行precharge。预充的电压为工作电压的一半。这样在打开字线后,位线上的轻微变化也能被放大器捕捉到,并在本地还原、暂存字线对应整行cell的电压。其实,当读了位线(电容放电)后,电容上的电荷就会发生了改变,这是一种破坏性读出。为了解决这个问题,就需要放大器在读取cell存储的数据后,利用暂存的cell电压写回字线单元行。
不过有一件事很重要:在 DRAM 芯片中,读出放大器把 cell 阵列分成了两半。为什么要把 cell 阵列分成两半?因为一个放大器需要同时接入两根位线。为什么一个放大器要接两根位线?因为放大器在捕捉、放大其中一条位线的电压波动时需要另一条位线的帮助。DRAM 芯片用到的放大器是“差分感测放大器”,它在放大信号波动时需要用一个基准和待测线作“比较”,此时接到放大器上的两条位线的其中一条就是所谓的基准,这条基准线经过预充电之后,其电压恒等于供电电压的一半。
到这里我们清楚了 cell 阵列的读取,但是还有一个问题:在读取单元行时,读取行的 cell 电容经过充/放电之后,原本的信息就丢失了,即原来有的电荷现在放掉了,而原来没有电荷的现在却有了电荷。这种会造成信息丢失的读取行为称为“破坏性读出”。
破坏性读出是不行的,因此在读取单元行之后我们还要恢复单元行的信息。如何恢复?在读取时,放大器还原并暂存了单元行每个 cell 的电容电压,因此可以在输出完毕之后再把这些暂存电压写回原单元行。具体做法是在读出数据之后,根据放大器锁存的值,把各条位线拉到供电电压或接到地,然后 cell 电容就会根据位线电压进行充电或放电,当 cell 电容充放电结束,就可以断开字线,断开字线也就宣告本次 DRAM 读取结束。
3.1.2 DRAM刷新
DRAM 叫做动态随机存储器,“动态”从何而来?前面说过,cell电容的电容值很小,存储电荷不多,无论是充电还是放电都很快,而先进 CMOS 工艺有“电流泄漏”问题,因此即使不打开字线,cell 电容也会缓慢损失电荷,久而久之信息就丢失了。解决这个问题的办法是“刷新”电容,即根据电容的旧值重新向 cell 写入数据。因为要经常动态地刷新电容,所以 DRAM 叫做动态随机存储器。
刷新电容如何实现的?在谈论“破坏性读出”时说过放大器可以还原并暂存 cell 信息,并把暂存的信息写回到 cell 电容,因此刷新电容也可以借助放大器。具体做法是对于每个单元行,每过一段时间就自主地进行读取,等放大器暂存好信息后就立刻写回。关于单元行的刷新时机也很有讲究,一般每 64ms 内就要对 cell 阵列进行一次全面刷新。
3.1.3 Prefetch原理
那么DRAM是怎么实现用比较低的核心传输频率来满足日益高涨的高速IO传输速率的需求呢?这就是靠prefetch来实现的。
从DDR开始到DDR3很好理解,Prefetch相当于DRAM core同时修了多条高速公路连到外面的IO口,来解决IO速率比内部核心速率快的问题,IO数据速率跟核心频率的倍数关系就是prefetch。
burst length的长度跟CPU的cache line大小有关。Burst length的长度有可能大于或者等于prefetch。但是如果prefetch的长度大于burst length的长度,就有可能造成数据浪费,因为CPU一次用不了那么多。所以从DDR3到DDR4,如果在保持DDR4内存data lane还是64的前提下,继续采用增加prefetch的方式来提高IO速率的话,一次prefetch取到的数据就会大于一个cache line的大小 (512bits),对于目前的CPU系统,反而会带来性能问题。
DDR4出现了Bank Group,这就是DDR4在不改变prefetch的情况下,能继续提升IO速率的秘密武器。DDR4利用Bank group的interleave,实现IO速率在DDR3基础上进一步提升。
Prefetch字面意思就是预存取,每一代的DDR预存取大小不同,详见上面的表格。以DDR3为例,它的Prefetch=8n,相当于DDR的每一个IO都有一个宽度为8的buffer,从IO进来8个数据后,在第8个数据进来后,才把这8个数据一次性的写入DDR内部的存储单元。下图是一个形象的解释,同时我们关注一下几个速率。DDR3的时钟是800MHz,Data Rate是1600Mbps,由于这个Buffer的存在,DDR内部的时钟只需要200MHz就可以了(注意DDR内部不是双比特翻转采样)。
DDR内部的最小存储单元(1bit)是一个晶体管+一个电容,电容会放电,需要不断的“刷新”(充电)才能保持正常的工作状态,由于电容充放电需要时间,DDR内部的频率受限于此,很难提高,目前技术一般在100~200MHz。因此需要用Prefetch技术来提内部数据高吞吐率(其实就是串并转换原理)。Prefetch位宽的提高,是DDR2,3,4非常显著的变化。
前段提到,对于DDR3,在第8个数据进来后,FIFO满了,然后才把这8个数据一次性的写入DDR内部的存储单元,那么必须要求DDR的内部时钟和外部时钟有一定的约束关系,FIFO满的时候一定是以DQS下降沿采样结束的,数据手册中对DQS的下降沿与clk有一个建立时间和保持时间的约束要求的目的原来是这样。
3.2 DRAM的读写
为实现单个比特的读写,必须为 cell 阵列配备一系列周围逻辑电路。图五是一个简单的示范。读者可能看不清里面的文字,cell 阵列左边的绿色标记模块是“字线译码模块”,下面的蓝色模块是“读出放大器”,读出放大器下面的模块是“多到一选择器”和“一到多分配器”的集合,最左边的蓝色模块依次是“行地址缓存”、“列地址缓存”。
行、列地址不共用地址线
为找到二维阵列中的某一个单元,必须给出该单元的行号/行地址和列号/列地址,行地址缓存保存从地址总线上获取的行号,列地址缓存保存从地址总线上获取的列号。
其中,行地址会送往“字线译码模块”。字线译码模块是一个译码器,可以把短行号译码成长的独热码,独热码会开启一条字线,并打开该字线对应的单元行。单元行开启后,放大器捕捉位线上的电压波动,从而还原、暂存数据到放大器本地。
之后放大器把暂存的数据送到选择器,同时列地址也会被送到选择器,选择器根据列地址把数据中的某一位送到输出线。输出数据之后,还要把单元行数据写回。
在上图的示范中,行地址和列地址是分别用两组总线送到 DRAM 芯片上的,这意味着 DRAM 芯片要为行地址和列地址准备两组输入口/ pin 口。而 cell 阵列越大,地址的位数就越多,当 cell 阵列很大时,准备两组输入口的代价十分昂贵,因此现代 DRAM 芯片让行地址和列地址共用一组总线,其效果图如图六。
下图行、列地址线各有三条,但它们对外只需要三个 pin 口,外界到 DRAM 芯片的三根地址线直接接到“时序控制模块”上,这个模块会选择性地把地址送到行地址缓存或是列地址缓存。在实际操作时,可以先给 DRAM 芯片输入行地址,然后再输入列地址。
行、列地址共用地址线
想要把地址选择性地送到地址缓存中,还需要一些控制线,即 RAS(row address select)和 CAS(colum address select),这两个控制信号指出当前地址线上的地址是行地址还是列地址。另外,为了向 DRAM 芯片写入数据,还需要“写使能”信号( WE,write enable )。把控制线和写使能加入芯片。
3.2.1 DRAM读过程
在读取DRAM芯片上单个比特数据时:
- 读取前,先给各条位线预充电(也称为precharge),即把位线电压拉高到供电电压的一半。拉高到一半的目的是和cell电容电压形成电压差,从而在打开单元行时,可利用电容的微弱充放电产生电压波动。预充电完成后,就可以断开位线与预充电电源的连接,此时位线处于悬空态,电压会保持为供电电压的一半。
- 开始读取,首先在地址总线上输入行地址,稍后立刻置“行地址选通”(即RAS)有效,置RAS有效后,DRAM芯片就把行地址缓存下来。
- 缓存好行地址之后,就把行地址送入译码模块,译码模块把行地址译码成独热码,独热码的每一位都接到对应的字线,然后把其中一条字线的电压值拉高。
- 把地址线上的地址从行地址转换成列地址,转换成列地址之后,外界会置“列地址选通”有效,然后DRAM会把列地址缓存起来。
- 拉高的字线所对应的单元行被打开,即单元行的所有晶体管导通,单元行的各个cell电容和位线连通。如果cell保存比特信息1,即cell电容的电压等于供电电压,此时cell电容电压高于位线电压,电容放电,位线的电压稍稍上升。如果cell保存比特信息0,即cell电容的电压等于地电压,即0电压,此时位线电压高于cell电容电压,位线向cell电容充电,位线电压稍稍下降。
- 放大器捕捉位线上的微弱电压波动,通过“差分感测”在本地生成并暂存cell电容电压。如果cell电容等于供电电压,那么位线电压稍稍上升,放大器比较此位线和另一条基准线的电压,通过模拟电路的反馈来放大两者的电压差,最终在本地生成一个等于供电电压的输出电压,并用锁存器把输出电压锁存下来。如果cell电容电压等于0,放大器最终生成等于0的输出电压,并用锁存器把0电压锁存下来。
- 放大器锁存好行数据之后,把行数据送往多到一选择器。
- 列地址缓存就把列地址送到多到一选择器,多到一选择器根据列地址,把单元行中的某一位送到输出线。
- 输出之后,还需要把放大器的数据写回到单元行,即根据放大器的锁存值,把位线拉高到供电电压或是0电压,位线向cell电容充放电,充放电结束之后,就可以关闭字线。
- 写回数据并关闭字线之后,连接位线和预充电电源,给位线预充电到供电电压的一半,为下一次读写做好准备。
请注意,上面的过程没有提到列地址哪里来的,实际上,在行地址被缓存下来之后,外界会把地址线上的地址从行地址转换成列地址,转换成列地址之后外界会置“列地址选通”有效,然后 DRAM 会把列地址缓存起来,等到第 7步放大器送数据过来时,列地址缓存就把列地址送到多到一选择器,参与输出比特的选择。更清楚地说,列地址的缓存发生在第 2 步之后、第8 步之前。
3.2.2 DRAM写过程
写过程和读过程比较类似,就不详细描述,主要描述有差异的地方:
- 位线预充电到供电电压的一半。
- 输入、缓存行地址,译码行地址,开通单元行,开通单元行后位线产生电压波动,放大器捕捉电压波动并还原、暂存行数据到本地。
- 输入、缓存列地址,与此同时置写使能有效,并在Data Buffer存进写入比特,注意,Data Buffer在读取DRAM时用来暂存输出比特,而在写DRAM时则用来暂存写入比特。
- 把写入比特送到一到多分配器,分配器根据列地址把写入比特送到对应的放大器中,放大器根据写入比特改写本地暂存值。
- 放大器根据暂存的电压值刷新单元行,刷新完毕后断开单元行的字线。
- 刷新完毕后,重新给位线预充电,为下一次读写做好准备。
你可能会疑问,要访问的一个字节的其他7bit是不是也存在这些单元行里,答案是否定的。其实,还存在7个这样的bit存储阵列,其中相同的行列地址在这7个bit存储阵列相同位置取出相应的bit,这样便得到了完整的8bit(一个字节)数据。
另外,在读写过程中,时间主要消耗在“开启单元行”与“放大电压波动并暂存数据”。单元行的栅极可以抽象成一个个电容的并联,因此字线的拉高就是给这么多电容充电的一个过程,这将是很耗时及耗电的。由于放大器大部分是模拟电路,所以他的工作也不快。那么怎么提高DRAM的读写速度呢?关键点在放大器的缓存区(row buffer),它缓存了单元行,但是一般我们只取出了其中的一个bit。如果要想提升速写速度,那就还访问这个单元行的其他bit,这时会直接从row buffer中取出相应的数据,不需要经历开启单元行、放大、读写数、写回的耗时过程。
3.2.3 时间消耗和行缓存
总的来说,读取一个比特的总体流程是:获得行号,译码行号,开启单元行,放大位线电压波动并暂存数据到放大器,获得列号并根据列号选择一位进行输出,写回数据,关闭字线,重新预充电。
而写一个比特的总体流程是:获得行号,译码行号,开启单元行,放大位线电压波动并暂存数据到放大器,获得列号并输入写入数据,根据列号把写入数据送到放大器并改写暂存值,写回数据,关闭字线,重新预充电。
在以上两个流程中,时间花费的大头是“开启单元行”、“放大电压波动并暂存数据”。
“开启单元行”之所以花费时间,是因为行地址译码器需要拉高一条字线,然后用这一条字线拉高单元行上所有晶体管的栅极电压,而拉高这么多的栅极电压很耗时间。为什么?现在我们可以把这些栅极抽象成一个一个电容,如果大家学过电路分析,就知道拉高电容的电压是需要时间的,电容越大,所需时间越长,而单元行上的栅极可以整体抽象成一个很大的电容, DRAM 读写就是用一根字线给这个很大的电容充电,因此时间消耗很大。如果cell阵列设计的不合理,即单元行上的 cell 数量太多,那么“开启单元行”会变得很昂贵。
放大器放大电压波动并暂存数据也很耗时间,因为放大器大是模拟电路,工作速度不快。
3.3 DRAM系统层次
DRAM的系统层次如下:channel->DIMM->rank->chip->bank->row/column组成的memory array->存储cell。
3.3.1 Channel
Channel: 通道,芯片支持多少个DDRC(DDR控制器)就支持多少个通道。一个主板上可能有多个插槽,用来插多根内存。这些槽位分成两组或多组,组内共享物理信号线。这样的一组数据信号线、对应几个槽位(内存条)称为一个channel(通道)。简单理解就是DDRC(DDR控制器),一个通道对应一个DDRC。CPU外核或北桥有两个内存控制器,每个控制器控制一个内存通道。内存带宽增加一倍。最常见的是双通道,民用级电脑都支持,服务器还有3通道和4通道等。
内存通道实际是对应一组时钟、命令、地址、数据线,这组总线也就是所谓的内存通道。一般一个内存通道,可以连接若干个DIMM。
3.3.1.1 DDR3/DDR4内存通道
DDR3/DDR4的内存通道,如下图所示:
DDR3/DDR4内存通道关键点,一个通道上可以插多根DIMM,每个DIMM只会占用一个通道。以双通道为例,就是在北桥(又称之为MCH)芯片级里设计两个内存控制器,这两个内存控制器可相互独立工作,每个控制器控制一个内存通道。
在这两个内存通过CPU可分别寻址、读取数据,从而使内存的带宽增加一倍,数据存取速度也相应增加一倍(理论上)。流行的双通道内存构架是由两个64bitDDR内存控制器构筑而成的,其带宽可达128bit。因为双通道体系的两个内存控制器是独立的、具备互补性的智能内存控制器,因此二者能实现彼此间零等待时间,同时运作。两个内存控制器的这种互补“天性”可让有效等待时间缩减50%,从而使内存的带宽翻倍。双通道是一种主板芯片组(Athlon64集成于CPU中)所采用新技术,与内存本身无关,任何DDR内存都可工作在支持双通道技术的主板上,所以不存在所谓“内存支持双通道”的说法。
3.3.1.2 DDR5内存通道
DDR5不会为每个DIMM提供一个64位数据通道,而是为每个DIMM提供两个独立的32位数据通道(考虑ECC时为40位)。也就是说一个DIMM会占用2个通道。
一个DDR5内存控制器,对应2个通道,每个通道32bit。这2个通道一起向一根DIMM累计提供64bit的数据访问。
与传统的DDR4双通道相比,一个DDR5内存控制器对应2个通道,2个DDR5内存控制器就对应4个通道。说的更直白就是,DDR4需要至少2根DIMM才能组成双通道;DDR5一根DIMM就可以组成双通道,2根DIMM就可以组成4通道。
3.3.1.3 通道支持多少内存条
关于这个问题,我之前也有个误区,想当然的认为每个通道,应该可以插无数根内存条,就看硬件厂商做了多少个插槽,而实际这是错误的。一个内存控制器无论是有一个或两个内存通道,但是一般单个通道支持的RANK数量是有限的。
比如,一个通道最大支持8个RANK,那么可能有以下几种情况:
- 若单条DIMM有1个RANK,那么这样的DIMM,可以插8根;
- 若单条DIMM有2个RANK,那么这样的DIMM,可以插4根;
- 若单条DIMM有4个RANK,那么这样的DIMM,可以插2根;
所以理论上,一个通道到底可以插多少根内存条,与通道支持的最大RANK数,以及DIMM的RANK数有关。
3.3.2 DIMM(dual inline memory module)
DIMM(Dual Inline Memory Module)双列直插内存模块,也就是我们常说的内存条。
3.3.2.1 DIMM历史
最初在80286时代,内存颗粒(Chip)是直接插在主板上的,叫做DIP(Dual In-line Package)。
到了80386时代,换成1片焊有内存颗粒的电路板,叫做SIMM(Single-Inline Memory Module)。由阵脚形态变化成电路板带来了很多好处:模块化,安装便利等等,由此DIY市场才有可能产生。当时SIMM的位宽是32bit,即一个周期读取4个字节。
到了奔腾时,位宽变为64bit,即8个字节,于是SIMM就顺势变为DIMM(Double-Inline Memory Module)。这种形态一直延续至今,也是内存条的基本形态。
DIMM说白了就是将若干个内存颗粒,单独焊接在一块独立的电路板上,方便模块化和安装。
3.3.2.2 DIMM分类
当然现在DIMM因其用途不同,又分为很多种,比如:
- RDIMM: 全称(Registered DIMM),寄存型模组,主要用在服务器上,为了增加内存的容量和稳定性分有ECC和无ECC两种,但市场上几乎都是ECC的。
- UDIMM:全称(Unbuffered DIMM),无缓冲型模组,这是我们平时所用到的标准台式电脑DIMM,分有ECC和无ECC两种,一般是无ECC的。
- SO-DIMM:全称(Small Outline DIMM),小外型DIMM,笔记本电脑中所使用的DIMM,分ECC和无ECC两种。
- Mini-DIMM:DDR2时代新出现的模组类型,它是Registered DIMM的缩小版本,用于刀片式服务器等对体积要求苛刻的高端领域。
3.3.2.3 DIMM容量计算
我们知道一根DIMM上可能有多个RANK,所以我们用通道位宽/内存芯片位宽,就可以得到一个RANK需要几颗芯片组成,然后使用芯片容量*芯片数,就可以得到一个RANK的容量大小,再结合RANK数量,可以计算出整根DIMM的容量。
DDR3或DDR4的DIMM计算公式,如下:
DIMM容量(DDR3/DDR4) = (芯片容量 * 64 / 内存芯片位宽) * RANK数
由于DDR3、DDR4,只能一根DIMM对应1个通道,所以上述公式,实际是默认计算的一个通道容量,而DDR5的DIMM对应2个通道。DDR5的DIMM计算公式,如下:
DIMM容量(DDR5) = (芯片容量 * 32 / 内存芯片位宽) * 每通道RANK数 * 2
3.3.3 Rank
3.3.4 Rank和DIMM
电脑用的内存芯片都嵌在一个电路板上,把这个电路板插入内存插槽后,就可增加电脑内存。电路板和板上的芯片,就是所谓的内存条,也称为DIMM条,全称 Dual-Inline-Memory-Modules ,中文名叫双列直插式存储模块)。内存条通过“内存通道”连接到内存控制器,一组可以被一个内存通道同时访问的芯片称作一个rank。一个rank中的每个芯片,都共用内存通道提供的地址线、控制线和数据线,同时每个芯片都提供一组输出线,这些输出线组合起来就是内存条的输出线。
下图是一个包含 8 颗芯片的 DIMM 条。这 8 颗芯片被一个内存通道同时访问,所以它们合称为一个 rank 。有的 DIMM 条有两面,即两面都有内存芯片,这种 DIMM 条拥有两个 rank 。
若每个芯片都包含8个bank,每个rank都包含8个芯片,那么这条内存条就可以一次读写8×8=64比特,其中第一个8是指每个芯片输出8位,第二个8是指这个rank总共有8颗芯片,因为这8颗芯片被同一个内存通道访问,所以其被访问的bank和bank内的行地址、列地址都是完全一致的。下图是一个描述这个过程的简图:显然,我们在读写8颗芯片同一个bank同一个位置的cell。注意,图中没有显示不在工作状态的bank。对一个rank读写,即同时读写rank内8个存储芯片内的同一位置的bank。
rank读写
电脑有时候可以插入多个内存条,多个内存条有助于提升电脑的内存容量,但是未必能提高电脑的速度。电脑的速度受“内存通道”数限制,如果电脑有四个插槽,却只有一个内存通道,那么CPU仍然只能一次访问一个rank。但如果电脑有四个插槽的同时还有四个内存通道,那么CPU就可以一次访问四个rank,很显然,四并行访问明显比串行访问快,假设每个rank可以输出64比特,那么四通道就可以一次访问4×64=256比特,而单通道只能访问64比特。
3.3.5 存储芯片/chip
一个 8 阵列 bank 一次读写 8 个比特,一颗存储芯片上一般含有多个 bank,下图是一颗含有 8 个 bank 的存储芯片的示意图。芯片每次读写都只针对一个 bank ,因此读写地址必须包含一个 bank 号,bank 号用于开启目标 bank,目标 bank 之外的 bank 是不工作的。
包含8个8bank阵列的存储芯片
3.3.6 bank
一个 cell 阵列一次可以提供一个比特,那么多个cell阵列就可以一次提供多个比特。假如CPU一次读写8个比特,那么我们就可以用 8 个 cell 阵列。查找cell阵列中的一个单元需要有其行号和列号,那CPU是否需要给8个cell阵列提供 8 组地址呢?不需要,8 个 cell 阵列可以共享一组行地址和列地址。共享行、列地址的一组 cell 阵列被称作一个 bank,下图展示了一个含有 8 个 cell 阵列的 bank 。它们共用行地址、列地址和地址选通、写使能,每个阵列提供一条输出线,8 个阵列最终组成 8 根输出线,可以输出 8 个比特。
8阵列bank
3.4 DRAM访问加速
3.4.1 burst模式
由于现在的处理器,CPU与DDR之间基本上都有cache,CPU在访问内存单个字的时候,不仅需要访问这个字,还需要把这个字所在的缓存行全部搬进cache中,因此内存不仅要一次提供一个字,还要提供一个缓存行(cache line)。缓存行一般比较大,比如8个64比特,因此内存要一次提供8×64=512比特数据。但如果前面介绍的方式访问内存,那么一次只能提取出64比特,即提取一个字,这并不满足缓存行的要求。为此,我们提出对内存使用“burst模式”。
由于缓存行内的各个字在内存上是紧邻的,我们就可以灵活地使用cell阵列中的行缓存(row buffer)。前面说到单元行进入放大器的行缓存之后,并不会在读写一个比特后立刻写回cell阵列,而是待在行缓存里等待下一个读写命令。如果下一个读写命令仍然发生在该单元行,那就可以行命中,直接操作row buffer。
在burst模式里,每当我们读取cell阵列中的一个比特,不仅把这个比特送到输出缓存中,而且紧接着把这个比特所在缓存行的各个比特都送到输出缓存,这样就完成了一次burst,即把目标比特周围的多个比特连续地读出。
3.4.2 bank并行和内存交错
前面我们比较详细地聊了在一个cell阵列中读取数据的过程,而CPU在访问内存时,还需要一些别的操作。总的来说,CPU访存大概要经过5个步骤:
- CPU发送指令给内存控制器。
- 内存控制器解析指令,并把“解析到的控制信息”发送到控制总线。
- bank接收控制信息,并读取数据。
- 内存芯片把读取出的数据放到数据总线。
- 内存控制器收取数据,并将其交给CPU。
如果CPU连续访问同一bank,那么CPU、内存控制器、总线和bank就必须串行操作,串行操作会让访存效率下降。我们假设CPU不可以在一个bank工作时,再给它发送新的指令。如果CPU连续不断地给一个bank发送指令,那么很可能前一个指令还没完成,后一个指令就改变了bank内的row buffer、列地址缓存或输出缓冲。
为了说明cpu访存过程中带来的时间消耗和造成的效率下降,下面以“总线延迟”为例:
光速是3×10^8m/s,而高性能CPU的频率可达3GHz,即3×10^9Hz。那么在CPU的一个时钟周期内,光可以运动10cm。但是电在硅中的传播距离大约是光的五分之一,经过测量,在电子线路中 电在一个CPU时钟周期内只能运动20mm左右。而CPU和内存芯片之间的距离远不止20mm,因此数据在总线上移动需要花费多个CPU时钟周期。
上面的计算说明,在CPU访存的5个步骤中,第2、第4步是要花很多时间的,而没有详细讨论的第1、第5步,大概率比这两步还要慢。因此让CPU、内存控制器、总线和bank串行操作是不明智的。实际上,我们完全可以在一个bank进行第3步时,让CPU、内存控制器、总线去操作新的bank,以此隐藏起它们的工作时间,从而营造起一种CPU、内存控制器和总线不需要消耗时间的假象。上面这种做法实现了“bank间并行”。
所谓在“bank间并行”就是让一个chip内的不同bank并行工作,让它们各干各的。为此CPU要连续、依次向不同的bank发送读取指令,这样在同一时间很多bank都在工作,第一个bank可能在输出,第二个bank可能在放大电压,第三个bank可能在开启单元行。当第一个bank burst输出完毕,第二个bank刚好可以输出。当第二个bank burst输出完毕,第三个bank刚好可以输出.......通过这样让“bank读取”和“CPU、内存控制器、总线工作”在时间上相互重叠的方式,我们可以成功地把CPU、内存控制器和总线的工作时间隐藏起来,从而打造出一种CPU无延迟访问内存、多个bank连续、依次“泵”出数据的理想情况。这种通过“bank间并行”实现“连续泵出数据”的方法,就是所谓的“内存交错”。
内存交错不仅隐藏了CPU、内存控制器和总线的工作时间,还隐藏了对单个bank而言row缺失所造成的多余访问时间(所谓“多余”是相对“row 命中”情况而言的),连续两次对同一个bank的访问,它们访问的row相同或者不同,对延迟的影响是相当显著的。
如果第二个命令是对同一个row访问,那么memory controller只需要发出Rd/Wr读写命令即可,称为行命中。如果第二个命令是对不同的row进行访问,那么memory controller需要发出PRE,ACT,Rd/Wr命令序列,称为行缺失。从命令序列的对比来看,可以看出行缺失的情形对性能的影响是糟糕的。下图显示了连续的行缺失的情形下的访存序列:
行缺失的访问序列
然而,如果我们有多个bank,然后将 A0,A1,A2...的访存序列,通过memory controller的address interleaving, 映射到多个bank上,也就是所谓banking。避免了连续访问同一个bank的不同row,造成的大量行缺失,就能够得到下面的访存序列:
流水线化的访问序列
显然,上图中的类似流水化的访问能够很大程度上掩盖访问DRAM的访存延迟,这也就是banking能够提高memory throughput的原因。
另外,memory controller的address interleaving是什么呢?
我们都知道在OS层面,有着从virtual address到physical address的地址映射。类似地,在memory controller层面,我们需要将physical address映射为对DRAM chip中具体的位置的访问,通过将bank映射到物理地址的相对低位(相对于row),可以使得对连续地址的访存请求被映射到不同的bank。
物理地址的bank映射