为什么要学习计算机组成原理
最近在学习计算机组成原理,了解到 CPU 的组成,虽然是软件开发,看上去和硬件无关,但软件和硬件存在千丝万缕的联系。很多优秀的设计都是依赖底层硬件设计,例如 Disruptor 使用了缓存行填充减少伪共享带来性能问题,通过无锁设计减少 cpu 切换带来的问题。通过虚拟内存实现在有限内存的情况下运行更大内存的软件等等。因此多了解一些硬件知识还是很有必要的。
下面的内容都是按我的理解整理而来,非标准答案,有错误欢迎指出。
组成 CPU 的层次结构
组成 CPU 的层次结构可以分成 4 层,开关元件,门电路,组合逻辑电路,功能单元。每一层基于下一层组合而来,例如门电路是由开关元件组合而来,而组合逻辑电路又是由门电路组成。我们从最底下的开关元件说起。
开关元件
计算机选择二进制是计算机物理结构导致的,不管是继电器,真空管,晶体管,都只能最稳定表达 1 和 0 的情况。晶体管虽说可以根据电压大小表达多值,但不够稳定,容易受外界影响导致识别错误。而布尔提出的布尔代数使得基于 0 和 1 的组合也可以有丰富的计算逻辑,为设计、分析和优化逻辑电路提供了理论基础。因此二进制在计算机领域脱颖而出,成为计算机的标准进制。
因为开关元件是底层,一个 cpu 就对应了成千上万,甚至上亿个开关元件,因此它的性能很大程度决定了 cpu 的性能。而计算机的发展史也和它密切相关。
计算机发展史
第零代- 继电器 机械计算机(-1945)
第一个计算机称为 马克1号,使用了 3500 个继电器,1秒能进行3次加法。缺点是速度慢,体积大,组件容易坏。
继电器 的结构非常简单,2* (电源+物理开关+通电线圈) 就组成了一个继电器,下面的开关通电,线圈产生磁力,把上面的开关关上,上面的电路也会通电。通过组合开关和线圈,就可以实现门电路。因为继电器结构比较简单,为了便于理解,后面的门电路都是通过继电器来实现。
第一代- 电子管 计算机(1945-1955)
比较有名的电子管计算机是 ENIAC,有17,468个真空管,1秒15000次加法。因为不涉及开关运动,速度比继电器要快。缺点是体积还是偏大,耗电巨大。
电子管 是一种利用电场对真空中的电子流进行控制和放大的电子器件。它主要由阴极、栅极和阳极组成,阴极负责发射电子,栅极控制电子流的强度,而阳极则负责收集电子并产生电流。
第二代- 晶体管 计算机(1955-至今)
现代计算机都用上了晶体管,因为集成电路的出现使得一个小小的 cpu 就可以集成几十亿个晶体管,1秒上亿次加法运算。晶体管的发明催生了硅谷和一大批互联网公司,使得个人计算机成为可能,是能改变历史的伟大发明。
晶体管结构比较复杂,这里就展开讲了。原理也是有3极,1极是控制极,通过控制极来控制电路。
门电路
门电路是由开关元件组成,有自己的符号。实现了一些布尔运算,正是这些布尔运算使得计算成为可能。
布尔运算种类有很多,最简单的就是”与”和”或”。下面是与门和或门的继电器实现。
上面的 V 是有电压的意思,会和接地线形成回路,所以下面的两个图是等价的。
异或门会复杂一些,他的计算如下,和或门的区别在于 1,1 或门会输出 1 ,而异或门会输出 0,因此需要或门基础上,在 1,1 的时候变成 0,这时候只要与一个非与门就行了。
A值 | B值 | 异或结果 |
---|---|---|
1 | 1 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
0 | 0 | 0 |
组合逻辑电路
半加器
有了门电路,就可以进行计算了,我们先看看最简单的加法是怎么实现的。先进行一个位的计算,然后再拓展到多个位。二进制的加法逻辑和十进制是一样的,会有进位。先把所有计算结果枚举出来,再看看计算特点。
A值 | B值 | 和 |
---|---|---|
1 | 1 | 10 |
0 | 1 | 1 |
1 | 0 | 1 |
0 | 0 | 0 |
可以发现,个位的计算符合异或门的特点,而判断要不要进位,只需要一个与门就行了,因此一个简单的一个位的加法可以用下面的门电路实现,输入2个位,输出2个位来表示一个位的结果和是否进位。而这个逻辑电路称为半加器。
全加器
称为半加器是因为还处理不了多个位,要处理多个位需要把进位也考虑上,例如要计算第二位的值,需要加上第一位的进位值,如果是 0 则不用加,如果是 1 还要加 1,还要判断加上进位制之后,第二位是否也要进位。3个输入(前一位是否进位和2个值),同样 2个输出。这就是全加器。
全加器由两个半加器组成,A B”和”后是否进位,”或”上 +1 之后是否进位,就得出最终是否进位。A,B “和”的结果再加上进位输入的 “和”就得出这一位的值是多少。
这时候再把多个全加器连起来,就变成了支持多位”和”计算的运算器了。
减法实现
如何支持减法呢? 已知:
- 正数 - 正数 = 正数 + 负数
- 负数 = 正数的补码 (反码 + 1)
得出
- 正数 - 正数 = 正数 + 正数的反码 + 1
减法变成了加法运算,就可以用全加器了。反码的实现很简单,用门实现就是加一个反门,而加 1 则刚好用上第一位的进位值。
反馈电路
复杂的电路带来一个问题,门的开合是需要时间的,一个电路从开始到最后的输出,经过了n 条路径,n个门,如何才能确定最终的值。例如下面的全加器,什么时候能确定异或之后的进位输出值呢? 这就需要到反馈电路来确定最终的值了。
反馈电路就是自己开关自己,不断循环,不断产生 0101。这种反馈电路可称为 振荡器,频率就是1s的周期数,单位是赫兹 Hz。我们常见的 CPU 的频率是以 GHz 为单位,1 GHz 为 10^9Hz,也就是 1s 产生 10^9 个周期,一个周期只需要 1 纳秒的时间。
简化之后就变成了
因此产生高低电路,一个周期我们称为一个 CPU 的时钟周期。这个电路我们是可以调节快慢的,我们可以规定在低电压的时候电路还没稳定,等到高电压的时候我们才确定最终的值。这样的话最长的电路执行时间就必须小于半个时钟周期。
其实根据上升沿这瞬间去确定最终的值更好,这样最长电路执行时间只要小于一个时钟周期即可,但电路会更复杂。为了方便理解下文使用高电压作为电路值确定的时间。
锁存器
有了时钟周期,会带来另一个问题,这个时钟周期多大合适。例如下面的例子,这个电路会经过2个加法器,假设经过一个加法器是要 1ns,为了确定最终的值,这个时钟周期只能设置成 2*2ns,相当于10ns 只能计算 5 次。
时间浪费在在第2ns 的时候其实加法器并没有进行计算,那能不能提升一下效率,变成 10ns 能计算 10次呢? 答案是只要增加一个锁存器,这个锁存器的功能就是把值存起来。第 1ns 的时候锁存器存储第一个加法器的值,第2ns 的时候第一个加法器进行第二次计算,第二个加法器根据锁存器的值进行计算,那后面每 1ns 就能进行一次运算了.
总结一下,我们需要一个可以存储数据的电路,在高电压时写入数据,低电压的时候保留高电压写入的数据。下面这个电路正好可以实现这个功能。我们先一步一步看。
下面的电路用了2个或非门,或非门是 0,0 的时候才是 1
A值 | B值 | 和 |
---|---|---|
0 | 0 | 1 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 0 |
电路有2种情况
上合下断
红色的线代表 1,黑色的线代表 0。这时灯泡是亮的,代表最终的值输出是 1。两个门中间的电路是 0。
这时候把上面的开关也断开,灯泡还是亮的,最终输出值还是 1。两个门中间的电路是 0。
上断下合
这时最终输出值是 0,两个门中间的电路是 1
下面开关断开之后,最终输出值还是 0,两个门中间的电路是 1
总结一下就是上下开关代表的 01 和 10 决定了最终的值,且变成 00 之后的值还是上一个开关状态的值。电路简化之后就是如下图。
这时我们可以引入一个保持位,这个保持位为1 代表可以写入数据,如果变成 0 则不能写数据,而且仍然输出之前写入的值。
因为上下开关只有 01,10 两种组合,因此可以加一个反门,把 2 个开关合并成一个。
只要把振荡器接入保持位,即可实现高电位可写入,低电位数据保持的功能。这就是 锁存器。如果把保持位当作写入位,也就是一个存储器了。
如果把8个锁存器连在一起,有 8 个输入数据,对应 8 个锁存器,就变成了一个 8位锁存器。可以存一个字节的数据。
存储器
锁存器虽然可以实现数据存储功能,但还不是一个完整的存储器,我们希望存储器可以存多个字节的数据,并根据地址选择对应的数据。那假设这个存储器有 多锁存器,怎么根据地址来选择对应的锁存器呢?
这时候就要用到选择器了。 选择器 也叫 译码器 8 个锁存器需要 3 位的地址(2^3=8) 来进行标识,这种选择器叫做 3-8 选择器,4 个锁存器需要 2个地址来标识,叫做 2-4 选择器。下面用 2-4 选择器举例。
2-4 选择器的作用是输入 2个位,输出 4 个位,根据 2 个位的组合进行输出,例如 00 -> 0001,01 -> 0010,如此类推,每次 4位都只有 1 位是 1 ,其他是 0
这时候只要配合上与门,即可实现选择的功能。写入的逻辑则是把选择器当成写入位,把写入位同时接到锁存器中即可(相当于把所有锁存器都输入,但因为只有一个锁存器写入是1 ,所以只有这个锁存器写入成功).
选择器的电路如下,看上去很复杂,但实际上看 D0 和 D1 就知道选择器的逻辑了。
如果想存储多个位的数据,只要多加几个锁存器即可。下面就是有 8 * 2位大小的存储,读写单位是 2 个位。
选择器除了可以用于地址译码,还能用于指令译码。也就是可以根据不同的指令来选择不同的电路,从而实现不同的功能。
总结
我们已经知道 CPU 是由运算器,存储器,控制器组成,但他们是怎么协作,使得 cpu 可以执行指令的呢? 下篇文章再进行分析。