BUAA_CO_P6
P6
支持30指令的五级全速流水线
指令分析
29条指令
- 计算类指令:
add
sub
and
or
slt
sltu
lui
addi
andi
ori
nop
- 访存类指令:
lb
lh
lw
sb
sh
sw
- 乘除类指令:
mult
multu
div
divu
mfhi
mflo
mthi
mtlo
- 分支类指令:
beq
bne
- 跳转类指令:
jal
jr
数据冒险
需要回写的有16条指令:add
sub
and
or
slt
sltu
lui
addi
andi
ori
lb
lh
lw
mfhi
mflo
jal
转发
阻塞
Tuse
: 在D级用数据为0,E级为1,M级为2,用不到数据为max(在编写时,max = 3)
add |
sub |
and |
or |
slt |
sltu |
lui |
addi |
andi |
ori |
nop |
|
---|---|---|---|---|---|---|---|---|---|---|---|
Tuse_RS |
1 | 1 | 1 | 1 | 1 | 1 | max | 1 | 1 | 1 | max |
Tuse_RT |
1 | 1 | 1 | 1 | 1 | 1 | max | max | max | max | max |
lb |
lh |
lw |
sb |
sh |
sw |
beq |
bne |
jal |
jr |
|
---|---|---|---|---|---|---|---|---|---|---|
Tuse_RS |
1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | max | 0 |
Tuse_RT |
max | max | max | 2 | 2 | 2 | 0 | 0 | max | max |
mult |
multu |
div |
divu |
mfhi |
mflo |
mthi |
mtlo |
|
---|---|---|---|---|---|---|---|---|
Tuse_RS |
1 | 1 | 1 | 1 | max | max | 1 | 1 |
Tuse_RT |
1 | 1 | 1 | 1 | max | max | max | max |
Tnew
:在D级产生数据并存入DEreg
为1,E级为2,M级为3,不产生数据为min(在编写时,min = 0)
add |
sub |
and |
or |
slt |
sltu |
lui |
addi |
andi |
ori |
nop |
|
---|---|---|---|---|---|---|---|---|---|---|---|
Tnew |
2 | 2 | 2 | 2 | 2 | 2 | 1 | 2 | 2 | 2 | min |
lb |
lh |
lw |
sb |
sh |
sw |
beq |
bne |
jal |
jr |
|
---|---|---|---|---|---|---|---|---|---|---|
Tnew |
3 | 3 | 3 | min | min | min | min | min | 1 | min |
mult |
multu |
div |
divu |
mfhi |
mflo |
mthi |
mtlo |
|
---|---|---|---|---|---|---|---|---|
Tnew |
min | min | min | min | 2 | 2 | min | min |
乘除法实现
我们首先回顾一下乘除法的实现过程:
对于乘法指令来说,当mult
指令进入D级时,Start
信号置1,维持一个周期。
这个信号会跟随该乘法指令传到E级进入乘除模块。
一个周期以后,start
指令置0,同时busy
指令置1,并维持五个周期。
五个周期以后,busy
指令置0,结果写入hi
和lo
寄存器。
除法只需要将上述“维持五个周期”改为“维持十个周期”即可。
需要指出的是,当busy
信号或者start
信号为1的时候,后续与乘除相关的8条指令都会被阻塞在D流水级。
Stall信号为1时会发生什么
D级中的指令不变,同时清空DEreg(E流水级)
,给D级下游形成一种D级的指令是空抛的效果。
思考题
1、为什么需要有单独的乘除法部件而不是整合进 ALU?为何需要有独立的 HI、LO 寄存器?
因为乘除需要多个周期,分开设计更简洁,降低模块内部复杂度,也降低了功能之间的耦合度。由于乘除法的结果需要被存下来,所以需要hi
和lo
寄存器。
2、真实的流水线 CPU 是如何使用实现乘除法的?请查阅相关资料进行简单说明。
乘法(移位相加法):CPU 将被乘数和乘数表示为二进制数,然后从最低位开始,将被乘数的每一位与乘数进行相与运算,并将结果向左移位相应的位数,最后将所有移位后的结果相加,得到最终的乘积。
除法(移位相减法):CPU 将被除数和除数表示为二进制数,然后从被除数的最高位开始,将被除数与除数进行比较。如果被除数大于等于除数,则将被除数减去除数,并将结果向左移位一位,同时商的相应位设置为1;如果被除数小于除数,则将被除数向左移位一位,同时商的相应位设置为0。重复这个过程,直到被除数小于除数为止,此时得到的商就是最终的结果。
3、请结合自己的实现分析,你是如何处理 Busy 信号带来的周期阻塞的?
将busy
传至位于D级的阻塞处理单元Hazard
。当busy
信号为1时代表正在处理乘除指令。若D级此时又出现一条新的乘除法指令,则必须阻塞。
4、请问采用字节使能信号的方式处理写指令有什么好处?
更加规范清晰。
5、请思考,我们在按字节读和按字节写时,实际从DM
获得的数据和向DM
写入的数据是否是一字节?在什么情况下我们按字节读和按字节写的效率会高于按字读和按字写呢?
- 对于按字节写入(
sb
):
由于我们需要将GRF[rt]
的低8位写入内存,所以我们只需将它的低8位重复4次构成m_data_wdata
。再通过设置m_data_byteen
(写使能)来控制具体写入地址的哪8位。
按半字写入(
sh
)和按字写入(sw
)是同样的道理。对于按字节读出(
lb
):我们将CPU外部存储器返回给我们的
m_data_rdata
传入LEXT
模块,这个模块是专门用来处理读出来的原始数据的。我们取
m_data_rdata
中的对应8位(由op
决定),并对它做符号拓展,作为最终结果WD
。按半字读出(
lh
)和按字读出(lw
)是同样的道理。所以实际从
DM
获得的数据和向DM
写入的数据不是一字节。
6、为了对抗复杂性你采取了哪些抽象和规范手段?这些手段在译码和处理数据冲突的时候有什么样的特点与帮助?
建立了编码和标签的映射关系,例如:
1 | wire ori = (opcode_DEin == 6'b001101) ? 1 : 0; |
这样做更具有可读性。
7、在本实验中你遇到了哪些不同指令类型组合产生的冲突?你又是如何解决的?相应的测试样例是什么样的?
mflo
mfhi
等指令和其他需要使用相应的操作数的指令的冲突。通过给mfhi
mflo
设置对应的Tnew
统一解决。