自己动手写CPU_step3_流水线数据相关问题

在第2篇的仿真测试结果中,我们是重复且循环读写一组寄存器,这些寄存器之间不存在数据相关问题,但是,如:

inst1:ori  r1  r2  imm;(r2) | imm => r1;

inst2:ori  r3  r1  imm;(r1) | imm => r3;

inst3:ori  r4  r1  imm;(r1) | imm => r4;

inst4:ori  r5  r1  imm;(r1) | imm => r5;

以上四条指令都存在数据相关,即指令1中的结果尚未写入r1,指令2、3、4便读取了r1寄存器的值,这个值是错误的,导致这些指令执行结果错误。


冲突如下:

IF        ID        EX          MEM        WB(写回)

           IF        ID(冲突)  EX            MEM            WB

                      IF            ID(冲突)    EX                MEM        WB

                                     IF              ID (冲突)       EX            MEM        WB


解决方法:

1、插入气泡。暂停当前指令流水线;

        影响效率,不宜使用

2、编译器优化。通过调度将指令间的冲突排除;

        不易实现,效果不佳

3、数据旁路。将第一条指令的执行结果提前给出到其他指令,不必等待写回后使用。

        实现简单,速度快

        因此,我们只需将EX模块、MEM模块、WB模块的写回数据、写回地址、写回使能都与ID模块相连即可,通过判断写回地址和当前指令读取的地址是否冲突,冲突则直接旁路获取。


仿真分析:

测试案例:

34011100        inst1:(r0) | 1100 => r1
34220022       inst2:(r1) | 0022 => r2
34230033       inst3:(r1) | 0033 => r3
34240044       inst4:(r1) | 0044 => r4

由图可知,在前四条指令的写回阶段,只有第一条指令正确写回,其他指令都存在冲突写回操作失败。


解决方案:

1、将执行阶段和访存阶段的输出结果回送至译码阶段,通过判断执行阶段或访存阶段的目的寄存器是否与当前读取的寄存器地址一致,若一致则接受这个旁路数据。

//译码阶段  对if_id传入的指令进行译码,分离出操作数和操作码
module id(
    input                           rst,
    input [`InstAddrBus]            pc,
    input [`InstDataBus]            inst,
    //读通用寄存器        读取        
    input [`RegDataBus]             reg1_data,
    input [`RegDataBus]             reg2_data,
    output reg                      reg1_rden,
    output reg                      reg2_rden,
    output reg [`RegAddrBus]        reg1_addr,  //源操作数1的地址
    output reg [`RegAddrBus]        reg2_addr,  //源操作数2的地址
    //送到ex阶段的值
    output reg [`RegDataBus]        reg1,       //源操作数1 32b
    output reg [`RegDataBus]        reg2,       //源操作数2 32b
    output reg                      reg_wb,     //写回目的寄存器标志    
    output reg [`RegAddrBus]        reg_wb_addr,//写回目的寄存器地址
    output reg [`AluOpBus]          aluop,      //操作码    
    //相邻指令的冲突,由EX阶段给出数据旁路
    input                           ex_wr_en,   //处于执行阶段的指令是否要写目的寄存器 
    input [`RegDataBus]             ex_wr_data, 
    input [`RegAddrBus]             ex_wr_addr,  
    //相隔一条指令的冲突,由MEM阶段给出数据旁路
    input                           mem_wr_en,  //处于访存阶段指令是否要写目的寄存器
    input [`RegDataBus]             mem_wr_data,
    input [`RegAddrBus]             mem_wr_addr
    );

/*      ORI指令                               
    31:26   25:21   20:16   15:0
     op      rs      rt      imm
*/

wire [5:0] op = inst[31:26];         //从指令中获取操作码   高6位
reg [`RegDataBus] imm;         //立即数

always @ (*) begin
    if (rst) begin
        reg1_rden       <= 1'd0;
        reg2_rden       <= 1'd0;
        reg1_addr       <= 5'd0;
        reg2_addr       <= 5'd0;
        imm             <= 32'd0;
        reg_wb          <= 1'd0;
        reg_wb_addr     <= 5'd0;
        aluop           <= 6'd0;
    end else begin
        reg1_rden       <= 1'd0;
        reg2_rden       <= 1'd0;
        reg1_addr       <= inst[25:21];   //默认从指令中读取操作数1地址
        reg2_addr       <= inst[20:16];   //默认从指令中读取操作数2地址
        imm             <= 32'd0; 
        reg_wb          <= 1'd0;
        reg_wb_addr     <= inst[15:11];  //默认结果地址寄存器rd        
        aluop           <= inst[31:26];  //高6位为操作码
        //操作类型
        case (op)
            `EXE_ORI:   begin           //或指令  rs寄存器值是操作数1,imm是操作数2,结果放到rt寄存器
                    reg1_rden   <= 1'd1;  //需要读取操作数1 rs寄存器的值
                    reg2_rden   <= 1'd0;  //不需要读取操作数2 rt寄存器值,
                    imm         <= {16'h0, inst[15:0]};   
                    reg_wb      <= 1'd1;
                    reg_wb_addr <= inst[20:16];
            end
        endcase
    end
end

always @ (*) begin
    if (rst) begin
        reg1 <= 32'd0;
    end else if (reg1_rden == 1'd1 && ex_wr_en == 1'd1 && ex_wr_addr == reg1_addr) begin    //执行阶段旁路
        reg1 <= ex_wr_data;
    end else if (reg1_rden == 1'd1 && mem_wr_en == 1'd1 && mem_wr_addr == reg1_addr) begin   //访存阶段旁路
        reg1 <= mem_wr_data;
    end else if (reg1_rden == 1'd1) begin   //从通用寄存器获取操作数
        reg1 <= reg1_data;
    end else if (reg1_rden == 1'd0) begin   //从指令中获取操作数
        reg1 <= imm;
    end else begin
        reg1 <= 32'd0;
    end
end
    
always @ (*) begin
    if (rst) begin
        reg2 <= 32'd0;
    end else if (reg2_rden == 1'd1 && ex_wr_en == 1'd1 && ex_wr_addr == reg2_addr) begin    //执行阶段旁路
        reg2 <= ex_wr_data;
    end else if (reg2_rden == 1'd1 && mem_wr_en == 1'd1 && mem_wr_addr == reg2_addr) begin   //访存阶段旁路
        reg2 <= mem_wr_data;
    end else if (reg2_rden == 1'd1) begin   //从通用寄存器获取操作数
        reg2 <= reg2_data;
    end else if (reg2_rden == 1'd0) begin   //从指令中获取操作数
        reg2 <= imm;
    end else begin
        reg2 <= 32'd0;
    end
end
    
endmodule

2、将写回阶段的数据同时回送译码阶段,通过判断写回阶段的目的寄存器地址和要读取的寄存器地址是否一致,若相同,则接受旁路数据。

//通用寄存器组,用来给出操作数以及写回结果
module id_reg(
    input                       clk,
    input                       rst,
    //读端口       
    input                       reg1_rden,
    input [`RegAddrBus]         reg1_addr,
    output reg [`RegDataBus]    reg1_data,
    input                       reg2_rden,
    input [`RegAddrBus]         reg2_addr,
    output reg [`RegDataBus]    reg2_data,
    //写端口
    input                       wr_en,
    input [`RegAddrBus]         wr_addr,
    input [`RegDataBus]         wr_data
    );
    
reg [`RegDataBus] regs [0:31];  //定义32个32位的寄存器

always @ (*) begin
    if (rst) begin
        reg1_data       <= 32'd0;
    end else if (reg1_addr == 5'd0) begin   //0号寄存器恒为0
        reg1_data       <= 32'd0;
    end else if (wr_en && reg1_rden && wr_addr==reg1_addr) begin    //要读的数据正好是写入数据,数据旁路
        reg1_data       <= wr_data;
    end else if (reg1_rden) begin
        reg1_data       <= regs[reg1_addr];
    end else begin
        reg1_data       <= 32'd0;
    end
end
    
always @ (*) begin
    if (rst) begin
        reg2_data       <= 32'd0;
     end else if (reg2_addr == 5'd0) begin   //0号寄存器恒为0
        reg2_data       <= 32'd0;
    end else if (wr_en && reg2_rden && wr_addr==reg2_addr) begin    //要读的数据正好是写入数据,数据旁路
        reg2_data       <= wr_data;
    end else if (reg2_rden) begin
        reg2_data       <= regs[reg2_addr];
    end else begin
        reg2_data       <= 32'd0;
    end
end
    
always @ (posedge clk) begin    //复位时不写,写0寄存器不允许
    if (!rst && (wr_en == 1'd1) && (wr_addr != 5'd0) ) begin
        regs[wr_addr] <= wr_data;
    end
end
    
endmodule

仿真结果:

如图所示,前四条指令的写回阶段都正确的写入了寄存器值。通过以上数据旁路的使用,该类数据冲突已解决。其他数据冲突暂时不会发生,发生时再做处理。

该篇到此结束,下面将要添加更多的指令到CPU中!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值