P4-CPU设计文档

本文最后更新于 2024年11月4日 下午

CPU设计文档

设计草稿

层次设计

mips.v为主电路文件,内含8个子电路文件

1
2
3
4
5
6
7
8
9
-- mips
-- uut1 - pc
-- uut2 - im
-- uut3 - grf
-- uut4 - alu
-- uut5 - nxtad
-- uut6 - dm
-- uut7 - ctrl
-- uut8 - ext

数据通路设计

数据通路

PC设计

在时钟上升沿,reset为1则将pc_out置位32'h3000;否则将pc_in传递给pc_out

1
2
3
4
5
6
module pc(
input clk,
input reset,
input [31:0] pc_in,
output reg [31:0] pc_out
);

IM设计

访问ROM中的pc地址,输出数据到instr,容量为\(4096\times 32bit\)

1
2
3
4
module im(
input [31:0] pc,
output [31:0] instr
);

GRF设计

一个由32个32位寄存器组成寄存器堆

1
2
3
4
5
6
7
8
9
10
11
12
module grf(
input write_enable, // 写入使能信号
input clk, // 时钟信号
input reset, // 同步复位信号
input [4:0] a1, // 读出数据的寄存器
input [4:0] a2, // 读出数据的寄存器
input [4:0] a3, // 写入数据的寄存器
output [31:0] rd1, // a1寄存器的读出值
output [31:0] rd2, // a2寄存器的读出值
input [31:0] wpc, // 当前指令地址
input [31:0] wd3 // a3寄存器的写入值
);

ALU设计

计算模块,根据ALUControl信号对src_a,src_b进行计算,结果输出到alu_result

1
2
3
4
5
6
7
module alu(
input [31:0] src_a,
input [31:0] src_b,
input [3:0] alu_control, // ALU控制信号
output reg [31:0] alu_result, // 计算结果
output zero // src_a, src_b相减为0时置1
);

NXTAd设计

计算下一个指令地址的模块

1
2
3
4
5
6
7
8
9
10
11
module nxtad(
input [31:0] pc, // 当前指令地址
input [31:0] instr, // 当前指令
input [31:0] gpr_rs, // GPR[rs]
input jump, // Jump信号(由指令jal激活)
input jr, // Jr信号(由指令jr激活)
input zero, // zero信号(来自于ALU)
input branch, // Branch信号(由指令beq激活)
output [31:0] next_pc, // 下一个指令地址
output [31:0] pc_plus_four // 当前指令地址 + 4
);

DM设计

数据存储器,容量为\(3072\times 32bit\)

1
2
3
4
5
6
7
8
9
module dm(
input clk,
input reset,
input MemWrite, // memory write enable
input [31:0] addr, // memory's address for write
input [31:0] din, // write data
input [31:0] pc, // instruction address
output [31:0] dout // read data
);

Ctrl设计

Control模块,根据指令输出对应的控制信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module ctrl(
input [31:0] in,
output RegWrite,
output MemWrite,
output [3:0] ALUControl,
output ALUSrc,
output MemtoReg,
output RegDst,
output Branch,
output Jump,
output ExtControl,
output Jr,
output Jal
);

Ext设计

扩位模块,根据信号ExtControl对立即数进行不同形式的扩展

1
2
3
4
5
module ext(
input [15:0] imm16,
input ExtControl, // 控制信号
output [31:0] imm32 // 32位输出(零扩展或者符号扩展)
);

测试方案

Python自动生成asm文件

利用python生成asm文件

Mars产生机器码文件及正确运行结果

1
2
java -jar Mars_perfect.jar mc CompactDataAtZero a dump .text HexText code.txt nc instruction.asm
java -jar Mars_perfect.jar mc CompactDataAtZero nc instruction.asm > right_ans.txt

机器码文件:

1
2
3
4
5
6
7
8
9
10
00000000
3555ec49
3c0b8baa
00ada820
...
0399e020
12bcfff3
341000b8
8e1d0034

正确运行结果:

1
2
3
4
5
@00003004: $21 <= 0000ec49
@00003008: $11 <= 8baa0000
@0000300c: $21 <= 00000000
...
@00003240: $21 <= 00001bef

iverilog产生检测文件运行结果

cmd执行以下命令:

1
2
iverilog -o wave -y docs tb_mips.v
vvp -n wave -lxt2 > your_ans.txt

运行结果输出到your_ans.txt

1
2
3
4
5
@00003004: $21 <= 0000ec49
@00003008: $11 <= 8baa0000
@0000300c: $21 <= 00000000
...
@00003240: $21 <= 00001bef

思考题

  1. 阅读下面给出的 DM 的输入示例中(示例 DM 容量为 4KB,即 32bit × 1024字),根据你的理解回答,这个 addr 信号又是从哪里来的?地址信号 addr 位数为什么是 [11:2] 而不是 [9:0] ?

    66.png

    addr来自ALU模块的计算结果,该计算结果为GPR[base]+SignExtend[imm],其中base是指令的25-21位,imm是指令的15-0位.

    由于DM容量为4KB,DM应当取[11:0],又因为按字节寻址,只要取[11:2],

  2. 思考上述两种控制器设计的译码方式,给出代码示例,并尝试对比各方式的优劣。

    记录指令对应的控制信号:

    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
    // 写入寄存器使能信号
    assign RegWrite = R || lui || ori || lw || jal;
    // 写入Memory使能信号
    assign MemWrite = sw;
    // ALU控制信号
    assign ALUControl[0] = ori || lui;
    assign ALUControl[1] = sub || lui || lw || sw || add || beq;
    assign ALUControl[2] = sub || beq;
    assign ALUControl[3] = 0;
    // ALUSrc:ALU读入端口来源控制信号(寄存器 or 立即数)
    assign ALUSrc = lui || lw || sw || ori;
    // MemtoReg:为1时,寄存器写入数据来自Memory,否则为ALU计算结果
    assign MemtoReg = lw;
    // RegDst:寄存器写入地址端口选择信号(1->15:11, 0->20:16)
    assign RegDst = R;
    // Branch:跳转信号
    assign Branch = beq;
    // Jump:jal,j跳转信号
    assign Jump = jal;
    // ExtControl:选择Ext扩位方式
    assign ExtControl = ori || lui;
    // Jr
    assign Jr = jr;
    // Jal
    assign Jal = jal;

    记录控制信号每种取值对应的指令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    case(option)
    beq:begin
    RegWrite = 1'b0;
    MemWrite = 1'b0;
    ALUControl=4'b110;
    // RegDst = 1'bX; (无关信号)
    // MemtoReg = 1'bX; (无关信号)
    ALUSrc = 1'b0;
    Branch = 1'b1;
    Jump = 1'b0;
    ExtControl = 1'b0;
    end
    ...
    endcase

    优劣:

    1. 前者在添加新的信号时修改代码更为方便,减少工作量,后者代码量较大,需要指明新指令对应的每个信号的值;
    2. 后者更为直观,容易检查错误,前者不够直观.
  3. 在相应的部件中,复位信号的设计都是同步复位,这与 P3 中的设计要求不同。请对比同步复位异步复位这两种方式的 reset 信号与 clk 信号优先级的关系。

    同步复位:reset小于clk

    异步复位:reset大于clk

  4. C 语言是一种弱类型程序设计语言。C 语言中不对计算结果溢出进行处理,这意味着 C 语言要求程序员必须很清楚计算结果是否会导致溢出。因此,如果仅仅支持 C 语言,MIPS 指令的所有计算指令均可以忽略溢出。 请说明为什么在忽略溢出的前提下,addi 与 addiu 是等价的,add 与 addu 是等价的。提示:阅读《MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set》中相关指令的 Operation 部分。

addi,add在发生溢出时,会放弃对寄存器赋值的操作,addiu,addu会忽略溢出.当在忽略溢出的前提下,addiaddiu等价,addaddu等价

附件

Python自动生成代码

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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# coding=gb2312
from random import *

number = 50
gpr = [0] * 32
mem = [0] * (2**10)

def print_add(f):
rt = randint(0, 31)
rs = randint(0, 31)
rd = randint(0, 31)
if -2**31 <= gpr[rs] + gpr[rt] <= 2**31-1: # 若溢出,则不打印这条指令
gpr[rd] = gpr[rs] + gpr[rt] if rd != 0 else 0
f.write('add ' + '$' + str(rd) + ',' +
'$' + str(rs) + ',' +
'$' + str(rt) + '\n')

def print_sub(f):
rt = randint(0, 31)
rs = randint(0, 31)
rd = randint(0, 31)
if -2**31 <= gpr[rs] + gpr[rt] <= 2**31-1: # 若溢出,则不打印这条指令
gpr[rd] = gpr[rs] + gpr[rt] if rd != 0 else 0
f.write('sub ' + '$' + str(rd) + ',' +
'$' + str(rs) + ',' +
'$' + str(rt) + '\n')

def print_ori(f):
rt = randint(0, 31)
rs = randint(0, 31)
imm = randint(0, 2**16-1)
if rt != 0:
gpr[rt] = gpr[rs] | imm
f.write('ori ' + '$' + str(rt) + ',' +
'$' + str(rs) + ',' +
str(imm) + '\n')

def print_lw(f):
base = randint(0, 31)
imm = randint(0, 2 ** 8)
if base != 0:
gpr[base] = imm
f.write('ori ' + '$' + str(base) + ',' +
'$0' + ',' +
str(imm) + '\n')
rt = randint(0, 31)
offset = (randint(-200, 200) // 4) * 4 - (gpr[base] % 4)
if 2**10 > offset + gpr[base] > 0:
if rt != 0:
gpr[rt] = mem[gpr[base] + offset]
f.write('lw ' + '$' + str(rt) + ',' +
str(offset) + '(' + '$' +
str(base) + ')\n')

def print_sw(f):
base = randint(0, 31)
imm = randint(0, 2 ** 8)
if base != 0:
gpr[base] = imm
f.write('ori ' + '$' + str(base) + ',' +
'$0' + ',' +
str(imm) + '\n')
rt = randint(0, 31)
offset = (randint(-200, 200) // 4) * 4 - (gpr[base] % 4)
if 2 ** 10 > offset + gpr[base] > 0:
if rt != 0:
gpr[rt] = mem[gpr[base] + offset]
f.write('sw ' + '$' + str(rt) + ',' +
str(offset) + '(' + '$' +
str(base) + ')\n')

def ori(f, key, num):
gpr[key] = num
f.write('ori ' + '$' + str(key) + ',' +
'$0' + ',' +
str(num) + '\n')

def add(f, rd, rs, rt):
gpr[rd] = gpr[rs] + gpr[rt]
f.write('add ' + '$' + str(rd) + ',' +
'$' + str(rs) + ',' +
'$' + str(rt) + '\n')

def sub(f, rd, rs, rt):
gpr[rd] = gpr[rs] - gpr[rt]
f.write('sub ' + '$' + str(rd) + ',' +
'$' + str(rs) + ',' +
'$' + str(rt) + '\n')

label_ct = 0
def print_beq(f):
global label_ct
rs = randint(1, 31) # 排除$0
rt = randint(1, 31)
while rt == rs:
rt = randint(1, 31) # rt != rs
key1 = randint(1, 31) # 中转
while key1 == rt or key1 == rs:
key1 = randint(1, 31)
key2 = randint(1, 31) # 中转
while key2 == rt or key2 == rs or key2 == key1:
key2 = randint(1, 31)


mod = randint(0, 1) # 跳转方向
equal = randint(0, 1) # 是否相等
label = 'labelx'.replace("x", str(label_ct))
label_ct += 1
in_ct = randint(0, 20) # 指令总数

# 这里为了避免溢出,我们取一个较小的数而且它不能是0
imm = randint(1, 2**10-1)
ori(f, key1, imm) # 将中转寄存器key1设为imm
ori(f, key2, imm)

if mod == 1: # 向下跳转
if equal == 1: # 相等

imm = randint(0, 2 ** 16 - 1)
gpr[rt] = gpr[rs] = imm
f.write('ori ' + '$' + str(rt) + ',' +
'$0' + ',' +
str(imm) + '\n')
f.write('ori ' + '$' + str(rs) + ',' +
'$0' + ',' +
str(imm) + '\n')
else: # 不相等(极小概率相等)
imm1 = randint(0, 2 ** 16 - 1)
imm2 = randint(0, 2 ** 16 - 1)
gpr[rt] = imm1
gpr[rs] = imm2
f.write('ori ' + '$' + str(rt) + ',' +
'$0' + ',' +
str(imm1) + '\n')
f.write('ori ' + '$' + str(rs) + ',' +
'$0' + ',' +
str(imm2) + '\n')
f.write('beq ' + '$' + str(rs) + ',' +
'$' +str (rt) + ',' + label + '\n')
run(f, in_ct)
f.write(label + ':\n')
else:
# 向上跳转
f.write(label + ':\n')
run(f, in_ct)
if equal == 1: # 相等,这里处理死循环的可能
sub(f, key1, key1, key2) # 第一次循环key1为0
imm = randint(0, 2 ** 16 - 1)
gpr[rt] = gpr[rs] = imm
f.write('ori ' + '$' + str(rs) + ',' +
'$0' + ',' +
str(imm) + '\n')
f.write('ori ' + '$' + str(rt) + ',' +
'$0' + ',' +
str(imm) + '\n')
add(f, rt, rt, key1) # gpr[rt] = gpr[rt] + 0(key1)
else:
imm1 = randint(0, 2 ** 16 - 1)
imm2 = randint(0, 2 ** 16 - 1)
gpr[rt] = imm1
gpr[rs] = imm2
f.write('ori ' + '$' + str(rt) + ',' +
'$0' + ',' +
str(imm1) + '\n')
f.write('ori ' + '$' + str(rs) + ',' +
'$0' + ',' +
str(imm2) + '\n')
f.write('beq ' + '$' + str(rs) + ','
'$' + str(rt) + ',' + label + '\n')

def print_lui(f):
rt = randint(0, 31)
imm = randint(0, 2**16-1)
if rt != 0:
gpr[rt] = imm << 16
f.write('lui' + ' $' + str(rt) +
',' + str(imm) + '\n')

def print_nop(f):
f.write('nop\n')

def print_jal(f):
global label_ct
f.write("jal labelx\n".replace("x", str(label_ct)))
run(f, randint(0,20))
f.write("labelx:\n".replace("x", str(label_ct)))
label_ct += 1

def run(f, ct):
op_set = ['add', 'sub', 'ori', 'lw', 'sw', 'lui', 'nop']
for _ in range(ct):
op = op_set[randint(0, len(op_set)-1)]
if op == 'add':
print_add(f)
elif op == 'sub':
print_sub(f)
elif op == 'ori':
print_ori(f)
elif op == 'lw':
print_lw(f)
elif op == 'sw':
print_sw(f)
elif op == 'lui':
print_lui(f)
else:
print_nop(f)

def main():
op_set = ['add', 'sub', 'ori', 'lw', 'sw', 'beq', 'lui', 'nop', 'jal']
path = 'instruction.asm'

with open(path, "w") as f:
for x in range(0, 32):
ori(f, x, randint(0, 2 ** 31 - 1))
for _ in range(number):
op = op_set[randint(0, len(op_set) - 1)]
if op == 'add':
print_add(f)
elif op == 'sub':
print_sub(f)
elif op == 'ori':
print_ori(f)
elif op == 'lw':
print_lw(f)
elif op == 'sw':
print_sw(f)
elif op == 'beq':
print_beq(f)
elif op == 'lui':
print_lui(f)
elif op == 'jal':
print_jal(f)
else:
print_nop(f)
# 接下来打印jr语句
f.write("jal labelx\n".replace("x", str(label_ct)))
run(f, randint(0, 20))
f.write("jal End\n")
run(f, randint(0, 20))
f.write("labelx:\n".replace("x", str(label_ct)))
run(f, randint(0, 20))
f.write("jr $ra\n")
# "结束"标签
f.write("End:\n")



if __name__ == '__main__':
main()


P4-CPU设计文档
https://meteor041.git.io/2024/11/04/P4-CPU设计文档/
作者
meteor041
发布于
2024年11月4日
许可协议