0%

计算机组成 P4 CPU 设计文档

思考题

1 DM输入示例

addr信号从ALU的输出结果取[11:2]得到,使用到DM的指令有lw和sw,都需要将对应寄存器值和立即数相加,因此这里DM的地址应为对应的ALU输出的相加结果。

位数是[11:2]而不是[9:0]因为DM是按字对齐,(目前)仅支持按字读取和写入,且DM内部存储数据是以类似这样的格式存储:

1
reg [31:0] im_reg [0:1023]

是按照32位数的数组进行存储的,因此如果输入时直接取[11:2]位(代表截去最低两位)可以直接取im_reg[addr]得到所需数据,不采用[9:0]的描述也是为了不混淆,明确展示这里取的是截取了末两位后取10位的ALU结果而不是直接取低10位的结果。

2 两种不同控制器译码方式

在Verilog编程中,记录指令对应的控制信号如何取值对应使用always @(*)模块,对于每个指令为控制信号赋值,以lui和jal指令为例,给出如下组合逻辑代码示例:(将输出各值定义为寄存器,此处为简便省略了default分支)

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
always @(*) begin
case(OPCode)
6'b00_1111: begin
// lui
Mem2Reg <= 2'b00;
MemWrite <= 1'b0;
PCSrc <= 2'b00;
ALUCtrl <= 4'b0100; // lui
ALUSrc <= 1'b1;
RegDest <= 2'b00;
RegWrite <= 1'b1;
end
6'b00_0011: begin
// jal
Mem2Reg <= 2'b10;
MemWrite <= 1'b0;
PCSrc <= 2'b10;
ALUCtrl <= 4'b0000;
ALUSrc <= 1'b0;
RegDest <= 2'b10;
RegWrite <= 1'b1;
end
// there should be a "default", but too long so we don't write it here
endcase
end

而对于记录控制信号每种取值所对应的指令,对应使用assign模块直接判断指令是否是某个值,以jal和lw为例,给出如下组合逻辑代码:(省略了部分output数据)

1
2
3
4
5
6
7
wire isJal, isLw;
assign isJal = (op == 6'b00_0011);
assign isLw = (op == 6'b10_0011);
assign Mem2Reg[0] = isLw;
assign Mem2Reg[1] = isJal;
assign RegWrite = isLw || isJal;
// there are more output wires but too long so we don't write it here

可以看到,第一种形式按照输入的指令类型进行区分,第二种形式根据输出的端口类型进行区分。第二种不需要额外增加reg变量且代码相对更加简便,对于许多种指令只需要每个控制码输出(的每个位)写一行,对所有该值取1的指令或一遍即可。但是第二种写法缺乏可读性,不能一目了然地得到每个指令对应的控制码,增加新指令时也容易写错,而第一种按照不同的指令区分,可读性较高。因此这里选择了第一种写法。

3 对比同步异步复位

同步复位中以clk上升沿为准,如果clk上升沿时有reset再复位,因此clk信号比reset信号优先

异步复位中只要reset为真就进行复位,不考虑clk的值,因此reset信号比clk信号优先

4 解释addi addiu以及add addu的等价性

MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set中描述指令行为的部分,Add指令的行为如下:

1
2
3
4
5
6
temp <- {GPR[rs][31], GPR[rs][31:0]} + {GPR[rt][31], GPR[rt][31:0]}
if temp[32] != temp[31] then
SignalException(IntegerOverflow)
else
GPR[rd] <- temp
endif

而Addu指令行为如下:

1
2
temp <- GPR[rs] + GPR[rt]
GPR[rd] <- temp

(Addi和Addiu指令大同小异,只是将对应的第二参寄存器值替换为符号扩展到33位(addi)或32位(addiu)的立即数而已,这里不额外附上对应伪代码)

可以看到,“有符号”加法Add在计算中将两个加数符号扩展到33位数,加和之后判断结果数第33位和第32位是否相等,不等的话报错“整型溢出”。逻辑很好理解——如果扩展到33位后结果数的符号位(第33位)和最高位(第32位)不等,代表使用32位数无法正确表达结果的正负性,此时就发生了结果溢出。如果忽略溢出,也就是在32位数可表达范围内规定最大值+1得到最小值,最小值-1得到最大值,则显然不需要上述处理。同理对于addi和addiu也是如此。

测试方法

采用了讨论区中石睿知同学分享的自动生成输出的魔改MARS,并且使用了如下的自动生成测试汇编代码的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
from random import randint as rd

reg_calci = ["ori"]
reg_calc = ["add", "sub"]
branch = ["beq"]
# load_memory = ["lw"]
# write_memory = ["sw"]
jump = ["jal"]
jump_reg = ["jr"]
load_high = ["lui"]
space = ["nop"]

IMM_HIGH = 100
IMM_LOW = 1
DATA_MAX = 10
auto_label_cnt = 0

def get_ran(arr):
return arr[rd(0, len(arr)-1)]

def get_ran_reg():
return rd(1, 31)

def put_calci(pos = -1, rt = get_ran_reg(), rs = get_ran_reg(),
imm = rd(IMM_LOW, IMM_HIGH)):
if pos == -1:
pos = rd(0, len(reg_calci)-1)
return "%s $%d, $%d, %d" % (reg_calci[pos], rt, rs, imm)

def put_calc(pos = -1, rd = get_ran_reg(), rs = get_ran_reg(),
rt = get_ran_reg()):
if pos == -1:
pos = rd(0, len(reg_calc)-1)
return "%s $%d, $%d, $%d" % (reg_calc[pos], rd, rs, rt)

def put_branch(pos = -1, rt = get_ran_reg(), rs = get_ran_reg(),
label = "", neg = False):
global auto_label_cnt
if pos == -1:
pos = rd(0, len(branch)-1)
if label == "" and neg:
auto_label_cnt += 3
return "jal auto_label_%d\nauto_label_%d:\n%s\njal auto_label_%d\n" \
"auto_label_%d\n%s $%d, $%d, auto_label_%d\nauto_label_%d:" % \
(auto_label_cnt-2, auto_label_cnt-1, put_calci(), auto_label_cnt,
auto_label_cnt-2, branch[pos], rt, rs, auto_label_cnt-1, auto_label_cnt)
if label == "":
auto_label_cnt += 1
return "%s $%d, $%d, auto_label_%d\n%s\nauto_label_%d:\n%s" % \
(branch[pos], rt, rs, auto_label_cnt, put_calci(),
auto_label_cnt, put_calci())
return "%s $%d, $%d, %s" % (reg_calci[pos], rt, rs, label)

def put_lw(base = get_ran_reg(), rt = get_ran_reg(), label = ""):
if label == "":
label = "data_label%d" % rd(1, DATA_MAX)
return "lw $%d, %s($%d)" % (rt, label, base)

def put_sw(base = get_ran_reg(), rt = get_ran_reg(), label = ""):
if label == "":
label = "data_label%d" % rd(1, DATA_MAX)
return "sw $%d, %s($%d)" % (rt, label, base)

def put_lsw(base = get_ran_reg(), rt = get_ran_reg(), label = ""):
if label == "":
label = "data_label%d" % rd(1, DATA_MAX)
return "%s\n%s" % (put_lw(base=base, rt=rt, label=label),
put_sw(base=base, rt=rt, label=label))

def prepare_data():
s = ".data\n"
for i in range(1, DATA_MAX+1):
s += "data_label%d: .space 4\n" % i
s += "\n.text\n"
return s

指令集综述

简要说明

包含指令集如下:

1
add sub ori lw sw beq lui jal jr nop

R I J 型指令说明

R型

包括

1
add sub and or nor sll srl jr
位置 (位数) 31~26 (6) 25~21 (5) 20~16 (5) 15~11 (5) 10~6 (5) 5~0 (6)
类型 操作码 寄存器地址 寄存器地址 寄存器地址 五位立即数 操作码
名称 OP rs rt rd shamt (sa) func
解释 恒为0x0 第一参 第二参 目的参 移位量 指示操作类型

I型

包括

1
addi andi ori xori lw sw beq bne lui
位置 (位数) 31~26 (6) 25~21 (5) 20~16(5) 15~0 (16)
类型 操作码 寄存器地址 寄存器地址 立即数
名称 OP rs rt imm
解释 指示操作类型 第一参 目的参 立即数

J型

包括

1
j jal
位置 (位数) 31~26 (6) 25~0 (26)
类型 操作码 指令地址
名称 OP address
解释 指示操作类型 目标跳转位置

(无)符号加 add

按照要求,依照add的机器码处理,但加法依照无符号数加法完成。取两个寄存器中的数,无符号相加(不考虑溢出)后存入目标寄存器。为R型指令。

位置 (位数) 31~26 (6) 25~21 (5) 20~16 (5) 15~11 (5) 10~6 (5) 5~0 (6)
名称 OP rs rt rd 0 func
解释 000000 第一加数地址 第二加数地址 结果存储地址 00000 100000

(无)符号减 sub

按照要求,依照sub的机器码处理,但减法依照无符号数加法完成。无符号相减(不考虑溢出)后存入目标寄存器。为R型指令。

位置 (位数) 31~26 (6) 25~21 (5) 20~16 (5) 15~11 (5) 10~6 (5) 5~0 (6)
名称 OP rs rt rd 0 func
解释 000000 被减数地址 减数地址 结果存储地址 00000 100010

或立即数 ori

按照要求,即为将某寄存器中数或一个16位立即数,将得到的数存到目标寄存器中。为I型指令。

位置 (位数) 31~26 (6) 25~21 (5) 20~16(5) 15~0 (16)
名称 OP rs rt imm
解释 001101 被或数地址 结果存储地址 立即数

加载字 lw

取内存中的一字长的内容存入目标寄存器。为I型指令。

1
2
addr <- GPR[base] + sign_ext(offset)
GPR[rt] <- memory[addr]
位置 (位数) 31~26 (6) 25~21 (5) 20~16(5) 15~0 (16)
名称 OP base rt offset
解释 100011 内存地址所在寄存器 结果存储地址 内存地址偏移量(立即数)

存储字 sw

将目标寄存器中的内容存入内存中一字长的位置。为I型指令。

1
2
addr <- GPR[base] + sign_ext(offset)
memory[addr] <- GPR[rt]
位置 (位数) 31~26 (6) 25~21 (5) 20~16(5) 15~0 (16)
名称 OP base rt offset
解释 101011 内存地址所在寄存器 目标寄存器地址 内存地址偏移量(立即数)

相等时转移 beq

当两个目标寄存器中存储的值相等时转移到目标指令处。为I型指令。

1
2
3
4
if (GPR[rs] == GPR[rt])
PC <- PC + 4 + sign_extend({offset,2{0}})
else
PC <- PC + 4
位置 (位数) 31~26 (6) 25~21 (5) 20~16(5) 15~0 (16)
名称 OP rs rt offset
解释 000100 第一目标寄存器 第二目标寄存器 转移目标指令地址

立即数加载至高位 lui

将立即数赋值到目标寄存器的高16位。为I型指令。

位置 (位数) 31~26 (6) 25~21 (5) 20~16(5) 15~0 (16)
名称 OP 0 rt imm
解释 001111 00000 目标寄存器 立即数

跳转并链接 jal

将当前的PC+4存储在GPR[31]($ra)中,按照给定的地址进行跳转。

1
2
PC <- {PC[31:28], instr_index, {2{0}}}
GPR[31]($ra) <- PC + 4
位置 (位数) 31~26 (6) 25~0 (26)
名称 OP instr_index
解释 000011 目标跳转位置

跳转至寄存器 jr

跳转到目标寄存器存储的地址指定的指令处

1
PC <- GPR[rs]
位置 (位数) 31~26 (6) 25~21 (5) 20~16 (5) 15~11 (5) 10~6 (5) 5~0 (6)
名称 OP rs 0 0 0 func
解释 000000 目标寄存器地址 00000 00000 00000 001000

空指令 nop

机器码为0x00000000,不进行任何有效行为。

IFU 取指令单元

功能要求

  • 内部包括 PC(程序计数器)、IM(指令存储器)及相关逻辑。
  • PC 同步复位,复位值为起始地址。
  • 起始地址:0x00003000。
  • IM 容量为 32bit × 4096字(按字取的地址宽度12位)。

输入端口

  • clk 时钟信号
  • Reset 同步复位信号
  • PCSrc [1:0] 决定下条PC指令使用何处输入
  • ALUZero 对应ALU的Zero输出,代表是否允许branch,1代表允许
  • PCImm [15:0] 符号扩展后的立即数(对应beq给定的立即数)
  • PCInstrIndex [25:0] J型指令的立即数值(对应jal给定的立即数)
  • PCJumpReg [31:0] 来自寄存器的跳转地址(对应jr指令对应地址读取到的值)

输出端口

  • Inst [31:0] 取得的指令
  • PCNxt [31:0] 对应PC+4,用于jal

行为逻辑

  • 支持同步复位
  • 任何时刻,输出当前PC寄存器对应地址中的指令
  • 每个clk上沿更新PC的值:
1
2
3
4
5
6
7
8
9
10
11
12
if (PCSrc == 2'b00 || (PCSrc == 2'b01 && ALUZero == 0)) begin // 无b/j指令或b指令无效,直接PC+=4
PC <= PC + 4;
end
else if (PCSrc == 2'b01 && ALUZero == 1) begin // b类指令生效
PC <= PC + 4 + {{14{PCImm[15]}}, PCImm, {2{1'b0}}};
end
else if (PCSrc == 2'b10) begin // jal
PC <= {PC[31:28], PCInstrIndex, {2{1'b0}}};
end
else begin // jr
PC <= PCJumpReg;
end

ALU 算术逻辑单元

功能要求

  • 提供 32 位加、减、或运算及大小比较等功能。
  • 加减法按无符号处理(不考虑溢出)。

输入端口

  • SrcA [31:0] 第一个参数输入
  • SrcB [31:0] 第二个参数输入
  • ALUCtrl [3:0] ALU操作类型控制码

输出端口

  • Zero 输出结果是否为0
  • ALUResult [31:0] 计算结果

控制信号说明

  • 或运算 0000
  • 与运算 0001
  • 加法运算(无符号无溢出) 0010
  • 减法运算(无符号无溢出) 0011
  • 立即数加载至高位 0100
  • 或立即数(将SrcB取低16位进行0扩展后和SrcA取或) 0101

GRF 通用寄存器组

功能要求

  • 用具有写使能的寄存器实现,寄存器总数为 32 个,应具有同步复位功能。
  • 0 号寄存器的值始终保持为 0。其他寄存器初始值(复位后)均为 0,无需专门设置。

输入端口

  • clk 时钟信号
  • Reset同步复位信号
  • WE 写使能信号
  • A1 [4:0] 第一读取地址
  • A2 [4:0] 第二读取地址
  • A3 [4:0] 写入地址
  • WD [31:0] 写入数据输入

输出端口

  • RD1 [31:0] 第一读取输出 输出A1地址的寄存器中的数据
  • RD2 [31:0] 第二读取输出 输出A2地址的寄存器中的数据

功能说明

  • 任何时刻,RD1和RD2都输出A1和A2对应地址的寄存器中的数据
  • Reset同步复位,将所有寄存器复位为0
  • 0号寄存器永远保持为0
  • 当WE为真时,将WD的数据在clk上沿写入A3对应地址的寄存器

DM 数据存储器(内存)

功能要求

  • 使用 RAM 实现,容量为 32bit × 3072字,应具有同步复位功能,复位值为 0x00000000。
  • 起始地址:0x00000000

输入端口

  • clk 时钟信号
  • Reset 同步复位信号
  • WE 写使能信号
  • Addr [31:0] 写入/读取地址
  • WD [31:0] 写入数据

输出端口

  • RD [31:0] 读取结果数据

功能说明

  • 任何时刻,在RD输出Addr对应内存地址的内存数据
  • 当WE为真时,将WD的数据在clk上沿写入Addr对应内存地址

EXT 扩展器

功能要求

  • 将输入的立即数进行扩展

输入端口

  • Imm [15:0] Inst指令中的立即数部分
  • ExtType 扩展方式

输出端口

  • ExtOut [31:0] 扩展后的结果

行为描述

ExtType 扩展行为描述
0 进行零扩展,即返回 {{16{1b0}}\{\{16\{1'b0\}\}, Imm}
1 进行符号扩展,即返回 {{16{Imm[15]}}\{\{16\{Imm[15]\}\}, Imm}

Controller 控制器

功能要求

将输入的opcode和funct转化为CPU各部分对应信号输出

实现方式

分类判断后指定各项输出的值。

输入端口

  • OPCode [5:0] 标识命令种类的指令,按MIPS汇编标准执行
  • func [5:0] 标识R型指令具体种类的指令,按MIPS汇编标准执行

输出端口

  • ExtType 决定扩展器扩展类型,0零扩展,1符号扩展
  • Mem2Reg [1:0] 决定GRF写入数据来源,00代表ALU输出,01代表DM输出,10代表PCNxt
  • MemWrite 决定DM是否允许写入
  • PCSrc [1:0] 决定下条PC指令使用何处输入,00代表PC+=4,01代表branch,10代表jal,11代表jr
  • ALUCtrl [3:0] 决定当前ALU执行何种运算,具体见ALU说明
  • ALUSrc 决定ALU的第二参来自GRF的RD2(对应值0)还是立即数(对应值1)
  • RegDest [1:0]决定GRF写入地址来源是Inst[20:16](rt, 对应值00,I型)还是Inst[15:11](rd, 对应值01,R型),还是31即$ra(对应值10,jal)
  • RegWrite 决定GRF是否允许写入

各指令对应Controller输出

op 00 0000 00 0000 00 1101 10 0011 10 1011 00 0100 00 1111 00 0011 00 0000
func 10 0000 10 0010 - - - - - - 00 1000
add sub ori lw sw beq lui jal jr
Mem2Reg 00 00 00 01 - - 00 10 -
MemWrite 0 0 0 0 1 0 0 0 0
PCSrc 00 00 00 00 00 01 00 10 11
ALUCtrl 2 3 5 2 2 3 4 - -
(CalcMode) add sub ori add add sub lui - -
ALUSrc 0 0 1 1 1 0 1 - -
RegDest 01 01 00 00 - - 00 10 -
RegWrite 1 1 1 1 0 0 1 1 0
ExtSign 0 0 0 1 1 1 0 0 0