MIPS42思路记录
31…26 | 25…21 | 20…16 | 15…11 | 10…6 | 5…0 | |
---|---|---|---|---|---|---|
R | op | rs | rt | rd | shamt | func |
I | op | rs | rt | immediate | ||
J | op | address |
1、指令分类
-
calc_i
-
calc_r
-
load
-
store
-
jump
-
jump and link
-
jump and link register
-
branch
2、功能实现
1.F级
NPC的生成,在PC4_F、NPC_D之间选择;
PC地址的生成
PC pc(
NPC_F,
PC_F,
PCWrEn_F,
clk,
rst
);
访问指令存储器IM,获得指令操作码
```Verilog
im_4k IM( PC_F[11:2], instr_F );
```
2.IF_ID级
主要包括PC4_F、instr_F指令的传输
IF_ID IF_ID(
PC4_F,
instr_F,
rst,
clk,
Flush_FD,
WrEn_FD,
PC4_D,
instr_D
);
3.D级
划分指令,获得各个有效字段
Control:通过opcode,funct,字段生成更多控制信号
Control control(
opcode,
funct,
Instr_Category_D,
RwSel_D,
EXTOp_D,
imm16_or_PC4_D,
ALUOp_D,
ALUSelB_D,
ALUSelA_D,
DMWr_D,
DMOp_D,// data memory optr
MemToReg_D
);
NPC:主要生成下一地址,
NPC npc(
opcode,
rt,
Instr_Category_D,
imm26_D,
BusS_D, //RS DATA
BusT_D, // RT DATA
PC4_D,
NPC_D,
NPCSel_D,
Flush_FD
);
RF: 访问寄存器堆,实现读写操作
RF GRF(
rs,
rt,
Rw_W,
PC4_W,
RwData_W,
clk,
rst,
RSD_D,
RTD_D,
);
EXT:扩展单元,实现0扩展,符号扩展、立即数加载至高位
EXT ext(
imm16_D,
EXTOp_D,
imm32
);
利用多选器生成Rw_D、imm16orPC4_D
4.D_E
ID_EX级的数据传输/
5.E级
利用多选器生成ALUOpndA_E、ALUOpndB_E,
ALU计算单元
ALU ALU(
ALUOpndA_E,
ALUOpndB_E,
ALUOp_E, //控制发生何种操作
ALUResult_E
);
6.E_M
EXE_MEM级的数据传输
7.M级
dm_4k: 访问数据存储器
dm_4k dm_4k( ALUResult_M[11:0], DMOp_M,BusT_M, DMWr_M, clk, dout_M );
8.MEM_WB
MEM_WB级的数据传输
9.W级
写回数据RwData_W的选择
3、旁路实现
使用旁路的根本目的都是为了保证该指令所使用的寄存器的值对于该指令当时来说是最新的。
对于旁路,最重要的是区分数据供给者、数据接受者,当供给者提供的寄存器编号与接收者接收的寄存器编号相同时,使用旁路;否则,仍然使用原寄存器的值;
provider:
EXE_MEM级的ALUResult_M, Rw_M;
MEM_WB级的RwData_W, Rw_W;
receiver:
npc:
RSD_D,RTD_D;
alu:
RSD_E, RTD_E
dm:
RTD_M
4、阻塞实现
补充阻塞的作用以及在逻辑上的实现:
阻塞是为了处理由于所使用的寄存器的值还未产生,导致即使适用旁路仍然无法解决的情况。在我设计的模块中,其实现阻塞主要是通过将PC_F写信号、IF_ID寄存器写信号、RwSel置零,即将D级指令截断在D级,等待阻塞消失后重新再次取相同的指令继续执行
阻塞Stall是专门为处理
Case1: ld/r 型数据冒险而设计的,我们可以通过检测 E级的指令类型以及 D级的指令类型实现对 ld/r 型数据冒险的判断;
需要阻塞一个周期;
if (Instr_Category_E == `load && (Instr_Category_D == `R_type ) && ( (RT_E == RS_D) || ( RT_E == RT_D ) )
Case2: xxxx / branch 型数据冒险设计,对于这种数据冒险来说,xxxx指令的目标寄存器会被branch型指令使用到,而xxxx型指令产生所需要的值至少要到 MEM级,具体可分为一下情况:
(1)calr除去 jr 指令(忽略 jr 指令方便处理) ,cali, 指令, 这些指令必须产生一个周期的阻塞;
(2)ld 型指令, 必须产生 两个周期的阻塞,但是仍然是一个周期接一个周期产生
//calr/branch型
((Instr_Category_E == `calc_r) && ((opcode == `beq )||(opcode == `bne)) && ((RD_E == RS_D)||(RD_E == RT_D)))||
((Instr_Category_E == `calc_r) && (opcode == `b_special)&&(RD_E == RS_D))||
//calc_i/branch型
((Instr_Category_E == `calc_i) && ((opcode == `beq)||(opcode == `bne)) && ((RT_E == RS_D)||(RT_E == RT_D))) ||
((Instr_Category_E == `calc_i) && (opcode == `b_special) && (RT_E == RS_D)) ||
//load/branch型
((Instr_Category_E == `load) && ((opcode == `beq)||(opcode == `bne)) && ((RT_E == RS_D)||( RT_E == RT_D ))) ||
((Instr_Category_E == `load) && (opcode == `b_special) && (RT_E == RS_D)) ||
((Instr_Category_M == `load) && ((opcode == `beq)||(opcode == `bne))&& ((RT_M == RS_D)||(RT_M == RT_D))) ||
((Instr_Category_M == `load)&& (opcode == `b_special) && (RT_M == RS_D)) ||
Case3:
由于将NPC计算逻辑移动至D级,需要在D级使用最新的RSD_D,而此时最新的RSD_D可能还未计算出来,其值还在流水线寄存器中,所以需要使用阻塞。
//(calc_r/calc_i/load)/jump_l_r型
((Instr_Category_E == `calc_r) && (Instr_Category_D == `jump_l_r) && (RD_E == RS_D))||
((Instr_Category_E == `calc_i) && (Instr_Category_D == `jump_l_r) && (RT_E == RS_D)) ||
((Instr_Category_E == `load) && (Instr_Category_D == `jump_l_r) && (RT_E == RS_D)) ||
((Instr_Category_M == `load) && (Instr_Category_D == `jump_l_r) && (RT_M == RS_D))
如果
判断生成阻塞, Stall = 1;
否则, Stall = 0;
5、调试心得
在调试代码的过程中,我深刻体会到对于Verilog编程的特点和RTL模型的特点,感到对于Verilog编程中出现的错误的分析才是我们进一步提高对体系结构了解的阶梯,对于我们的知识的掌握非常重要。另一方面,对于错误的分析也对我们对于结构的理解和把握要求很高,这要求我们一定要在将整体思路和各指令的实现方式彻底了解之后在进行编程构建。
我收获了如下几点:
-
通过输出的写寄存器、存储器的 数据的值,我们可以对照Mars调试器进行对比,对出现不同的地方进行比较分析,可以根据Instr_Categry_D以及opcode字段判断出现错误信号的位置,在将与该错误有关的信号进行分析,观察是否存在异常。
-
对于错误的分析,要求我们深入理解每条指令的执行过程,同时错误也可以加深我们对于指令运行的理解;
-
出现的常见错误有:
- 未考虑初始化时信号全部归零这个特例
- 端口位数不对应
- 考虑不周导致特殊指令组合无法正常运行如在Stall模块编写时,我没有考虑到 我将NPC模块移至D级导致对于load/jr,load/jalr, (cal_r/cal_i)/(jr/jalr)必须要使用阻塞
- 对于特殊情况缺乏考虑,如调试指令中出现的 jalr 指令伴随发生阻塞,由于 jalr 指令本身一定会导致出现控制冒险Flush_FD = 1,我在编写没有考虑到这种情况,使 Flush_FD 信号的判断优先级高于 WrEn_FD 信号,导致 Instr_D 信号被清零(正常情况是保持不变),从而导致错误。