课程链接:
计算机组成_北京大学_中国大学MOOC(慕课)
5 - 1 - 501-处理器的设计步骤(14-'49--)_哔哩哔哩_bilibili
处理器,或者说是CPU,是现代计算机中最为复杂的一个部件。不过先不要劝退,要设计一个简单但是能工作的处理器,也没有那么的神秘。这一节我们就一起来探索处理器是怎么设计出来的。
0. 处理器的设计步骤
要设计一个处理器,主要分为以下几个步骤。
1. 首先,需要分析指令系统。指令系统是在处理器设计之前就由软件和硬件的设计人员共同协商决定的,关于指令系统,可以参考这篇博客:【计算机组成 课程笔记】2.1 设计自己的计算机_Elaine_Bao的博客-CSDN博客。通过分析指令系统,我们可以得出指令所要操作的数据需要通过怎样的一个电路结构,这就是数据通路。
2. 在我们得到这样的需求之后,我们就可以为数据通路选择合适的集成电路组件,比如加法器,减法器,寄存器等。
3. 选好了合适的组件之后,我们就按照最开始分析出的需求把这些组件连接起来,就构成了完整的数据通路。
4. 但是仅有数据通路是不够的,我们还要控制这些数据通路应该如何工作。因此第4步,需要分析每条指令的实现,以确定控制数据通路工作的控制信号。
5. 最后是把这些控制信号汇总起来,形成完整的控制逻辑,也可以称之为控制器。
下面我们还是以MIPS指令系统为例进行讲解。不过整个MIPS指令系统的指令还是太多了,我们进行一个简化,只考虑以下几条指令:
假设这就是我们当前用来设计处理器的指令系统。那么我们下面就要分析这个指令系统对数据通路有什么样的需求。
1. 指令系统对数据通路的需求
首先我们对指令的位域进行分解,来看看各个指令的含义。
对于R型指令,它一共分为6个位域,最高的6-bit是操作码,接下来连续的3个5-bit的位域各自标明了一个寄存器的编号。然后的5-bit在完整的MIPS指令系统中是用作标记移位的数量,而在咱们简化后的版本中没有用到,因此我们可以看成是保留的位域。最后6-bit是功能位域。因此,当我们取到一条R型指令后,就可以将它分解为以上的6组控制信号。
与之类似,I型指令包含了4个位域,也就可以被分解为4组控制信号。
而且我们要注意,这些指令的编码都是从存储器中取得的,因此我们首先需要一个存放指令的存储器。对于指令存储器来说,它不需要支持写入的功能,只要可读就可以了,而且我们希望对于这个存储器,外界给它32位的地址,它就会给出对应的32位的数据。
那么这个32位的地址又是从哪里来呢?这就是我们的另一个需求,我们需要一个存放指令地址的32位寄存器,称为PC,也就是程序计数器。
满足了这两个需求,我们就可以取得想要的指令了。
然后我们再从指令的操作,来分析其他的需求。
首先来看加法和减法指令。
这两条指令的主体功能都是选择两个不同的寄存器,对它进行加法或减法的运算,然后将结果存入另一个寄存器。因此我们首先需要有一组存放数据的通用寄存器,每个寄存器都是32位的,这样一组通用的寄存器我们称为寄存器堆。从加法和减法的指令我们还可以看出,在运算时我们需要同时读取两个寄存器的内容,并写入一个寄存器(两读一写)。
再来看一下立即数的逻辑或指令。
在运算时,它只需要读取一个寄存器的内容,另一个操作数是一个立即数,其中16位是直接填写在指令位域当中的,但是我们的运算是32位的,因此这里还有一个需求是将16位立即数扩展到32位。
上述3条指令都是运算指令,因此我们还要支持不同的运算类型的运算器。这个运算器的操作数既可以是寄存器,也可以是扩展后的立即数。
这些就是运算指令的需求了。
我们再来看一下访存指令。
对于LOAD指令来说,它需要从存储器中读出一个字,而这个字所在的存储单元的地址是由一个寄存器的内容+一个立即数的符号扩展。取出这个字之后,会存放到寄存器堆当中由rt指定的寄存器。
与之相对的还有STORE指令,它是将rt寄存器的内容,写入到存储器中。
对于这两条访存指令,我们的需求是:首先我们需要一个存放数据的存储器,这个存储器既要可读也要可写,它的地址输入以及输入输出的数据都是32位的。另外这个地址的计算中需要堆立即数进行符号扩展。这就是访存指令的主要需求。
最后我们来看分支指令。
对于分支指令,首先要判断两个寄存器中的内容是否相等,如果相等就将指令位域中立即数的部分经过变换加到PC上,从而得到新的PC。如果不等则新的PC=PC+4。
因此分支指令的需求,首先是要能否比较两个寄存器的内容,并判断是否相等。然后还需要PC寄存器支持两种自增的方式。一种是+4,一种是+一个立即数。前者对于之前提到的其他指令也都是需要的。
2. 根据需求选择合适的组件
我们把之前提到的需求总结一下就可以得到对组件的需求。
选择了上述合适的组件后,我们就可以开始着手建立数据通路的工作了。
3. 连接组件建立数据通路
要建立数据通路,基本原则是分析指令系统中的每一条指令,根据其需求连接组件,从而建立数据通路。那么指令的需求又分为两大类,一类是所有指令的共同需求,另一类是不同指令的不同需求。
首先我们来看所有指令的共同需求。
首先需要取指令。指令是放在存储器中的,要从存储器中取得指令,需要一个地址,这个地址则是存放在PC寄存器中。我们已经有了一个32位的PC寄存器,我们就把PC寄存器的输出连接到指令存储器,而指令存储器则根据地址的输入,选中对应的存储单元,并将其内容输出。这样我们就得到了所需指令的二进制编码。
那除了取得当前的指令之外,我们还需要为下一条指令做准备,这就需要更新PC寄存器。这又分成了两种情况。
大多数时候,指令是顺序执行的,这种情况下PC只要加上当前指令的长度,就可以得到下一条指令的地址。在MIPS指令系统中,每一条指令都是4个字节的,所以PC=PC+4。那在我们刚才的结构上面,需要增加一个简单的加法器,实现PC+4的操作。这样,当前PC寄存器的内容,既会送到指令存储器,获得指令编码,又会送到加法器,从而计算出一个PC+4的值,在下一个时钟上升沿来临时,PC寄存器就会将PC+4的值存入其中,然后再将这个更新后的内容送到指令存储器和加法器,如此周而复始。
而如果遇到分支指令,那下一条指令的地址就不是简单的PC+4,而是由分支指令进行指定。因此还需要继续修改这个结构。我们需要增加一个二选一的多选器,在顺序执行时,我们选择这个多选器的0号输入端,在发生分支时,选择1号输入端,也就是由分支指令指定的目标地址。那在下一个时钟上升沿到来的时候,PC寄存器就会采样这个多选器的输出,并将其保存起来。
这样一个结构就完成了不断取指令的功能,我们把它称为取指部件(Instruction Fetch Unit, IFU)。IFU作为一个整体,同外界只有一个时钟信号clk的输入,和一个多选器选择信号nPC_sel的输入,并且提供一个指令编码Instruction Word的输出。
我们只用在系统启动时给PC寄存器一个合适的初始值,并在指令存储器中存放好我们需要运行的指令,然后在运行过程中给出合适的多选器的选择信号,这个IFU就可以在时钟信号的驱动下,自动地连续工作起来了。
这些就是所有指令的共同需求。
然后我们再根据指令的不同类别,分析它们的各自需求。
首先我们来看加法和减法指令。这两个指令是R型执行,读写的都是寄存器,所以我们需要寄存器堆这个组件。指令中读取的寄存器是rs和rt,写入的寄存器是rd,所以我们只需要把这3个位域的值连接到寄存器堆的输入上(Ra,Rb,Rw),这样在寄存器堆的输出端,busA就会输出rs寄存器的内容,busB则会输出rt寄存器的内容。然后我们将busA和busB连接到ALU的输入端,并且我们根据指令编码中的操作码和功能位域就可以知道当前是加法还是减法指令,通过ALUCtr控制信号来选择当前ALU提供的运算的类型。然后还要将ALU的输出连接到寄存器堆的输入端,也就是busW。在下一个时钟上升沿到来的时候,如果寄存器堆的写使能信号RegWr是有效的,busW信号上的内容就会写入rd所指定的寄存器中。这样,我们就在一个时钟周期完成了一条加法或减法的指令。
然后我们来看逻辑运算指令的需求。这是一条I型指令,那么刚才建立的数据通路,要满足这条指令的运算,还存在几个问题:1. 目的寄存器是rt而非rd,2. ALU的输入包含一个立即数,3.立即数只有16位。
针对这几个问题,我们要把数据通路进行一个改造。针对问题1,增加一个二选一的多选器,来选择目的寄存器是rd还是rt。针对问题2,增加一个二选一的多选器来选择ALU的输入来自于busB(rt寄存器)还是立即数。针对问题3,增加一个零扩展部件对立即数进行扩展。
这样我们就通过增加两个多选器和一个零扩展部件来满足逻辑运算指令带来的新需求。
然后我们再来看访存指令的需求。访存指令也是I型指令。先来看其中的LOAD指令。LOAD指令中存入的地址是rt寄存器,这个需求已经被满足了。而访问的地址是将rs寄存器的内容+立即数的符号扩展。这里存在的问题是:1. 目前还不支持符号扩展,2. 这个计算得到的地址应该是去访问存储器,从而获得数据,而不是直接连到寄存器堆的写入端。
针对这两个问题,我们继续对数据通路进行改造。针对问题1,将原来的零扩展的部件改造为一个多功能的扩展部件,通过控制信号来控制是进行零扩展还是符号扩展。针对问题2,新增一个数据存储器,这个存储器根据地址就可以得到对应的存储单元中的数据。另外增加一个多选器,控制写入到寄存器中的数据是来自于ALU的输出还是来自于数据存储器。
再来看一下另一条访存指令,STORE指令。这条指令会把rt寄存器的内容写入到数据存储器中。所以数据存储器需要接收一个输入,来自于busB(rt寄存器),当控制信号MemWr有效时,会进行写入的操作。这样我们就满足了STORE指令的需求。
现在除了比较特殊的分支指令之外,我们已经分析了其他指令的需求,并将各个组件连接了起来。再加上之前已经构造的IFU部件,我们就初步完成了数据通路的建立工作。
现在在处理器的设计步骤中,我们已经完成了前三步。后两步将会在后续博客中介绍。