在我们现在这个事例的指令系统当中,分支指令只有一条,
它的格式是I型的,那我们首先来看一看分支指令是如何工作的。
左边是一段C语言的代码,是一段典型的if else语句,
那我们看对应如何产生MIPS的汇编语言代码,会是怎么样的。
首先就是一条beq的分支指令, 看来编译器已经把 i
和 j 这两个局部变量 分别放到了s3和s4这两个寄存器当中,
那么这条指令就是比较s3和s4的值, 如果它们相等,则会跳转到True这个标号所标明的地址。
那么这里是一条加法指令,这条指令就是执行了f=g+h这条语句,
也就是C语言代码当中,if条件为真时,所要执行的语句。
执行完这条语句之后,程序将顺序地执行后面的内容。
而如果if语句的判断条件不成立,那对应的这条beq
指令在执行时,也会发现s3和s4寄存器的内容不相等, 从而不发生分支转移,而是执行顺序的后一条指令。
后一条指令是一条减法指令,
那与刚才我们看到的那条加法指令相对应,实际上,它执行的就是f=g-h这条语句,
也就是C语言当中,else的这条语句。
那么执行完这条指令之后,下一条指令是一条无条件的转移指令,
直接跳到Next,那这就是应用条件分支指令的实例。
那我们就来看一看beq这条指令的控制信号是如何生成的。
beq指令的操作,同样也可以分为三步,
第一是取指令;第二条,是判断rs和rt两个寄存器的内容是否相等,
那我们可以用一个减法来进行判断;第三步,是更新PC寄存器。
那么对于beq指令来说,所谓的分支,就是如何去改写PC寄存器,
所以它的重点在于第三步。我们先来看条件不成立的 情况,也就是else对应的PC=PC+4,
那在转移条件不成立的时候,我们是顺序执行后一条指令, 这就和其他的运算指令、访存指令是一样的。
那如果条件成立的时候,PC的更新条件则相对复杂一些,
其中也有PC+4,然后需要加上这个16位的立即数的符号扩展,并乘以4,
也就是说,在beq指令当中,所带的这个立即数, 也就是刚才在事例中出现的那个目标地址的标号True,
它实际的数值,是转移目标地址和下一条指令地址之间的差值,
而且这个差值是以4个字节,也就是32位为一个单位的, 那么这个规则,是在制度MIPS指令系统的时候约定的。
我们现在重点是看如何去实现相应的控制信号。
同样,我们直接来看第二步,那么对于这一步,
要做的操作包括,从寄存器堆当中,取出两个寄存器的内容,而且进行减法运算,
那这个操作和我们之前学习的减法指令需求是一样的,
因此,现有的结构不需要修改,就可以完成这个功能。
我们注意,当取回一条指令之后,rs的位域被连接到寄存器堆,
它所指定的寄存器的内容,会放到busA上,然后连接到ALU的一个输入端,
rt位域的信号会被连接到寄存器堆的Rb的输入端,
它所指定的寄存器的内容,会通过busB信号,再经过这个多选器
传递到ALU的另一个输入端,然后这个ALU就可以执行这个减法,
不过问题在于,之前的减法运算指令,会将这个ALU 运算的结果,通过这个多选器之后,写回到寄存器堆当中去,
而beq指令是不需要写回寄存器堆的,而且也不应该写回。
我们希望通过这个ALU,得出一个判断,就是这个减法操作的结果是不是0。
因此,我们还需要增加一个新的功能,来完成这样的一个判断,
判断一个数是否等于0,是非常简单的,所以我们可以很轻松地在ALU当中增加这个功能,
并让ALU提供一个信号的输出,标明当前的运算结果是否为0, 我们把这个信号命名为zero。
如果运算结果为0,ALU会把zero信号置为1,否则,置为0。
那因为运算结果是否为0,将会影响到IFU如何去更新PC寄存器,
所以我们需要把zero信号连接到IFU, 这样,我们就可以把第二步操作的描述补充完整。
那么在这一步操作中,这些控制信号又是如何设置的呢?
首先,下一个PC的选择方式,我们就不能再设置为加4了,
但是究竟如何更新,IFU还需要做一些工作,这个我们一会儿 再说。所以我们先把这个选择信号标记为branch,
然后我们再来看其他的控制信号。
现在我们已经知道,ALU要执行一个减法运算, 而且它的两个操作数都应该来自寄存器堆,
所以这一个多选器就应该选择通道0。
由此,扩展部件功能选择信号可以任意设置,
而ALU的功能选择信号,则需要设置为减法,那现在虽然我们新增了这个zero信号,
但是ALU原本的功能还是必须保持的。所以,当我们设置ALU执行减法运算时,它的输出
依然会是减法运算的结果,并送到数据存储器的地址端和下一个多选器的0号通道。
那为了保证数据存储器不被改写,那我们还要设置数据存储器的写使能信号为0。
那对于条件分支指令来说,它是不要回写寄存器堆的,
所以这个多选器无论选择哪一条通道,都是没有意义的, 那我们可以把它的选择信号任意设置为0或者1。
最后我们来看寄存器堆这一边,因为不需要回写寄存器堆,
所以我们必须要设置寄存器堆的写使能信号为0,以免错误地更改其中的内容,
那因为写使能信号已经设为0,那寄存器堆的写入寄存器的编号,则可以任意的设置。
这样,我们就可以看出,beq指令执行时真正有效的信号了。
不过我们要注意,这一步仅仅是完成了判断, 那我们还要根据判断的结果,对PC寄存器进行更新,
因此,这条指令的第三步和其他指令是不一样的。
那好,现在对于IFU来说,它有了两个输入的信号, 一个是之前就有的nPC_
select,还有一个是我们后来增加的zero, 而我们知道,对于IFU如何更新PC寄存器,
其关键,就是这个多选器如何选择的问题。
它的0号通道连接的是PC+4,1号通道连接的是分支指令的目标地址。
那现在我们这个选择信号应该如何生成呢? 我们不妨把输入列一个表,来进行观察。
当nPC_select的信号为0的时候,就代表当前在执行的指令是运算指令,
或者是访存指令,而不是分支指令,那在这个时候,无论zero信号是0还是1,
这个多选器都应该选择0号通道,从而顺序地执行下一条指令。
而当nPC_select的信号等于1时,说明当前正在执行一条分支指令,
但如果此时zero信号为0,表示分支的判断条件不成立,
那这个多选器仍然应该选择0号通道,从而顺序地执行下一条指令。
只有当nPC_select的信号为1,说明当前是一条分支指令, 而且sero信号也为1,说明当前的判断条件成立,
这时,这个多选器才可以选择1号通道,从而将分支的目标地址更新到PC寄存器当中去,
这样在下一个时钟周期, 指令存储器就会将分支目标地址所指向的那条指令
的编码送出来,从而实现指令执行流向的改变。
那通过这张表,我们是否能够得出,这个多选器的控制信号的生成方法呢?
请你想一想。
给一点提示,实际上只需要一个逻辑门就可以了。
如果还没有想出来,那我们不妨回来再观察一下,对于
这个多选器的选择信号,只有在nPC_select和zero信号都为1时,它才会为1,
在其他时候,这个选择信号均为0。
那这个描述大家是不是很熟悉呢?这是哪个逻辑门的功能描述? 我们还是来看最右边吧,
其实,只需要一个与门就可以了。
那基于这样的分析,我们就可以对IFU进行进一步的改造, 从而支持beq指令的需求。
不过这里还有一个问题,那就是分支目标地址究竟是如何生成的?
那现在我们就来完成最后这一项工作,这个分支目标地址有两个部分, 一部分是PC+4,
一部分是对立即数进行符号扩展,然后乘以4, 而这个立即数就是指令编码当中的低16位,
因此,我们先把这一部分信号连出来。
现在,我们把这个立即数取出来,连接到一个符号扩展的部件上,
对于这个符号扩展的部件,我们再增加一个很简单的小功能,就是向左再移动两位, 左移两位就相当于乘以4,
因此经过这个部件,我们将这16位的立即数扩展成了32位,并且完成了乘以4的操作。
那现在我们有了这个算式的后半部分,而前半部分是PC+4,
幸运的是,我们现在已经有了PC+4,就是这个加法器的输出,
那我们只需要直接把它连出来,然后再增加一个加法器,这样就可以得到了分支指令的目- 标地址,
现在这个IFU,我们也已经补充完整了。