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,触发冒险
在仿真波形中检查:
- 在
add指令进入ID阶段时,stall信号拉高一个时钟周期; - PC和IF/ID寄存器在该周期保持不变;
- ID/EX阶段的控制信号被清零(气泡插入);
- 下一个周期,
forward_a为2'b10,ALU的alu_src1直接取自mem_wb_result; - 最终
$t1的值为mem[0($s1)] + $s2,与预期一致。
通过标准:波形显示恰好插入1个气泡,且add指令在停顿后正确使用load结果完成计算,无数据错误。