从逻辑门到 CPU

目的,造一个很简单的,概念上的 CPU,虽然简单,但是是五脏俱全的 CPU
从最基础的逻辑门开始造,零基础可以看

一、制造基本武器:与门、非门、或门

现在计算机都是二进制,那二进制是一开始就能想到的吗?显然不是,历史上还真的出现过十进制的计算机,毕竟十进制才符合有十个指头的人类的认知,自行了解世界上第一台通用的可编程计算机:ENIAC
后来人们发现这个十进制的计算机也太复杂了吧,我要用只能表示开通和关断的真空管来构造这个十进制的大家伙,想想就麻烦,既然已经发明了出了这种只能表示 0 和 1 的东西,干脆就用二进制好了,而且二进制有两大好处:
1.更好的排除干扰,非 0 即 1
2.数学上布尔代数是一门完整的理论,专门处理01运算
而且重要的是,数学上已经证明,计算机最高效的进制是e进制,但这无理数在物理上肯定没法实现,最接近它的是三进制,所以三进制应该比二进制更高效,但二进制更容易实现。
所以总结一句就是:使用二进制,高效

这里加一个 tips:可能有读者好奇真空管是个什么东西,和逻辑门有什么关系,答案就是:逻辑门是一个概念,真空管是逻辑门的物理实现,而逻辑门的物理实现在历史上也是不断演化的,大概流程就是:继电器(每秒开关几十次)->真空管(每秒开关数千次) ->贝尔实验室刚发明的晶体管(每秒开关上万次) ->现代晶体管(每秒上百万次),所以这些东西都可以实现逻辑门

好了,接着讲逻辑门是啥:逻辑门就是处理逻辑运算的,逻辑运算是啥,基本逻辑运算就三种:

  1. 与运算 1 & 1 = 1, 0 & 1 = 0, 0 & 0 = 0
  2. 或运算 1 | 1 = 1, 1 | 0 = 1, 0 | 0 = 0
  3. 非运算 !1 = 0,!0 = 1
    还有一些延伸的常用逻辑运算:
    异或运算:两数相异,异或为 1;两数相同,异或为 0 故: 1 ^ 1 = 0, 0 ^ 1 = 1, 0 ^ 0 = 0 (有的同学记不清,相异为 1 还是相同为 1,就不要记啦,直接记异或就是无进位相加, 二进制中1+1=10,无进位的话应该是 0,所以1 ^ 1 = 0~后两个式子同理)

搞懂了逻辑门能进行逻辑运算,ok,等等,我好像没说逻辑门为什么能进行逻辑运算,好吧,说一下吧:
不管是继电器、真空管还是晶体管,他们都有一个共同的功能(这里回答开关功能的都不准确),这个功能就是 ”用一个 0 或 1 信号来控制开关“ 的功能,什么意思呢?以晶体管为例,如下图(原谅我手绘渣渣)左上角是一个晶体管,他可以实现当 G 输入 0 时,DS 之间导通,当 G = 1 时,DS 之间断开:
image-20230714113336484
看上图的右上角,以非门实现为例,我给这个晶体管输入一个 1 或者 0 的信号,这个晶体管上的 DS 之间就会开通或者闭合,所以如果我以图中所示端作为输出,当晶体管开关断开的时候,输出就是和高电平端相连,输出就是 1;继电器开关连通的时候,输出就是和低电平端相连,输出就是 0,这样就实现了输入 1-> 晶体管开关闭合->输出 0;输入 0-> 晶体管开关断开->输出 1的功能,这就是非门要的效果,所以,一个晶体管,就是一个天生的非门~
然后与门和或门就是上图的左下角和右下角啦,具体实现自己看图理解一下,很容易的。
至此,我们手中就有了三把武器:与门、非门、或门(至于晶体管是怎么造出来的,这个我真不会)

发起进攻的第一声号角:半加器和全加器

实现半加器

解释一下为什么要造半加器,回顾一下我们的目标:造一个 cpu 出来,cpu 最主要的功能是什么?计算!所以从加法开始吧,用 与或非 门实现加法的第一步就是实现一个半加器(就是一种不考虑进位的加法器,所以叫半加器),看图吧~关键点说明:

  1. 异或门的构建:与或非门全上阵才能实现异或的功能
  2. 通过半加器的真值表发现,半加器的 SUM 能通过异或门实现,半加器的进位能通过与门实现,所以 异或门+与门 = 半加器
    image-20230714113403035

实现全加器

好了,现在,我们已经实现半加器了,再进一步呗,写完我就睡觉,做一个全加器 ,思路是这样的:
半加器只能执行 2 个 bit 相加,对吧,而全加器之所以能叫”全“加,是因为它能处理进位,处理进位说白了就是,算上进位,一共 3bit 相加,所以要构造全加器,就是构造一个能处理 3bit 相加的东西(这个逻辑简直太棒了),所以问题就从如何构造一个全加器变成了如何构造一个 3bit 相加的东西,那很简单了,一个半加器能实现 2bit 相加并输出 1bit,我再加一个半加器不就可以实现 3bit 相加了嘛,所以,所谓2 个半加器构成一个全加器,应该是这么理解的~
image-20230714113716475

二、负数独白:我在计算机中的日子-补码和减法器

实现减法器--的准备工作:计算机中如何表示一个负数

刚刚实现了一个加法器,接着就要想到来实现第二个算数运算单元:减法器,要明白一点的是,我们的计算机是一层一层抽象、累加、优化而来的,所以要学会利用已有的工具来构造减法器,看一下现在手里的武器:有三种基本逻辑门、虽不是基本但很常用的逻辑门--异或门、半加器、全加器,用这些东西来构造减法器,很容易想到的一个思路就是:减法就是加上一个负数,那用加法器就可以实现减法器,但一个需要解决的问题就是:在计算机中如何表示一个负数?

就你 tm 叫原码啊(陈凯哥哥著名台词)

忘记以前学校学的什么原码补码反码那套反人类的教学方式,从头来看10 进制转 2 进制这件事:计算机中任何规则都是人制定的,同理,进制转换这事也是人制定的规则,开始呢,大家想的比较简单,10 进制之所以能表示正负是因为有符号:+号和-号,带上什么符号这个数就是什么数,那按照这种思路,也给二进制一个符号吧,问题出现了: 我们使用二进制就是为了计算机存储方便,假如二进制有符号了,一个数是-101010,那又得思考怎么存储-号这个符号,想到了,既然只有+和-的区别,这不就是天生契合 0 和 1 嘛,人为规定一下:一个二进制数的最高位是符号位, 1 表示-号,0 表示正号,符号位就是单纯的符号位,不与二进制数的大小联系,比如一个 4bit 的有符号二进制数:1001,表示的就是-1,能表示的范围就是-7~7
嗯,目前看上去一切正常,但是我们随便来验算一下,会发现问题:
4 - 2 (10进制)= 4 + (-2)= 0 100 + 1 010 (二进制原码) = 1110 (二进制原码)= -6 (10进制) 也就是如果用这套规则,4-2 会算出-6,当然不对了,所以这套规则不能用于计算机中的计算(其实早期是有使用原码进行计算的计算机的,比如IBM 7090这种早期的二进制计算机,但要用原码计算显然不能像刚刚那样直接相加,需要有单独的电路来判断原码的正负,后面我们会看到反码和补码都可以直接相加,无需额外的电路判断数字的正负)
顺便说一下,无聊的人把这套规则转换出来的二进制数叫做原码,不过要我给他起名字就叫”最高位是符号位 码”。

原码就是垃圾啊,算个减法都算不对,反码登场

既然这套规则是错的,那就再造一套规则吧,就是所谓的反码了,反码是从原码演变而来的,所以那套最高位 1 位负 0 为正还是保留了,规定正数的反码是原码,负数的反码是 负数的原码除了符号位其余按位取反(也可以说 负数的反码 负数的绝对值的原码全部位按位取反,不同的书表述不一样,你记死一个就行),所以 0 就有了两个表示, 以 4bit 为例:把 0 看做正数的话,+0 的反码是0000,把 0 看做负数的话,-0 的反码是1111,所以在反码表示中,0000 和 1111 都可以表示 0
反码的负数编码格式不像原码那样直观,但是却可以将减法转换成加法了,反码减法规则为:A - B = A + (-B),如果最高位发生了溢位,则需要在最低位加上1,如下面两个例子:
1)4 - 2 (10进制)= 4 + (-2)= 0 100 + 1 101 (二进制反码) = 1 0001 (二进制反码,发生了溢位)= 0001 + 0001(最低位加1) = 0010 (二进制反码)= 2(10进制)
2)2 - 2 (10进制)= 2 + (-2)= 0 010 + 1 101 (二进制反码) = 1111 (二进制反码)= - 0 (10进制)
所以看到了把,反码是可以正确计算减法的,所以早期的计算机确实采用了这套规则,如CDC 6000、LINC、PDP-1等都是使用反码的,但反码还有一些问题:

  1. 反码中的 0 会有两种表示,+0 (0000)和 -0 (1111) (其实这里我认为不算问题,有两个 0 没什么问题,无非是加一个判断电路,如果是 0000 或 1111,都认为是 0 即可)
  2. 反码减法的算法规则比较复杂,需要增加计算机内部逻辑组件额外判断溢位,会影响计算效率。(这个确实是问题)

反码虽然能凑合着用,但你不想更进一步吗?来了,补码!

首先,我要说一句,那句经典的:”负数的补码就是他的反码+1“这句话,没错,但有多少人以为补码就是反码的基础上得来的?再加上课本上基本都是把补码的章节安排在反码之后,再补上一句”负数的补码就是他的反码+1“,这简直就是一场对于学习者计算机认知领域的谋杀,下面忘记那句求补码的话,听我说:
这里有一个惊人的事实,刚刚我们一直在尝试做的一件事,实现二进制的减法,对吧,不管是原码还是反码,都在尝试将负号引入二进制中,从而实现减法,但是,鸡贼的计算机科学家们发现了一件事:不把负号引入二进制也能实现减法,所以补码的根本思想就是:利用了计算机有位数限制的特点,相加结果如果超过了位数会溢出,减法的结果和溢出的多少是有关系的
先来看一个例子:如果说现在一个圆形表盘上有12 个数字,时针现在停在10点钟,想让他到 9 点种,怎么办?两种方案:时间正拨11小时,或是倒拨1小时,所以在这个例子中,有一个伟大的等式成立了:
10-1=9=10+11(看懂掌声)
所以,减去 1 就相当于加上 11,而 11 就叫做 1 的同余数,这就解释了怎么”不把负号引入二进制也能实现减法“,就是利用加上减数的同余数来实现的
这时有人发现了华点:不对啊,你这就是套了层壳子忽悠我啊,你的同余数,比如刚刚的 1,同余数是 11,怎么求得?还不是 12-1 = 11,你这个所谓的加同余数还是需要减法来先求出同余数啊
问的很好,接下来就是讲一下这个同余数怎么得到的,看这段开头,”负数的补码就是他的反码+1“,补码就是同余数,求补码没用到减法,所以求同余数也不需要用到减法,讲完了
讲的有点快了哈,仔细推导一下:以 4bit 为例
1, 一个负数的反码(比如-2 是 1101) + 这个负数的绝对值的反码(比如 2 是 0010) = 1111 B(这是由反码的定义决定的)
2, 1111B 再加 1 = 10000B = 16,也就是四位二进数的模
3,综合 1 和 2 得到: 一个负数的反码(比如-2 是 1101) + 1 + 这个负数的绝对值的反码(比如 2 是 0010) = 10000B = 16,也就是四位二进数的模
移项得到: 一个负数的反码(比如-2 是 1101) + 1 = 10000B - 这个负数的绝对值的反码(比如 2 是 0010) = 模数 - 这个负数的绝对值的反码(比如 2 是 0010)
重点看等号右边: 模数 - 这个负数的绝对值的反码(比如 2 是 0010) 不!就!是!同!余!数!的!定!义!吗!
所以 一个负数的反码(比如-2 是 1101) + 1 = 这个负数绝对值的同余数,所以看到了吗??!!我没用减法,就用 【负数的反码(比如-2 是 1101) + 1】 就得到了2 的同余数 14!
说白了就是:计算机科学家经过以上一顿推导,发现了 负数反码+1 就是 负数绝对值的同余数,就把这个玩意儿取了个名字叫 补码!

这样,我们手里就有了补码这么厉害的武器,而且求起来也很简单,就是 反码+1(对于负数),关键是用补码算数因为刚刚说的原理,他是一种溢出算数(术语:模N加减法),所以不看进位的话,肯定是能算对的,而且弥补了反码的缺点,不用给 0 有两个编号了,0 就是 0000,所以 4bit 有符号数在源码和反码中只能是-7~7 15 个·数,而用补码的话,4bit 可以表示-8~7 16 个数
总结一下:采用补码,就可以在不引入符号位的情况下实现减法,能算对;而且不用指定什么特殊规则(不像反码那样有高位溢出低位还得加 1 这种奇怪的规则),咔咔算就行;而且 +0 和-0 表示都是 0000,统一了,看看,在十进制中看上去很简单的减法要想完备第移植到二进制,还是费了点功夫的,不过还好我们找到了完美的解决方案:补码,补码的加减法才符合我们正常的思维逻辑!,所以现在计算机底层都是用补码来计算的。(提醒一点啊,只有有符号数字要表示的时候才用到所谓的原码、反码、补码,无符号数就不谈什么编码方式了,直接咔咔转就行)

终于要来实现减法器

减一个数等于加一个负数 等于 加这个负数的绝对值的同余数 等于 转化为两个补码相加,所以要把加法器改装一下,用逻辑门实现一个负数求补码(就是把这个负数的绝对值送入非门,然后和 1 一起送入加法器),然后把这个补码送入加法器中,于是,,,减法器就实现了,哈,我也是写到这里才发现,实现减法器的难点在于负数在计算机中的正确表示,所以如果有了补码,实现减法器就很简单了~看图吧

image-20230714113900673实现简单的 ALU

厚积薄发,前面说的都理解的话,看上面那张图就理解了怎么实现最简单的 ALU

实现多路复用器

多路复用器的概念:多路输入,然后有一个选择位,我们可以选择多路中的一路作为输出,所以这个玩意儿可以用来搭建 ALU

实现最简单的 ALU

忘了说 ALU 是什么(Arthriem-logic-unit)单词拼错了,我懒得查了,就是算数-逻辑-单元,他是 CPU 中进行算数和逻辑运算的部分,而我们有了 加法器、减法器、基本逻辑门 再加上多路复用器,把这些东西封装起来,就可以实现最基本的 ALU 了,直接把输入给到 ALU,至于 ALU 最终输出是加法结果还是减法结果,就看多路复用的选择了,反正我把所有结果都算了给到多路复用器中

三、算出来的数往哪儿存-寄存器和内存

刚刚已经实现了一个 ALU,那么我们的最终目标是做一个 CPU 对吧,CPU 内还需要寄存器,比如CPU 控制单元的指令寄存器(从内存中取到指令放在这儿)、指令地址寄存器(负责存储当前在 CPU 中执行的指令的内存地址)、比如要计算内存中两数相加,要先把这两个数加载到 CPU中的普通寄存器,然后再送入ALU 中

实现一个 1bit 的记忆元件--1 位锁存器

本小节所有文字请配合图来看

一个普通的或门--A和B 是输入,out 是输出端,我们把B和输出端out相连--得到一个 1 位锁存器--可以锁住 1--这个锁住的意思是:只要 A 输入 1,out 输出就是 1,不管 A 以后怎么变,输出总是 1--所以说这个小电路就把 A 输入的 1 锁住了
一个普通的与门--A和B 是输入,out 是输出端,我们把B和输出端out相连--得到一个 1 位锁存器--可以锁住 0--这个锁住的意思是:只要 A 输入 0,out 输出就是 0,不管 A 以后怎么变,输出总是0--所以说这个小电路就把 A 输入的 0 锁住了

实现 SR 和 DQ 锁存器--1 位锁存器的进化

但是有个问题,这件简单的电路锁住 0 或者 1 之后,没办法改变状态了,我们理想的锁存器是可以配置他锁 0 还是锁 1 的,所以把上面两个电路结合起来,有了 SR 锁存器
SQ 锁存器有个小问题,就是不够方便,我们希望锁存器的最终形态是有一个数据输入端,有一个使能端,当使能端打开的时候,可以存住数据输入端的数据,所以有了 SR 的改进版-DQ 锁存器

把锁存器并排放着--寄存器

以上实现的不管是SR锁存器还是DQ锁存器。他们都是一位的。如果我们把n位锁存器并排放着,就可以一次性存储n位数据。这样的一组锁存器就叫做寄存器,寄存器就是一组锁存器一起存一个东西呀!
寄存器的"位宽”就是指寄存器里有多少个锁存器,一次可以存储多少 bit 的数据,而我们一般说 64bit 的 CPU 其实就是说 CPU 内的寄存器位宽是 64bit,即一个寄存器里有 64 个 1bit 锁存器,一次可以存住 64bit 数据

把锁存器排列成矩形的形状--内存 RAM

这个需要看图理解,当锁存器不太多的时候,我们可以并排放着,就是寄存器,寄存器在 CPU 内部,一个寄存器只能存一个值,而内存是挂在 CPU 外部的总线上的,而且内存远比寄存器大,但是寄存器和内存底层最小单元都是锁存器,由于内存中锁存器太多了,所以需要矩阵排列来实现节省连线的目的,比如图中的 256bit 的内存,如果还按照锁存器那样子一排 256 个的话,需要线256*2+1 = 513 条,所以把256 个锁存器排列成矩阵,只需要 35 条线(1条"数据线", 1条"允许写入线", 1条"允许读取线”,还有16行16列的线用于选择锁存器的行和列) 就可以组装在一起
看图看图:

image-20230714114156127好了,这张结束,我们已经有了CPU 内部的寄存器,也有了 CPU 外部的内存,接下来就是组装环节了

四、工具都准备好了--开始组装 CPU

了解 CPU 的功能

准确来说,CPU 的功能不是计算,而是”处理“,也就是执行指令,指令一般分为两大种:

  1. 计算指令-不管是算数还是逻辑,CPU会使用 ALU 进行运算
  2. 内存指令(也叫 IO 指令)-CPU 会和内存通信,从内存把值读入 CPU 或把从CPU把值写入内存

开始搭建 CPU

下面我们开始搭建 CPU,我们把重点放在功能,而不是一根根线具体怎么连

  1. 从工具箱中拿两个寄存器出来,命名为 指令寄存器 和 指令地址寄存器,分别和内存连在一起,根据 指令地址寄存器中 指示的内存地址,该内存地址上的值会开通 READ ENABLE,会把这个值给到指令寄存器中,这就是 CPU 的 ”取指令阶段“
  2. 指令寄存器拿到指令,送给特定的逻辑电路(这个东西叫控制单元)判断是那种指令,这是 CPU 的第二个阶段叫 "解码阶段”
  3. CPU 的第三个阶段叫 "执行阶段”,判断出是哪种指令后,开始执行,如果是 IO 指令,控制单元就会操作CPU 内部寄存器和内存通信,如果是计算指令,控制单元就会调用 ALU 执行计算

所以 CPU 中主要单元:

  1. ALU 算数逻辑运算单元
  2. 控制单元:内部包含指令寄存器、指令地址寄存器、解码电路 ,所以主要负责三大功能:1. 取从内存中指令, 2. 指令解码,3. 控制 ALU 单元进行运算或者控制内存和CPU内寄存器通信
  3. 普通寄存器 :CPU 内负责存储临时变量数据

感觉写完了啊,越写到后面越轻松,可能是前面写的太透彻了?哈哈哈,总结一下吧
晶体管--逻辑门--半加器--全加器--减法器--ALU
晶体管--逻辑门--锁存器--寄存器--内存
CPU = 控制单元 + 普通寄存器 + ALU
控制单元 = 解码电路 + 指令地址寄存器 + 指令寄存器

当然,这里实现的是一个简单的 CPU,不过也别小看他,如果加上键盘和显示器,那这台计算机就是实现了大名鼎鼎的冯诺依曼结构的,即:采用二进制、具有运算器、控制器、存储器(RAM 或 ROM)、输入设备输出设备 五大部分

参考资料:

  1. 《编码》
  2. crash course

这里是我的博客地址

posted @ 2020-10-07 23:16  byFMH  阅读(2610)  评论(0编辑  收藏  举报