将陆续上传本人写的新书《自己动手写CPU》,今天是第34篇,我尽量每周四篇
感兴趣的朋友可以在亚马逊、当当、京东等查找。
另外,开展晒书评送书活动,在亚马逊、京东、当当三大图书网站上,发表《自己动手写CPU》书评的前十名读者,均可获赠《步步惊芯——软核处理器内部设计分析》一书,大家踊跃参与吧!活动时间:2014-9-11至2014-10-20
本章将为OpenMIPS处理器添加转移指令,转移指令包括跳转、分支两种,区别在于前者是绝对转移,后者是相对转移,但实现方法是相似的。转移指令涉及延迟槽,所以首先在8.1节介绍了延迟槽的概念,接着在8.2节对MIPS32指令集架构中定义的所有转移指令的格式、作用、用法进行了说明。在8.3节介绍了OpenMIPS实现转移指令的思路,以及对数据流图、系统结构的修改。8.4节通过修改代码实现了转移指令,最后通过两个测试程序,验证转移指令是否实现正确。
8.1 延迟槽
在实现转移指令之前,先介绍一下延迟槽的概念。在第5章已经介绍了流水线中存在的三种相关:数据相关、结构相关、控制相关。其中控制相关是指流水线中的转移指令或者其他需要改写PC的指令造成的相关。这些指令改写了PC的值,所以导致后面已经进入流水线的几条指令无效,比如:如果转移指令在流水线的执行阶段进行转移条件判断,在发生转移时,会导致当前处于取指、译码阶段的指令无效,需要重新取指。如图8-1所示。
也就是在流水线执行阶段进行转移判断,并且转移发生,那么会有2条无效指令,导致浪费了两个时钟周期。为了减少损失,规定转移指令后面的指令位置为“延迟槽”,延迟槽中的指令被称为“延迟指令”(也可称之为“延迟槽指令”)。延迟指令总是被执行,与转移发生与否没有关系。引入延迟槽后的指令执行顺序如图8-2所示。OpenMIPS处理器就计划使用延迟槽技术。
但是,即使引入延迟槽,在转移发生时仍然会导致已经进入取指阶段的指令无效,也就是仍浪费一个时钟周期,要解决这个问题,可以在译码阶段进行转移判断,这样就可以避免浪费时钟周期。OpenMIPS处理器就设计为在译码阶段进行转移判断。
8.2 转移指令说明
MIPS32指令集架构中定义的转移指令共有14条,可分为如下两类。
- 跳转指令: jr 、jalr 、j 、jal
- 分支指令:b、bal、beq、bgez、bgezal、bgtz、blez、bltz、bltzal、bne
其中,跳转指令是绝对转移,分支指令是相对转移。本节分别介绍这两类指令。
1、跳转指令
跳转指令的格式如图8-3所示。
从图8-3可知,j、jal指令可以通过指令码进行判断,jr、jalr指令的指令码为SPECIAL,还需要依据功能码进一步判断。
- 当指令中的指令码为SPECIAL,功能码为6‘b001000时,表示是jr指令
指令用法为:jr rs
指令作用为:pc <- rs,将地址为rs的通用寄存器的值赋给寄存器PC,作为新的指令地址。
- 当指令中的指令码为SPECIAL,功能码为6‘b001001时,表示是jalr指令
指令用法为:jalr rs 或者jalr rd, rs
指令作用为:rd <- return_address, pc <- rs,将地址为rs的通用寄存器的值赋给寄存器PC,作为新的指令地址,同时将跳转指令后面第2条指令的地址作为返回地址保存到地址为rd的通用寄存器,如果没有在指令中指明rd,那么默认将返回地址保存到寄存器$31。
- 当指令中的指令码为6‘b000010时,表示是j指令
指令用法为:j target
指令作用为:pc <- (pc+4)[31,28] || target || ‘00’,转移到新的指令地址,其中新指令地址的低28位是指令中的target(也就是图8-3中的instr_index)左移两位的值,新指令地址的高4位是跳转指令后面延迟槽指令的地址高4位。
- 当指令中的指令码为6‘b000011时,表示是jal指令
指令用法为:jal target
指令作用为:pc <- (pc+4)[31,28] || target || ‘00’,转移到新的指令地址,新指令地址与指令j相同,不再解释。但是,指令jal还要将跳转指令后面第2条指令的地址作为返回地址保存到寄存器$31。
j、jal、jr、jalr指令在转移之前都要先执行延迟槽指令。
2、分支指令
分支指令的格式如图8-4所示。
从图8-4可知,前5条指令beq、b、bgtz、blez、bne可以直接依据指令中的指令码进行判断,确定是哪一条指令,而后5条指令bltz、bltzal、bgez、bgezal、bal的指令码都是REGIMM,这是一个宏定义,值为6‘b000001,需要根据指令中16-20bit的值进一步判断,从而确定是哪一条指令。
从图8-4还可知,所有分支指令的0-15bit存储的都是offset,如果发生转移,那么将offset左移2位,并符号扩展至32位,然后与延迟槽指令的地址相加,加法的结果就是转移目的地址,从该地址取指令。
转移目标地址 = (signed_extend)( offset || ‘00’ ) + (pc+4)
- 当指令中的指令码为6‘b000100时,表示是beq指令
指令用法为:beq rs, rt, offset
指令作用为:if rs = rt then branch,将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值进行比较,如果相等,那么发生转移。
- 当指令中的指令码为6‘b000100,且16-25bit为0时,表示是b指令
指令用法为:b offset
指令作用为:无条件转移,从图8-4可知,b指令可以认为是beq指令的特殊情况,当beq指令的rs、rt都等于0时,即为b指令,所以在OpenMIPS实现的时候不需要特意实现b指令,只需要实现beq指令即可。
- 当指令中的指令码为6‘b000111时,表示是bgtz指令
指令用法为:bgtz rs, offset
指令作用为:if rs > 0 then branch,如果地址为rs的通用寄存器的值大于零,那么发生转移。
- 当指令中的指令码为6‘b000110时,表示是blez指令
指令用法为:blez rs, offset
指令作用为:if rs ≤ 0 then branch,如果地址为rs的通用寄存器的值小于等于零,那么发生转移。
- 当指令中的指令码为6‘b000101时,表示是bne指令
指令用法为:bne rs, rt, offset
指令作用为:if rs ≠ rt then branch,如果地址为rs的通用寄存器的值不等于地址为rt的通用寄存器的值,那么发生转移。
- 当指令中的指令码为REGIMM,且16-20bit为5‘b00000时,表示是bltz指令
指令用法为:bltz rs, offset
指令作用为:if rs < 0 then branch,如果地址为rs的通用寄存器的值小于0,那么发生转移。
- 当指令中的指令码为REGIMM,且16-20bit为5‘b10000时,表示是bltzal指令
指令用法为:bltzal rs, offset
指令作用为:if rs < 0 then branch,如果地址为rs的通用寄存器的值小于0,那么发生转移,并且将转移指令后面第2条指令的地址作为返回地址,保存到通用寄存器$31。
- 当指令中的指令码为REGIMM,且16-20bit为5‘b00001时,表示是bgez指令
指令用法为:bgez rs, offset
指令作用为:if rs ≥ 0 then branch,如果地址为rs的通用寄存器的值大于等于0,那么发生转移。
- 当指令中的指令码为REGIMM,且16-20bit为5‘b10001时,表示是bgezal指令
指令用法为:bgezal rs, offset
指令作用为:if rs ≥ 0 then branch,如果地址为rs的通用寄存器的值大于等于0,那么发生转移,并且将转移指令后面第2条指令的地址作为返回地址,保存到通用寄存器$31。
- 当指令中的指令码为REGIMM,且21-25bit为0,16-20bit为5‘b10001时,表示是bal指令
指令用法为:bal offset
指令作用为:无条件转移,并且将转移指令后面第2条指令的地址作为返回地址,保存到通用寄存器$31。从图8-4的指令格式可知,bal指令是bgezal指令的特殊情况,当bgezal指令的rs为0时,就是bal指令,所以在OpenMIPS实现时,不用特意考虑bal指令,只要实现bgezal指令即可。
综上,b、bal指令不用单独实现,需要OpenMIPS实现的分支指令只有8条。
所有的分支指令在转移到目标地址前都要先执行延迟槽中的指令。