这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战。
大家好,我是程栩,一个即将入职鹅厂的校招新人。本系列文章将会以一个演进的视角介绍计算机组成原理,并以一个采取Mips指令集的CPU作为范例。
指令设计
在正式设计之前,聪明的你准备梳理一下需求,将目的明确为8位数据的以下的一些运算:
- 加:两个运算数一个结果
- 减:两个运算数一个结果
- 乘:两个运算数一个结果
- 除:两个运算数一个结果
- 与:两个运算数一个结果
- 或:两个运算数一个结果
- 非:一个运算数一个结果。
同时,需求也从加法器(add)变成了算逻单元(alu)。在确定了需求以后,聪明的你决定用以下的方式来表示命令:
运算 | 命令 |
---|---|
加 | 0000 |
减 | 0001 |
乘 | 0010 |
除 | 0011 |
与 | 0100 |
或 | 0101 |
非 | 0110 |
之所以使用四位来表示命令,是因为聪明的你考虑到了未来可能会有新的命令需要加入,提前留出了空间。
所以对于类似加法的指令就变成了:
0000-00000000-00000001 // 1+1
那么自然而然的,类似非运算就变成了:
0110-00000000 // ~1
Verilog描述
那么落实到Verilog上,可以用如下的方式来进行描述:
module alu(out, opcode, a, b);
output[7:0] out; //结果
input[3:0] opcode; //命令
input[7:0] a, b; //运算数
always@(opcode or a or b) begin
case(opcode)
4'b0000: out = a + b;
…… //此处省略类似加的二元运算
4'b0110: out = ~a;
endcase
end
endmodule
从代码上看,我们可以看到,通过一个case语句来判断了opcode的类型,然后进行相关的运算再进行输出。
如果从电路图上看,大致是这样的:
如果我们尝试去窥探alu内部的实现,我们需要比较多的数电的知识。首先数电并不存在加这类的运算,都是通过与、或、非这些运算来实现的,例如我们看一个加法器的电路图:
可以看到这里都是由一些与或非门来实现的,我们的alu也是通过这种组合电路的方式实现的。
那么一个很自然的问题就会出现,随着我们的指令的变多,我们的电路必然会越来越复杂,不论是为了进行命令的判断,还是为了执行命令,都需要更多的电路做支撑。
假设我们需要一条特别专用的指令,我们也可以用命令加数据的方式实现,例如我们可以在这里加一个计算函数x+1的命令,命名为addOne,opcode为0111,单输入单输出。
CISC
通过不断地叠加新的命令和电路的方式来拓展指令,是传统的CISC(Complex Instruction Set Computer:复杂指令集)的做法,通过硬件的方式来实现指令,即使会出现像我们前面提到的专用指令的情况。我们经常说的x86就属于CISC。
那么随着指令的发展,电路就会越来越复杂。聪明的你想到虽然在学习代码的过程中学习了很多的特性和关键词,但是只有一些会被经常使用,例如:for、while等。
是的,这就是经常说的马太效应:可能有百分之20的指令在百分之80的时间里被使用,而剩下的百分之80的指令只在百分之20的时间里使用。为了百分之20的指令而让架构不断的臃肿,这显然是一种巨大的浪费。
此外,随着指令的变多,硬件也会越来越复杂,需要的成本就越高,于是聪明的你在想了有没有其他的指令设计方式呢?
CISC?RISC?
在数电里我们说,其实大部分的电路也是通过逻辑电路的组合实现的,比如我们的加法,其实也是由很多的与或非运算来实现,那么我们是不是可以把加法指令转变成这些运算呢?
那么我们可以怎么实现这样的转变呢?
一种方式是通过微指令的方式来实现,当我们的计算机获取到一条指令时,在硬件上对这个指令进行解码并转换成微指令,再由计算机去运行这些微指令。
另一种方式则是通过指令的方式来实现,在编译的时候就直接把比较复杂的指令转换成多条简单的指令,再传入到计算机中进行运行。
前者是CISC的做法,而后者则是RISC(Reduced Instruction Set Computers:精简指令集)的做法,我们经常听到的ARM、Mips都从属于这一阵营。
从指令的角度来看,RISC指令集是一种很学院派的指令集,它是由很少的一系列定长的指令组成,通过指令的组合来实现复杂的操作,这就使得RISC指令集编译后的长度普遍偏长。但是因为RISC在编译的时候就直接将指令变成了多条简单指令,在运行的时候并不需要去做一个解码的工作,所以运算速度会快一些。但是RISC仍然在很长的一段时间里面临着商业上的失败。
然而随着时间的推移,两种指令集也互相学习,不断地进行更新,这是后话。
偏长的指令集?
那么为什么RISC指令集编译后的长度普遍偏长呢?聪明的你陷入了沉思……
后记
今天的文章只是对RISC和CISC做了一些简单的阐述,如果想了解更多的同学可以自行检索相关资料。未来我们将会更多的基于RISC做讲述,在整体的架构讲完以后,会加入一些微架构的知识。
从整理的讲述思路上,个人希望能够以设计者的角度来讲述,但是因为能力问题,在整体的讲述过程中还是有些磕磕绊绊,希望大家可以多提一些建议。
我们明天再见!