在第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中!
自己动手写CPU_step3_流水线数据相关问题
于 2024-08-19 23:15:01 首次发布