CPU运行指令

搭建计算机

数据不保存,是没有意义的。所以我们需要制造可以保存数据的电路,这个电路既可以读取,也可以写入数据。之前我们所有的电路都是一路向前的,有没有想过把输出接到输入上会怎样?

我们来看一个吃自己的小电路,这是一个或门,我们把输出接回到其中一个输入上。然后再增加电源,给它来一个1,也就是高电压。你看或门的输出变成了1。然后这个1又回到了或门的第二个输入,也就是它把自己的输出又吃了回去。现在,即使我们断开第一个输入,或门的输出也还是1,这样我们就保存了一个1。

Untitled

但是这个电路有个小问题,就是这个改变是永久性的,只要不断电,那么这个或门的输出就永远是1,这样的话也没啥大用。我们想要一个可以按照需求改变的电路,把这个或门电路拓展一下,我们就得到了这样的一个电路。有2个输入,1个SET,1个RESET。

SET可以让电路保存1,RESET可以让电路保存0。看起来好像可以工作了的样子,功能上没啥问题,就是太费线了。如果要保存8位数据,那么需要8个这样的电路。就需要16根线,来分别控制每个电路的SET和RESET。

Untitled

这要是存个1024个字节,那就是1024x2根线。1024个字节,我们称为1KB。而1MB=1024x1KB,1GB=1024x1MB,那就很难想象如何存取1MB甚至1GB的数据。那得需要多少条线啊!

那么,我们再改进一下,做一个这样的电路。这个电路也有2个输入,1个写入,1个使能。使能相当于一把锁,可以控制数据的写入。当这把锁是0,写入无论怎么改变,都不能保存,这就是锁上的状态;当这把锁是1,也就是锁打开了,你写入1,然后再把锁关闭,那么这个1就锁在里面了。

Untitled

你看,我们又可以保存1个比特的信息了,这个电路比上个电路优点在哪呢?假设我们要存1个字节也就是8位的数据,那么我们可以用8个这样的电路。然后让这8个电路公用同一把锁,1根线就够了。数据写入是分开的,共有8根线。当锁打开的时候,8个位都可以写入;当锁关闭的时候,8各位都不能写入。这样一共只需要9根线,就可以组成1个8位的存储单元了。

Untitled

如果你堆叠的更多,可以形成16位、32位、64位的存储单元。这样的电路多用在寄存器上,它的执行速度比较快。我们称之为SRAM,静态随机访问存储。也可以用SRAM来做内存,但是成本比较高。

SRAM存储1位,就要用到6个逻辑门,10个晶体管。在芯片上占的面积大,而且很贵啊!所以在实际的CPU中,寄存器的数量不会太多。百个左右,也就是KB级别的。而主流的内存都是用DRAM,动态随机访问存储。

这个动态是哪里在动呢?我们先来了解一个新的元件-电容。电容的结构很简单,一般有两片金属电极,中间夹一层绝缘材料。然后两个电极上接上导线,一个电容就做成了。当我们给电极通电,电荷会在金属电极上积聚;当你把电断开后,电荷仍存在。

Untitled

这是由于同性相斥、异性相吸形成的一个静电场。我们来搭建一个简单的电路,使用一个电容,以及一个PNP三极管作为开关。可以看到当开关闭合后后,三极管导通,电容就开始充电;当开关断开后,三极管也断开,但是电容仍然带电,OUTPUT会输出一个高电压,这时OUTPUT就保存了1。

Untitled

但是呢,电容里的电量,会一点点漏掉,最终会完全放电,而电压也就消失了。所以需要定期给电容充电,以让它保持电压。也即保证数据不丢失,定期充电我们称之为刷新。这就是动态的由来,8个这样的存储单元可以保存1个字节。SRAM,只要0或者1保存进去,它就会一直在那里。DRAM,如果要保持0或者1,就要一直刷新,所谓静态与动态是相对的概念。

好了,我们现在有了制造寄存器和内存单元的电路,如果我们有多个存储单元,比如有4个,那么我们怎么找到它们呢?也就是我们如何处理这些存储单元定位的问题。我们搭建一个电路,有2个输入X、Y,还有A、B、C、D4个输出。X、Y分别有0、1这2种状态,那么组合一共有00、01、10、11四种状态。X、Y都为0,则C的输出为1;X=0,Y=1,则D的输出为1;X=1,Y=0,则A的输出为1;X、Y分别为1,则B的输出为1。

Untitled

A、B、C、D这4根线,分别接到4个1字节存储单元的ENABLE线上,是不是就可以控制4个存储单元的写入了?输入为00、01、10、11时,可以分别找到C、D、A、B。00、01、10、11对应一个特定的位置,就像一个门牌号。我们把X、Y称之为地址线,而A、B、C、D称之为地址。2根线最多寻找4个地址,这就是2根线的寻址能力。

Untitled

如果拓展到3根线,则可以表示8个位置,同样可以通过设计相应的电路实现。以111为例,通过2个与门即可让输入111时,找到门牌号为111的那根线。那么3根线的寻址能力为2x2x2,也就是8个地址。

Untitled

我们来做一个4根线的选择器,它的寻址能力为2的4次方,也就是16个地址,这是我们会用到的一个内存组。它可以保存16个字节的数据,它的地址从0000,一直延伸到1111。

Untitled

我们再来制造一个寄存器组,每个寄存器单元为1个字节,所以需要一组8位宽的数据线。然后在放置8个寄存器单元,把前面制作的3线选择器放在上方,这样就可以找到每一个寄存器单元,这就形成了一个8个字节的寄存器组。每一个寄存器都可以单独定位,执行写入和读取。

Untitled

现在我们有了更高级的元件,可以开始搭建CPU了。首先我们放一个ALU,这个是在上一期视频中搭建的。接受2个输入,每个输入位1字节,也就是8位。输出是1字节,也是8位,同时也输出结果是否为0,是否溢出等逻辑状态。然后再放4个通用寄存器,分别为A、B、C、D。这4个寄存器主要是用于计算过程中使用,它们都是8位的寄存器。IC和IP是两个专用寄存器,IC用来给指令计数,IP用来存储指令地址。

Untitled

计算机中的指令,在最底层是以电压的高低存在。高高低低的电压,进入不同的电路,产生各种结果。我们用1和0来表示电压高低,这种二进制形式的指令,称之为机器语言。它比电压的高低还是好记一些,现在我们制定几个简单的指令。

LOAD_A为0000,功能是把紧跟在后面的一个4位地址的内容加载到寄存器A。LOAD_B为0001,与LOAD_A类似,只不过把数据加载到寄存器B。ADD为0100,是加法指令,把指定的两个寄存器中的数字相加,然后保存到第一个寄存器。STORE_A是存储指令,把A中的数据存储到紧跟在后面的4位地址中,它的二进制编码,是1000。

Untitled

0001、0100是机器语言指令,会被转换为高低电压信号,然后被计算机执行。而0001、0100这种二进制数字是在是难以记忆的,所以人们给它们起了诸如LOAD_A、ADD等别名,这样对编程人员会更加友好一些。这样的别名组成的语言,就是汇编语言。

而计算机是如何识别汇编语言呢?比如你写了一个LOAD_B,那么LOAD_B首先会被转为2进制的形式,就是0001。计算机当然也不会理解0001,它是通过电路来理解具体的指令。

Untitled

我们这里做一个解码电路,它只有在输入为0001时,输出才为1。它可以判断指令是否为0001,其他的指令也是同理。

Untitled

内存部分也可以加进来了,我们的内存一共有16个字节,共需4位的门牌号来标示每个地址。解码器也放置到位,它可以接受4位的指令,解码后再输入到相应的电路。用个框框圈起来,这一部分被称为控制单元,也就是CU。当然CU还包括一些存取的电路,这里我就不画了。

我们把4个寄存器连接到CU,建立CU与寄存器的连接,同时寄存器也连接到内存,这样数据就可以在寄存器与内存间传递。然后把ALU的输入和输出连接到CU,寄存器中的内容可以由CU送到ALU的输入。ALU的输出也可以送到CU,然后再保存到寄存器。最后把内存也连接到CU,CU可以从内存中读取指令,也可以把数据写入到内存等。

这里要注意一下,内存到CU有两组线,地址线和数据线。CU要负责找到相应的地址,并且读取数据、指令等。然后我们再加一个框框,框起来的部分我们叫CPU。这样,我们就搭建了一台冯·诺伊曼架构的计算机。

Untitled

冯·诺伊曼架构是指存储程序计算机,CPU要执行的指令,以及要处理的数据,都要先进入内存,然后再进入CPU。现代CPU还会有多级缓存,主要是为了解决CPU运算速度够,而读取数据不够的问题。

Untitled

还会有分之预测的电路,也是为了提高运算速度。我们这个小小的CPU,主要是为了研究原理。那么多电路估计各位也记不住,主要是让你形成印象,知道CPU内部是怎么做计算的就可以了。

Untitled

运行指令

现在我们就用这几个指令来写个小程序,目的是为了把2个数字相加,然后再把结果存到内存中。程序对应的代码就是这4行

Untitled

计算机执行这些指令分三个步骤,把指令拿过来,看看什么意思,然后送去执行。执行完毕后再进行下一条,还是同样的步骤:取指、解码、执行。

Untitled

我们把刚才写的小程序放到计算机内存里,冯·诺伊曼架构的计算机是存储程序计算机,所以我们的指令和数据必须先放到内存。计算机里存储数据、指令包括内存地址都是二进制的,为了方便查看,我们把二进制指令转换为汇编指令。把二进制内存地址也转为十六进制。

Untitled

我们把这个小程序拆分成具体的动作,指令计数器在CPU初始化完成后,被置为0。然后控制单元到内存地址为0的位置取指令,这里存储的指令为LOAD_A 7,指令会进入IP。经过解码器解码后,进入执行阶段,这是一个加载指令。把门牌号为7的内存地址的内容,加载到寄存器A。7号地址存的是数字8,数字8就进入了寄存器A。第一条指令执行完成,然后IC加1,到下一个位置取指令。同样经过取指、解码、执行三个阶段,LOAD_B 8执行后,数字6进入了寄存器B。

Untitled

然后IC继续加1,这里的指令是ADD A B。寄存器A中的数字8和寄存器B中的数字6,被送往ALU的两个输入端,经过计算后产生结果14。14会被控制单元再存回寄存器A,这时寄存器A的内容为数字14。IC再加1,得到指令STORE_A 15。STORE_A指令会把寄存器A中的内容,存到门牌号为15的内存。数字14进入15号内存地址,这个程序就执行完毕了。

Untitled

这就是绝大多数的计算机执行指令的过程