在前几期,我们花了不少的篇幅对ALU的整数运算单元进行了初窥。实际上,ALU中,设计更复杂,占用面积更大的是浮点单元。在早期(1995年以前)的x86处理器中,浮点单元甚至被单独拆出来,作为一颗独立的芯片(8087/80287/80387/80487),成为计算机的可选配置,以降低计算机整机的入门成本。由于浮点数的复杂性,我们将这部分放在最后讲。
而ALU内部为了完成基本的算术和逻辑运算,至少需要以下的运算电路:
我们知道,具体需要ALU执行什么运算,实际上是通过指令码来确定的。
什么是指令码呢?让我们举一个例子:
在ARM V8.2指令集中,每条指令为32bit,其中高10bit (bit 31~bit20)为操作码 1001000100时,为加法指令ADDG,而1001000110代表减法指令SUBG。二者的差别在于bit 21。
再以一种相对简单的ALU:74HC181 为例,74HC181是4bit的ALU,支持16条指令,通过S0-S3这4条线来确定指令码,来决定执行的运算是什么。
也就是说,74HC181芯片内部,到底让加法器、乘法器、其他逻辑运算单元中的哪一个执行操作,是由S0-S3来决定的。假设执行不同指令的硬件单元的启用与否,是通过一个叫CS (Chip Select,片选)的信号来决定的:各单元的CS信号默认为1,而哪个单元收到了为0的CS信号,说明该单元处于工作状态。如指令为加法运算时,加法器的CS为0,其他均为1;
也就是说,要进行这样的转换:
0000 -> 1111 1111 1111 1110
0001 -> 1111 1111 1111 1101
0010 -> 1111 1111 1111 1011
0011 -> 1111 1111 1111 0111
……
这种转换的电路叫做译码器。
最简单的译码器为2-4译码器,其真值表如下:
A | B | Q3 | Q2 | Q1 | Q0 |
---|---|---|---|---|---|
0 | 0 | 1 | 1 | 1 | 0 |
0 | 1 | 1 | 1 | 0 | 1 |
1 | 0 | 1 | 0 | 1 | 1 |
1 | 1 | 0 | 1 | 1 | 1 |
在数字电路相关的教科书上,也可以很容易地找到它的实现:
接下来我们可以用2个2-4译码器组装出3-8译码器:
如图,将3位输入信号的最高位,作为一片2-4译码器的EN信号,同时,最高位经过非门后作为另一片2-4译码器的EN信号,就可以组成一个3-8译码器了。以此类推,还可以实现4-16译码器,5-32译码器等。
对于RISC (精简指令集)处理器,如ARM和MIPS等,其指令操作编码 (opcode)往往较短,6-8bit的译码器就可以够用了。而对于CISC (复杂指令集)处理器,如Intel x64等,指令操作编码会相对长,有可能长达16bit以上,要更为复杂的译码器来执行译码。
特别地,在90年代后期,Intel 引入了MMX,SSE等SIMD(单指令多数据)指令后,CPU的指令数量有了突飞猛进地增加,译码电路也越来越复杂。
Intel的工程师们针对这种现象,给出的解决方案是:
将CISC指令转换为RISC指令,并在CPU内部构造一个RISC处理器来实现各主要指令的执行。
这样,也就实现了所谓的“殊途同归”。
实际上,在CPU中,译码器除了实现指令的译码,还可以实现地址的译码、寄存器编号的译码等。可以认为,译码电路是最基本的电路,在指令执行、寻址等过程中起到了重要作用。
让我们再次复习经典的CPU的流水线结构:
其中,IF代表从内存取指令,ID代表将指令进行译码,EX代表执行指令,MEM代表进行内存读取,WB代表写回内存。
我们在前面的专题中也提到了计算机系统中,各类存储器层级的概念。从下期开始,我们将介绍这些存储器的硬件电路实现。