「BUAA-CO」P5课上


P5课上三题有明显得规律,第一题一般只涉及ALU的修改,第二题是b类型的跳转指令,第三题稍微难一点,涉及访存以及对转发阻塞逻辑的修改。下面我们结合题目详细说明一下解题思路和步骤。

CCC

opcode = 忘了

tmp1 = GPR[rs]的前导零
tmp2 = GPR[rt]的前导一
if (tmp1 > tmp2) GPR[rd] <- GPR[rs][31:16] || GPR[rt][15:0]
else GRP[rd] <- GPR[rt][31:16] || GPR[rs][15:0]

Answer:在ALUtmp1tmp2的值计算出来,并通过ALUOp控制ALU输出不同的结果。在CTRL模块新增指令,使得该指令写入GPR[rd],并且写入数据为ALU输出结果。在Stall模块中TnewTuse无脑跟add保持一致即可。

BLONEZ?(应该吧)

opcode = 111111

tmp = GPR[rs]前导1
GPR[31] <- PC + 8;
if (tmp1 > GPT[rt][5:0])
PC <- IMMbeq的跳转方式

这个是考场题目,但是比较简单,大家感兴趣可以看一看,我们用一道推荐题来说明这类型题目的解决方法。

BLZTAL

opcode = 100111

看题:
BLZTAL

Answer:对于b型指令,我们要在D_CMP模块中判断b_jump,如果是条件链接(像这道题),我们就要将D_CMP模块中的b_jump指令随着流水线一直流水下去,从而满足每一级的CTRL判断GRFA3selGRFWDsel信号的需求;如果是无条件链接(像BLONEZ),那CTRL直接生成控制信号写入31号寄存器,写入值是PC + 8即可。在STALL模块TuseTnewBeq保持一致。

对于链接类指令Tuse,Tnew参照jal就行

特别注意一下PC的变化基数是D_PC还是F_PC。

还有一种情况可能会涉及清空延迟槽的指令,像这样:

BONALL

为什么要清空延迟槽呢,我理解就是不想执行跳转类指令的下一条指令了,把他用Flush清除掉。具体实现需要在D_CMP增加一个接口Flush_checkassign Flush_check = (CMPOp == `CMP_BONALL && !B_jump);,在mips中将这一接口与D_reg_flush**相连,assign D_reg_flush = (!stall && D_flush_check);值得注意的是,我们要保证此时流水线不是阻塞状态.如果是阻塞状态我们就等到它阻塞结束再清空,想想为什么呢?因为如果流水线阻塞在D级的话,D级放的其实是跳转类指令,相当于是把自己送走了**,后面的流水级就丢失了这条指令。

LBGET

opcode = 110101

LBGET

Answer:这是写入地址可选择类condition: GPR[rt] <- RD; else: GPR[rs] <- RD,我们只有在DM完成计算才能知道要写入哪个寄存器,那么D,E,(M)就都没GRFA3的视野,因此难以判断冲突条件是否成立。此时我们有两个选择:暴力阻塞;条件阻塞

暴力阻塞:只要D级指令要访问GPR,并且访问地址非0,并且Tuse < Tnew,我们就阻塞。对应代码实现

assign E_stall_rs = ((E_lbget ? 1 : (E_GRFA3 == D_rs)) && D_rs != 0) && (rs_Tuse < E_Tnew);
assign E_stall_rt = ((E_lbget ? 1 : (E_GRFA3 == D_rt)) && D_rt != 0) && (rt_Tuse < E_Tnew);
assign M_stall_rs = ((M_lbget ? 1 : (M_GRFA3 == D_rs)) && D_rs != 0) && (rs_Tuse < M_Tnew);
assign M_stall_rt = ((M_lbget ? 1 : (M_GRFA3 == D_rt)) && D_rt != 0) && (rt_Tuse < M_Tnew);

条件阻塞:D级指令要访问GPR,并且访问地址非0,并且访问地址和当前级流水线的 rsrt 冲突,并且 Tuse < Tnew,我们阻塞。对应代码实现。

assign E_stall_rs = ((E_lbget ? (D_rs == E_rt || D_rs == E_rs) : (E_GRFA3 == D_rs)) && D_rs != 0) && (rs_Tuse < E_Tnew);
assign E_stall_rt = ((E_lbget ? (D_rt == E_rs || D_rt == E_rt) : (E_GRFA3 == D_rt)) && D_rt != 0) && (rt_Tuse < E_Tnew);
assign M_stall_rs = ((M_lbget ? (D_rs == M_rs || D_rs == M_rt) : (M_GRFA3 == D_rs)) && D_rs != 0) && (rs_Tuse < M_Tnew);
assign M_stall_rt = ((M_lbget ? (D_rt == M_rs || D_rt == M_rt) : (M_GRFA3 == D_rt)) && D_rt != 0) && (rt_Tuse < M_Tnew);

以上是两种阻塞方式,暴力阻塞可能会超时,条件阻塞也可能比测评机要求的周期少(可能是跑的太快了😥)。接着还有两种译码位置,M级译码W级译码

W级译码:对于判断条件Condition,我们不在DM进行计算,而是在顶层模块mips中进行判断,DM只需要更改DMOp从而输出判断需要的数据即可。这里你可能会发现,如果我要写入的数据来自DM并且和我判断需要的数据冲突了怎么办呢,那就只好再给DM开一个端口往后传判断需要的数据了。在W级判断出condition后传到w_ctrl,对W_GRFA3判断。显然,在这种情况下,DEM级都没有GRFA3的视野所以E_Tnew = 3,M_Tnew = 2,并且条件阻塞写法如下:

//Stall
assign E_stall_rs = ((E_lbget ? (D_rs == E_rt || D_rs == E_rs) : (E_GRFA3 == D_rs)) && D_rs != 0) && (rs_Tuse < E_Tnew);
assign E_stall_rt = ((E_lbget ? (D_rt == E_rs || D_rt == E_rt) : (E_GRFA3 == D_rt)) && D_rt != 0) && (rt_Tuse < E_Tnew);
assign M_stall_rs = ((M_lbget ? (D_rs == M_rs || D_rs == M_rt) : (M_GRFA3 == D_rs)) && D_rs != 0) && (rs_Tuse < M_Tnew);
assign M_stall_rt = ((M_lbget ? (D_rt == M_rs || D_rt == M_rt) : (M_GRFA3 == D_rt)) && D_rt != 0) && (rt_Tuse < M_Tnew);

这样就可以得到正确的运算结果,但是不难看出,这并不是最优解,我们在M级其实就可以拿到GRFA3,这样就可以删除M级冲突判断的不确定性,从而减少阻塞的发生,提高CPU性能。

至于为什么会提到这种在W级译码的方式,21级的很多学长似乎采用了这种做法,优点是当condition判断需要的数据和DM输出的数据相同时,可以对电路做最小的改动实现功能,缺点也很明显运行效率较低并且当condition判断需要的数据和DM输出的数据不相同时需要新增接口。对了,笔者在测评机提交这种方法时出现了一个测试点TLE,所以这种方法大家听个思路,考场慎用吧。

M级译码:对于判断条件condition,我们在DM计算出并且随流水线流到W级,这样M级流水线寄存器就可以较早地拿到GRFA3。

assign M_GRFa3 = (M_GRFA3_sel == `GRFA3rt) ? M_rt :
(M_GRFA3_sel == `GRFA3rd) ? M_rd :
(M_GRFA3_sel == `GRFA331 ) ? 5'd31 :
(M_GRFA3_sel == `GRFA3rs) ? M_rs :
(M_GRFA3_sel == `GRFA30 && M_lbget && M_condition1 == 1) ? M_rt :
(M_GRFA3_sel == `GRFA30 && M_lbget && M_condition1 == 0) ? M_rs :
5'd0;

在Stall模块中因为要判断M_GRFA3,所以需要将condition信号传入Stall

module STALL (
input [31:0] D_Instr,
input [31:0] E_Instr,
input [31:0] M_Instr,
input Condition1,
output Stall
);

M_GRFA3已知,所以E_Tnew = 2 ,M_Tnew = 1,并且条件阻塞判断如下:

//Stall
assign E_stall_rs = ((E_lbget ? (D_rs == E_rt || D_rs == E_rs) : (E_GRFA3 == D_rs)) && D_rs != 0) && (rs_Tuse < E_Tnew);
assign E_stall_rt = ((E_lbget ? (D_rt == E_rs || D_rt == E_rt) : (E_GRFA3 == D_rt)) && D_rt != 0) && (rt_Tuse < E_Tnew);
assign M_stall_rs = ((M_GRFA3 == D_rs) && D_rs != 0) && (rs_Tuse < M_Tnew);
assign M_stall_rt = ((M_GRFA3 == D_rt) && D_rt != 0) && (rt_Tuse < M_Tnew);

注意,关于GRFA3的译码,如果按照笔者个人的习惯,会将condition传入CTRL模块完成GRFA3_Sel译码,但是我们发现在M级,

m_ctrl(.Condition(condition),.DMOp(DMop)) ;
m_dm(.DMOp(DMop),.Condition(condition));

ctrl和dm在交互,这就导致了输出信号会是XXXXXXXX,所以我们选择在外部译码。虽然我也不知道这么干为什么不行。。。

CCS

这个是考试题

high_ones = memword[31:16] 1 的个数
low_ones = memword[15:0] 1 的个数
if high_ones > low_ones {
memword ^ GPR[rt] -> GPR[31]
}
else {
memword -> GPR[high_ones]
}

这道题目和上一题的区别在于GPR[high_ones]直到M级才知道,因此冲突判断直接无脑置1即可

assign E_stall_rs = ((E_lbget ? 1 : (E_GRFA3 == D_rs)) && D_rs != 0) && (rs_Tuse < E_Tnew);
assign E_stall_rt = ((E_lbget ? 1 : (E_GRFA3 == D_rt)) && D_rt != 0) && (rt_Tuse < E_Tnew);
assign M_stall_rs = ((M_GRFA3 == D_rs) && D_rs != 0) && (rs_Tuse < M_Tnew);
assign M_stall_rt = ((M_GRFA3 == D_rt) && D_rt != 0) && (rt_Tuse < M_Tnew);

剩下的操作跟LBGET一样,大家依葫芦画瓢应该问题不大。

写在最后

笔者的个人仓库里有P5_pro,实现了BLZTAL,LBGET,BONALL,大家如果真的对自己的bug毫无想法可以参考一下哦😋。

Lyrics Sharing

你是我的眼
带我领略四季的变换
你是我的眼
带我穿越拥挤的人潮
你是我的眼
带我阅读浩瀚的书海
因为你是我的眼
让我看见
这世界
就在我眼前

文章作者: Cordial-Kid
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cordial-Kid !
  目录