计算机在没有齿轮的时候是如何负责运算的呢?
ALU就是计算机里负责运算的组件,这篇文章就是教你自己做一个ALU
第一个ALU
1970年,第一个封装在单个芯片内的完整ALU——英特尔74181诞生,这在当时是惊人的工程壮举!
算术单元
二进制中,1=true,0=false
两个数字相加
加法电路半加器(不可处理进位)
两个bit(bit是0或1)相加。
两个输入A B,一个输出为AB的和。这三个值都是单个比特(0或1)
0 0=0
转换为逻辑门就是两个输入都为false,输出也是false。和XOR逻辑门一致
1 0=1,0 1 =1
转为逻辑门就是一个输入true,一个输入false,输出为true。这个也和XOR的逻辑门一致
(特殊)1 1=0 需要进位
1 1的结果是0,1进到下一位。可以看到XOR的逻辑门并不支持这个(输入都为true输出为false只对了一部分)还需要一个输出线用于表示进位,所以我们需要稍加修改
根据上面的情况,新加一个输出线表示进位数字。这个进位的输出只有在1 1的时候才会输出1( 输入都为true的时候他才会为true)这个是不是很像AND的逻辑门。
把输入的两个线接到专门用于处理进位的AND逻辑门中,这样一个一位的加法器(此时不能处理进位的输入,也叫做半加器)就做好了
抽象封装为独立组件
将其封装为一个单独的组件。
两个输入AB,两个输出SUM表示总和,CARRY表示进位(下一位计算的时候要把上一位的进位加上)
全加器(可处理进位的加法器)
刚刚提起到进位:下一位计算的时候要把上一位的进位加上,上面只是完成了一个比特的加法(不需要处理进位输入的加法),如果有多位需要进行运算这时候就需要多一个输入进位了。这种需要进行三个输入的加法器叫做全加器用于计算进位的。
看下全加器的运算表格:
两个输入AB还有一个是上一个的进位C
两个输出一个代表进位用于给下一个加法器作为下一个加法器的C,SUM代表这一位的计算结果
实现思路
输出SUM
先来看看我们人是怎么做计算的:先对两个输入AB相加然后在和进位相加。第一步的计算是单个比特进行相加,第二步的结果和进位相加也是单个比特进行相加,所以我们组装两个半加器。
第一个半加器用于计算结果(前两个输入:AB) ;
第二个半加器接受上一个半加器的结果和进位作为输入(上一个半加器的SUM和进位【第三个输入】)
输出CARRY
首先来看下需要进位的两个case:
1.两个输入本身就需要进位(AB都为1的情况,1 1本身就需要进位)。这种情况不就是第一个加法器的进位嘛~~
2.两个AB的计算结果是1进位也是1也需要进位。这种情况不就是第二个加法器(AB的输出和进位相加)输出的进位嘛~~
可以看到两个条件有一个成立,这时候就需要进位。因此使用OR门连接到两个半加器的进位相连输出到全加器的CARRY输出中
解释:
注意这仍然是一个一位的加法器只不过是比半加器支持多输入一个进位,这种加法器叫做全加器。
验证的时候是这么个流程:左边的AB是输入,右边的CS是进位和总和。第二个加法器的输入AB(其实不是输入的AB代表的是第一个加法器的SUM和输入的C)不要搞混了
读者可以配合上面的全加器表格输入ABC,进行验证最后对应的SUM和CARRY是否正确
抽象封装为独立组件
比半加器多了一个输入进位
制作八位加法器
上面所说的是一位的加法运算。多个位就组合多个加法器就行。
undefined
表示数字:
A,B代表两个八位数字作为输入,A0,A1,A2...用于表示第几位。
undefined
进行计算:
显然A0和B0由于没有进位所以使用半加器运算即可。
接着用全加器链接A1B1和A0B0的进位 这三个作为输入进行运算。
第三位,第四位以此类推.....
看图:第一位的计算使用半加器之后使用全加器(注意全加器的输入C是上一个加法器的进位输出)
不断的进行计算,最后将sum0,sum1,各个位上的总和按顺序排列就是结果
八位加法器意味着计算机是以8位长度来表示处理数据块的,也就是八位计算机
溢出
当第九位还有进位(也就是carry7为1)代表两个数字的和太大了,超过了8位
比如999 1第三位输出进位为1也就是第四位的进位为1.结果是1000超过了三位。这个就是溢出
一般来说”溢出“的意思是两个数字的和太大了超过了原来表示的位数,这回导致错误和不可预期的结果
“吃豆人”溢出事件
吃豆人游戏用8位存当前关卡数,如果你玩到第256关(8位bit最大表示是256),ALU就会溢出 【因为需要9位去表示这个关卡数,8位表示的数已经不够了对比上面的例子就是需要加一位1000来表示】 造成一系列错误和乱码,使得关卡无法进行。
这个bug成为了厉害吃豆人玩家的代表
避免溢出
避免溢出的方式也很简单,和上面举得999 1的例子一样,就是再加一位,也就是意味着需要再加一个全加器进行运算多出来的那一位(999 1用的是三位的加法,如果要变成四位1000就需要支持第四位的加法)。
举的例子是十进制的,读者明白意思即可
因此解决方案就是加更多全加器,可以操作16或32位数字,让溢出更难发生,但代价是更多的逻辑门,而且每个进位的运算都需要一点时间,虽然很快但是现在这个时代是每秒几十亿次计算,再小的延时都会被放大。
超前进位加法器
现代计算机用的加法电路有点不同,使用的是超前进位加法器。它更快做的事情也是一样的,二进制数相加。
其他数学运算
ALU还支持其他数学运算,一般是下面的八种.和加法器一样也是通过逻辑门构成
乘除法
简单的ALU电路没有乘除法,而是把乘法用多次加法来实现。
比如12X5和把12加五次是一样的,要经过五次ALU运算才能得出结果
虽然慢但是能用!哈哈,简单的处理器都是这样搞得,比如电视遥控器,微波炉,恒温器。
但是计算机和手机有更好的处理器,专门处理这种复杂操作的算术单元 ,其本质都是更多的逻辑门组装
逻辑单元
逻辑单元执行逻辑操作,比如之前的AND,OR,XOR简单逻辑,但是他也能做一些复杂的逻辑判断
下面这个电路是用来判断输出的数字是否位0的电路,当数字为0时输出为true1
通过OR门只要有一个1最后的输出就是0false因为该数字中有一个bit是1
抽象封装为独立组件
首先INPUTA,INPUTB是两个八位数字的输入(因为我们是八位的ALU)
OPERATION CODE(操作码):用四位的操作码表示让ALU进行怎样的运算,比如加法ADD是1000,减法是1100
OUTPUT就是输出结果同样也是8Bit的,那么如何知道是否溢出呢?别着急下面就会列出来
右边的FLAGS是1位(1Bit)代表的是某种特定状态(下面这三种状态是最常用的,高级的ALU会有更多FLAGS)
- OVERFLOW溢出标志代表的就是是否溢出。这条线是连接在加法器的进位上的
- ZERO代表的是结果是否为0( 通过上面那个判断数字是否为0的逻辑单元运算)
- NEGATIVE代表的是负标志,如果A-B小于0那么就是1
总结
好了,经过上面的讲解,你已经做出了一个可以处理八位的ALU(算数单元和逻辑单元) ,而文章开篇提到的英特尔74181只能处理四位的ALU,你做出来一个比英特尔还好的ALU!虽然没有完整造出来,但是理念和原理你都已经清楚了。
74181有70个电路,不能处理乘除运算,但是它向小型化迈出了一大步,使得计算机更小更便宜
接下来的文章就是要利用到这个ALU去做一个CPU,但在此之前,计算机需要”内存“来记录这些计算后的结果,如果只是计算后就扔掉没有多大意义,我们需要将结果存储起来以便后面使用。因此我们下一篇文章讲解内存模块
我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表