你好,我是海纳。
之前的课程,我们从软件的角度学习了内存管理的基本知识。从这一节课开始,我们把注意力转向内存的硬件实现。掌握硬件篇的知识,是你学习计算机组成原理和体系结构的基础。而且,计算机体系结构中最常用的手段就是合理地使用各种器件,通过体系手段来使得它们扬长避短,形成有机的整体。
可以说,深刻地掌握计算机的体系结构,就是你写出高性能代码的关键。那么,这么重要且基础的部分,为什么我会放到现在才讲呢?这是因为,程序员日常打交道的是软件接口,硬件的感知度不高。所以在有了前面软件篇的知识后,我们才能更好地理解硬件上的各种晦涩的概念。
整个硬件篇的内容主要就是聚焦于,各种不同的存储器和它们的器件是如何组成高效、大容量、低成本的存储体系结构的。而各类存储器的基本原理是存储体系结构的基础。
我们把用于存储数据的电路叫做存储器,按照到CPU距离的远近,存储器主要分为寄存器、缓存和主存。今天这节课,我们就来重点分析这三种存储器的特点、原理,以及应用场景。
存储器是由基本的存储单元组成的,要想搞清楚存储器原理,我们还要先搞明白基本的存储单元是什么,它又是怎么工作的,我们先按寄存器、缓存和主存的顺序,逐个分析。
首先,我们来看寄存器的存储单元是什么样的。
在导学(一)里,我曾经讲过组合逻辑电路和时序逻辑电路的区别。组合逻辑电路是指,输出仅由输入信号的状态决定的电路。而时序逻辑电路是指,电路的输出值同时依赖于电路过去的状态和现在的输入,所以时序逻辑电路中含有用于记忆电路状态的存储单元。
接下来,我们就从最简单的具有存储功能的电路开始,逐步将它扩展成相对复杂的存储电路,以此来深度拆解存储电路的运行原理。
我们把具有存储信息能力的电路,称为存储器。其中,RS锁存器(Latch)是最简单的一种存储器电路,它可以存储一个比特,如下图所示:
上图中的电路由两个或非门组成,它的特点是,图上方的或非门的输出作为图下方的或非门的输入,反过来,图下方的或非门的输出,也是图上方的或非门的输入。我来分析一下这个电路的特点,你就能理解这个电路是如何完成一个比特的存储的。
一开始,输入端R和S都是低电压,代表0,上方的或非门输出为0,下方的或非门输出为1,这是一种合法状态。或者下方的或非门的输出为0,上方的或非门的输出为1,这也是一种合法状态。也就是说这个电路在R和S都为0的时候,有两种合法的稳定状态。
如果此时,S变成高电压,也就是1,那么下方或非门的输出就变成0,进而导致上方或非门的输出变成1,也就是Q变成1。这个时候,如果S又变成低电压的话,因为上方或非门的输出为1,所以下方或非门的输出仍然保持为0。而输入端R仍然为0,这就使得上方或非门的两个输入端都为0,进而Q的高电压可以得到保持。
于是,我们就看到了神奇的一幕:输入端S变为1以后,可以将输出端Q变成1,但是当S变为0以后,输出端仍然保持1。这就说明这个电路可以存储1。
当R和S都是0、Q为1时,如果此时R变成1,由于电路是对称的,可以很容易分析得到Q将变成0,Q反(表示$\bar{Q}$,下同)变成了1。同样地,如果此时R再变成0,Q为0,Q反为1的状态仍然可以保持。这就实现了一个可以保持一个比特的存储器。
经过上面的分析,我们总结出它的真值表,如下所示:
简单说,就是当S为1,R为0时,Q为1;当S为0,R为1时,Q为0。当S和R都为0时,Q保持原来的状态,若原来Q的状态是0,则Q保持为0,如果原来是1,则保持为1。Q和Q反一直保持相反的状态。
R和S都为1是非法状态,这并不是说这种电路真的不允许R和S都为1,而是当它们都为1时,Q和Q反都为0,这不符合Q和Q反保持相反状态的假设,从而失去了存储信息的功能。所以我们对于S和R都为1的情况就不再讨论了。
除了或非门组成的锁存器,还可以使用与门和或门构成锁存器,也可以使用与非门来构成锁存器。锁存器的特点是输入一旦发生变化,输出端立即就能反应出这种变化。我们知道,RS锁存器的输入信号可能会不稳定,那当输入端的毛刺影响输出端时,该怎么办呢?
为了让存储电路可以保持记忆的能力,我们可以考虑为这种电路引入保持位,当保持位为1时,数据可以被存储进电路,当存储完成以后,保持位变为0,输入就不能再影响输出了。
如下图所示,通过增加两个与门来实现保持位C。当保持位为1时,RS锁存器的功能与不加保持位时完全相同,当保持位为0时,则R端和S端不论取什么值,都不再起作用了。
我们再对上图进行更深一步的分析,R和S同时为0的情况,可以使用保持位为0来代替。R和S同时为1的情况是非法情况,所以我们可以使用一个非门,把R和S合并成一个信号D,这样的话,电路的输入端就可以进一步化简,如下图所示:
上面电路的保持位为1时,输出Q可以反应输入D的变化,当保持位为0时,输出Q保持原来的值不变。这种电路被称为电平触发的D型锁存器。其中的D代表数据Data。
我们知道信号在传输的过程中容易产生毛刺等不稳定的现象,所以在保持位为1的期间,如果输入信号还是不能稳定的话,那么输出将随着输入的改变而发生相应的变化。这种情况下,锁存器的状态就难以稳定。我们把在保持位为1期间,锁存器发生多次翻转的情况称为空翻。
在实际应用中,人们还是希望存储电路有良好的稳定性,从这个角度上看,电平触发的锁存器的抗干扰能力相对较差。
为了解决锁存器稳定性不好的问题,人们在实现存储电路时,常用的方法是使用两个D锁存器来实现一个触发器。如下图所示:
D触发器有D(Data)和C(Clock)两个输入信号,Q和Q反两个输出信号。D触发器由两个D锁存器组成。当时钟信号C为0时,前端锁存器输出信号D的值,后端锁存器保持之前的数据。当C为1时,前端锁存器保持之前的数据,后端锁存器将前端锁存器保持的数据直接通过Q输出。
可以看出来,对于D触发器而言,只有在C从0变成1的瞬间,输入数据D的值才会反应到输出Q上。所以这种触发器就被称为上升边沿触发的触发器。
虽然触发器可以在一个CPU时钟周期内完成读写,是最快的一类存储单元,但是它的造价十分高昂,用于存储一个比特的触发器就要使用几十个晶体管。所以它往往用来实现CPU内部的寄存器,一个寄存器的位宽不过64比特,它的电路面积的消耗还是可以接受的。
但如果我们需要大规模,比如几十K,甚至几M的存储时,就不得不考虑更加节约的方案了,这就要用到大容量的存储器。接下来,我们再来看一下大容量存储器的工作原理。
普通的大容量存储器大致可以分为两类,一类是只能读,不能写,这种存储器称为只读存储器(Read Only Memory,ROM),在生产的时候,厂家将内容写入存储器之后,用户就只能从其中读取数据,不能再更改其中的内容了。
另一类是可以支持读和写操作的存储器,它们被称为随机访问存储器(Random Access Memory, RAM),随机访问这个名字是与顺序访问相对应的。早期的存储设备,例如纸带,磁带等等,只能顺序访问,如果想要跳到某一个特定位置进行播放,只能将磁带快进到我们所需要的那个位置,然后再顺序地访问该位置的数据。而支持随机访问的存储器则可以用同样的速度访问存储器内任何位置上的数据。
由于ROM大多用在一次烧制,永远不需要更新的地方,这往往不是软件程序员所关心的话题。所以,我们这个专栏课里所讲的内存,都是指可读写的RAM,所以我们下面就来详细地分析RAM的工作原理。
RAM大体上分为两类:静态随机访问存储器(Static RAM, SRAM)和动态随机访问存储器(Dynamic RAM,DRAM)。SRAM的特点是速度快、造价高,往往用来制作高速缓存,集成在CPU里,它的容量一般不会超过几兆字节。DRAM的特点是速度慢、造价低,计算机中的主存就是DRAM,单根内存条就可以有十几,甚至几十G的容量。
接下来,我们就分别考察SRAM和DRAM的电路结构,来搞清楚它们的原理。
为了节约电路面积,SRAM采用了6管式的存储电路。如图所示:
在上图中,M1和M3两个MOS管,是N沟道场效应管,在高电压时导通;而M2和M4这两个MOS,则是P沟道场效应管,在低电压时导通。本质上,M1和M2一起组成了一个非门,M3和M4一起组成了另一个非门,这两个非门的输出互为对方的输入,这样,两个非门就组成了一种可以存储比特值的电路。
访问SRAM时,字线(Word Line, WL)加高电平,使得每个基本单元的两个控制开关:晶体管M5与M6开通,把存储单元与位线(Bit Line, BL)连通。位线用于读取或写入基本单元的保存状态。
我们假定储存的内容为1,即在Q处的电平为高。读取周期开始时,两条位线预先设成高电平,随后字线WL变成高电平,使得两个访问控制晶体管M5与M6导通。Q的高电平使得晶体管M1导通,而Q反与BL反的预充值不同,使得BL反经由M1与M5放电而变成逻辑0。在位线BL一侧,Q反的低电平使得M4导通,再加上M6通路,位线就连接到VDD的高电压。
如果储存的内容为0,相反的电路状态将会使BL反为1,而BL为0。这时,只需要BL与BL反有一个很小的电位差,读取的放大电路就会辨识出哪条位线是1,哪条是0。也就是说,当敏感度越高时,读取的速度就越快。
在写入周期开始时,把要写入的状态加载到位线。如果要写入0,则设置BL反为1且BL为0。随后字线WL加载为高电平,位线的状态被加载进SRAM的基本单元。
简单说,SRAM存储单元的特点是使用6个晶体管来实现。其中两个P型MOS管和两个N型MOS管组成两个反相器用于存储信息。还有两个用于控制存储单元是否选通。6管SRAM的结构比触发器简单,速度也比较快。
讲到这里,你就清楚SRAM的电路结构了,下面我们来看看DRAM的电路结构。
DRAM相比起SRAM,它的电路结构更简单,它是由一个CMOS开关和一个电容组成的,如图所示:
当WL为高电压时,开关打开,存储单元选通,如果此时数据线BL为高电压,则向电容中充电,这就是DRAM存储单元中写1,如果数据线为低电压,电容放电,这就是往存储单元中写0。
在读取的时候,同样要打开开关,如果电容放电,那么就表示这个单元里原来存的值是1,如果电容不放电,则表示原来的值是0。可见,DRAM的读取是破坏性的,它会使得原来为1的存储单元变成0。
为了解决这个问题,在读取DRAM的数据的时候,人们要想办法给原来为1的比特再进行一次充电。另外,电容本身也会缓慢漏电,所以存储器也要每隔一段时间就为电容补充电荷。这也是Dynamic这个名称的由来。
DRAM的存储单元使用一个MOS管和一个电容实现,其特点是电路相比SRAM更加简单,也就更容易大规模集成,成本也更低,但是它的读取速度比较慢。
到这里,我们就把用于存储一个比特的常见电路介绍完了。但存储器的容量是十分巨大的,这又是怎么做到的呢?换句话说,一个字节是由8个比特组成,而一根容量为4G的内存条,它包含的比特是$4\times2^{32}\times8$=$2^{37}$个。存储器是如何知道CPU要读写哪个比特的呢?这就要进一步了解存储器是如何对字节和比特进行地址编码的。
存储器在存储数据时,一定要区分数据是要存在哪个地方的,也就是说,我们要想办法对存储器里的各个单元进行编码,并且能将地址总线的数据转换成相应存储单元的使能信号,然后进一步区分控制总线的读写信号。如果是读操作,存储控制器就要将存储单元内的值读入数据总线,如果是写操作,控制器就应该把数据总线的数据写入存储单元。
我们假设存储器是按字节进行编码,一次读写最少也要操作一个字节,数据总线的宽度是8位。这种存储器在制造时,要将每一个字节的第n比特的位线都连接到数据总线的第n位,也就是8个比特的位线分别与数据总线的8位相连。而存储控制器则根据地址总线的值将对应的字节的字线设成高电平,以选通目标字节。
它的简单示意图如下所示:
从这个角度,我们也就能理解,存储单元的字线和位线为什么要这样命名了。这是因为字线可以对存储单元所在的机器字进行选通,而位线是真的接在了数据总线的某一位上。
现在的核心问题在于,控制器如何对地址信号进行转换,使得目标字节的字线变成高电压?
其实,这个工作需要译码器来完成。我们以4位地址总线来举例。首先,4位地址总线可以编码的地址是0x0~0xf,共计16个。如上图所示。
上图中的电路有4个输入,16个输出,16个输出分别对应了存储器的16个存储单元的字线。当地址总线上的数据是0000时,我们希望第0个字节的字线是高电压,当地址总线上的数据是0001时,第1个字节的字线就是高电压,依次类推。我们会发现,地址总线上的数据刚好就是存储单元地址编码的二进制数。这里也就说明了为什么地址总线的宽度会影响存储编码范围了。
我们可以把这个电路的真值表总结出来:
接下来,我们由真值表写出16个输出端的表达式:
Y0 = (~A0) & (~A1) & (~A2) & (~A3),Y2 = (~A0)& (~A1) & (~A2) & A3……,Y15 = A0 & A1 & A2 & A3
这个表达式再翻译成组合逻辑电路是简单的,只需要将A0、A1、A2、A3都取反,然后将这个4个信号使用与门连接,它的输出就是Y0了。同样地,把A0、 A1、 A2取反,再与A3一起使用与门连接,这个输出就是Y1。
作为示意,我画出了Y0、Y1和Y15的电路图,其余的作为练习,希望你可以模仿我的例子自己补充。
这种电路就是译码器,我们把4输入,16输出的译码器称为4-16线译码器。使用译码器就可以解决字线选通的问题。
但这样做会带来一个新的问题,那就是随着存储器地址范围变大,译码器的输出端口会变得非常多,比如32位地址总线会有4G个输出端。为了解决这个问题,人们把地址平分成高低两个部分,并且把存储单元做成矩阵式排列,使用高地址决定存储单元所在的行,使用低地址决定它所在的列。
为了方便,我们仍然以16个字节为例。它排成存储矩阵以后是这样子的:
我们将4位地址分成两组,分别送入两个译码器。高地址送入行译码器,用于选通行线,低地址送入列译码器,用于选通列线。只有行线和列线同时选通的存储单元才是有效的。在这种设计里,32位地址总线的字线由原来的4G下降为64K+64K=128K。这样就大大减少了字线的数量。
使行线和列线同时生效也有两种做法,一种是在每个存储单元引入一个与门,这需要为每个字节多增加两个MOS管,会降低芯片的集成度。
另一种是在存储芯片里再增加一行的缓存,读取时分两步进行,第一步先使行线生效,将目标存储单元所在行,整行读入到缓存中;第二步再使列线生效,从缓存中读取目标单元的值。这种做法需要两次读取,性能会差一些。
存储器的编码主要依赖译码器这种电路结构。它的特点是每一个输出端与一个存储单元的字线相连。译码器将地址总线发送过来的地址作为输入,然后只有一个输出成为高电平,这样就选通了一个存储单元,从而实现了对多个存储单元的地址编码。
在不同场景根据相应的约束,选择可能就会不一样。从上面的分析中,我们可以看到,没有哪一种存储电路是十全十美的,每一种都有自己的优点和缺点,如何把它们用在合适的场景,从而以系统化的方法做到扬长避短,才是存储系统设计的首要目标。
虽然我们说只读存储器往往被用在固件中或者闪存中,断电后,存储的数据也不会消失。它不是内存,但作为现在存储体系的必要组成,对它有所了解还是必要的。所以,这里我就对ROM做一个简单的介绍。
存储的本质就是把0和1存到特定的地方,然后在需要恢复的时候,再把数据恢复出来。RAM的特点是存储数据时需要加电,一旦断电,则存储的数据消失。而ROM中的信息断电也不会消失,它非常像纸笔这种记录信息的手段,不易更改,保存时间长。
早期的ROM,是基于熔丝制作的,写入操作是通过烧断熔丝进行的,这种类型的ROM只能在初始化时写入一次,之后就不能再更改了。当前,这种类型的ROM已经不存在了。
人们在MOS管上增加浮置栅,这个特殊的浮栅中可以存储电荷。当有电荷时代表1,没有电荷时代表0。在一定的条件下可以为浮栅充电,也可以为它放电。所以,这就是可编程ROM(Programmable ROM, PROM)。
早期的PROM,是可以使用紫外线进行擦除的,这就是紫外线可擦除可编程ROM(Ultral Violet Erasable Programmable ROM,UV-EPROM)。使用光进行擦除,有一个缺点就是擦除速度比较慢,而且可擦除次数有限。
为了克服上面的两个缺点,人们又发明了电可擦除ROM(Electronic Erasable ROM, EEROM)。这就是当今使用得最广泛的一种存储器件,它有个专门的名字叫做闪存。
到这里,几种存储器件是如何存储一个比特的,这个问题,我们就已经搞清楚了。
这节课,我们分别学习了CPU中寄存器的存储单元,SRAM和DRAM的存储单元,以及ROM的存储单元的基本原理。从中,我们能了解到,CPU中的寄存器使用触发器存储一个比特,读写速度最快,但所占电路面积最大。
在这节课开头,我带你从最简单的锁存器电路开始,先改进成电平触发的D锁存器,然后通过两个D锁存器搭建了上升沿触发的D触发器。D触发器配合时钟工作,提供稳定的,可控的,快速的存储能力。
D触发器的优点很多,但是缺点也很明显,那就是电路比较复杂、成本高、难以高度集成。所以我们使用6管存储单元来替换D触发器,6管存储单元所组成的存储器就是静态随机读写存储器(SRAM)。
SRAM的读写速度比较快,造价也更低,但集成度仍然不如单MOS管的动态随机存储器(DRAM)。DRAM的原理是使用电容的充放电状态来代表0和1,电容的特点是读数据时,它的状态会被破坏,所以需要以一定的频率对电容进行充电刷新。
最后,我们简单了解了ROM和闪存的发展历史和分类,从中也可以看到ROM这个名字其实已经名不副实了。我们常把闪存归到外存储器分类中,所以我们这个专栏就没有使用大量的篇幅对它进行介绍,但是闪存和当前非常流行的非易失性内存是存储系统的重要部分。如果你对这部分内容比较感兴趣,可以继续阅读《大话存储 》等资料进行学习。
我们在设计各种存储器件的时候,要不断地平衡它们的优缺点。请你思考一下,在存储电路设计中人们要平衡哪些因素呢?举个例子,既然D触发器的速度最快,那我使用D触发器来制作主存可以吗?为什么?欢迎你在留言区分享你的想法和收获,我在留言区等你。
好啦,这节课到这就结束啦。欢迎你把这节课分享给更多对计算机内存感兴趣的朋友。我是海纳,我们下节课再见!
评论