CPU 设计文档

一、指令类型

  • 解读:将指令按类型分类,并对指令按照功能划分

R类型指令

R类型指令 Op(31~26) Rs(25~21) Rt(20~16) Rd(15~11) Shamt(10~6) Func(5~0)
Add 000000 Rs Rt Rd XXXXX 100000
Sub 000000 Rs Rt Rd XXXXX 100010
Jr 000000 Rs 00000 00000 00000 001000

add:

Alt text

sub:

Alt text

jr:

Alt text

I类型指令

I类型指令 Op(31~26) Rs(25~21) Rt(20~16) immediate or address
Ori 001101 Rs Rt imm16
Lui 001111 00000 Rt imm16
Lw 100011 Rs Rt imm16
Sw 101011 Rs Rt imm16
Beq 000100 Rs Rt imm16

ori:

Alt text

lui:

Alt text

lw:

Alt text

Alt text

sw:

Alt text

beq:

Alt text

J类型指令

J类型指令 Op(31~26) 26 address
j 000010 target
jal 000011 target

jal:

Alt text

二、主要模块

mips(顶层模块)

端口 方向 描述
clk I 时钟信号,控制所有需clk信号输入的模块
reset I 同步复位信号,控制所有需reset信号输入的模块

Conroller(控制器)

控制信号类型及含义

Branch
>

  • Beq 1: 若Zero=1,PC输入选择加法器Nadd
  • Jal 2: PC输入选择Jal_Adr
  • Jr 3: PC输入选择Rdata1
  • Other : 0,PC输入选择加法器Add输出(PC+4)

RegDst
>

  • 0: 选择Rd
  • 1: 选择Rt
  • 2:选择31(jal指令)

RegWirte
>

1: 表示寄存器堆可写入

MemtoReg
>

  • R型指令 0: 选择 ALU 输出
  • Lui 1 : 选择shift输出
  • Lw 2: 选择数据存储器DM输出
  • Jal 3:选择Adder(地址加法器)输出

ALUSrc
>

  • R型指令 0: 选择寄存器堆的 Read data2 输出
  • Ori 1: 选择zero_ext 输出
  • Lw 2: 选择Signext的输出
  • Sw 2: 选择Signext的输出
  • Beq 0: 选择Read data2输出

ALUOp1 + ALUOp2
>

  • 输入到ALU controller 控制单元

MemRead
>

  • 0:表示DM不可写入
  • 1:表示DM可写入

MemWrite

  • 0:表示DM不可写入
  • 1:表示DM可写入

shiftSrc
>

  • 0: input(shift)选择Rdata2输出,shamt(shift)选择shamt输入
  • 1:input(shift)选择imm16 输出,shamt(shift) 选择0x10输入

指令运算类型选择

指令 Func字段 ALUOp ALU运算类型 ALU Oper
Lw XXXXXX 00 + 000
Sw XXXXXX 00 + 000
Beq XXXXXX 01 >/</= 010
Ori XXXXXX 11 \ \ 011
Add 100000 10 + 000
Sub 100010 10 - 001

指令实现控制信号的转换

  • x表示指令实现与该输入无关
指令 Opcode Branch RegDst RegWrite MemtoReg ALUSrc ALUOp MemWrite MemCate shiftSrc
Add/Sub 000000 0 0 1 0 0 2 0 x x
Lw 100011 0 1 1 2 2 0 0 x x
Sw 101011 0 x 0 x 2 0 1 0 x
Beq 000100 1 x 0 x 0 1 0 x x
Ori 001101 0 1 1 0 1 3 0 x x
Lui 001111 0 1 1 1 x x 0 x 1
Nop 000000 0 x 0 x x x 0 x 0
Jal 000011 2 2 1 3 x x 0 x x
Jr 000000 3 x 0 x x x 0 x x

GRF(寄存器堆)

端口 方向 描述
Clk I 时钟信号
Reset I 复位信号,将寄存器的值全部清零
1: 复位
0:无效
WE I 写使能信号
1: 可写入数据
0: 不可写入数据
ReadRes1 I 读出地址输入(5位),读出到Read Data1
ReadRes2 I 读出地址输入(5位),读出到Read Data2
WriteRes I 写入地址输入(5位)
WriteData I 写入数据(32位)
WPC I 输入当前pc地址
ReadData1 O Read register1地址对应寄存器的输出
ReadData2 O Read register2地址对应寄存器的输出

ALU(算数逻辑单位)

端口 方向 描述
A I 操作数1(32位)
B I 操作数2(32位)
ALUOper I ALU功能选择(4位)
Zero O 判断操作数1与操作数2是否相等(1位)
Result O 结果输出(32位)

ALU control(ALU控制单元)

端口 方向 描述
ALUOp I 指令类型区分(4位)
Func I 指令[5:0]位
ALUOper O ALU功能选择(4位)

IFU (取指令单元)

端口 方向 描述
Clk I 时钟信号
Reset I 指令存储器(im_reg)所有地址的存储值清零
1: 复位
0:无效
Branch I PC值分支跳转指令判断(4位)
Zero I beq指令判断PC是否跳转条件(1位)
index I 跳转指令的target中间值(26位)
immSiExt I 16位立即数有符号扩展输入(32位)
ReadData1 O GRF(寄存器堆)中Read register1地址对应寄存器的输出
pc O PC当前值(32位)
instr O PC地址对应IM(指令存储器)的指令输出(32位)
Adder O pc + 4 对应值(32位)

DM(数据存储器)

端口 方向 描述
Clk I 时钟信号
pc O PC当前值(32位)
Reset I 数据存储器(RAM)所有地址的存储值清零
1: 复位
0:无效
MemWrite I 写使能信号,
1: 可写入数据
0: 不可写入数据
MemCate I 判断向数据储存器写入数据的操作指令类型(4位)
Address I 读写地址(12位)
WriteData I 写入数据(32位)
ReadData O Address地址对应数据储存器(RAM)的储存值输出

shift(移位器)

端口 方向 描述
input I 操作数
shamt I 偏移量(5位)
output I 操作数移位后输出

三、数据通路

  • 加粗表示该列存在分支
指令 A B PC IM Adr Reg1 Reg2 Wreg Wdata A B DM Adr DM Wdata Sign-ext A B shift zero-ext
Adder: ALU: Nadder: /
R型指令与访存 PC 4 Adder PC Rs Rt Rd ALU Rdata1 Rdata2 / / / / / /! /
Lw PC 4 Adder PC Rs / Rt DM Rdata1 Sign_ext ALU / imm16 / / / /
Sw PC 4 Adder PC Rs Rt / / Rdata1 Sign_ext ALU Rdata2 imm16 / / / /
Beq PC 4 Adder/Nadder PC Rs Rt / / Rdata1 Rdata2 / / imm16 Adder Sign-imm16(左移2) / /
Ori PC 4 Adder PC Rs / Rt ALU Rdata1 zero_ext / / / / / / imm16
Lui PC 4 Adder PC / / Rt shift / / / / / / / imm16 / /
Nop PC 4 Adder PC / Rt Rd shift / / / / / / / Rdata2 /
jal PC 4 Jal_Adr PC / / 31 Adder / / / / / / / / /
jr / / Rdata1 PC Rs / / / / / / / / / / / /

四、测试

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
ori $a1, $a0, 456 #$a0与立即数取或存入$a1
ori $a1, $a0, 2147 #$a0与立即数取或存入$a1
ori $a0, $0, 2147 #$0与立即数取或存入$a0
nop #空操作指令
jal func #跳转并链接指令
lui $a2, 0 # 符号位为 0,将立即数存入$a2高位
lui $a2, 1 # 符号位为 0,将立即数存入$a2高位
lui $a2, 20 # 符号位为 0,将立即数存入$a2高位
lui $a2, 43 # 符号位为 0,将立即数存入$a2高位
lui $a3, 65 # 符号位为 1,将立即数存入$a3高位
lui $a3, 655 # 符号位为 1,将立即数存入$a3高位
lui $0, 625 # 符号位为 1,将立即数存入$0高位
ori $a3, $a3, 123 # $a3与立即数取或存入$a3
add $s0, $a0, $a2 # 正正 将$a0与$a2相加存入$s0
add $s1, $a0, $a3 # 正负 将$a0与$a2相加存入$s1
add $s2, $a3, $a3 # 负负 将$a3与$a3相加存入$s2
ori $t0, $0, 0x0000 #$0与立即数取或存入$t0
sw $a0, 0($t0) #将sign_extend(0)+$t0对应地址的值存入$a0
sw $a1, 4($t0) #将sign_extend(4)+$t0对应地址的值存入$a1
sw $a2, 8($t0) #将sign_extend(8)+$t0对应地址的值存入$a2
sw $a3, 12($t0) #将sign_extend(12)+$t0对应地址的值存入$a3
sw $s0, 16($t0) #将sign_extend(16)+$t0对应地址的值存入$s0
sw $s1, 20($t0) #将sign_extend(20)+$t0对应地址的值存入$s1
sw $s2, 24($t0) #将sign_extend(24)+$t0对应地址的值存入$s2
lw $0, 0($t0) #将$0的值存入sign_extend(0)+$t0对应地址
lw $a0, 0($t0) #将$a0的值存入sign_extend(0)+$t0对应地址
lw $a1, 12($t0) #将$a1的值存入sign_extend(12)+$t0对应地址
sw $a0, 28($t0) #将sign_extend(28)+$t0对应地址的值存入$a0
sw $a1, 32($t0) #将sign_extend(32)+$t0对应地址的值存入$a1
nop #空操作指令
ori $a0, $0, 1 #$0与立即数取或存入$t0
ori $a1, $0, 2 #$0与立即数取或存入$a1
ori $a2, $0, 1 #$0与立即数取或存入$a2
beq $a0, $a1, loop1 # 不相等 $a0与$a1值进行比较,相等则跳转,反之不跳转
beq $a0, $a2, loop2 # 相等 $a0与$a2值进行比较,相等则跳转,反之不跳转
loop1:sw $a0, 36($t0) #跳转后执行的指令:#将sign_extend(36)+$t0对应地址的值存入$a0
loop2:sw $a1, 40($t0) #跳转后执行的指令:#将sign_extend(40)+$t0对应地址的值存入$a1

func:
ori $a0, $0, 1 #$0与立即数取或存入$t0
jr $ra #取出31号寄存器的值,返回跳转并链接指令的下一条指令
ori $a0, $0, 0 #$0与立即数取或存入$a0
  • Mars导出的机械码(储存在code.txt中的文件):
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
34040000
348501c8
34850863
34040863
00000000
0c000c26
3c060000
3c060001
3c060014
3c06002b
3c070041
3c07028f
3c000271
34e7007b
00868020
00878820
00e79020
34080000
ad040000
ad050004
ad060008
ad07000c
ad100010
ad110014
ad120018
8d000000
8d040000
8d05000c
ad04001c
ad050020
00000000
34040001
34050002
34060001
10850001
10860001
ad040024
ad050028
34040001
03e00008

利用$readmemh指令读取code.txt中的机械码指令并储存在IFU_mips模块的指令存储器im_reg(4096个32位寄存器)当中,运行mips顶层模块,通过$display指令检测存入寄存器堆(GRF)和数据存储器(DM)时的pc地址,相应数据以及存入的寄存器或数据存储器地址,将结果与Mars对拍运行时的数据做比对,以此检测Verilog代码编写的单周期CPU是否正确。

五、思考题

Q1

  • addr是从ALU计算结果输入
  • 由于DM的一个地址对应一个字节的储存空间,同时存入数据的位数是32位(字),故为满足一次性存入32位数据,则需取出addr的[13:2],保证每次存入数据的地址都是4的倍数(32位数据存储的起始地址),即可完成一次性存入32位数据。

Q2

  • 控制信号每种取值对应的控制信号
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
`define Op_R 0
`define Func_Add 6'h20
`define Func_Sub 6'h22
`define Func_nop 6'h0
`define Func_Jr 6'h8
`define Op_Lw 6'h23
`define Op_Sw 6'h2b
`define Op_Beq 6'h4
`define Op_Ori 6'hd
`define Op_Lui 6'hf
`define Op_Jal 6'h3
wire add = (Opcode == `Op_R && Func == `Func_Add);
wire sub = (Opcode == `Op_R && Func == `Func_Sub);
wire lw = (Opcode == `Op_Lw);
wire sw = (Opcode == `Op_Sw);
wire beq = (Opcode == `Op_Beq);
wire ori = (Opcode == `Op_Ori);
wire lui = (Opcode == `Op_Lui);
wire nop = (Opcode == `Op_R && Func == `Func_nop);
wire jal = (Opcode == `Op_Jal);
wire jr = (Opcode == `Op_R && Func == `Func_Jr);
assign Branch = jr ? 3 :
jal ? 2 :
beq ? 1 :
0;
assign RegDst = jal ? 2 :
lui | ori | lw ? 1 :
0;
assign RegWrite = add | sub | lw | ori | lui | jal ? 1 : 0;
assign MemtoReg = jal ? 3 :
lw ? 2 :
lui ? 1 :
0;
assign ALUSrc = lw | sw ? 2 :
ori ? 1 :
0;
assign ALUOp = ori ? 3 :
add | sub ? 2 :
beq ? 1 :
0;
assign MemWrite = sw ? 1 : 0;
assign MemCate = 0;
assign shiftSrc = lui ? 1 : 0;
  • 指令对应的控制信号的取值
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
`define Op_R 0
`define Func_Add 6'h20
`define Func_Sub 6'h22
`define Func_nop 6'h0
`define Func_Jr 6'h8
`define Op_Lw 6'h23
`define Op_Sw 6'h2b
`define Op_Beq 6'h4
`define Op_Ori 6'hd
`define Op_Lui 6'hf
`define Op_Jal 6'h3

initial begin
Branch = 0;
RegDst = 0;
RegWrite = 0;
MemtoReg = 0;
ALUSrc = 0;
ALUOp = 0;
MemWrite = 0;
MemCate = 0;
shiftSrc = 0;
end
always @(*) begin
if(Opcode == `Op_R && Func == `Func_Add) begin
Branch = `Branch_normal;
RegDst = `RegDst_Rd;
RegWrite = 1;
MemtoReg = `MemtoReg_ALU;
ALUSrc = `ALUSrc_ReadData2;
ALUOp = 2;
MemWrite = 0;
end
else if(Opcode == `Op_R && Func == `Func_Sub) begin
Branch = `Branch_normal;
RegDst = `RegDst_Rd;
RegWrite = 1;
MemtoReg = `MemtoReg_ALU;
ALUSrc = `ALUSrc_ReadData2;
ALUOp = 2;
MemWrite = 0;
end
else if(Opcode == `Op_Lw) begin
Branch = `Branch_normal;
RegDst = `RegDst_Rt;
RegWrite = 1;
MemtoReg = `MemtoReg_DM;
ALUSrc = `ALUSrc_SiExt;
ALUOp = 0;
MemWrite = 0;
end
else if(Opcode == `Op_Sw) begin
Branch = `Branch_normal;
RegWrite = 0;
ALUSrc = `ALUSrc_SiExt;
ALUOp = 0;
MemWrite = 1;
MemCate = `MemCate_Sw;
end
else if(Opcode == `Op_Beq) begin
Branch = `Branch_Beq;
RegWrite = 0;
ALUSrc = `ALUSrc_ReadData2;
ALUOp = 1;
MemWrite = 0;
end
else if(Opcode == `Op_Ori) begin
Branch = `Branch_normal;
RegDst = `RegDst_Rt;
RegWrite = 1;
MemtoReg = `MemtoReg_ALU;
ALUSrc = `ALUSrc_ZeExt;
ALUOp = 3;
MemWrite = 0;
end
else if(Opcode == `Op_Lui) begin
Branch = `Branch_normal;
RegDst = `RegDst_Rt;
RegWrite = 1;
MemtoReg = `MemtoReg_shift;
MemWrite = 0;
shiftSrc = `shiftSrc_0x10;
end
else if(Opcode == `Op_R && Func == `Func_nop) begin
Branch = `Branch_normal;
RegWrite = 0;
MemWrite = 0;
end
else if(Opcode == `Op_Jal) begin
Branch = `Branch_Jal;
RegDst = `RegDst_31;
RegWrite = 1;
MemtoReg = `MemtoReg_Adder;
MemWrite = 0;
end
else if(Opcode == `Op_R && Func == `Func_Jr) begin
Branch = `Branch_Jr;
RegWrite = 0;
MemWrite = 0;
end
else begin
Branch = `Branch_normal;
RegWrite = 0;
MemWrite = 0;
end
end

优劣分析

  • 记录每种控制信号对应的指令
    • 优势:
    1. 通过assign语句实现,保证进入每一个新的时钟周期都对每一个控制信号赋值。
    2. 按照控制信号进行分类,可以方便查找每一个控制信号所对应的指令类型。
    3. 在添加指令时按照该指令所需的控制信号,对相应的控制信号进行添加指令即可,同时若要删减指令,删除所有控制信号中所出现的删减指令即可。
    • 劣势:
    1. 不方便查找每一个指令所对应的控制信号。
    2. 对指令的控制信号进行修改时所需的调整过大。
  • 直接控制指令对应的控制信号的取值
    • 优势:
    1. 通过always语句块以及if-else语法实现,进入每一个时钟周期需要对指令进行判断,再对该指令所需的控制信号进行赋值。
    2. 按照指令类型进行分类,可以方便查找每一个指令所对应的控制信号。
    3. 在添加指令时加入新的else if语句块,添加判断条件以及赋值新的控制信号,若要删减指令,直接删除该else if语句块即可。
    • 劣势:
    1. 不方便查找每一个控制信号所对应的指令类型
    2. 当需要对控制信号进行修改时所需的调整过大。

Q3

  • 同步复位:clk的优先级大于reset的优先级。

    只有clk处于上升沿(根据实际情况发生改变),reset信号才起作用。
  • 异步复位: reset的优先级大于clk的优先级.

    因为对于需要由clk和reset控制的组件,当reset信号处于高电平时,该部件一直处于复位状态,clk处于上升沿时该部件无法存入数据,只有当reset信号处于低电平时,clk处于上升沿时该部件方可存入数据。

Q4

  • addi 与 addiu

    • addi指令和addiu指令正常运行(不存在溢出)时都是将寄存器Rs中值和符号扩展的16位立即数相加存入寄存器Rt当中,当寄存器Rs中值和符号扩展的16位立即数相加发生溢出(溢出判断,最高位进位和次高位进位不同),addi指令会发生溢出异常中断,发生报错。故在忽略溢出的前提下,addi 与 addiu 是等价的。
  • add 与 addu

    • add指令和addu指令正常运行(不存在溢出)时都是将寄存器Rs和Rt中的值相加后存入寄存器Rd当中,当寄存器Rs和Rt中的值相加发生溢出(溢出判断,最高位进位和次高位进位不同),add指令会发生溢出异常中断,发生报错。故在忽略溢出的前提下,add 与 addu 是等价的。