——————————————————————————————
版权声明:
本文作者:烓围玮未。主要从事ISP/MIPI/SOC/车规芯片设计/SOC架构设计
知乎专栏:芯片设计进阶之路
微信公众号:芯片设计进阶之路(x_chip)
转发必须授权,同时保留这段声明,盗版必究!
——————————————————————————————
在当今这个数据驱动的时代,内存子系统的性能对各种计算系统的表现起着至关重要的作用。DDR(Double Data Rate)内存技术自2000年推出以来,经历了多次迭代和优化,已成为高性能计算、数据中心、消费电子和嵌入式系统中不可或缺的一部分。
在这篇文章中,我将从基本结构来探讨DDR的基本操作,从而深入理解这一复杂而重要的技术。让我们回到最初,从DDR的基本组成结构开始。
DDR的基本操作
DDR内存的基本单元是存储单元(Memory Cell),这些存储单元组成了存储阵列,构成了DDR内存的核心部分。
每个存储单元由一个电容和一个MOSFET晶体管组成,这种结构被称为1T1C(1 Transistor 1 Capacitor)。电容用于存储电荷,表示数据的“0”或“1”状态;晶体管用于控制电容的充电和放电。
电容是我们非常熟悉的电子元器件了,这里主要利用了电容存储电荷的功能。在DRAM中,电容的充电状态表示逻辑“1”,放电状态表示逻辑“0”。
这和SRAM是有本质区别的,SRAM的组成是基于锁存器的,结构复杂,而DDR是基于一个简单的电容。这也是DDR能做到低成本和高容量的原因。
当然,使用电容就必须克服它的缺点,主要是电容的漏电特性:
由于介质材料不可能完全绝缘,随着时间的变化,电容里面的电荷会慢慢移动,导致电容的电压会慢慢下降。比如如果电容原来电压值是1.0v,慢慢下降到了低于0.5v,那么数字电路里面就会判断这个电容存储的值由1变成了0.
所以,我们必须在电容值降到0.5v之前,对它重新进行一次充电,这就是DDR的刷新操作的由来,是由底层结构决定的。这也是DRAM被称为“动态”随机存储器的由来。
MOSFET晶体管主要作为一个开关,用于控制电容的充电和放电。
通过在栅极(Gate)上施加一个小电压,来打开开关。当源极(Source)电压高,漏极的电容电压低的时候,就开始对电容充电。
同样,通过在栅极(Gate)上施加一个小电压,来打开开关。当源极(Source)电压低,漏极的电容电压高的时候,就开始对电容放电。
原理其实非常简单,结构也简单。到这里我们解决了存储0和1的问题。同时,问题也来了,如果我们要读一下里面的数据是0还是1,该怎么办呢?0还好,如果是1,那么只能通过电容放电,我们才能知道电容存储的是1,这样电容的值就改变了。所以这种结构是一种自毁型的,只能读一次,这显然不能满足我们对存储的要求。
由于每个memory cell只表示一个bit,显然我们不会一个bit一个bit的去处理,而是把多个cell组成一个非常大的阵列来处理,可能达到65536行x8192列这么大的Memory Array,其中一部分如下所示:
把一行的栅极连接到一根线上,称为Wordline(字线),把一列的源极连接到一起,称为Bitline(位线)。通过这些Wordline和Bitline就把整个Memory Array连接起来。
注意:这里的Wordline和Bitline是交叉的,但是不接触的,他们说在两个层级,之间是不导通的。3D图如下所示:
这样我们通过Wordline和Bitline就可以选择到任何的一个memory cell。
这就是DDR的地址了。Wordline对应的就是行地址,Bitline对应的就是列地址。
那么我们怎么组织行地址和列地址呢?直接连接到Wordline和Bitline可以吗?
对于行地址来说,可以直接连接到Wordline,但是对于列来说,如果直接连接到Bitline就会有问题。比如读一个cell的数据,先发送行地址,把某一行通电,称为激活某一行,这时候,对应的这一行所有的电容都被导通了,这一行都直接输出了,没法选择某个特定列:
(图中黄色电容表示1,其他表示0.)
同时,别忘了,我们还有一个问题没解决,读数据会导致电容被充放电,不能保持原值。为了一次解决这两个问题,引入了感知放大器(Sense Amplifiers).
Sense Amplifier(感应放大器)是DDR关键组件之一,用于检测和放大存储单元中的数据。由于存储单元中的电荷非常微小,Sense Amplifiers需要放大这些微弱的电压信号,使其达到可以被电路识别为逻辑高(1)或逻辑低(0)的水平。Sense Amplifiers通常以差分放大器的形式工作,它们检测位线(Bitline)和位线-bar(Bitline-bar,互补位线)之间的电压差异,并将这个差异放大。
DDR的读操作
铺垫了这么多,下面我们看看基本的读操作。
假设电容用1v来表示“1”,用0V来表示“0”,那么读操作具体如下:
1)第一步,将Bitline预充电到工作电压,也就是1V的一半,0.5V;
预充电(Precharge)操作:在读取或写入操作之前,需要将位线(bitlines)预充电到一个已知的电压水平,以便进行数据的读取或写入。
预充电其实就是把Bitline连接到0.5v的电压上,做这个操作的原因是因为Sense Amplifier需要感应电容是放电还是充电。如果电容是1V(bit 1),那么电压会高于Bitline,这样电容会放电,导致对应的Bitline上的电压高于0.5v,这个电容增加会被Sense Amplifier感应,然后放大成为1。同样,电容是0V就反之。
2)第二步,发送行地址,选中某一行,也就是把某一行的Wordline导通。
Wordline选通后,电容和Bitline的开关就被打开了,就出现了电容的充放电。
每条Bitline的末端有一个Sense Amplifier,Sense Amplifier感应到Bitline的电压大于或者小于0.5v,进行判断,然后放大输出1或者0.
可以看到,一个Memory 阵列有一行的Sense Amplifier。Sense Amplifier具体原理这里就不展开了。作用就是感应放大,同时Sense Amplifier里面有一个锁存器,可以把数值1或者0锁存到Sense Amplifier里面,而不会丢失。
到这一步,我们就读出了这一行存储的数值,并且把数字锁存在了Sense Amplifier里面,不会丢失.
3)第三步,我们需要上某个电容器的值,而不是一行的值,这样我们需要通过一个列地址来选择某个列的数值。这就是我们很熟悉的多路选择器MUX。
到这里,我们就读出来了我们需要的某个Memory cell的值。但是这里还没结束,因为读的过程中,破坏了电容原有的值,导致我们只能读到一次正确的值,这显然不是我们想要的。我们需要把这一行的值恢复回去。
怎么恢复呢?刚才说过了Sense Amplifier里面的值是锁存的,不会丢失,而且和这一行的值是一致的。我们只需要把Sense Amplifier里面锁存的值写回到这一行对应的Memory Cell里面就能恢复这一行的值了。
4)第四步,把Sense Amplifier里面的锁存数据,写回到对应行的电容器里面。
到这一步,我们就达成了不破坏Memory Cell同时读取Memory Cell的数值的全部操作。
现实中,我们都是以8个bit为最小单位来使用DDR的,这个解决方法就更简单了。我们只要把8个Memory Cell作为一个整体,每次读的时候都按照8bit来整体读就行了。只是这里的MUX设计稍微修改一下就行了。
总结一下,读操作如下:
步骤 | 描述 |
1 | 预充电位线(Pre-charge the bitlines) |
2 | 激活字线(Activate a wordline),允许电容器充电或放电,改变位线的电压;传感器放大器(Sense Amplifier)检测这些电压变化,并在其锁存器中记录值 |
3 | 多路复用器将地址值路由到输出数据线 |
4 | 把传感器放大器值写回到对应行的电容,确保电容值没有被改变 |
DDR的写操作
由于写操作是向某个Memory cell写入0或者1,所以在操作上与读有些不同。
1)第一步,将Bitline预充电到工作电压,也就是1V的一半,0.5V;
2)第二步,发送行地址,选中某一行,也就是把某一行的Wordline导通。
前两个步骤和读操作是一样的。
3)第三步,我们需要把输入送给某个Memory cell,所以需要执行和MUX相反的操作,也就是需要一个DeMux。通过列地址选择这一行的某一列,然后把输入送给对应的Memory cell。DeMux如下图所示:
通过改造MUX变成MUX--DEMUX,既有MUX功能,又有DEMUX功能,在读写进行切换。通过列地址进行选择,然后修改对应的Sense Amplifier里面的锁存数据,比如需要修改第二个Memory cell的值为1,那么就先修改Sense Amplifier里面的锁存值为1.
4)第四步,把Sense Amplifier里面的锁存数据,写回到对应行的电容器里面。
同读操作一样,仍然需要把数据写回,因为打开了Wordline就开始电容充放电了,破坏了电容的数值。只有Sense Amplifier里面的锁存数据才是可靠的。
到这一步,我们就完成了对DDR某个memory cell的写操作,并且没有破坏这一行的原始数值。
总结一下,写操作如下:
步骤 | 描述 |
1 | 预充电位线(Pre-charge the bitlines) |
2 | 激活字线(Activate a wordline),允许电容器充电或放电,改变位线的电压;传感器放大器(Sense Amplifier)检测这些电压变化,并在其锁存器中记录值 |
3 | 通过解复用器输入期望值以定位特定锁存器 |
4 | 把传感器放大器值写回到对应行的电容,确保修改了对应的cell值,并且保持其他值不变 |
DDR的刷新操作
前面我们提到了,虽然电容非常简单,但是它也有缺点,就是电容会漏电:
由于介质材料不可能完全绝缘,随着时间的变化,电容里面的电荷会慢慢移动,导致电容的电压会慢慢下降。比如如果电容原来电压值是1.0v,慢慢下降到了低于0.5v,那么数字电路里面就会判断这个电容存储的值由1变成了0.
所以,我们必须在电容值降到0.5v之前,对它重新进行一次充电,也就是必须对DDR进行周期性的刷新操作。
DDR刷新操作是DRAM中非常重要的一个过程,主要目的是保持存储在电容上的数据不会因为电荷的自然泄漏而丢失。JEDEC标准要求在常温下每64ms刷新所有DRAM存储单元一次,高温下为32ms。可以看到,这个漏电还是比较慢的,比我们读写操作时间的ns级别还是慢很多。
DDR支持自动刷新(Auto Refresh,ASR)和自刷新(Self Refresh,SR)两种模式 。
自动刷新是由内存控制器定期发送刷新命令,DRAM接收到命令后自动执行刷新操作 。
自刷新是系统进入低功耗状态时,由DRAM内部的刷新控制器自主管理刷新操作 。
我们先不管这些复杂的模式是怎么实现的,我们还是从最底层实现来看看基本原理。
通过之前的读写操作的分析,我们其实很容易就能解决电容的刷新问题,也就是通过读出电容值,然后写回去,就完成了一次刷新。具体操作和读很像,但是不需要输出。
1)第一步,将Bitline预充电到工作电压,也就是1V的一半,0.5V;
2)第二步,发送行地址,选中某一行,也就是把某一行的Wordline导通。传感器放大器(Sense Amplifier)检测这些电压变化,并在其锁存器中记录值。
3)第三步,把传感器放大器值写回到对应行的电容,刷新电容的值。
这里不需要MUX-DEMUX进行任何操作。
总结一下,刷新基本操作如下:
步骤 | 描述 |
1 | 预充电位线(Pre-charge the bitlines) |
2 | 激活字线(Activate a wordline),允许电容器充电或放电,改变位线的电压;传感器放大器(Sense Amplifier)检测这些电压变化,并在其锁存器中记录值 |
3 | 把传感器放大器值写回到对应行的电容,确保电容值没有被改变 |
至于,整个DDR的刷新会更加复杂,主要原因是因为有整个Memory Array都需要刷新,而且什么时候刷新,不影响正常的读写操作这些都是整体需要考虑的。
同时,我们由于只有一行的传感器放大器(Sense Amplifier),所以DDR一个Memory Array(Bank)的刷新是以行为单位,一行一行的刷新,这也增加了刷新机制的难度。
刷新操作虽然本质上很简单,但是周期性的刷新其实给DDR设计带来了很多困难:
只有一行的传感器放大器(Sense Amplifier),只能一行一行的刷新。而一个DDR5的bank可能有65536行,虽然刷新一行时间比较短,但是刷新完成65536行时间就比较长了。
由于刷新和读写都是共用Sense Amplifier,所以刷新的时候就不能进行正常的读写了,必须等待刷新完成。这就造成了读写延时变大,导致性能下降。
为了解决这些问题带来的影响,后面出现了各种技术:Self Refresh,SAME-BANK Refresh等等。但是最底层的机制其实和上面分析的过程是没有区别的,我们只要理解最本质的操作,就很好理解这些看起来比较高大上的技术了。
后纪
技术很重要,技术背后的思想更重要!
技术背后的某些思想就是你解决以后问题的钥匙。我的文章可能一篇中知识点不太多,但是力求让你能深入理解,为你进阶打下基础。如果有一点点收获,也算是我对中国芯片行业的一点点贡献吧。
赠人玫瑰,手有余香。如果你有所收获,麻烦花一秒时间帮我点个赞和在看吧,谢谢!
知乎专栏:芯片设计进阶之路
微信公众号:芯片设计进阶之路 x_chip
——————————————————————————————
参考文献
https://www.youtube.com/watch?v=7WnbIeMgWYA