6.2 数据通路的建立
计算机组成
6 单周期处理器
6.2 数据通路的建立
现在一方面我们已经有了指令系统的总体需求,另一方面,我们也准备好了几个关键的组件。那现在我们就来一起看一看能否根据这些需求,将这些关键的组件拼合起来,构造出一个完整的数据通路。
要建立一个数据通路,基本原则是分析指令系统当中每一条指令的需求,并根据指令的需求连接我们已经选好的组件。那么指令的需求又分为两大类,有一些需求是所有指令所共同需要的;另外,不同的指令也有一些各自特殊的需求。
首先,我们来看所有指令的共同需求。
我们要执行指令,首先就需要取指令。我们知道,指令是放在指令存储器当中的,而我们要访问指令存储器取得指令,就得有一个地址,这个地址则存放在PC寄存器当中。我们已经有了一个32位的PC寄存器,那我们把PC寄存器的输出就作为指令的地址,连接到指令存储器,而指令存储器则会根据地址的输入选中对应的存储单元,并将其内容输出,这样我们就得到了我们所需的那条指令的二进制编码。
那除了取得当前的指令之外,我们还得为取下一条指令做好准备,这就需要更新PC寄存器。这又分成了两种情况:
大多数时候指令是顺序执行的。在这种情况下,PC只需要加上当前这条指令的长度,就可以得到下一条指令的地址了。在MIPS指令系统当中,每一条指令都是4个字节的,所以只需要简单地进行PC加4的运算。那在我们刚才的这个结构上面,我们需要增加一个简单的加法器,其中一个输入是PC寄存器的输出;另一个输入是一个常数4。然后我们将这个加法器的输出连接到PC寄存器的输入。这样,当前PC寄存器的内容既会送到指令存储器,以获得指令的编码;也会送到加法器的输入,从而计算出一个PC+4的值。在下一个时钟上升沿到来的时候,PC寄存器就会将PC+4的值存入其中,然后再将这个更新后的内容,同时送到指令存储器和加法器。如此周而复始,就完成了每个时钟上升沿时,更新PC寄存器的内容,然后指令存储器就会送出新一条指令的二进制编码。
当然我们这说的是顺序执行时的情况,而如果遇到分支指令,那下一条指令的地址就不是简单的PC+4,而是用分支指令进行指定。因此我们还需要继续修改这个结构。
我们需要增加一个二选一的多选器。在顺序执行时,我们选择这个多选器的0号输入端,也就是PC+4的内容;而在发生分支时,我们就选择这个多选器的1号输入端,也就是由分支指令指定的目标地址。那在下一个时钟上升沿到来的时候,PC寄存器就会采样这个多选器的输出,并将其保存起来。
那这样一个结构,就完成了不断取回指令的功能。
我们把它称为取址部件,也简称为IFU。IFU作为一个整体,从外界只有一个时钟信号的输入和一个多选器选择信号的输入,并且提供一个指令编码的输出。我们只用在系统启动时,给PC寄存器一个合适的初始值,并在指令存储器中存放好我们需要运行的指令,然后在运行的过程中,给出合适的多选器的选择信号。这样这个IFU就可以在时钟信号的驱动下自动的连续工作起来了。
这些就是所有指令的共同需求。
然后我们再根据指令的不同类别,来分析他们的各自需求。
首先,我们来看加法和减法指令。
这两条指令都是R型指令,它的主体操作是先用rs和rt这两个5位信号,访问寄存器堆获得两个寄存器的内容,并对他们执行相应的运算,这个运算目前我们提供了加法和减法两种。然后,将运算的结果放到另一个5位信号rd所指定的寄存器当中。因此,我们就需要用到寄存器堆这个组件, 这是一个两读一写的寄存器堆,两个需要读出的寄存器的编号,是由指令编码当中rs和rt这两位域指定;而要写入的寄存器的编号,则由rd这个位域指定的。所以,我们只需要把这三个位域的信号的值,连接到对应的输入上。这样,在寄存器堆的输出端,busA就会输出rs所指定的寄存器的内容,busB则会输出rt所指定的寄存器的内容。然后我们将busA和busB连到ALU的输入端,并且我们根据指令编码当中的操作码和功能位域,就可以知道当前是加法还是减法指令,我们要通过一个控制信号,来选择当前ALU提供的运算的类型。然后,我们还要将ALU的输出连接到寄存器堆的输入端,也就是busW信号,在下一个时钟上升沿到来的时候,如果寄存器堆的写使能信号是有效的,在这里,就用这个RegWr信号等于1的时候,寄存器堆就会采样busW信号上的内容,并将其存入到Rd这个信号所指定的寄存器当中去。这样就在一个时钟周期完成了一条加法或者减法的指令,我们要注意的是,这里用红色表明的这两个信号都称为控制信号。
然后,我们来看逻辑运算指令的需求。在我们的简化版本当中,提供了逻辑运算,就是这个对立即数进行或操作的指令。这条指令是一条 I 型指令,那我们刚才已经建立的这个数据通路,是否能满足逻辑运算指令的需求呢?经过分析,我们可以发现几个问题:
第一,逻辑运算指令。它的目的寄存器是rt,而不是rd,而我们现在寄存器堆的Rw输入端连接的是rd这个位域,这样就没法满足这条逻辑运算指令要把运算结果写入到rt所指向的寄存器当中这样一个目的;
第二,这条指令其中一个操作数是一个立即数,而我们现在ALU的两个输入都是来自寄存器堆。对这一点也无法支持;
第三,我们在指令当中,只提供了一个16位的立即数,而ALU的输入都是32位的。所以,还需要对这个16位的立即数进行零扩展。
针对这个三个问题,我们就需要对现在的数据通路进行改造,我们来注意这个数据通路的图,看我们需要增加哪些部件。
针对问题1,我们增加了一个二选一的多选器。当执行之前的加法或者减法指令时,我们让多选器的选择信号为1,这时就会和刚才一样,将rd信号的内容传递到寄存器堆的Rw信号端;而在执行现在这条逻辑运算指令时,我们就可以选择这个多选器的0号输入端,将rt这个信号的值传递到寄存器堆的Rw信号端。这就为写入rt所指定的寄存器提供了支持。
针对问题2,我们在ALU的输入端增加了一个多选器。对于之前的功能,我们通过这个多选器的0号输入端,将寄存器堆的busB信号和ALU的输入端相连;而对于这条逻辑运算指令提出了新的需求,我们可以通过这个多选器的1号输入端,将立即数与ALU相连。当然,这个16位的立即数还需要通过一个零扩展部件(ZeroExt)扩展成32位,再接到这个多选器的输入上。
这样我们就通过增加了两个多选器和一个零扩展部件,满足了逻辑运算指令提出的新需求。我们需要注意的是,我们在满足新需求的时候,一定要保证原有的需求同样是得到满足的。所以,在这里我们就需要增加多选器,而不是直接修改各个部件的输入。
那在现在这条数据通路的基础上,我们再来看访存指令的需求。
访存指令也是 I 型指令,我们先来看其中的Load指令。Load指令是要对数据存储器进行访问,而访存的地址是由rs所指定的寄存器的内容,加上立即数进行符号扩展后的值,而且访存得到的数据也要保存在rt所指向的寄存器当中。那现在的数据通路,已经支持了写入rt所指向的寄存器了。而访存地址的运算,是一个寄存器加上一个立即数,那我们现在的数据通路,也大体支持这样的一个功能,但仍然还是有问题的。首先,我们还不支持符号扩展;其次,这个ALU运算的结果,应该是作为地址去访问存储器从而获得数据,而不是直接连到寄存器堆的写入端。因此,我们还需要对这个数据通路进行改造。
针对符号扩展的需求,我们将原先的零扩展的这个功能部件,改造为一个多功能的扩展部件,通过控制信号(ExtOp),我们可以选择进行零扩展或者进行符号扩展。对于这条load指令,我们可以选择将16位的立即数符号扩展为32位的数,并通过这个多选器连接到ALU的一个输入端;而ALU的另一个输入端,则是rs所指定的寄存器的内容。然后,执行加法运算之后,获得了存储器的地址,我们在这里就需要新增一个数据存储器,这个存储器根据地址就可以得到对应的存储单元中的数据。因为我们最终是要将这个数据写入到寄存器堆,所以还需要增加这样一个多选器(MemtoReg),将数据存储器当中输出的内容传送到寄存器堆的输入数据端。
那这就是load指令所提出的需求,我们的解决方案是将原有的零扩展部件增加符号扩展的功能,并增加一个数据存储器,当然,还包括相应的多选器。
那我们还有另一条访存指令,那就是Store指令。Store指令的地址运算方式和Load指令是一样的,所不同的是,Store指令是将rt所指定的寄存器当中的内容存放到数据存储器(Date Memory)当中去。
那我们来看现在的这个数据通路,我们首先需要rt所指定的寄存器的内容。在现在的这个数据通路中,rt所指定的寄存器的内容会从busB信号上出来,那我们就需要将这个信号连接到数据存储器的数据输入端。但是对于除了Store之外的其他指令,我们都不希望将busB上的信号写入到数据存储器当中。所以,我们还需要给数据存储器连接一个控制信号(MemWr),只有在这控制信号有效的时候(WrEn = 1),才会进行写入的操作。这样我们就满足了Store指令的需求。
现在,除了比较特殊的分支指令之外,我们分析了其他指令的需求,并将各个组件连接起来,再加上之前已经构造的IFU部件,我们就初步完成了数据通路的建立工作。
现在,在处理器的设计步骤的这五步当中,我们已经完成了第三步。
现在,我们已经基本完成了一个数据通路,看起来也并不复杂嘛。不过,这个数据通路是否已经完整了呢?而它又是否能够正确地工作呢?这些都要通过一条一条指令来进行更细致的检查和确认。