P6_L0_document

设计草稿

设计说明

支持如下指令集:

add, sub, and(op==0, func==100100), or(func == 100101), slt(func == 101010), sltu(func == 101011), lui ,addi(op ==001000), andi(op==001100), ori ,lb(op == 100000), lh(op==100001), lw, sb(op == 101000), sh(op == 101001), sw, mult(func == 011000), multu(func==011001), div(func == 011010), divu(func == 011011), mfhi(func == 010000), mflo(func == 010010), mthi(func == 010001), **mtlo(func == 010011) ** ,beq, bne(op == 000101), jal, jr

的五级流水线CPU,且不考虑溢出异常。

设计要求

IMDM模块外置,不用实现。需要实现单独的乘除法模块数据扩展模块

mips.v顶层模块的端口发生了改变:

信号名 方向 描述
clk I 时钟信号
reset I 同步复位信号
i_inst_rdata[31:0] I F级对应的32位指令
m_data_rdata[31:0] I M级从数据存储器的读出相应数据
i_inst_addr[31:0] O 需要进行取指操作的流水级PC(一般为F级)
m_data_addr[31:0] O 待写入/读出的数据存储器(DM)的相应地址
m_data_wdata[31:0] O M级待写入数据存储器DM的相应数据
m_data_byteen[3:0] O 四位字节使能
m_inst_addr[31:0] O M级PC
w_grf_we O W级GRF写使能信号
w_grf_addr[4:0] O W级GRF待写入寄存器编号
w_grf_wdata[31:0] O W级GRF中待写入数据
w_inst_addr[31:0] O W级PC

新增模块

乘除模块

为了支持multmultudivdivumfhimflomthimtlo等乘除法相关的指令,需要在E级独立设计乘除功能部件。

MulDiv
信号名 方向 描述
clk I 时钟信号
reset I 同步复位信号
start I 乘除法开始信号
Ope1[31:0] I 操作数一
Ope2[31:0] I 操作数二
Opeop[1:0] I 操作的类型
Hiout[31:0] O hi寄存器的值
Loout[31:0] O lo寄存器的值
busy O 处理相应乘除法的延迟信号

在乘除模块中为了实现要求的延迟的效果,采用此前的状态机的设计,即在乘除模块内置一个状态量cnt用来记录还需要延时的周期数,当cnt为0时,假如start_E为1,那么此时将开始进行乘除运算,注意:此处要用一个临时的reg变量存储当前乘除运算的结果,不可等到向hilo寄存器写入值得周期再计算(因为那时RsRt早已面目全非了),并将busy置1,将cnt置相应的延时周期数(乘法5,除法10)。当cnt为1时这时候需要结束相应的延时了,将临时变量中的值写入hilo中,并且将busy置0,cnt置0,重新等待下一个乘除运算。

字节使能模块(BE)

端口定义:

信号名 方向 描述
LTwoAW_M[1:0] I 写入地址的最低两位
MemWrite_M I 写使能信号
BEOp_M[2:0] I 控制写入的方式:000:sw;001:sh;010:sb
WD_M_final[31:0] I 需要处理的写入数据存储器中的数据
m_data_byteen[3:0] O 控制写入数据存储器中数据的位置
m_data_wdata[31:0] O 进行处理后的写入数据

数据扩展模块(DE)

进行将从数据存储器读出的数据进行符号扩展,例如lb,数据扩展模块输入数据寄存器的32位数据,根据ALU计算出来的地址最低2位从中取出特定的字节,并以该字节的最高位为符号位做符号扩展。

端口定义:

信号名 方向 描述
LTwoAR_M[1:0] I 读出地址的最低两位
Din[31:0] I 输入的32位从数据存储器中读出的数据
DEOp_M[2:0] I 数据扩展控制码:000:无扩展(lw);001:无符号字节数据扩展(lbu);010:符号字节数据扩展(lb);011:无符号半字数据扩展(lhu);100:符号半字数据扩展(lh)
Dout[31:0] O 扩展后的32位数据

流水线寄存器堆

相应的由于我采用的是集中式译码,因此新增的乘除槽、字节使能模块和数据扩展模块所需要的选择信号等需要进行相应的流水,即在E级流水线寄存器堆需要增加start、Opeop、selMvfr、BEOp和DEOp信号的流水,而M级流水线寄存器堆需要增加BEOp、DEOp信号的流水。

阻塞和转发

对于新增的andorsltsltu直接并入P5的NorR类指令即可,并在ALU模块中对应相应的选择信号增加相应的运算;addi(注意:该指令的立即数扩展是符号扩展和其他的I类指令的0扩展不同)、andi并I类指令;sbsh并入Save类指令;lblh并入Load类指令;bne并入Bjump类指令。这些类指令的阻塞和转发在P5中都已经得到了相应的实现,而对于乘除法相关的指令multmultudivdivumfhimflomthimtlo等指令将按照对于相应的寄存器的存取特性,分为MulDivMvfrMvto三类,根据AT模型做好相应的TuseTnew分析后即可得到正确的转发,但是由于乘除槽的特性,假如乘除槽正在工作即start_Ebusy_E任意一个为1时,那么此时如果此时在D级的指令是同乘除槽相关的(MulDivMvfrMvto),便需要将该指令阻塞在D级,因此阻塞信号还需要再添加一种乘除槽导致的情况(stall_MulDiv)。

测试方案

典型测试样例

计算、访存类指令测试

先通过对除了分支、跳转之外的计算访存指令进行测试,该部分测试由python代码自动生成,大致生成思路是,连续枚举4条连续指令,将NorRISaveLoadMulDivMvfrMvtoLUI等8种类型(由于lui指令与I类型指令类似,于是将其同I类型指令合并)的指令进行排列组合(每一个类型随机取出一条指令),总共有2401种排列。为了增加冲突发生的概率,将被读写的寄存器范围调整到有限的5个,当测试样例足够多时,基本上可以覆盖所有的冲突情况。

生成代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# This Python file uses the following encoding: GBK
import random
import time

list_NorR = ["add", "sub", "and", "or", "slt", "sltu"]
list_I = ["ori", "addi", "andi", "lui"]
list_Load = ["lw", "lh", "lb"]
list_Save = ["sw", "sh", "sb"]
list_MulDiv = ["mult", "multu", "div", "divu"]
list_Mvfr = ["mfhi", "mflo"]
list_Mvto = ["mthi", "mtlo"]

length = 4 #寄存器使用范围
num = 1 #生成的指令数组数
maxI = 65535 #立即数的最大值
maxSaveLoad = 12286 #存取指令的最大地址范围


def NorR_test(file, n):
num = random.randint(0, len(list_NorR) - 1)

for item in range(n):
rd = random.randint(0, length)
rs = random.randint(0, length)
rt = random.randint(0, length)
file.write("{0} ${1}, ${2}, ${3}\n".format(list_NorR[num], rd, rs, rt))

#对于R类型指令的写入

def I_test(file, n):
num = random.randint(0, len(list_I) - 1)

for item in range(n):
rt = random.randint(0, length)
rs = random.randint(0, length)
imm = random.randint(0, maxI)
if (num == 3):
file.write("lui ${0}, {1}\n".format(rt, imm))
else:
file.write("{0} ${1}, ${2}, {3}\n".format(list_I[num], rt, rs, imm))

#对于I类型指令的写入

def Load_test(file, n):
num = random.randint(0, len(list_Load) - 1)

for item in range(n):
rt = random.randint(0, length)
offset = random.randint(0, maxSaveLoad)
if (num == 0):
while (offset % 4 != 0):
offset = random.randint(0, maxSaveLoad)
elif (num == 1):
while (offset % 2 != 0):
offset = random.randint(0, maxSaveLoad)
file.write("{0} ${1}, {2}($0)\n".format(list_Load[num], rt, offset))

#对于Load类型指令的写入

def Save_test(file, n):
num = random.randint(0, len(list_Save) - 1)

for item in range(n):
rt = random.randint(0, length)
offset = random.randint(0, maxSaveLoad)
if (num == 0):
while (offset % 4 != 0):
offset = random.randint(0, maxSaveLoad)
elif (num == 1):
while (offset % 2 != 0):
offset = random.randint(0, maxSaveLoad)
file.write("{0} ${1}, {2}($0)\n".format(list_Save[num], rt, offset))

#对于Save类型指令的写入

def MulDiv_test(file, n):
num = random.randint(0, len(list_MulDiv) - 1)

for item in range(n):
rs = random.randint(0, length)
rt = random.randint(0, length)
if (num == 0 or num == 1):
file.write("{0} ${1}, ${2}\n".format(list_MulDiv[num], rs, rt))
else:
sel = random.randint(0, 1) #sel为0,用ori指令给除法运算的除数置数;sel为1,用lui指令,防止除数是0的情况出现
while (rt == 0):
rt = random.randint(0, length)
if (sel):
file.write("lui ${0}, {1}\n".format(rt, random.randint(1, maxI)))
else:
file.write("ori ${0}, $0, {1}\n".format(rt, random.randint(1, maxI)))
file.write("{0} ${1}, ${2}\n".format(list_MulDiv[num], rs, rt))

#对于MulDiv类型指令的写入

def Mvfr_test(file, n):
num = random.randint(0, 1)

for item in range(n):
rd = random.randint(0, length)
file.write("{0} ${1}\n".format(list_Mvfr[num], rd))

#对于Mvfr类型指令的写入

def Mvto_test(file, n):
num = random.randint(0, 1)

for item in range(n):
rs = random.randint(0, length)
file.write("{0} ${1}\n".format(list_Mvto[num], rs))

#对于Mvto类型指令的写入

testnames = [NorR_test, I_test, Load_test, Save_test, MulDiv_test, Mvfr_test, Mvto_test]

random.seed(time.time())

file = open("C:\\Users\\yumo\\Desktop\\mars\\mips1.asm","w")
file.write(".text\n")
file.write("lui $28, 0\n")
file.write("lui $29, 0\n") #消除对拍时由于Mars中$28和$29寄存器原来的值导致的非程序性错误

for cnt in range(num):
for i in range(0, 7):
testnames[i](file, 1)
for j in range(0, 7):
testnames[j](file, 1)
for k in range(0, 7):
testnames[k](file, 1)
for l in range(0, 7):
testnames[l](file, 1)

file.close()

分支、跳转指令测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
.text
lui $28, 0
lui $29, 0

addi $1, $0, 1
sub $2, $0, $1
ori $3, $0, 213
slt $1, $2, $3
beq $1, $2, lab0
nop
lab0:
bne $1, $0, lab1
nop

lab1:
addi $1, $0, 1
sub $2, $0, $1
ori $3, $0, 213
sltu $1, $2, $3
beq $1, $3, lab2
nop
lab2:
bne $1, $3, lab3
nop
lab3:

addi $1, $1, 123
beq $1, $3, lab4
nop
lab4:
bne $1, $3, lab5
nop
lab5:

andi $1, $1, 124
beq $1, $0, lab6
nop
lab6:
bne $1, $0, lab7
nop
lab7:

sw $1, 24($0)
sh $1, 12($0)
sb $1, 1($0)
sb $1, 2($0)
sb $1, 3($0)
sb $1, 4($0)
lb $2, 4($0)
beq $2, $1, lab8
lb $3, 3($0)
add $3, $1, $2
lab8:
bne $1, $3, lab9
lb $4, 2($0)
lab9:
beq $3, $4, lab10
lb $5, 1($0)
lab10:
bne $4, $5, lab11
lh $6, 12($0)
lab11:
beq $6, $1, lab12
lw $7, 24($0)
bne $7, $1, lab13
sh $7, 2($0)
sb $7, 1($0)
lab12:
lab13:

lui $3, 0
lui $4, 0
ori $1, $0, 542
lui $2, 5452
mult $1, $2
mfhi $3
mflo $4
beq $3, $4, lab14
mthi $4
mfhi $3
lab14:
beq $3, $4, lab15
mtlo $0
mfhi $3
lab15:
bne $3, $0, lab16
multu $1, $2
lab16:

mfhi $3
mflo $4
beq $3, $4, lab17
mthi $4
mfhi $3
lab17:
beq $3, $4, lab18
mtlo $0
mfhi $3
lab18:
bne $3, $0, lab19
and $1, $3, $2
#Bjump
lab19:
bne $1, $0, lab20
nop

lab21:
or $1, $31, $0
addi $2, $0, 1
mult $1, $2
mfhi $3
mflo $14
jr $14
nop

lab20:
jal lab21
lw $10, -1000($31)
beq $14, $31, lab22
nop
#mult

lab23:
or $1, $31, $0
addi $2, $0, 1
multu $1, $2
mfhi $3
mflo $14
jr $14
nop

lab22:
jal lab23
lw $10, -1000($31)
#multu

beq $14, $31, lab24
nop

lab25:
or $1, $31, $0
addi $2, $0, 1
div $1, $2
mfhi $3
mflo $14
jr $14
nop

lab24:
jal lab25
lw $10, -1000($31)
#div

beq $14, $31, lab26
nop

lab27:
or $1, $31, $0
addi $2, $0, 1
divu $1, $2
mfhi $3
mflo $14
jr $14
nop

lab26:
jal lab27
lw $10, -1000($31)
#divu

beq $0, $0, lab28
slt $1, $0, $10

lab30:
or $4, $31, $2
addi $5, $31, 0
slt $31, $31, $5
sltu $31, $31, $4
mthi $5
mtlo $5
mfhi $6
jr $6
ori $31, $1, 445

lab28:
ori $1, $0, 1
sub $2, $0, $1
sltu $3, $2, $31
bne $3, $0, lab29
nop
lab29:
jal lab30
or $2, $1, $1

beq $31, $31, lab31
addi $1, $31, 0
lab31:
beq $31, $1, lab32
addi $31, $0, 213

lab33:
sltu $2, $0, $31
ori $3, $2, 1
bne $3, $2, lab34
and $4, $2, $3
lab34:
mtlo $31
mflo $5
jr $5
or $5, $31, $5

lab32:
jal lab33
slt $1, $31, $0

思考题

1.为什么需要有单独的乘除法部件而不是整合进 ALU?为何需要有独立的 HI、LO 寄存器?

1.乘除法在实际的实现中的延迟比ALU模块大得多,假若整合进ALU,那么CPU的整体周期数将大幅提升。

2.独立的HI、LO寄存器可以使得乘除法指令同其它的指令并行运行,提高了CPU的运行效率。

2.真实的流水线 CPU 是如何使用实现乘除法的?请查阅相关资料进行简单说明。

1.乘法通常利用若干个较小的组合逻辑的乘法单元组成,这些乘法单元你内部通过若干个全加器构成,然后每个周期计算特定的几位,依次利用全加器累加起来,便能在几个周期后得到正确的最终结果。

2.除法通常使用试商法,使用组合逻辑在一个周期内计算出4位左右的商,经过8个周期正好可以计算结束。

3.请结合自己的实现分析,你是如何处理 Busy 信号带来的周期阻塞的?

将乘除槽产生的busy信号和当前处于E级的指令的start信号传入产生阻塞信号的模块中,当乘除槽正在工作即startbusy任意一个为1时,那么此时如果此时在D级的指令是同乘除槽相关的(MulDivMvfrMvto),便需要将该指令阻塞在D级,因此阻塞信号还需要再添加一种乘除槽导致的情况(stall_MulDiv)。

4.请问采用字节使能信号的方式处理写指令有什么好处?(提示:从清晰性、统一性等角度考虑)

1.清晰性:使用字节使能信号使swshsb指令的实现更加清晰,例如sw指令全写入,即对应的4个字节的使能信号值均置1,相应的shsb指令也是类似的形式。

2.统一性:使用字节使能信号便于将新增加的shsb以及上机过程中可能遇到的奇葩写入指令同原有的sw指令的数据通路相统一,并且将决定写入位置与否提前在数据存储模块外决定好也体现了模块功能的统一性。

5.请思考,我们在按字节读和按字节写时,实际从 DM 获得的数据和向 DM 写入的数据是否是一字节?在什么情况下我们按字节读和按字节写的效率会高于按字读和按字写呢?

1.不是。从DM获得的数据是计算地址所在字的数据,向DM写入的数据是对应需要写的字节改动,不需要写入的字节是保持原状,因此相当于是写入一个针对原有地址对应数据的只改变对应字节的数据。

2.在一些语言中存在一些只占一个字节的数据类型如,C语言中的char,对于这些数据的处理按字节访问内存相较于按字访问就会更有优势。

6.为了对抗复杂性你采取了哪些抽象和规范手段?这些手段在译码和处理数据冲突的时候有什么样的特点与帮助?

1.将指令按照不同行为分为NorRILUIBjumpJjumpRjumpSaveLoadMulDivMvfrMvto等类型,将不同指令的控制信号的产生和数据冲突的处理抽象为这些类型指令的统一行为,进而简化了相应的控制信号的产生以及阻塞和转发的实现,并且对于一种大类型下可能的小类型指令的不同控制信号则通过控制信号的指定输出以及宏定义定义的选择来清楚明了的反映不同指令的行为。

2.这样一来,上机时除非是指令的行为极其特殊不然都可以归入已有的指令大类型中,只需要对于特有的控制信号的输出以及运算进行修改就可以了,省去了同类型指令的阻塞和转发排查,使编码具有高内聚低耦合性,同时宏定义使代码的可读性提高,便于bug的排查。

7.在本实验中你遇到了哪些不同指令类型组合产生的冲突?你又是如何解决的?相应的测试样例是什么样的?

控制模块输出写入内存信号RegToMem忘记了将新增的shsb指令添加上,导致出现写入地址的错误以及写入数据的错误,通过编写相关的测试样例如下:

1
2
3
ori $s1, $0, 0xabcd
ori $t0, $0, 0x0524
sb $s1, -1($t0)

定位到可能出现问题的地方并进行相应的修改。

由于新增的乘除槽的特殊性,需要特别关注多个乘除法相关指令的连续出现的情况下阻塞和转发的实现是否正确,构造出如下的样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
lui $t0,0x0123
lui $t2,0xffff
ori $t0,$t0,0x9576
ori $t1,$t1,0xa312
mult $t0,$t1
mfhi $s0
mflo $s1
mult $t0,$t2
mfhi $s0
mflo $s1
multu $t0,$t2
mfhi $s0
mflo $s1

找到了乘除槽模块内进行运算选择时出现了宏定义错乱的问题。

8.如果你是手动构造的样例,请说明构造策略,说明你的测试程序如何保证覆盖了所有需要测试的情况;如果你是完全随机生成的测试样例,请思考完全随机的测试程序有何不足之处;如果你在生成测试样例时采用了特殊的策略,比如构造连续数据冒险序列,请你描述一下你使用的策略如何结合了随机性达到强测的效果。

我采用的是半随机生成半人工生成的方式,利用随机生成合法的不含有分支和跳转指令的样例(如存取指令的地址合理性、除法运算中除数为0的处理等),对于分支和跳转指令,采用人工手动生成的方式,在P5手搓的测试样例的基础上,将原来的对于R、I类型指令的运算更改为新增的乘除指令和按字节存取和按半字存取的指令。

随机生成的数据仍存在覆盖率不足的情况,在同一种排列方式下,寄存器冲突的排放方式仍存在可能的潜在冲突,除此之外,对于同一类型的微弱差别也不一定能够在一次随机生成的数据中完全覆盖到,但是对于多次随机生成的数据可以覆盖尽可能大的范围。

9.[P5、P6 选做] 请评估我们给出的覆盖率分析模型的合理性,如有更好的方案,可一并提出。

由于是选做,所以你懂的