LoongArch CPU设计实验_实践任务8:阻塞技术解决相关引发的冲突
声明
由于未能成功配置Loongarch交叉编译工具,本实验使用的是exp压缩包(压缩包下载链接见文尾参考资料)。
实验要求
本实践任务要求在实践任务7实现的CPU基础上完成以下工作:
- 加入适当的逻辑处理寄存器写后读数据相关引发的流水线冲突(本任务中只要求使用阻塞技术)。
- 运行exp8对应的func,要求成功通过仿真和上板验证。
请参照第2.3.1节中介绍的方式获取本次实践任务所需的实验开发环境。具体的实验环境仍位于 mycpu_env/ 目录下,且仍使用soc_bram/子目录。
流水线冲突分析
前提:单发射静态流水线。
- 控制相关引发的流水线冲突,因为五级流水线的译码阶段能够完成所有跳转指令的处理,并产生前递数据,因此不会因为控制相关导致冲突。
- 结构相关引发的流水线冲突,因为五级流水线各级间有严格的握手逐级互锁,因此当某一级阻塞时所有流水线都可以停止。
- 寄存器写后读引发的冲突,可能某条指令A处于ID阶段时,指令B处于EXE阶段,且B的目的寄存器为A的源寄存器。此时B指令还没来得及将新值写回寄存器,A就读取了旧的寄存器值。
- 读后写,因为读取的是最新的数据,读取刚写入的新值不会冲突。
- 写后写,因为写和写之间互不影响,单发射同一时间只能有一条指令写寄存器,不会冲突。
关于跳转指令
⭐重点在于使用br_taken
信号让FS和DS都阻塞一个时钟周期,并在此周期内更新next_pc
和inst
的内容,从而达到跳过jmp
指令后的那一条指令的目的。
⭐需要注意的是,bne
和beq
指令,在这两条指令因为等待源寄存器更新而阻塞在DS时,需要将br_taken
信号也阻塞(赋值为0),因为此时跳转指令相关计算未完成。
代码修改
寄存器号传递
EXE_stage:
output [ 4:0] es_to_ds_dest, //执行级目的操作数寄存器号
assign es_to_ds_dest = dest & {5{es_valid}};
MEM_stage:
output [ 4:0] ms_to_ds_dest //访存级目的操作数寄存器号
assign ms_to_ds_dest = ms_dest & {5{ms_valid}};
WB_stage:
output [ 4:0] ws_to_ds_dest //写回级目的操作数寄存器号
assign ws_to_ds_dest = ws_dest & {5{ws_valid}};
ID_stage:
input [4:0] es_to_ds_dest,
input [4:0] ms_to_ds_dest,
input [4:0] ws_to_ds_dest
阻塞信号产生
// IF stage
assign fs_ready_go = ~br_taken; // if taken is valid, if stage block
always @(posedge clk) begin
if (reset) begin
fs_pc <= 32'h1bfffffc; //trick: to make nextpc be 0x1c000000 during reset
end
else if (to_fs_valid && (fs_allowin || br_taken)) begin
// if taken is valid, to skip the delay slot instruction, next_pc should be the instruction after the jump inst
fs_pc <= nextpc;
end
end
// if taken is valid and if stage is block, get the instruction after the jump inst
assign inst_sram_en = to_fs_valid && (fs_allowin || br_taken);
ID_stage:
wire inst_no_dest;
wire src_no_rj;
wire src_no_rk;
wire src_no_rd;
wire rj_wait;
wire rk_wait;
wire rd_wait;
wire no_wait;
// taken signal need to be block too!!!!
assign br_taken = ( inst_beq && rj_eq_rd
|| inst_bne && !rj_eq_rd
|| inst_jirl
|| inst_bl
|| inst_b
) && ds_valid & no_wait;
assign inst_no_dest = inst_st_w | inst_b | inst_beq | inst_bne;
assign src_no_rj = inst_b | inst_bl | inst_lu12i_w;
assign src_no_rk = inst_slli_w | inst_srli_w | inst_srai_w | inst_addi_w | inst_ld_w | inst_st_w | inst_jirl |
inst_b | inst_bl | inst_beq | inst_bne | inst_lu12i_w;
assign src_no_rd = ~inst_st_w & ~inst_beq & ~inst_bne;
assign rj_wait = ~src_no_rj && (rj != 5'b00000) && ((rj == es_to_ds_dest) || (rj == ms_to_ds_dest) || (rj == ws_to_ds_dest));
assign rk_wait = ~src_no_rk && (rk != 5'b00000) && ((rk == es_to_ds_dest) || (rk == ms_to_ds_dest) || (rk == ws_to_ds_dest));
assign rd_wait = ~src_no_rd && (rd != 5'b00000) && ((rd == es_to_ds_dest) || (rd == ms_to_ds_dest) || (rd == ws_to_ds_dest));
assign no_wait = ~rj_wait & ~rk_wait & ~rd_wait;
转移计算未完成
DS为jmp指令,ES为Load指令。
IF_stage:
wire br_stall;
wire pre_if_ready_go;
assign {br_stall, br_taken, br_target} = br_bus;
// pre-IF stage
assign to_fs_valid = ~reset && pre_if_ready_go;
assign pre_if_ready_go = ~br_stall;
// if taken is valid and if stage is block, get the instruction after the jump inst
assign inst_sram_en = to_fs_valid && (fs_allowin || br_taken) && pre_if_ready_go;
ID_stage:
input es_to_ds_load_op;
wire br_stall;
wire load_stall;
// es is load and ds is jmp(taken)
assign br_stall = load_stall & br_taken & ds_valid;
assign load_stall = es_to_ds_load_op & (((rj == es_to_ds_dest) & rj_wait) |
((rk == es_to_ds_dest) & rk_wait) |
((rd == es_to_ds_dest) & rd_wait));
assign br_bus = {br_stall,br_taken,br_target};
EXE_stage:
output es_to_ds_load_op;
mycpu.h:
`define BR_BUS_WD 34 //修改
Testbench
测试通过
参考资料
[1] CPU设计实战(汪文祥)第四章
[2] LoongArch CPU设计实验_实践任务8
[3] CDP_EDE_local
[4] 龙芯架构32位精简版参考手册
[5] exp实验发布包下载地址
[6] Loongarch个人赛指令集