北航计算机组成P5
北航计算机组成P5
前言
- P5工作量确实比P4大了不少,画设计图写文档一天半,搭CPU加测试一天,不过如果课下做好准备过了强测那课上100%能过甚至直接ak走人了
课下
- 写完P6已经1700行了,主要是我采取了针对指令的译码方式,所以整体代码量会稍微大一点(),主要这样我比较方便debug,所以就没改了
译码方式
集中式译码方式
- 最大的优点就是
方便从P4改过来,其实我选择集中式译码的理由是方便添加一些指令,而且译码行为在D级就可以全完成,所以如果加入一些额外的指令信号不会出现高阻态或者其他的比较抽象的错误(分布式译码课上如果出锅了可能就是这个原因) - 最大的缺点就是需要流水的信号实在是太多了,因为所有要用的信号必须在D级就全部生成完毕,所以向E级就会流水各种信号,不过课下认真设计,仔细连线保证没有bug那在课上也没啥,反正课上最多增加两三个新增的端口,反正不会一次写两个寄存器()
- 最大的优点就是
分布式译码
- 最大的优点就是流水的东西少,几乎可以只流水指令,然后每次在接收到指令之后再译码,从里面接出需要的信号,然后提供给这个级
- 缺点是,缺点是啥来着,我刚开始不想用分布式译码主要是觉得每个级单独译出$T_{new}$信号什么的特别复杂,还不如刚开始就全部翻译处出来了,但是往届学长还是有很多优秀的分布式译码框架的,只是我没有去参考
译码语句没有选
assign
,那和我搭logisim有什么区别(怒),我verilog就要用不一样的东西,事实是不如assign
,代码量还是太大了,不过课上加指令的适合比较方便,毕竟每个指令之间不会互相影响always@(*) begin
NPCSel = `NPC_normal;
ExtOp = `EXT_zero;
GRFA3Sel = `GRFA3_zero;
...
case(instr) begin
`Op_add: begin
GRFA3Sel = `GRFA3_rd;
calr = 1'b1;
end
...
endcase事实上也可以像logisim一样用与或门阵列的方式,但是我不使用的原因是P3课上加指令的时候加的太慢而且老是搭错线。。。,优点是代码量压缩程度很优秀,尤其适合P6支持更多指令的时候
wire add, sub.....;
wire[1: 0] GRFA3Sel...;
assign add = (Opcode == 6'b0) & (Funct == `Func_add);
assign GRFA3Sel[0] = (add | ....)
assign GRFA3Sel[1] = ....
//或者
assign GRFA3Sel = (add | ....) ? 2'b00 :
(ori | .....) ? 2'b01
数据通路设计
F
PC
- 同理我们先建模
最简单(最重要)的PC寄存器,理由同单周期CPU,我们需要PC寄存器指向当前的指令地址端口 位宽 方向 描述 clk 1 input 时钟信号 reset 1 input 同步复位信号 we 1 input PC寄存器使能信号 NPC[31:0] 32 input 传入更新的值 PC[31:0] 32 output 传出目前的PC值
IM
- 指令寄存器,同单周期CPU,没有任何改变
端口 位宽 方向 描述 PC[31:0] 32 input 传入PC值供取指 Instr[31:0] 32 input 获得指令
D
D_REG
F/D
流水寄存器,将F级的信息传递给D级端口 位宽 方向 描述 clk 1 input 时钟信号 reset 1 input 同步复位信号 we 1 input D流水寄存器使能信号 F_PC[31:0] 32 input F级传入PC值 F_Instr[31:0] 32 input F级传入指令 D_PC[31:0] 32 output 传入D级PC值 D_Instr[31:0] 32 output 传入D级别PC值
IM_SPL
- D级分线器,将指令分为若干部分
端口 位宽 方向 描述 Instr[31:0] 32 input 传入解析指令 Opcode[5:0] 6 output 指令操作码 Funct[5:0] 6 output R型指令函数操作码 Rs[4:0] 5 output 源寄存器 Rt[4:0] 5 output 目标寄存器 Rd[4:0] 5 output 目的寄存器 Shamt[4:0] 5 output 移位值 Imm_i[15:0] 16 output I型指令立即数 Imm_j[25:0] 26 output J型指令立即数
GRF
- D级寄存器堆,取出对应寄存器值
端口 位宽 方向 描述 clk 1 input 时钟信号 reset 1 input 同步复位信号 A1[4:0] 5 input 第一个读寄存器 A2[4:0] 5 input 第二个读寄存器 A3[4:0] ( W
)5 input 写寄存器 WD[31:0] ( W
)32 input 寄存器写入值 RD1[31:0] 32 output 读寄存器值(已内部转发) RD2[31:0] 32 output 读寄存器值(已内部转发)
EXT
- D级拓展单元
端口定义
端口 位宽 方向 描述 Imm[15:0] 16 input 拓展立即数 ExtOp[1:0] 2 input 控制立即数拓展 Ext_imm[31:0] 32 input 拓展后立即数 控制信号定义
信号名 取值 描述 EXT_zero 2’b00 控制ExtOp信号零拓展 EXT_sign 2’b01 控制ExtOp信号符号位拓展 EXT_lui 2’b10 控制ExtOp信号高位加载
NPC
- D级次地址计算单元
端口定义
端口 位宽 方向 描述 F_PC[31:0] 32 input F级PC值 D_PC[31:0] 32 input D级PC值 Rs[31:0] 32 input 跳转寄存器值 Imm[25:0] 26 input 立即数 NpcSel[1:0] 2 input 控制NPC NPC[31:0] 32 output 次地址 控制信号定义
信号名 取值 描述 NPC_normal 2’b00 NpcSel正常跳转 NPC_branch 2’b01 NpcSel条件跳转 NPC_jadder 2’b10 NpcSel跳转地址 NPC_jrs 2’b11 NpcSel跳转寄存器
CMP
- D级branch指令前移比较器
- 端口定义
端口 位宽 方向 描述 Rs_val[31:0] 32 input rs寄存器读出值 Rt_val[31:0] 32 input rt寄存器读出值 CmpOp 1 input 比较类型 Flag 1 output 比较结果 - 控制信号定义
信号名 取值 描述 CMP_eq 1’b0 控制CmpOp信号相等比较 CMP_ne 1’b1 控制CmpOp信号不等比较
- 端口定义
Control
- D级集中译码器
端口 位宽 方向 描述 Opcode[5:0] 6 input 指令操作码 Funct[5:0] 6 input 函数码 Tuse_rs[1:0] 2 output rs寄存器的Tuse Tuse_rt[1:0] 2 output rt寄存器的Tuse Tnew[1:0] 2 output 指令Tnew NpcSel[1:0] 2 output 控制NPC CmpOp 1 output 控制CMP ExtOp[1:0] 2 output 控制EXT GRFA3Sel[1:0] 2 output 控制GRFA3Mux ALUASel 1 output 控制ALUAMux ALUBSel[1:0] 2 output 控制ALUBMux ALUOp[1:0] 2 output 控制ALU DMWe 1 output DM使能 DMOp[2:0] 3 output DM写入方式 GRFWDSel[1:0] 2 output 控制GRFWDMux
MUX
GRFA3Mux
- 选择GRF写入地址的多路选择器
信号名 取值 描述 GRFA3_rt 2’b00 选择rt寄存器值 GRFA3_rd 2’b01 选择rd寄存器值 GRFA3_ra 2’b10 选择$ra寄存器 GRFA3_zero 2’b11 默认不写寄存器
- 选择GRF写入地址的多路选择器
RSFDMux
- 对于读出的rs寄存器进行转发
RTFDMux
- 对于读出的rt寄存器进行转发
E
E_REG
D/E
流水寄存器端口 位宽 方向 描述 clk 1 input 时钟信号 reset 1 input 同步复位信号 we 1 input 写使能信号 D_rs[4:0] 5 input D级rs编号 D_rt[4:0] 5 input D级rt编号 D_a3[4:0] 5 input D级A3 D_rs_val[31:0] 32 input D级rs寄存器值 D_rt_val[31:0] 32 input D级rt寄存器值 D_ext_imm[31:0] 32 input D级ext_imm值 D_shamt[4:0] 5 input D级shamt值 D_pcwith8[31:0] 32 input D级PC+8 E_rs[4:0] 5 output E级rs编号 E_rt[4:0] 5 output E级rt编号 E_a3[4:0] 5 output E级A3 E_rs_val[31:0] 32 output E级rs寄存器值 E_rt_val[31:0] 32 output E级rt寄存器值 E_ext_imm[31:0] 32 output E级ext_imm值 E_shamt[4:0] 5 output E级shamt值 E_pcwith8[31:0] 32 output E级PC+8 D_ALUASel 1 input 控制ALUAMux D_ALUBSel[1:0] 2 input 控制ALUBMux D_ALUOp[1:0] 2 input 控制ALU D_DMWe 1 input DM使能 D_DMOp[2:0] 3 input DM写入方式 D_GRFWDSel[1:0] 2 input 控制GRFWDMux E_ALUASel 1 output 控制ALUAMux E_ALUBSel[1:0] 2 output 控制ALUBMux E_ALUOp[1:0] 2 output 控制ALU E_DMWe 1 output DM使能 E_DMOp[2:0] 3 output DM写入方式 E_GRFWDSel[1:0] 2 output 控制GRFWDMux D_Tnew[1:0] 2 input Tnew E_Tnew[1:0] 2 output Tnew
ALU
- E级计算单元
端口定义
端口 位宽 方向 描述 A[31:0] 32 input ALU第一个操作数 B[31:0] 32 input ALU第二个操作数 ALUOp[1:0] 2 input ALU进行的操作 ALU_out[31:0] 32 output ALU结果数 控制信号定义
信号名 取值 描述 ALU_add 2’b00 加法操作 ALU_sub 2’b01 减法操作 ALU_or 2’b10 或运算操作
MUX
ALUAMux
信号名 取值 描述 ALUA_rs 2’b0 选择rs寄存器(一般而言) ALUA_rt 2’b1 选择rt寄存器(移位操作) ALUBMux
信号名 取值 描述 ALUB_rt 2’b00 选择rt寄存器(一般R型) ALUB_imm 2’b01 选择ext_imm ALUB_shamt 2’b10 选择shamt ALUB_rs 2’b11 选择rs寄存器(R型移位) RSFEMux
RTFEMux
M
M_REG
E/M
流水寄存器端口 位宽 方向 描述 clk 1 input 时钟信号 reset 1 input 同步复位信号 we 1 input 写使能信号 E_rt[4:0] 5 input rt寄存器的编号 E_rt_val[31:0] 32 input rt寄存器的值 E_alu_out[31:0] 32 input ALU运算结果 E_a3[4:0] 5 input A3结果 E_imm[31:0] 32 input lui结果 E_pcwith8[31:0] 32 input PC+8结果 M_rt[4:0] 5 output rt寄存器的编号 M_rt_val[31:0] 32 output rt寄存器的值 M_alu_out[31:0] 32 output ALU运算结果 M_a3[4:0] 5 output A3结果 M_imm[31:0] 32 output lui结果 M_pcwith8[31:0] 32 output PC+8结果 E_DMWe 1 input DM写使能信号 E_DMOp[2:0] 3 input DM选择 E_GRFWDSel[1:0] 2 input 选择GRFWD M_DMWe 1 input DM写使能信号 M_DMOp[2:0] 3 input DM选择 M_GRFWDSel[1:0] 2 input 选择GRFWD E_Tnew[1:0] 2 input Tnew M_Tnew[1:0] 2 input Tnew
DM
- M级数据存储器
端口定义
端口 位宽 方向 描述 clk 1 input 时钟信号 reset 1 input 同步复位信号 we 1 input 写使能 Adder[31:0] 32 input 地址 WD[31:0] 32 input 写入值 RD[31:0] 32 output 读出值 DMOp[2:0] 3 input 选择DM读写方式 控制信号定义
信号名 取值 描述 DM_w 3’b000 字单位写入读出 DM_h 3’b001 半字单位写入,符号拓展读出 DM_hu 3’b010 半字单位写入,无符号拓展读出 DM_b 3’b011 字节单位写入,符号拓展读出 DM_bu 3’b100 字节单位写入,无符号拓展读出
MUX
RTFMMux
W
W_REG
M/W
级流水寄存器端口 位宽 方向 描述 clk 1 input 时钟信号 reset 1 input 同步复位信号 we 1 input 写使能信号 M_alu_out[31:0] 32 input ALU运算结果 M_dm_rd[31:0] 32 input 内存读出结果 M_a3[4:0] 5 input A3结果 M_imm[31:0] 32 input lui结果 M_pcwith8[31:0] 32 input PC+8结果 W_alu_out[31:0] 32 output ALU运算结果 W_dm_rd[31:0] 32 output 内存读出结果 W_a3[4:0] 5 output A3结果 W_imm[31:0] 32 output lui结果 W_pcwith8[31:0] 32 output PC+8结果 M_GRFWDSel[1:0] 2 input 选择GRFWD W_GRFWDSel[1:0] 2 output 选择GRFWD
GRF
W级寄存器堆
端口 位宽 方向 描述 clk 1 input 时钟信号 reset 1 input 同步复位信号 A1[4:0] ( D
)5 input 第一个读寄存器 A2[4:0] ( D
)5 input 第二个读寄存器 A3[4:0] 5 input 写寄存器 WD[31:0] 32 input 寄存器写入值 RD1[31:0] ( D
)32 output 读寄存器值(已内部转发) RD2[31:0] ( D
)32 output 读寄存器值(已内部转发)
MUX
GRFWDMux
信号名 取值 描述 GRFWD_alu 2’b00 选择alu_out写入 GRFWD_dm 2’b01 选择dm_rd写入 GRFWD_pcwith8 2’b10 选择PC+8写入 GRFWD_imm 2’b11 选择ext_imm写入 说了一大堆,主要注意D级的若干部件还是最大的D_reg,E_reg两个流水寄存器,最好写的时候像我一样将数据、控制信号什么的分开来,而且一定要先写设计文档,详细设计!!!,然后定义端口写模块照着你的设计文档就好了,数据通路的连接最好依照你的设计图,不管是手画还是用其他软件画的,
关于阻塞与转发
- 为了方便加入指令,我们将所有指令分为若干类别
- ALU计算R型指令
cal_r: add, sub
- ALU计算I型指令
cal_i: ori
- 特殊指令
lui: lui
- 存储指令
store: sw
- 访存指令
load: lw
- 分支指令
branch: beq
- 跳转链接指令
j_l: jal
- 跳回指令
j_r: jr
- ALU计算R型指令
转发输出口
接下来分析转发电路,明白哪些地方会产生寄存器的新值
- 首先
ALU_out
与DM_rd
这两个端口必然会产生寄存器的新值(这里实际上考虑无脑转发可以将DM_rd
换为GRF_wd
) - 对于
lui
指令,在D阶段后的ext_imm
端口已经产生了值,可以转发 - 对于
j_l
指令,在D阶段后的PC+8
端口已经产生了值,可以转发
转发输出 指令类型 ALU_out@M cal_r、cal_i GRF_wd@W(过度转发) (cal_r)、(cal_i)、load、(lui)、(j_l) ext_imm@{E、M} lui PC+8@{E、M} j_l 寄存器内部转发(M -> D) 略 - (指令)代表该指令已经转发过正确的值了
- 由于寄存器的内部转发使得$W$不需要向$D$转发,而$W$向$E$转发的内容除了
load
指令外都是ALU_out
的值,这里在$M$就已经向$D$转发过了,所以本身就是正确的,不过无脑转发有什么错呢,(大家只是拿一个正确的值替换另一个正确的值罢了,硬件电路不需要感情) - 根据需要往grf写入什么来判断需要转发什么,这里只是输出转发的内容,还需要配套转发内容的接收端的选择信号
assign FWD_E = (E_GRFWDSel == `GRFWD_imm) ? E_imm :
(E_GRFWDSel == `GRFWD_pcwith8) ? E_pcwith8 : 32'b0;
- 首先
转发接入口
分析转发的接入端口
- D级可以接收来自E、M、W级别的转发,但是W级转发我们已经在内部实现了
assign val1 = (A1 == 0) ? 32'b0 : grf[A1];
assign RD1 = (A1 != 0) & (A1 == A3) ? WD : val1; - E级ALU计算前也可以接收转发
- 最后单独对于store类型指令,rt寄存器在M阶段才会使用,所以DM的输入端口也可以接收转发
转发输入 指令类型 GRF_rs@D cal_r、cal_i、store、j_r、branch GRF_rt@D cal_r、branch GRF_rs@E 同上 GRF_rt@E 同上(store指令可能也转了) DM_wd@M store 寄存器内部转发(M -> D) 略 - 最后同上,正确的值可能被多次转发,例如store类指令的rt寄存器,在D、E阶段可能都被转发了一次,但是没关系,最后还是对的(),况且真的要考虑只转发一次极容易忽略转发路径,而且在考试的时候容易漏情况,所以我使用无脑转发了
assign D_rs_val = (D_rs == 5'b0) ? 32'b0 :
(D_rs == E_a3) ? FWD_E :
(D_rs == M_a3) ? FWD_M :
(D_rs == W_a3) ? FWD_W : D_grf_rd1; - 这里有一个疑惑,万一正确的数据没生成或者目前是错的但是我们已经无脑转发回去了怎么办?首先我们目前实现的指令集不需要考虑第一种情况,因为它对应着阻塞,Tuse < Tnew时会直接阻塞。因为这种情况来不及转发回去,也就是数据没生成就转发了;第二种情况因为Tuse >= Tnew,那么Tuse等于0时,Tnew一定也是0,说明正确的数据已经生成了并且已经转回去了,覆盖率原来错误的数据。
- 注意:其实可以使用恰当转发,如加入Tnew==0条件,生成数据才转发回去,不过实际上我们目前不会使用,课上倒是又可能,不过也有其他办法规避,至少目前可以认为我们不选恰当转发(我后面会更新的
- D级可以接收来自E、M、W级别的转发,但是W级转发我们已经在内部实现了
阻塞
根据指令类别判断$T_{use}$,$T_{new}$
指令类别 $ T_{use}:rs $(@D) $ T_{use}:rt $(@D) $T_{new} $@E $ T_{new} $@M $ T_{new} $@W cal_r $1$ $1$ $1$ $0$ $0$ cal_i $1$ ($3$) $1$ $0$ $0$ lui ($3$) ($3$) $0$ $0$ $0$ store $1$ $2$ ($0$) ($0$) ($0$) load $1$ ($3$) $2$ $1$ $0$ branch $0$ $0$ ($0$) ($0$) ($0$) j_l ($3$) ($3$) $0$ $0$ $0$ j_r $0$ ($3$) ($0$) ($0$) ($0$) - ($3$)代表不会需要读这个寄存器,所以不需要关注它的$T_{use}$,直接认为在W级使用即可,
反正没有指令会在W级才用寄存器 - ($0$)代表不会写寄存器,也即不会影响后续的读取,即
A3@{E、M、W}
都为0,所以转发会被我们排除,同时也不会干扰阻塞 - 由于$T_{use}$只会在译码阶段使用所以不需要考虑后续流水,但是$T_{new}$信号将参与流水,表达式为$T_{new}$@{N+1} $= \max{$ $T_{new}$@{N} $ -1,0}$
- ($3$)代表不会需要读这个寄存器,所以不需要关注它的$T_{use}$,直接认为在W级使用即可,
当D级需要使用rs或者rt且满足(以rs举例):$rs \ne 0$ && $rs == rd$@{N} && $T_{use} \lt T_{new}$@{N}则阻塞
由于我们使用的是暴力转发,能转则转,应转尽转,所以不需要考虑先前是否转发过了,后续就算转发的也一定是正确内容,且转发$E \gt M \gt W \gt GRF$
ok,言尽于此,少年加油吧,搭完课下再来看课上的内容。跟我念P5一遍过前来还愿
课上
T1 xe
- R型指令
xe rd, rs, rt
temp1 <- GPR[rs]
temp2 <- GPR[rt]
for i in range(0, 16):
temp <- temp1[2*i+1] ^ temp2[2*i+1]
GPR[rd] <- temp
T2 jtl
- I型指令
jtl rs, rt, label
temp1 <- GPR[rs] + GPR[rt]
temp2 <- PC + 4 + sign_ext(offset || 00)
if temp1 > temp2:
PC <- temp2
else:
PC <- temp1
Nullifycation()
T3 lwoc
- I型指令
lwoc rt, offset(base)
adder <- GPR[base] + sign_ext(offset || 00)
vadder <- adder[31: 2]
temp1 <- Mem[vadder]
temp2 <- temp1[3: 0]
if temp1 < 0x80000000:
GPR[temp2] <- temp1
else:
GPR[rt] <- temp1 - D级译码一个check信号然后流水,就可以在流水线中识别有没有这条指令了
- 这题说一嘴,我刚开始给他设置了lw的阻塞信号搭配暴力阻塞低16个寄存器,但是WA了两个sw的点,其实这里就是无脑转发的问题
lwoc $3, 124($0)
sw $3, 124($0)- 假设最后没有向
$3
写入,但是我们在W级才将$3
修改为实际写入的寄存器,这个时候lwoc在M级别就向sw转发了一个完全错误的内容,但是没有像一般的lw一样在W级向sw再次转发,导致sw接收到错误的转发值 - 解决方案一:刚开始就设置
GRFA3Sel == `GRFA3_zero
这样就不会向前转发,然后把rt和低16个寄存器堵住,等到W级知道要写什么的情况在继续即可assign stall_rs_E = (E_check ? (D_rs == E_rt || D_rs < 5'd16) : D_rs == E_a3) & (D_rs != 5'd0) & (Tuse_rs < E_Tnew)
//assign stall_rt_E
//assign stall_rs_M
//assign stall_rt_M - 解决方案二;改为恰当转发,当一个指令得到最后正确的结果才会向前转发,详情可见前面讲暴力转发的时候提了一嘴
assign stall_rs_E = (E_check ? D_rs < 5'd16 : 1'b1) & (D_rs == E_a3) & (D_rs != 0) & (Tuse_rs < E_Tnew)
//assign stall_rt_E
//assign stall_rs_M
//assign stall_rt_M
assign D_rs_val = (D_rs == 5'b0) ? 32'b0 :
((D_rs == E_a3) && (E_Tnew == 3'b0)) ? FWD_E :
((D_rs == M_a3) && (M_Tnew == 3'b0)) ? FWD_M :
(D_rs == W_a3) ? FWD_W : D_grf_rs;
//W级一定已经产生数据了
- 假设最后没有向
- 最后怎么AC的呢,当然我考场上并没有发现可能出现的错误,但是不能完全暴力阻塞遇到这条指令则阻塞(课程组卡了一下t),所以我把
$31
寄存器放出来了(纯粹是因为眼缘,毕竟卡着31号寄存器大概率没啥用),所以就过了,总体而言还是暴力阻塞。