CPU Design Document

1. CPU Design

1.总体设计

​ 通过对于《计算机组成与设计》一书的阅读,对于P3的CPU设计我有如下思考,设计MIPS架构的CPU的本质在于从指令存储器中读出32位MIPS指令,并对MIPS指令进行解析进而完成指令中要求的操作。在本次实验中,要求实现的指令有add,sub,ori,lw,sw,beq,lui,nop其中,R型指令有add,sub,I型指令有ori,lw,sw,beq,lui,特殊的有nop指令,32位全为0。

RIJ类型指令图:

image-20231029210509165

此次实验中要求实现的基本指令:

  1. R型
OperationOp-Codersrtrdshamt(移位)func
add00000000000100000
sub00000000000100010
  1. I型
OperationOp-Codersrt16-bit-immediate-value
ori001101
lw100011
sw101011
beq000100
lui001111
  1. nop

​ 32’b0

​ 将我们要实现的CPU分为几个模块:IFU(取指令单元),GRF(寄存器堆),ALU(算术逻辑单元),DM(数据存储器),EXT(扩展单元),Controller(控制器)。

下面给出设计电路模块图:

image-20231026201931449

1. IFU取指令单元

​ 由于题目中要求寄存器的0x00003000对应ROM的0地址,即第一条指令的地址,且寄存器异步复位到初值0x00003000,这里涉及到类似于为寄存器赋初值的操作,可以对寄存器进行地址映射,利用寄存器当前值完成“赋初值”,当寄存器值为0时加上0x00003000进行下一个地址NPC的计算,而在计算取ROM中指令地址时再减去0x00003000。注意寄存器中保存的为以字节为单位的偏移量,ROM中一条指令为4个字节,需要将pc寄存器中的值右移两位后才对应到ROM中对应的行地址。

2.NPC计算下一条指令地址

​ 利用PCSrc信号选择取出PC+4地址的指令还是进行beq跳转得到的地址,应当注意的是beq中跳转为以字为单位,要先对移位量shift左移两位得到对应的字节偏移量。

3.ALU计算单元

ALUOp运算是否判断溢出
0000andno
0001orno
0010adduno
0011subuno
0010addyes(结合控制信号judge-overflow选择是否进行判断溢出问题)
0011subyes
0100luino
0101xorno
0110norno
0111logical-leftno
1000logical-rightno
1001Arth-rightno
1010sign-compareno
1011unsigned-compareno

注:由于教程中要求对于add sub不实现溢出判断 即把add sub当作addu subu用,而在指令中不具体实现addu subu

4. GRF寄存器堆

​ 寄存器中有32个通用寄存器,

  • 注意0号寄存器的值只能为0,故将使能端WE设置为常量0
  • 在写入端的解复用器DMX需要设置为三态,否则在写入一个值的时候其他值会被清零

5.Controller总控制器

​ Controller的输入为读取出指令的Op-Code,即[31:26]位,输出为八个控制信号,其中输送给ALU Controller的控制信号ALUOp为2位,下面对不同指令需要哪些控制信号进行分析,对不同指令按照其Op-Code进行分类.

1. R型指令
1. 数据通路

image-20231029210536844

2.输出信号

Op-Code:000000,对R型指令对应的Controller输出情况列表

信号名称取值情况
RegDst1
ALUSrc0
MemtoReg0
RegWrite1
MemRead0
MemWrite0
Branch0
EXTOp0
2.lw指令
1.数据通路

image-20231029210547206

2.输出信号

Op-Code:100011,对应的输出情况列表

信号名称取值情况
RegDst0
ALUSrc1
MemtoReg1
RegWrite1
MemRead1
MemWrite0
Branch0
EXTOp1
3.sw指令
1.数据通路

image-20231029210600170

2.输出信号

Op-Code:101011

信号名称取值情况
RegDstx
ALUSrc1
MemtoRegx
RegWrite0
MemRead0
MemWrite1
Branch0
EXTOp1
4.beq指令
1.数据通路

image-20231029210611308

2.输出信号

Op-Code: 000100

信号名称取值情况
RegDstx
ALUSrc0
MemtoRegx
RegWrite0
MemRead0
MemWrite0
Branch1
EXTOp1

注:由于sw与beq指令中都不需要向寄存器中存储数字,即RegWrite=0,此时RegDst与MemtoReg可以为不定值x因为无论为多少都不会写入

5.lui指令
1.数据通路

​ 将原16位imm后拼接16位0,存入目标寄存器,位拼接操作在ALU中实现,对应操作编码为1000。此处rs寄存器为$0。

image-20231106124946845

2.输出信号

Op-Code:001111,对应的输出情况列表

信号名称取值情况
RegDst0
ALUSrc1
MemtoReg0
RegWrite1
MemRead0
MemWrite0
Branch0
EXTOp0
6.ori指令
1.数据通路

image-20231029210637470

2.输出信号

Op-Code:001101

需要注意的是ori指令中16位立即数的范围是:0~65535,如果位数超出16位则会发生截断保留低位

信号名称取值情况
RegDst0
ALUSrc1
MemtoReg0
RegWrite1
MemRead0
MemWrite0
Branch0
EXTOp0

注:不需要向DM中进行读或写操作,MemRead=MemWrite=0

指令操作码操作funcALU动作ALU控制信号
lw取字xxxxxx0010
sw存字xxxxxx0010
beq相等跳转xxxxxx0110
add1000000010
sub1000100110
and1001000000
or1001010001
ori或立即数xxxxxx0001
lui存半字xxxxxx低位拼接01000
7.利用与或门阵列实现对指令的识别和产生相应的控制信号
1.指令识别

​ 对于指令的识别主要用到32位指令中的opcode字段([31-26]位)与funct字段([5:0]位),只有R型指令会利用funct字段进行指令的识别,其他类型指令只需要opcode字段即可。思路是,先利用opcode进行识别,识别出除R型指令之外的其他类型具体指令后,再利用funct字段对R型指令进行识别。

2.产生控制信号

​ 我将理论课中介绍的Controller与ALU Controller合并为一个整体,Controller产生的控制信号直接控制ALU进行运算,运用或阵列进行操作,只将输出信号表中为1的接到对应的信号或门上,对于0或x值均无连接,连接时有一个小技巧为将opcode与funct点为该指令的码,之后只需要将亮色的线连接到与门上。

8.对补充的指令的实现思路进行总结
1.补充的R型指令
  1. 运算指令

    通过在ALU中传递不同的ALUOp实现,如add,sub,and,or,nor,xor

  2. 移位指令

    如sll,srl,sllv,srlv,sra,srav,在ALU中通过Shifter实现逻辑左移,逻辑右移,算术右移。其中sll,srl,sra是将rt中的数值移动s([4:0]位立即数)位存储到rd中,在ALU的传入移位量端口控制信号shiftvar=0,sllv,srlv,srav将rt中的值移动rs位后存储入rd,需要注意的是rs只取低五位,相当于s<——GPR[rs]4...0,控制信号shiftvar=1

  3. 置位指令

    slt,sltu(set if less than),同样在在ALU中实现,slt对两个操作数进行符号比较,sltu对两个操作数进行无符号比较,比较结果进行0扩展至32位赋值到rd

  4. 跳转指令

    jr,jalr.跳转指令需要着重进行分析,

    1. jr :跳转到寄存器rs中存储的地址,在电路中对应GRF中读取的数据RD1,在NPC模块中实现跳转。
    2. jalr: PC跳转到GPR[rs]中保存的地址,并将当前PC+4保存在GPR[rd]中,相比jr多了将PC+4存入GPR[RD]的功能。在NPC模块中输出PC+4,在寄存器堆写入数据端口,控制信号PC+4=1,(控制信号PC+4只有jal和halr指令会涉及到将当前PC+4存入寄存器)选择写入寄存器信号RegDst=1ralink=0控制信号ralink=1时写入31号寄存器,jal指令专属
2.补充的J型指令

​ 跳转指令在NPC中实现。

  1. j ,j跳转指令默认的地址为26位,需要在NPC模块中进行补全,形式为(PC+4)31...28 | j-address |00
  2. jal指令与j指令计算地址的方式相同,同时将PC+4存入31号寄存器即ra寄存器,给出重要的控制信号:ralink=1,pc+4=1,jump=1
3.补充的I型指令
  1. 运算指令

    凡是带立即数运算的如addi,subi,ori,andi等都需要在ALU控制信号ALUSrc=1,选择符号扩展后的32位立即数,运算控制信号ALUOp与R类运算相同

  2. B类跳转指令

    注意:B类跳转指令对16位地址偏移量都是进行符号扩展,Control Unit输出branch,branchOp,完成对具体指令的识别,对于是否满足条件进行跳转的判断则在sel-B模块中实现,尤其要注意在sel-B模块中由于是要进行大于0小于0的判断,比较器应该选择为有符号的。其中beq和bne指令由ALU中输出信号equal判定。

  3. 置位指令

    slti,sltiu,比较时对第二个操作数ALUSrc=1

  4. 访存指令

    sw,sh,sb,lw,lh,lb,lbu,lhu等指令的具体实现已经在HorB模块中有了比较详细的叙述,利用address[1:0]进行判断并进行"插入"操作

6.DM数据存储

​ 一定要注意DM需要时钟信号控制!DM在时钟上升沿写入数据,一开始没有给DM加时钟信号导致数据没有及时存入QAQ

​ **补充指令sb,lb,sh,lh,lbu,lhu,在DM模块中增加新模块HorB,根据控制信号b or h选择按照byte进行操作还是按照half word进行操作。**首先分析传入地址,传入地址是以字节为单位的,我们可以在已知对字/半字/字节操作的的情况下,分析出具体的半字/字节。记传入地址最后两位address[1:0]

操作单位address[1:0]操作对象
word00
half word00第一个半字节
half word10第二个半字节
byte00第一个字节
byte01第二个字节
byte10第三个字节
byte11第四个字节

思路:当操作类型为load(读取):先读取出来一个字,再从中选择部分进行load,当操作类型为save(存入):先读取出来对应地址上的一个字,再在相应位置上插入想要存入的内容,这就要求sb,lb,sh,lh,lbu,lhu进行操作前需要先从对应地址上读取,即MemRead = 1.

电路图如下:

image-20231106120304715

7.EXT位扩展

​ 位扩展单元由EXTOp信号控制,选择进行符号扩展还是0扩展,其中sw,lw,beq均为符号扩展,ori为0扩展。

8.sel-B具体判断B指令

​ 实现的B类型跳转指令有,beq,bne,bgez,bgtz,blez,bltz。可以大致分为两类。第一类为beq,bne,这两个指令是对于两个操作数进行比较然后进行跳转(比较在ALU中完成),第二类中都是进行与0的比较(在sel-B中完成)。branchOp列表如下:

branchOp指令
000beq
001bltz
001bgez
010bgtz
011blez
100bne

​ 其中beq指令与bne指令都由equal信号判断,故对于beq\bne的选择通过最高位选择。在001编码中,对bltz和bgez进行了合并,因为这两种指令的opcode一致为000001,故在control unit中无法实现对这两种指令的识别,需要利用这两种指令的[20:16]进行分辨,即judge信号,bltz:00000,bgez:00001,这样实际上需要选择的只有四个信号,利用branchOp的低两位进行判断,电路图如下:

image-20231106123917609

尤其需要注意的是:在sel-B模块中比较器应当选择有符号的而不是unsigned,因为他们都是与0进行比较,而在ALU模块中slt指令需要进行符号比较,sltu指令需要进行无符号比较.

2. test scheme

​ 课下测试利用课程组提供的jar包进行测试,从MARS中导出16进制文件load到我的单周期CPU中,运行CPU,将存储在寄存器中的数据与MARS运行结果进行比对。

1.基本指令测试
1. ori-test

ori指令中的立即数为无符号扩展,不存在复数的情况,测试指令

ori $a0,$0,123
ori $a1,$a0,456
2. lui-test
lui $a2,123 #符号位为0
lui $a3,0xffff #符号位为1
3. add-test
lui $a2, 123            # 符号位为 0
lui $a3, 0xffff         # 符号位为 1
ori $a3, $a3, 0xffff    # $a3 = -1
add $s0, $a0, $a2      # 正正
add $s1, $a0, $a3      # 正负
add $s2, $a3, $a3      # 负负
4.sw-test
ori $a0,$0,1
ori $a1,$0,2
ori $a2,$0,3
ori $t0, $0, 0x0000
sw $a0, 0($t0)
sw $a1, 4($t0)
sw $a2, 8($t0)
5.lw-test
ori $a0,$0,1
ori $a1,$0,2
ori $a2,$0,3
ori $t0, $0, 0x0000
sw $a0, 0($t0)
sw $a1, 4($t0)
sw $a2, 8($t0)
lw $a0, 0($t0)
lw $a1, 12($t0)
sw $a0, 28($t0)
sw $a1, 32($t0)
6.beq-test
ori $a0, $0, 1
ori $a1, $0, 2
ori $a2, $0, 1
beq $a0, $a1, loop1     # 不相等
beq $a0, $a2, loop2     # 相等
loop1:sw $a0, 36($t0)
loop2:sw $a1, 40($t0)
7.将以上指令综合测试
ori $a0, $0, 123
ori $a1, $a0, 456
lui $a2, 123            # 符号位为 0
lui $a3, 0xffff         # 符号位为 1
ori $a3, $a3, 0xffff    # $a3 = -1
add $s0, $a0, $a2      # 正正
add $s1, $a0, $a3      # 正负
add $s2, $a3, $a3      # 负负
ori $t0, $0, 0x0000
sw $a0, 0($t0)
sw $a1, 4($t0)
sw $a2, 8($t0)
sw $a3, 12($t0)
sw $s0, 16($t0)
sw $s1, 20($t0)
sw $s2, 24($t0)
lw $a0, 0($t0)
lw $a1, 12($t0)
sw $a0, 28($t0)
sw $a1, 32($t0)
ori $a0, $0, 1
ori $a1, $0, 2
ori $a2, $0, 1
beq $a0, $a1, loop1     # 不相等
beq $a0, $a2, loop2     # 相等
loop1:sw $a0, 36($t0)
loop2:sw $a1, 40($t0)

需要注意的是MARS设置中不应当开启delayed branching,否则会运行跳转指令如beq的下一条指令,与本地CPU不符

2.补充指令后计算指令综合测试
3.补充指令后访存指令综合测试
.text

li	$s0, 0xABCF1234
li	$s1, 0x1234ABCD
li	$s2, 0xa1b2c3d4
li	$s3, 0x7fff0000

sb	$s1, 100($0)
sb	$s2, 103($0)
sb	$s3, 105($0)
sb	$s4, 107($0)
sh	$s4, 108($0)
sh	$s3, 110($0)
sh	$s2, 112($0)
sh	$s1, 114($0)
sw	$s0, 0($0)
sw	$s1, 4($0)
sw	$s2, 8($0)
sw	$s3, 12($0)
lw	$t0, 0($0)
lw	$t1, 4($0)
lw	$t2, 8($0)
lw	$t3, 12($0)
sw	$t0, 16($0)
sw	$t1, 20($0)
sw	$t2, 24($0)
sw	$t3, 28($0)
lb	$t0, 3($0)
lb	$t1, 7($0)
lb	$t2, 11($0)
lb	$t3, 13($0)
sw	$t0, 32($0)
sw	$t1, 36($0)
sw	$t2, 40($0)
sw	$t3, 44($0)
lbu	$t0, 3($0)
lbu	$t1, 7($0)
lbu	$t2, 11($0)
lbu	$t3, 13($0)
sw	$t0, 48($0)
sw	$t1, 52($0)
sw	$t2, 56($0)
sw	$t3, 60($0)
lh	$t0, 2($0)
lh	$t0, 4($0)
lh	$t0, 8($0)
lh	$t0, 12($0)
sw	$t0, 64($0)
sw	$t1, 68($0)
sw	$t2, 72($0)
sw	$t3, 76($0)
lhu	$t0, 2($0)
lhu	$t0, 6($0)
lhu	$t0, 8($0)
lhu	$t0, 12($0)
sw	$t0, 80($0)
sw	$t1, 84($0)
sw	$t2, 88($0)
sw	$t3, 92($0)
li	$v0, 10
syscall
4.补充指令后跳转指令综合测试
.text
main:	
li	$s0, 0
li	$s1, -1000
li	$s2, 1000
lui	$s3, 0x8000	#s3 is  -2147483648
lui	$s4, 0x8000
ori	$s4, 0x0001	#s4 is  -2147483647
lui	$s5, 0x7fff
ori	$s5, 0xffff	#s5 is  2147483647
lui	$s6, 0x7fff
ori	$s6, 0xfffe	#s6 is 2147483646


beq_1:	beq	$s0, $s0, beq_1_test
beq_10:  beq	$s5, $s6, beq_10_test
beq_end:

bne_1:	bne	$s0, $s1, bne_1_test
bne_10:	bne	$s3, $s3, bne_10_test
bne_end:

blez_1: blez	$s0, blez_1_test
blez_10: blez	$s6, blez_10_test
blez_end:

bgtz_1:	 bgtz	$s2,bgtz_1_test
bgtz_10: bgtz	$s1,bgtz_10_test
bgtz_end:

jal_1:	jal	jal_1_test
jal_5:	jal	jal_5_test
jal_end:

jalr_1:	or	$v0, $0, 12916
	jalr	$v1, $v0
jalr_5:	or	$v0, $0, 12948
	jalr	$v1, $v0

sw	$t0, 0($s0)
sw	$t1, 0($s0)
sw	$t2, 0($s0)
sw	$t3, 0($s0)
sw	$t4, 0($s0)
sw	$t5, 0($s0)
li	$v0, 10
syscall
	
beq_1_test: addi	$t0,$t0, 1
	   j	beq_10
beq_10_test: addi	$t0,$t0, 1
	   j	beq_end

bne_1_test: addi	$t1,$t1, 1
	   j	bne_10
bne_10_test:addi	$t1,$t1, 1
	   j	bne_end  	      	      	   

blez_1_test:	addi	$t2,$t2, 1
		j	blez_10
blez_10_test:	addi	$t2,$t2, 1
		j	blez_end

bgtz_1_test:	addi	$t3,$t3 1
		j	bgtz_10
bgtz_10_test:	addi	$t3,$t3 1
		j	bgtz_end
		
jal_1_test:	addi	$t4, $4, 1
		jr	$ra
jal_5_test:	addi	$t4, $4, 1
		jr	$ra

jalr_1_test:	addi	$t5, $t5, 1
		jr	$v1
jalr_5_test:	addi	$t5, $t5, 1
		jr	$v1

2.编写python脚本完成自动化测试

​ 利用正则表达式替换,对ROM中内容进行改写,并生成新的电路。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import os
import re
#generate ROM-File
command="java -jar MARS_mod.jar CPUtest.asm nc mc CompactTextAtZero a dump .text HexText rom.txt"
os.system(command)
content = open("rom.txt").read()

#write ROM-File to Circle—File
cur = open("CPU.circ", encoding="utf-8").read()
cur = re.sub(r'addr/data: 12 32([\s\S]*)</a>',"addr/data: 12 32\n" + content + "</a>", cur)
with open("cpu_remake.circ","w", encoding="utf-8") as file:
 file.write(cur)

注意:cpu_remake是根据原CPU新生成的文件,若有改动应该在原CPU中完成,若在cpu_remake中进行改动,改动会发生“丢失”,因为cpu_remake的每一次生成都是根据CPU生成

3.思考题

1.单周期CPU的模块中哪些发挥状态存储功能,哪些发挥状态转移功能?

​ 答:在单周期CPU中,我们以splitter为界可以将CPU分为一个Moore机与一个Mealy机,两个状态机各有一个状态存储模块与一个状态转移模块。状态存储:IM,GRF,DM,状态转移:NPC,ALU

2.IM使用ROM,DM使用RAM,GRF使用Register合理吗

​ 合理,Information Memory用来存储运行的程序指令,要保证程序不会被更改,使用ROM(Read Only Memory), Data Memory是数据存储区,需要进行写入和读取,使用RAM。

3.额外实现的单元

​ 我将IFU中的取指令功能与计算下一指令地址的逻辑分开,计算下一指令地址的功能单独由NPC模块实现,包括对B型和J型指令的跳转。具体细分判断B类型跳转指令的sel-B模块,HorB,存储或读取时对字节和半字进行操作的模块。

4.nop空指令不需要加入信号真值表

​ 实际上nop指令会被识别为sll指令,具体形式为sll $0,$0,0,即将0号寄存器左移0位,确实不会发生影响。

5.评价MIPS指令及汇编语言一节中测试样例的强度

​ 测试样例的覆盖是不全面的,有些寄存器没有用到,有些负数的特殊情况没有涉及,跳转的指令没有涉及等。