3. 数据冒险检测与转发

任务目标

在五级流水线(取指IF、译码ID、执行EX、访存MEM、写回WB)中,当后续指令的操作数寄存器恰好是前序尚未写回指令的目的寄存器时,会产生数据冒险。本案例实现一个数据冒险检测单元,并添加两条转发路径:EX/MEM→EX(ALU到ALU)与MEM/WB→EX(访存到ALU),使流水线在大多数情况下无需停顿即可获得正确操作数。

实现方案

在EX阶段前增加转发控制逻辑。检测条件基于流水线寄存器中的寄存器号比较:

  • EX冒险:EX/MEM寄存器中的目的寄存器与ID/EX中源寄存器匹配,且该转发源确实要写寄存器(RegWrite有效且目的寄存器非x0)。
  • MEM冒险:MEM/WB寄存器中的目的寄存器与ID/EX中源寄存器匹配,且RegWrite有效且非x0。当EX冒险与MEM冒险同时命中时,EX/MEM的数据更新(更近),优先选择EX转发。

转发数据通路:EX阶段ALU的两个输入操作数不再直接取自ID/EX寄存器堆读出值,而是经过一个三选一多路器,选择来源为:寄存器堆正常输出、EX/MEM的ALU结果、MEM/WB的写回数据。

关键完整代码

// hazard_unit.v —— 数据冒险检测与转发控制
module hazard_unit (
    input  wire [4:0] id_ex_rs1,
    input  wire [4:0] id_ex_rs2,
    input  wire [4:0] ex_mem_rd,
    input  wire       ex_mem_regwrite,
    input  wire [4:0] mem_wb_rd,
    input  wire       mem_wb_regwrite,
    output wire [1:0] forward_a,  // 00:正常 01:MEM/WB 10:EX/MEM
    output wire [1:0] forward_b
);
    // EX冒险:上一条ALU指令结果直接转发
    wire ex_hazard_rs1 = ex_mem_regwrite && (ex_mem_rd != 5'd0) && (ex_mem_rd == id_ex_rs1);
    wire ex_hazard_rs2 = ex_mem_regwrite && (ex_mem_rd != 5'd0) && (ex_mem_rd == id_ex_rs2);
    // MEM冒险:更早指令从访存/写回级转发
    wire mem_hazard_rs1 = mem_wb_regwrite && (mem_wb_rd != 5'd0) && (mem_wb_rd == id_ex_rs1) && ~ex_hazard_rs1;
    wire mem_hazard_rs2 = mem_wb_regwrite && (mem_wb_rd != 5'd0) && (mem_wb_rd == id_ex_rs2) && ~ex_hazard_rs2;

    assign forward_a = ex_hazard_rs1 ? 2'b10 : (mem_hazard_rs1 ? 2'b01 : 2'b00);
    assign forward_b = ex_hazard_rs2 ? 2'b10 : (mem_hazard_rs2 ? 2'b01 : 2'b00);
endmodule

// 转发多路器(在EX阶段数据通路中使用)
module ex_forward_mux (
    input  wire [31:0] reg_data,    // 寄存器堆正常输出
    input  wire [31:0] ex_mem_alu,  // EX/MEM级ALU结果
    input  wire [31:0] mem_wb_data, // MEM/WB级写回数据
    input  wire [1:0]  sel,
    output wire [31:0] src_data
);
    assign src_data = (sel == 2'b10) ? ex_mem_alu :
                      (sel == 2'b01) ? mem_wb_data : reg_data;
endmodule

集成方式

在流水线CPU顶层中,将hazard_unit的输入连接到流水线寄存器对应字段,输出forward_a/forward_b分别接入EX阶段ALU两个输入端的ex_forward_mux选择信号。本设计未处理load-use冒险(load指令后紧跟使用其结果的指令仍需停顿一拍),该情况由独立的停顿检测逻辑处理,不在本案例范围内。

验证方法

编写测试平台,向流水线输入以下指令序列,观察EX阶段ALU输入值是否正确:

// 汇编序列
addi x1, x0, 5     // x1 = 5
addi x2, x0, 7     // x2 = 7
add  x3, x1, x2    // 需要x1、x2,均来自前两条指令
sub  x4, x3, x1    // 需要x3(EX冒险)和x1(MEM冒险)

通过条件:仿真波形中,add x3进入EX时,forward_a与forward_b均为10(从EX/MEM转发);sub x4进入EX时,forward_a为10(转发x3的ALU结果),forward_b为01(从MEM/WB转发x1)。最终x3=12、x4=7,且流水线无停顿周期插入。

预期仿真结果

// 仅作逻辑推演示例,非实测数据
时钟周期3:EX阶段执行 add x3,forward_a=10, forward_b=10,ALU输入=5和7,输出12
时钟周期4:EX阶段执行 sub x4,forward_a=10(转发12), forward_b=01(转发5),ALU输出7