这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战。
大家好,我是程栩,一个即将入职鹅厂的校招新人。本系列文章将会以一个演进的视角介绍计算机组成原理,并以一个采取Mips指令集的CPU作为范例。
指令长度
为什么RISC的指令长度会长于CISC呢?
首先我们要考虑到RISC的指令是定长的,例如所有的指令都是32位,其中假设有10位是数据位,其他的位数用来表示是什么命令。基于我们前面考虑到的多运算数的情况,RISC为了保证指令的定长,总是会以多运算数为基准。例如我们现在最多允许两个运算数,那么10个数据位可以分成两个5位的数据,如下图:
中间的两个五位数是作为数据位而存在,那么无论这两个位是否被使用,这10位的空间总是存在的。
反观CISC,因为允许变长的存在,所以如果有些变量不需要的话,大可以将其丢弃,那么这样整体的码长就变短了。
同时,在设计指令码的时候,我们可以引入权重的概念,聪明的你一定会想到哈夫曼编码。是的,我们可以根据指令命令码的出现频率构建一棵带权重的哈夫曼树,从而实现出现频率高的操作码长相对较短,而频率低的操作码码长相对较长,这样也可以降低整体的码长。
指令与硬件
在设计指令的过程中,聪明的你意识到了很多问题,譬如我们的alu只能够进行8位二进制的运算,不能保存数据,也不知道指令的来源,这和我们生活中的计算机相差甚远。于是你决定为这个alu添加更多的部件让它变成更强大。
作为软硬件的接口,指令集的变化和硬件的变化是一致的,所以随着硬件的变化,指令集也需要有相应的变化。
为了能够更好地设计,我们在这里假设这是一个32位的机器,无论是指令的长度还是数据的长度。
指令存储顺序执行
聪明的你首先想到了冯诺依曼结构中的指令存储与顺序执行的想法,那么指令该存在哪里呢?
当然是给它找个地方存储啦。比如像下面这样的地方:
我们给这样一个存储指令的地方命名为指令存储器(IM:Instruction Memory)
那么自然而然的,为了能够做到顺序执行,我们需要有一个指向地址的门牌,这样才能够一条条的获取到指令。我们将这样的数据存储在一个专门的模块里,每次执行拿去以后就根据运行的情况跳转到下一条指令。我们将它命名为程序计数器(PC:Program Counter)。如下图:
如图所示,PC根据上次的指令计算出下一次的指令地址,然后将这个地址传输给我们的IM,在IM里获取到对应地址的数据,将这个数据再传出去,交给计算机进行运算。
问题来了:下一条指令是存储上的下一条指令吗?
我们不妨想想平常写的代码,难道都是一行一行顺序运行的吗?当然不是这样的,我们总是会有if、for这样的语句来控制语句的执行,所以其实我们的PC并不仅仅受到上一条指令的地址影响,还需要考虑到当前运行的指令,如下图所示:
数据存储
我们写的程序,总是会存储很多的数据,比如如下的代码:
a = 1;
b = 2;
a = a + b;
如果我们不能存储a和b的值的话,第三句话就无法正确的运行,所以我们自然需要一个地方来存储数据。我们模仿IM的设计,设计一个数据存储器(DM:data memory),在这里面存储的是数据。如下图:
需要注意的是,我们在前面设计的数据长度也是32位,所以自然这里的位宽也是32位,而里面的数字而可以看成是存储的二进制的十进制的值。
命令
现在我们加入了三个模块,可以分别进行指令存储、数据存储、指令获取。那么这样的计算器可以运行了吗?
我们不妨用以下的代码尝试一下:
#include<stdio.h>
int main(){
int a = 0;
if (a == 1){
return 1;
}
return 0
}
似乎出现问题了,我们不能正确的处理if这样的跳转语句。因为我们根本没有规定这样的指令。
而同时我们想到前面我们的PC不止受到上一条指令影响,所以我们应该有更多的指令,同时似乎应该有更多的部件。一方面我们需要能够解析更多的指令,因为我们的alu并不需要应该处理这些指令;另一方面,我们针对指令做更多的分类,不止是运算指令,还应该有像跳转这样的指令。
那么我们该怎么做呢?
后记
事实上,昨天是写的最没有感觉的一天,今天没有被推荐上首页也是一种证明。从今天开始,会尝试更多的以具体的例子去进行讲解,希望能和大家一起进步。
我们明天再见!