概述:

  • 简单介绍ALU,因特尔74181
  • ALU有2个单元,1个算术单元和1个逻辑单元
  • 算术单元
    • 半加器(处理1个bit,2个输入)
    • 全加器(处理1个bit,3个输入)
    • 8 bit加法(1个半加器,7个全加器)
    • 溢出的概念,吃豆人例子
    • 乘法、除法
  • 逻辑单元
    • 检测数字是否为0的电路(一堆OR门最后加个NOT门)
    • ALU抽象成一个V符号
    • Flag标志(是否相等,是否小于,是否溢出等等)

上篇教程,我们讲了如何使用二进制表示数字。比如二进制00101010是十进制42,表示和存储数字是计算机的重要功能。但真正的目标是计算,有意义地处理数字(or manipulating numbers in a structured and purposeful way),比如把两个数字相加,这些操作由计算机的“算术逻辑单元”(Arithmetic and Logic Unit)处理,但大家会简称ALU。ALU是计算机的数学大脑,等你理解了ALU的设计与功能后,你就理解了现代计算机的基石。ALU就是计算机里负责运算的组件,基本其他所有部件都用到了它。
首先来看看这个美丽的东西(look at this beauty),这可能是最著名的ALU,Intel 74181。1970年发布时,它是第一个封装在单个芯片内的完整ALU,这在当时是惊人的工程壮举(huge engineering feat)。今天我们用上周学的布尔逻辑们,做一个简单的ALU电路,功能和84181一样。然后接下来几集,用它从头做出一台电脑,所以会有点复杂,但我觉得你们可以搞定。
ALU有2个单元,1个算术单元,1个逻辑单元。我们先讲“算术单元”,它负责计算机里的所有数字操作,比如加减法,它还做很多其他事情,比如给某个数字+1,这叫做增量运算,之后会说。今天的重点是一切的根本,“把两个数相加”,我们可以使用单个晶体管一个一个地拼,把这个电路做出来,但很快就会复杂的难以理解,所以与其用晶体管,不如使用更高层的抽象,用逻辑门来做。我们会用到AND,OR,NOT和XOR逻辑门,最简单的加法电路,是将2个bit加在一起(0或1),有两个输入:A和B,1个输出:就是两个数字的和,需要注意的是:A,B,输出,这三个都是单个bit(0或1),所以输入只有四种可能,前三个是:

  • 0+0=0
  • 1+0=1
  • 0+1=1
    记住二进制里,1和true相同,0和false相同,这组输入和输出和XOR门逻辑完全一样,所以我们可以把XOR用作1位加法器(adder)。
    但是第四个输入组合,1+1是个特例,1+1=2,但二进制里没有2,二进制1+1的结果是0,1进到下一位,结果是10(二进制)。XOR门的输出,只对了一部分,1+1输出0,但是我们需要一根额外的线代表“进位”,只有输入是1和1时,进位才是true,因为算出来的结果用1个bit存不下,方便的是我们刚好有个逻辑门可以做这个事儿,AND门。这个电路叫做“半加器”(half adder)。没那么复杂,就两个逻辑门而已。让我们抽象化,把半加器封装成一个单独组件,两个输入A和B都是1位,两个输出SUM和CARRY,即“总和”和“进位”。
    如果想处理超过1+1的运算,我们就需要“全加器”了。半加器输出了进位,意味着,我们计算系一列时,还有之后的每一列,我们得加3个位在一起,并不是2个。全加器复杂了些,有三个输入:A,B,C(都是1个bit),所以最大的输入值是1+1+1的情况,SUM总和是1,CARRY进位是1,所以两条输出线:“总和”和“进位”。我们可以用半加器实现全加器,我们先用半加器将A和B相加,然后把C输入到第二个半加器,最后用一个OR门检查进位是不是true,这样就做出了一个全加器。我们可以再提升一层抽象,把全加器作为独立组件,全加器会把A,B,C三个输入加起来,输出“总和”和“进位”。
    现在有了新组件,我们可以相加两个8位数字,叫做A和B。我们从A和B的第一位开始,A0和B0,现在不用处理任何进位,因为是第一次加法,所以我们可以使用半加器,来加这两个数字,输出结果是sum0。现在加A1和B1,因为A0和B0的结果有可能进位,所以这次使用全加器,输入除了A1和B1,还要连上A0和B0相加的上进位,输出叫sum1。然后把这个全加器的进位连到下个全加器的输入,处理A2和B2,以此类推,把8个bit都搞定,注意每个进位是怎么连到下个全加器的,所以叫做“8位行波进位加法器”(8-bit ripple adder)。注意最后一个全加器有“进位”的输出,如果第9位有进位,代表2个数字的和太大了,超过了8位,这叫做“溢出”(overflow)。一般来说,“溢出”的意思是,两个数字的和太大了,超过了表示的位数,这会导致错误和不可预期的结果。
    著名的例子是,吃豆人用8位存当前关卡数,如果你玩到了第256关(8位bit最大表示255),ALU会溢出,造成一连串错误和乱码,使得该关卡无法进行,这个bug成了吃豆人玩的厉害的玩家的代表。如果想避免溢出,我们可以使用更多的全加器,可以操作16或32位数字,让溢出更难发生,但代价是跟多的逻辑门,另一个缺点是,每次进位都要一点时间。当然时间不久,因为电子移动地很快(electonics move fast)。但是如今的量级是每秒几十亿次运算,所以会造成影响。所以,现代计算机用的电路加法电路有点不同,叫做“超前进位加速器”(CARRY LOOK AHEAD ADDER),它更快,做的事情是一样的,把二进制相加。
    ALU的算术单元,也能做一些其他数学运算,一般支持8个操作,就像加法器一样,这些操作也是由逻辑门构成的。有趣的是,你可能注意到没有乘法和除法,因为简单的ALU没有专门的电路来处理,而是把乘法用多次加法来实现,假设想算12x5,这和把“12”加5次是一样,所以5次ALU操作来实现这个乘法,很多简单处理器都是这样做的。比如恒温器,电视遥控器和微波炉,慢是慢,但是能搞得定。然而笔记本和手机有更好的处理器,有专门做乘法的算术单元,你可能猜到了,乘法电路比加法复杂,没有什么魔法,只是更多的逻辑门。所以便宜的处理器没有这个特性。
    好了,我们现在讲ALU的另一半:逻辑单元,LOGIC UNIT
    逻辑单元执行逻辑操作,比如之前讨论过的AND,OR,NOT操作,它也能做简单的数值测试,比如一个数字是不是负数,例如这是检查ALU输出是否是0的电路,它用一堆的OR门检查其中一位是否是1,哪怕只有一个bit是1,我们就知道这个数字肯定不是0,然后用一个NOT门取反,所以只有输入的数字都是0,输出才是1。以上就是ALU的一个高层次概括。我们甚至从零做了几个主要组件,比如行波进位加法器,它们只是一大堆逻辑门巧妙连在一起而已。
    让我们回到开始提到的ALU,Intel 74181,和我们做的8位ALU不同,74181只能处理4位输入,也就是你刚刚做了一个比Intel 74181还好的ALU,其实,差不多。我们虽然没有全部造出来,但是你了解了整体概念。74181用了大概70个逻辑门,但不能执行乘除,但是它向小型化迈出了一步,让计算机可以更加强大且便宜,4位ALU已经要很多逻辑门了,但是我们的8位ALU会需要数百个逻辑门,工程师不想在用ALU的时候去想那些事情,所以想了一个特殊符号来代表它,看起来像是一个大“V”,又是一个抽象。
    我们的8位ALU有两个输入,A和B,都是8位(bits),我们还需要告诉ALU执行什么操作,例如加法或减法,所以我们用4位的操作代码,简而言之“1000”可能代表加法命令,“1100”代表减法命令。操作代码告诉ALU执行什么操作,输出结果都是8位,ALU还会输出一堆标志(flag),标志位是1位,代表特定状态,比如相减两个数字,结果是0,我们之前说的测试是否是0的电路会将零标志(ZERO)设置为ture(1),如果想知道这两个数字是否相等,这个非常有用。如果想知道A是否小于B,我们可以用ALU来计算A-B,看负标志(NEGATIVE)是否是true(1),如果是true,代表A是小于B的。最后,还有一条线连到加法器的进位,如果有溢出,我们就知道,这叫做溢出标志(OVERFLOW)。高级ALU有更多标志,但这个3个标志是ALU普遍使用的,其实,我们之后的教程会用到它们。
    现在你知道了,计算机怎么在没有齿轮或杠杆的情况下进行计算,接下来我们会使用ALU做CPU,但是在此之前,计算机需要一些“记忆”(memory),下篇教程见。 https://www.bilibili.com/video/BV1EW411u7th?p=5