4. 加载使用冒险处理

任务目标

在五级流水线(取指IF、译码ID、执行EX、访存MEM、写回WB)中,load指令的结果在MEM阶段末尾才可用,而紧随其后的指令若在EX阶段就需要该结果作为源操作数,则产生加载使用冒险。本案例实现冒险检测单元:当检测到该冲突时,插入一个气泡(stall)使后续指令停顿一拍,同时通过转发路径将load结果从MEM阶段直接送至EX阶段,保证程序正确执行。

核心逻辑与实现

冒险检测条件为:ID/EX流水线寄存器中的指令使用寄存器rt,且其值与IF/ID寄存器中紧随指令的rs或rt相等,同时该指令为load类型。满足条件时,将控制信号清零(插入气泡)并冻结PC和IF/ID寄存器。转发逻辑处理EX/MEM与MEM/WB阶段的结果回传,本案例重点展示load-use场景下的MEM-to-EX转发。

// hazard_unit.v
module hazard_unit (
    input  wire [4:0] id_ex_rt,      // ID/EX阶段指令的rt
    input  wire       id_ex_memread, // ID/EX阶段是否为load
    input  wire [4:0] if_id_rs,      // IF/ID阶段指令的rs
    input  wire [4:0] if_id_rt,      // IF/ID阶段指令的rt
    output reg        pc_write,      // 允许PC更新
    output reg        if_id_write,   // 允许IF/ID寄存器更新
    output reg        control_mux    // 选择控制信号清零
);

    always @(*) begin
        // 默认无冒险:正常流水
        pc_write    = 1'b1;
        if_id_write = 1'b1;
        control_mux = 1'b0;

        // load-use检测:load结果在MEM末尾才可用,
        // 下一条指令在EX阶段就需要该值
        if (id_ex_memread &&
           ((id_ex_rt == if_id_rs) || (id_ex_rt == if_id_rt))) begin
            pc_write    = 1'b0;  // 冻结PC
            if_id_write = 1'b0;  // 冻结IF/ID
            control_mux = 1'b1;  // 将ID/EX控制信号置零(插入气泡)
        end
    end
endmodule
// forwarding_unit.v (MEM-to-EX转发,用于load-use)
module forwarding_unit (
    input  wire [4:0] id_ex_rs,
    input  wire [4:0] id_ex_rt,
    input  wire [4:0] mem_wb_rd,      // MEM/WB阶段的目标寄存器
    input  wire       mem_wb_regwrite,
    output reg  [1:0] forward_a,      // ALU输入A的选择
    output reg  [1:0] forward_b       // ALU输入B的选择
);

    always @(*) begin
        forward_a = 2'b00; // 默认来自寄存器堆
        forward_b = 2'b00;

        // MEM/WB转发:load结果在MEM末尾已就绪,
        // 经MEM/WB寄存器后可直接转发给EX阶段
        if (mem_wb_regwrite && (mem_wb_rd != 0) &&
            (mem_wb_rd == id_ex_rs))
            forward_a = 2'b10; // 选择MEM/WB结果
        if (mem_wb_regwrite && (mem_wb_rd != 0) &&
            (mem_wb_rd == id_ex_rt))
            forward_b = 2'b10;
    end
endmodule

流水线集成与数据通路

将上述两个模块接入流水线控制逻辑。冒险检测单元的输出control_mux控制ID/EX阶段控制信号的清零多路选择器;pc_write和if_id_write分别冻结PC与IF/ID寄存器。转发单元的输出forward_a/forward_b连接到ALU的两个输入多路选择器,将MEM/WB阶段的load结果直接送入ALU。

// 集成示例(数据通路连接片段)
wire        stall;
wire [1:0]  fwd_a, fwd_b;

hazard_unit hu (
    .id_ex_rt     (id_ex_rt),
    .id_ex_memread(id_ex_memread),
    .if_id_rs     (if_id_rs),
    .if_id_rt     (if_id_rt),
    .pc_write     (pc_write),
    .if_id_write  (if_id_write),
    .control_mux  (stall)
);

forwarding_unit fu (
    .id_ex_rs       (id_ex_rs),
    .id_ex_rt       (id_ex_rt),
    .mem_wb_rd      (mem_wb_rd),
    .mem_wb_regwrite(mem_wb_regwrite),
    .forward_a      (fwd_a),
    .forward_b      (fwd_b)
);

// ALU输入多路选择
wire [31:0] alu_src1 = (fwd_a == 2'b10) ? mem_wb_result : reg_data1;
wire [31:0] alu_src2 = (fwd_b == 2'b10) ? mem_wb_result :
                       (fwd_b == 2'b01) ? ex_mem_result : reg_data2;

验证方法

编写测试平台,构造以下指令序列:

lw   $t0, 0($s1)   # load指令
add  $t1, $t0, $s2  # 紧随其后使用$t0,触发冒险

在仿真波形中检查:

  1. 在add指令进入ID阶段时,stall信号拉高一个时钟周期;
  2. PC和IF/ID寄存器在该周期保持不变;
  3. ID/EX阶段的控制信号被清零(气泡插入);
  4. 下一个周期,forward_a为2'b10,ALU的alu_src1直接取自mem_wb_result;
  5. 最终$t1的值为mem[0($s1)] + $s2,与预期一致。

通过标准:波形显示恰好插入1个气泡,且add指令在停顿后正确使用load结果完成计算,无数据错误。

load-use冒险处理时序 Cycle 1 Cycle 2 Cycle 3 Cycle 4 Cycle 5 lw: IF lw: ID lw: EX lw: MEM lw: WB add: IF add: ID 气泡 add: IF add: ID MEM→WB转发 检测到冒险,插入气泡 stall信号 高电平1周期