505-分支指令的控制信号
# 505-分支指令的控制信号
分支指令,是一类特殊的指令,它能够改变程序的流向,因此,想要执行分支指令,还需要对现有的结构进行进一步的改造。
在我们现在这个事例的指令系统当中,分支指令只有一条, 它的格式是 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 指令
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,我们也已经补充完整了。
# 小结
现在,我们已经分析完了这个指令系统当中的最后一条指令了。 那我们已经知道,对于每一条指令, 每一个控制信号,我们应该赋予什么样的值。 但是还有一个问题,我们依然不是很清楚, 就是这些信号的值,是如何自动地产生的呢? 我们在下一节就来一起探讨这个问题。