自己动手写CPU(8)转移指令+延迟槽解决流水线控制相关

延迟槽

流水线中存在的三种相关:数据相关、结构相关、控制相关。其中控制相关是指流水线中的转移指令或者其他需要改写PC的指令造成的相关。这些指令改写了PC的值,所以导致后面已经进入流水线的几条指令无效。

比如当第一条指令为分支跳转指令,那么在执行阶段才会知道要不要跳转;但当时第二条指令已经进入译码;第三条指令已经进入取指阶段。在流水线执行阶段进行转移判断,并且转移发生,那么会有两条无效指令,导致浪费两个时钟周期,为减少损失发明了“延迟槽”

我们规定转移指令后面的指令位置为延迟槽

但是,即使加入延迟槽,在转移发生时仍然会导致已经进去取指阶段的指令无效,仍浪费一个时钟周期。所以我们可以在译码阶段进行转移判断,并且让延迟槽后一指令继续执行。

此时需要注意的是:

延迟槽里的指令,在目标跳转指令前面执行,所以延迟槽里的指令不能修改目标跳转指令会用到的寄存器或者变量的内容,否则会出错。

下面我们来介绍转移指令

其中跳转指令是绝对转移,分支指令是相对转移

跳转指令

指令格式

自己动手写CPU(8)转移指令+延迟槽解决流水线控制相关_第1张图片

jr指令

用法:jr rs

作用:pc <- rs

将地址为rs的通用寄存器的值赋给寄存器PC,作为新的指令地址

jalr指令

用法:jalr rs 或者 jalr rd,rs 

作用:rd <- return_address,pc <- rs

将地址为rs的通用寄存器的值赋给寄存器PC,作为新的指令地址,同时将跳转指令后面第2条指令的地址作为返回地址保存到地址为rd的通用寄存器,如果没有在指令中指明rd,那么默认将返回地址保存到寄存器$31

j指令

用法:j target

作用:pc <- (pc+4)[31,28]||target||‘00’

转移到新的指令地址,其中新地址的低28位是target左移两位后的值,新指令地址高4位是后一指令的高四位

因为处理器按照字节寻址,二指令存储器每个地址是一个32bit字,所以要给指令中的立即数乘4,即左移两位

jal指令

用法:jal target

作用:pc <- (pc+4)[31,28]||target||‘00’

转移到新的指令地址,其中新地址的低28位是target左移两位后的值,新指令地址高4位是后一指令的高四位,jal指令要将跳转指令后面的一条指令地址(pc+4)写入$31寄存器

分支指令

指令格式

自己动手写CPU(8)转移指令+延迟槽解决流水线控制相关_第2张图片

指令说明

beq、b、bgtz、blez、bne这5条指令可以直接依据指令中的指令码进行判断是哪一条指令,bltz、bltzal、bgez、bgezal、bal这5条指令指令码相同,依据指令中16~20bit的值进一步判断是哪一条指令

所有分支指令的第0~15bit存储的都是offset,如果发生转移,那么将offset左移2位,并符号扩展至32位

转移目标地址 = (signed_extend)(offset||‘00’)+(pc+4)

beq指令

用法:beq rs,rt,offset

作用:if rs = rt then branch

将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值进行比较,如果相等,则发生转移

b指令

用法:b offset

作用:无条件转移

beq指令的rs,rt都为0时的情况,实现时不需要特意实现b指令,只需要实现beq即可

bgtz指令

用法:bgtz rs,offset

作用:if rs > 0 then branch

如果地址为rs的通用寄存器的值大于0,则发生转移

blez指令

用法:blez rs,offset

作用:if rs <= 0 then branch

如果地址为rs的通用寄存器的值小于等于0,则发生转移

bne指令

用法:bne rs,rt,offset

作用:if rs != rt then branch

将地址为rs的通用寄存器的值与地址为rt的通用寄存器的值进行比较,如果不相等,则发生转移

bltz指令

用法:bltz rs,offset

作用:if rs < 0 then branch

如果地址为rs的通用寄存器的值小于0,则发生转移

bltzal指令

用法:bltzal rs,offset

作用:if rs < 0 then branch

如果地址为rs的通用寄存器的值小于0,则发生转移,并且将指令后面的指令地址作为返回地址,保存到通用寄存器$31

bgez指令

用法:bgez rs,offset

作用:if rs >= 0 then branch

如果地址为rs的通用寄存器的值大于等于0,则发生转移

bgezal指令

用法:bgezal rs,offset

作用:if rs >= 0 then branch

如果地址为rs的通用寄存器的值大于等于0,则发生转移,并且将指令后面的指令地址作为返回地址,保存到通用寄存器$31

bal指令

用法:bal offset

作用:无条件转移

如果地址为rs的通用寄存器的值大于等于0,则发生转移,并且将指令后面的指令地址作为返回地址,保存到通用寄存器$31

bal是bgezal指令的特殊情况,即bgezal指令的rs为0,不用特意实现这个指令

代码的修改

  • 在译码阶段增加转移判断步骤,PC分成三种取值
  • 译码阶段增加延迟槽处理信号

PC的三种取值:

1.PC = PC + 4(下一条指令)

2.PC不变(流水线暂停)

3.PC等于转移判断的结果(如果是转移指令且满足转移条件,将转移目标给PC)

译码阶段:

如果处于译码阶段的指令是转移指令,并且满足转移条件,那么ID模块设置转移发生标志 branch_flag_o为 Branch,同时通过branch_target_address_o接口给出转移目的地址,送到PC模块,后者据此修改取指地址。

如果处于译码阶段的指令是转移指令,并且满足转移条件,那么ID模块还会设置next_inst_in_delayslot_o为 InDelaySlot,表示下一条指令是延迟槽指令,其中 InDelaySlot是一个宏定义。next_inst _in_delayslot_o信号会送入ID/EX模块,并在下一个时钟周期通过ID/EX模块的is_in_delayslot_o接口送回到ID模块,ID模块可以据此判断当前处于译码阶段的指令是否是延迟槽指令。

如果转移指令需要保存返回地址,那么译码模块还要计算返回地址,并通过link_addr_o接口输出,该值最终会传递到EX模块,作为要写入目的寄存器的值。

修改PC代码:

always @ (posedge clk) begin
    if (ce == `ChipDisable) begin
        pc <= 32'h00000000;
    end else if(stall[0] == `NoStop) begin
        if(branch_flag_i == `Branch) begin
        	pc <= branch_target_address_i;
        end else begin
        	pc <= pc + 4'h4;
        end
    end
end

总结

要注意延迟槽里的指令,在目标跳转指令前面执行,所以延迟槽里的指令不能修改目标跳转指令会用到的寄存器或者变量的内容,否则会出错。

还要在本次添加宏定义

`define Branch 1'b1				//发生转移
`define NotBranch 1'b0			//不发生转移
`define InDelaySlot 1'b1		//是延迟槽指令
`define NotInDelaySlot 1'b0		//不是延迟槽指令

你可能感兴趣的:(自己动手写CPU,fpga开发)