用逻辑门设计一个CPU
- 前言
总是看到各种资料,都说cpu是通过逻辑门来制作的,但是我一直很疑惑简单的逻辑门到底是如何变成CPU的?于是我通过logisim进行了一番尝试,成功用基础的逻辑门来搭建了一个简单的CPU,可以运行简单的程序,算是一个有趣的小项目。这里我想要写一遍文章,聊一聊制作过程,希望能帮助更多人搞明白CPU的原理。先看看效果吧,这是运行计算斐波那契数列的效果:
-
- 使用的软件
制作使用的软件是logisim,可以方便的进行数字电路逻辑门设计和仿真,并且操作比较方便,恰到好处的满足了这个工程的需求。
-
- 参考资料
制作过程中主要参考了一篇博客,帮了我很大的忙。
https://www.cnblogs.com/kingduan/p/4054484.html#_Toc402178272
-
- 说明
这个项目已经完成了一个8位CPU的设计,后来又开始尝试做一个16位的,并且使用了更加合理和优美简洁的布局。但是16位CPU没做完(鸽王本鸽),只做到了编译器部分。因此我在这篇文章中讲解原理使用了更清晰的16位版本,最后运行的是8位版本。最后会附上两个项目的截图。
- 运算器
- 逻辑门
计算机的基础部件,就是逻辑门。简单来说,逻辑门就是有两个输入一个输出的元件,比如与门,或门,非门。
这玩意有啥用?比如,如果有一个核弹的发射按钮,只有两个开关同时按下的时候才能激活,那么就可以使用“与门”电路了。Emmm看起来好像真的没啥用?不过如果用它做个电脑出来,就有用了。
这些逻辑门可实现最基本的逻辑运算,任何一种复杂的逻辑,都可以通过这些基本逻辑门组合得到。因此我们可以想办法设计一个计算的逻辑,然后把它用基本逻辑门把它组合出来,不就得到了一个可以计算的电路了吗?
-
- 一位全加器
我们先来看看怎么制作加法电路。
想想我们计算的过程,都是先计算数字的每一位,然后将进位加进去,得到了计算结果。
比如,计算
2 | 6 | |
+ | 3 | 8 |
= | 6 | 4 |
的时候,我们先把个位6和8相加,得到14,再计算2+3+1得到6,结果就是64。模仿这个过程,制作一个计算一位数的加法器,让它分别计算每一位,就足以应对各种情况了。
另一个问题是,为什么使用二进制?
首先,如果使用二进制来表达,那么我们的数字只有0和1,对应逻辑电路的电压高和电压低,更容易制作。
然后,如果使用二进制来计算,那么每个加数要么是0,要么是1,要考虑的情况大大减少(如果是十进制,就要考虑0123456789了)。
所以,一个最简单的,二进制加法的所有可能,可以表示成下面的真值表
对应算式 | 进位 | 加数A | 加数B | 结果进位 | 结果 |
0+0+0=0 | 0 | 0 | 0 | 0 | 0 |
0+0+1=1 | 0 | 0 | 1 | 0 | 1 |
0+1+0=1 | 0 | 1 | 0 | 0 | 1 |
0+1+1=10 | 0 | 1 | 1 | 1 | 0 |
1+0+0=1 | 1 | 0 | 0 | 0 | 1 |
1+0+1=10 | 1 | 0 | 1 | 1 | 0 |
1+1+0=10 | 1 | 1 | 0 | 1 | 0 |
1+1+1=11 | 1 | 1 | 1 | 1 | 1 |
注意这是二进制计算! |
PS:想一想如果使用十进制这个表会多么的庞大。
前面说过,任何复杂的逻辑,都可以转换成基础逻辑部件的组合。具体怎么得到的可能就要先上一节离散数学课了,这里就略去了。我们可直接看这个逻辑对应的电路就行:
这个逻辑电路叫一位全加器。将这样的一位全加器进位输出和另一个的进位输入串联起来,就能得到计算更多位的电路了。比如这样:
注意每次左上和坐下两个数的组合,都等于右边的数。
现在我们就可以用这个电路来计算加法了!
使用相同的思路可以设计出计算减法甚至是乘除法的电路。比如通过补码的方法,只需少量修改就能将加法器变为减法器。不过其他电路的原理比较复杂,而且我这里只想讲解一下大致的思路,还是略去吧。另外后面我其实使用了软件提供的元件,所以不会再详细解释原理了,没看懂的可以忽略上面部分,继续往下看吧。
-
- 运算器效果
直接使用logisim的加减乘除元件,摆一个可以进行各种运算的模块,并且用开关决定输出那个运算类型,这样我们就得到了啥都能算的电路了。
补充一下,最右侧的标志位是用来判断运算结果的各种状态的。如判断正负,判断溢出,判断结果为偶数等。这些标志位将会用于条件跳转指令等。
- 寄存器组
前面我们介绍了如何使用电路制作加法的电路。但是,如果我们要计算十个数的和该怎么办呢?一个想法是,将十个加法器串联起来计算。但是对于每个计算都搞一个专用的计算电路出来,但是这明显太麻烦了。有没有更好的方法呢?
我们可以把计算结果保存下来,然后把结果重新放回输入端口,就可以一步一步的计算结果了。并且使用这种方法,还可以任意组合加减乘除,计算任何表达式。
-
- RS锁存器
进行下一步之前,我们先考虑一下如何制作存储器。存储器就是可以把计算结果保存下来的电路,有了它我们可以缓存计算结果,进行分步运算等。
基础的存储器叫RS锁存器,由两个异或门组成。
它的特殊效果,是可以保存当前的电路状态。当输入为高电平时,即使输入不在为高电平,输出依然保存着高电平状态,直到另一端出现高电平。这样通过多个锁存器保存数字每一位的情况,我们就能够保存数字了。
同样的Logisim提供了许多元件,可以直接实现寄存器或者存储器等等,所以我们直接使用他们来进行制作。
-
- 寄存器组
我这里使用了16个寄存器组成寄存器组(也就是最多可以保存16个数)。用选择器可以控制哪一个寄存器的数据输出,用译码器控制寄存器的写有效信号控制那个寄存器可以保存数据。这里可以同时读两个寄存器,写一个寄存器。
PS:图没截全,下面都是相同的结构。
使用这样的结构,我们的寄存器组可以支持同时读取两个数,和写入一个数。这么设计是为了跟运算器搭配,下面会介绍。
- 控制器
我们使用了三根总线来传输数据。当进行算术运算的时,两条总线传输两个运算数,和一条总线传输运算结果,然后将他们传回寄存器保存。因此,用三个总线传输就可以在一个周期内完成计算。
各个部件都连接在总线上,然后用开关来决定他们是否输出。
-
- 运算过程
好了,搞了这么麻烦的设计,我们实现了什么效果?如果要计算加法,我们只要控制开关,将两个相加数的寄存器,连接到两个输入总线上,然后打开运算器的输入开关,数据就进入了运算器。然后,打开运算器的输出开关,运算器将运算结果放到输出总线上。打开寄存器数据写入开关,和寄存器写有效开关,就可以将数据存到寄存器里了。
也就是说,我们现在只要控制各个开关,就能指挥CPU进行各种计算了。想想如果不这么做,难道要给运算的每一步都做一个加法器然后相连吗?那样现在的每个应用,都要设计专门的硬件芯片,想想都觉得十分麻烦。
-
- 程序指令
但是通过电路开关来控制cpu,恐怕还是太麻烦了。电路一秒就能进行数以亿计的运算,指望你一个个首波开关来控制怕是APM上万也控制不了。怎么办?计算机先驱们给出了一个答案:让计算机自己控制自己!
我们可以将要计算的步骤,表示成一个一个的数字。每个数字都代表了一种运算,然后只要将这些数字输入计算机,不就可以了吗?等下,这不就是编程嘛。。。。
在真实cpu中就是这样实现的。CPU会有一个寄存器(名字叫PC,程序计数器)保存当前的指令地址,每进行一次运算,cpu就将内存中一个数读取到寄存器中(名字叫IR,指令寄存器),然后将他翻译成对应的指令执行。执行完毕后,CPU就将PC加一,然后接着读取下一条指令。如此不停进行,CPU就能执行程序了。
-
- 译码器
现在还有一些问题需要处理。首先, CPU的各种开关很多,如果每个开关都用一个数位表示,最后一条指令就需要大量空间。但是一般只会进行少数运算,既我们一般只想进行少数运算,我们把这些运算编个号,然后让CPU根据编号来控制开关,不就可以节省很多空间了吗?
这个想法确实很好。实现这个过程的电路叫做译码器。前面说过,利用简单电路就能制作出任意逻辑电路,那么我们制作一个输入是编号,输出是各个开关的状态的电路,就能实现译码了。
‘
上图为译码器电路。这个电路是用logisim自动生成的。
另外还有一种方法。我们可以把指令保存在一个内存中,指令的编号就是内存的地址,指令控制的开关就是内存的各个数位,也能实现同样效果。并且这么做还保留了改变指令内容的余地。这种方法叫做微指令,也是一种实用的方法。
最后,有时候我们并不想一直顺序执行指令,而是根据条件,跳转到其他地方执行。也就是可能执行完15号指令后,不顺序执行16号,而是跳转到25号继续。编程中的条件跳转语句和循环语句,就是使用这样的跳转实现的。
这里还有一些细节,比如我设计的电路允许分两次读入指令,以便读取更长的指令。(专业点说,叫做变长指令)还有分周期控制取值,pc计算啥的,不详细介绍了。
最后的控制器布局:
(PS:这个电路我自己都已经看不懂了)
- 运行程序
到这里整个CPU已经可以运行了。为了方便使用做了个小编译器,就是把汇编语句翻译到机器码,方便编写指令。好了,现在可以给这个CPU写一些程序让他跑了。
写一个斐波那契数列函数程序来验证一下吧。
源码:
编译结果
这个格式可以导入到logisim中
,然后我们开始运行:
可以看到右边那个内存中已经有了2,3,5,8,13(d)这个斐波那契数列了。完美!
- 附录
- 指令集,8位版
-
- 指令集,16位版
- 指令集,16位版
-
- 相关资料下载
百度网盘
链接:https://pan.baidu.com/s/1J2qqDoV6fd9_iMrwGqZXpQ
提取码:m365