1. 分析指令集
- 经过分类,指令共包含R型指令和I型指令
- 列出其对应的32位机器码,寻找共性差异
R型指令:
Type |
Op |
Rs |
Rt |
Rd |
Shamt |
Func |
解释 |
add |
000000 |
(5) |
(5) |
(5) |
00000 |
100000 |
相加(rs+rt->rd) |
sub |
000000 |
(5) |
(5) |
(5) |
00000 |
100010 |
相减(rs-rt->rd) |
I型指令:
Type |
Op |
Rs |
Rt |
Immediate |
解释 |
ori |
001101 |
(5) |
(5) |
(16) |
或运算(rs|immediate->rt) |
lui |
001111 |
00000 |
(5) |
(16) |
立即数加载至高16位({immediate||{16{1’b0}}}->rt) |
Type |
Op |
Rs |
Rt |
Offset |
解释 |
lw |
100011 |
(5) |
(5) |
(16) |
加载字(rs+offset在memory中data->rt) |
sw |
101011 |
(5) |
(5) |
(16) |
保存字(rt->rs+offset在memory中的data) |
beq |
000100 |
(5) |
(5) |
(16) |
rs与rt相等则PC偏移offset*4 |
J型指令
type |
Op |
instr_index |
解释 |
jal |
000011 |
(26) |
跳转至index并且PC+4 -> $ra |
type |
Op |
rs |
0 |
0 |
func |
解释 |
jr |
000000 |
(5) |
{10{1’b0}} |
{5{1’b0}} |
001000 |
跳转至$rs中的地址 |
2. 阶段分析

F阶段PC值已经确定,进行的是根据PC访存相应的Instruction操作。此时指令已经确定好了
D阶段是将instruction转换成对应的操作数、产生控制信号。
E阶段是将操作数转换成计算结果。
M阶段是方寸数据内存。
W阶段是写入GRF。
3.命名规则
采用首字母大写命名法,每个单词首字母大写。
4. PC设计
端口设计:
方向 |
name |
位宽 |
input |
PcOp |
[3:0] |
input |
BeqPc |
[31:0] |
input |
JalPc |
[31:0] |
input |
JrPc |
[31:0] |
input |
reset |
1 |
input |
clk |
1 |
output |
Pc_F |
[31:0] |
转移设计:
PcOp |
解释 |
0000 |
Pc_F <= Pc_F + 4 |
0001 |
Pc_F <= BeqPc |
0010 |
Pc_F <= JalPc |
0011 |
Pc_F <= JrPc |
5. CU控制信号分析
端口设计:
type |
name |
位宽 |
解释 |
input |
Op |
[5:0] |
机器码高6位 |
input |
Func |
[5:0] |
机器码低6位 |
output |
RegDst |
[1:0] |
GRF被写入寄存器是rt还是rd。0是rt,1是rd,10是$ra |
output |
AluSrc |
[1:0] |
ALU第二个运算值是rt还是立即数。0是rt,1是立即数 |
output |
MemToReg |
[1:0] |
GRF写入值是ALUans还是DMans。0是ALUans,1是DMans,10是PC + 4 |
output |
RegWrite |
1 |
是否写入GRF |
output |
MemWrite |
1 |
是否写入RAM |
output |
PcSel |
[3:0] |
当前指令是否是beq\jal\jr |
output |
ExtOp |
[7:0] |
扩展操作类型类型,详见Extender |
output |
AluOp |
[7:0] |
ALU运算类型,详见ALU |
output |
InstrType |
[7:0] |
指令类型 |
output |
TNew_D |
[3:0] |
从D级开始,经过多少周期该指令产生运算结果 |
output |
TUse1_D,TUse2_D |
[3:0] |
从D级开始,经过多少周期要用到运算结果 |
- 内部电路采用最小项表达式判断法,先判断Op,再判断Func
每种指令所需控制信号:
|
RegDst |
AluSrc |
MemToReg |
RegWrite |
MemWrite |
PcSel |
ExtOp |
AluOp |
TNew |
TUse1 |
TUse2 |
add |
01 |
00 |
00 |
1 |
0 |
0000 |
|
ADD |
2 |
1 |
1 |
sub |
01 |
00 |
00 |
1 |
0 |
0000 |
|
SUB |
2 |
1 |
1 |
ori |
00 |
01 |
00 |
1 |
0 |
0000 |
0000 |
OR |
2 |
1 |
10 |
lui |
00 |
01 |
00 |
1 |
0 |
0000 |
0010 |
ADD |
2 |
10 |
10 |
lw |
00 |
01 |
01 |
1 |
0 |
0000 |
0001 |
ADD |
3 |
1 |
10 |
sw |
11 |
01 |
|
0 |
1 |
0000 |
0001 |
ADD |
0 |
1 |
2 |
beq |
11 |
00 |
|
0 |
0 |
0001 |
0011 |
EQUAL |
0 |
0 |
0 |
jal |
10 |
|
10 |
1 |
0 |
0010 |
0100 |
|
0 |
10 |
10 |
jr |
11 |
00 |
|
0 |
0 |
0011 |
|
ADD |
0 |
0 |
10 |
6. 流水线寄存器存储信息分析
F级:
name |
位宽 |
Pc_F |
[31:0] |
Instruction_F |
[31:0] |
D级:
name |
位宽 |
Pc_D |
[31:0] |
RD1_D |
[31:0] |
RD2_D |
[31:0] |
WR_D |
[4:0] |
ExtAns_D |
[31:0] |
MemToReg_D |
[1:0] |
RegWrite_D |
1 |
AluSrc_D |
[1:0] |
MemWrite_D |
1 |
AluOp_D |
[7:0] |
InstrType_D |
[7:0] |
TNew_D |
[7:0] |
Addr1_D |
[4:0] |
Addr2_D |
[4:0] |
E级:
name |
位宽 |
Pc_E |
[31:0] |
AluAns_E |
[31:0] |
WR_E |
[4:0] |
RD2_E |
[31:0] |
MemToReg_E |
[1:0] |
RegWrite_E |
1 |
MemWrite_E |
1 |
InstrType_E |
[7:0] |
TNew_E |
[7:0] |
Addr2_E |
[4:0] |
M级:
name |
位宽 |
Pc_M |
[31:0] |
AluAns_M |
[31:0] |
WR_M |
[4:0] |
DmAns_M |
[31:0] |
MemToReg_M |
1 |
RegWrite_M |
1 |
InstrType_M |
[7:0] |
TNew_M |
[7:0] |
W级:
name |
位宽 |
Pc_W |
[31:0] |
AluAns_W |
[31:0] |
WR_W |
[4:0] |
DmAns_W |
[31:0] |
MemToReg_W |
[1:0] |
RegWrite_W |
1 |
InstrType_W |
[7:0] |
TNew_W |
[7:0] |
7. 流水线转发数据
E级:
instruction |
Data_E |
add |
0 |
sub |
0 |
ori |
0 |
lui |
0 |
beq |
0 |
jal |
Pc_E + 8 |
jr |
0 |
lw |
0 |
sw |
0 |
M级:
instruction |
Data_M |
add |
AluAns_M |
sub |
AluAns_M |
ori |
AluAns_M |
lui |
AluAns_M |
beq |
0 |
jal |
Pc_M + 8 |
jr |
0 |
lw |
0 |
sw |
0 |
W级:
instruction |
Data_W |
add |
AluAns_W |
sub |
AluAns_W |
ori |
AluAns_W |
lui |
AluAns_W |
beq |
0 |
jal |
Pc_W + 8 |
jr |
0 |
lw |
DmAns_W |
sw |
0 |
8. 测试方案
1. 数据通路测试:先不考虑转发和阻塞,单纯检验数据通路是否正确,为了避免冒险情况,相邻两个指令之间间隔三个nop指令。



2. 转发阻塞测试:测试连续访存的指令,跳转指令,与MARS行为对应。
9. 思考题
- 原因:我们跳转指令的判断在D级进行,但实际上是在D级的最后进行的。在此之前,我们需要完成指令译码、产生控制信号、阻塞判断、GRF读取、转发判断等操作,之后再依据跳转指令类型决定后续操作(beq:判断两个寄存器值是否相等;jal:立即数位扩展;jr:$rs寄存器的值)。这些前置步骤会消耗一定的时间,导致最后剩余给跳转判断的时间很紧张,容易产生数据冒险,使跳转判断不准确,因此并非总能提高效率。并且由于在D级就要使用数据,所以Tuse=0,这使得如果要使用的数据还未被算出,则要阻塞很长时间,导致指令效率下降。
例子:
1 2 3 4 5
| lui $t0 1 sw $t0 0($0) lw $t1 0($0) beq $t1 $0 label label:
|
- 因为jal本意是PC跳转到标签处,并把当前指令的下一条要执行的指令存入$ra,当没有延迟槽的时候,当前指令的下一条指令就是PC + 4。而有延迟槽时,jal指令并不会立即跳转,而是会先执行PC + 4这条指令再跳转,因此真正的下一条要执行的指令是PC + 8。这样可以多执行一条指令,又不耽误之后的指令执行,提高指令执行效率。
- 因为功能部件产生转发数据是需要时间的,如果我们使用功能部件产生的数据转发,那么之后我们还要用转发的数据再进行一些操作,这些操作挤占在一个时钟周期内反而会降低效率。如果我们使用流水寄存器的数据,我们可以在周期刚开始就获得转发数据,从而有充足的时间去进行后续操作。
- 因为W级的数据会写入GRF,同时D级数据也会读取GRF。如果被写入数据和被读取数据是同一个,那么被读取的数据应该是写入的结果而不是原来的结果。此时可以用GRF内部转发解决问题。实现方式为:判断该寄存器是不是$0,如果是则为0,如果不是,判断写入寄存器和读取寄存器是否是同一个,如果是,则读取写入结果,否则读取原来寄存器的值。
- 转发数据的需求者可能来自于D,E,M级,转发供给者可能来自于E,M,W级。转发数据通路有:D级接受E,M,W级的转发,E级接受M,W的转发,M级接受W级的转发。
- 可能扩展ALU的运算类型,控制信号的类型,寄存器传递的控制信号的种类,阻塞条件等。
- 我采用集中式译码器,在D级集中产生控制信号,并随着流水线传递控制信号。这种架构的好处是只需要一个译码器,只需要产生一次控制信号,进行一次状态判断。不足之处是流水线寄存器需要传递较多控制信号,操作量比较大,且修改需要一并修改后面的流水线寄存器。
- 测试方案分为数据通路测试和转发阻塞测试。数据通路测试不涉及转发阻塞,单纯用来检测每一条指令能否正常执行,为了避免产生冒险,在编写测试代码的时候,相邻指令之间要相隔3-4个nop指令。测试转发和阻塞数据时,主要针对冒险情况来编写代码,可以列举哪些指令相邻出现需要转发或者阻塞,然后抽样测试,与MARS行为进行对照。