计组-实验-P3
CPU设计
概述
本次我设计的logisim
单周期CPU支持21条指令,采用多个模块进行设计。
所采用模块化设计为:
IFU
:指令单元,接受指令地址PC来给出当前周期处理的指令GRF
:寄存器模块,通过该模块统一进行32个寄存器的读写NPC
:计算下一条指令的地址,接受jump,branch等指令来支持跳转操作ALU
:进行运算操作,该ALU操作码为4为DM
:内存组,通过内置的RAM实现ControlUnit
:指令控制单元,通过接受op
和func
来发出指令,控制数据通路中的MUX
支持指令
支持21条指令,为:
- R指令:
add,addu,sub,or,sll,sllv,slt,sltu
- I指令:
ori,lui,beq,sw,lw,sh,sb,lh,lb
- J指令:
j,jr,jal,jalr
数据通路模块定义
IFU:指令单元
该模块内部包含PC(程序计数器)和IM(指令存储器)。PC通过logism
内置的寄存器实现, IM通过Logisim内置的ROM实现(容量为24*32bit)。可以根据PC的值从IM取出对应的指令。
端口定义
信号名 | 方向 | 位宽 | 具体翻译 |
---|---|---|---|
clk |
I | 1 | 时钟信号 |
reset |
I | 1 | 复位信号,为异步复位 |
PC_next |
I | 32 | 下一条执行指令的地址 |
inStr |
O | 32 | 当前正在执行的指令 |
PC |
O | 32 | 当前正在执行指令的地址 |
功能定义
通过指令地址PC取得指令。
该组件支持异步复位,复位后输出的是地址为0x00003000
处的指令。
NPC:指令地址单元
端口定义
信号名 | 方向 | 位宽 | 具体翻译 |
---|---|---|---|
PC |
I | 32 | 当前正在执行指令的地址 |
imm |
I | 26 | 对于J型指令时跳转的偏移量 |
offest |
I | 16 | 对于I型指令跳转时的偏移量 |
if_branch |
I | 1 | 是否进行I型指令的分支跳转 |
if_jump |
I | 1 | 是否进行J型指令的直接跳转 |
rs |
I | 32 | rs寄存器中的值 |
jumpSrc |
I | 1 | 跳转的地址选择是imm 还是rs |
PC_next |
O | 32 | 输出计算后得到的下一个PC地址 |
PC+4 |
O | 32 | 输出当前输入PC值加4得到的PC值 |
功能定义
指令一共有三种情况:
- 不跳转,则
PC=PC+4
- 以
beq
为代表的跳转,指令计算方式为PC
和offest
的计算,计算结果为PC_branch
- J型指令跳转,指令计算有两种,通过
jumpSrc
进行选择,计算结果为PC_jump
- 以
j
为代表的跳转,指令计算方式为立即数imm
和PC
的计算 - 以
jr
为代表的跳转,指令计算方式为寄存器中储存的地址,即输入的rs
寄存器中储存的值
- 以
同时,以为jal
,jalr
等指令会存储返回的地址,需要输出一个PC+4
的地址
GRF:通用寄存器组
采用了P0课下实现的GRF。
端口定义
信号名 | 方向 | 位宽 | 具体翻译 |
---|---|---|---|
clk |
I | 1 | 时钟信号 |
reset |
I | 1 | 复位信号,为异步复位 |
WE |
I | 1 | 写使能信号 |
A1 |
I | 5 | 读取编号1 |
A2 |
I | 5 | 读取编号2 |
A3 |
I | 5 | 写地址 |
WD |
I | 32 | 写数据 |
RD1 |
O | 32 | 输出读取编号1对应寄存器的值 |
RD2 |
O | 32 | 输出读取编号2对应寄存器的值 |
功能定义
实现了寄存器的读写功能,利用了MUX和DUX。
ALU:计算单元
端口定义
信号名 | 方向 | 位宽 | 具体翻译 |
---|---|---|---|
inA |
I | 32 | 输入数据1 |
inB |
I | 32 | 输入数据2 |
op |
I | 4 | 计算类型 |
shift |
I | 5 | 偏移量 |
result |
I | 32 | 计算结果 |
zero |
O | 1 | 计算结果是否为0 |
功能定义
实现了如下操作加、建、与、或、异或、与或、左逻辑移位、右逻辑移位、右算数移位、大小判断操作。对应ALU操作码如下:
操作码 | 操作 |
---|---|
0000 | A+B |
0001 | A-B |
0010 | A and B |
0011 | A or B |
0100 | A xor B |
0101 | ~ ( A OR B ) |
0110 | B << shift |
0111 | B >> shift |
1000 | B >>> shift |
1001 | B << 16 |
1010 | A < B |
DM:内存
利用内置的RAM模拟了内存,可以对内存中的数据进行读写。支持对字、半字、字节的读写操作。
端口定义
信号名 | 方向 | 位宽 | 具体翻译 |
---|---|---|---|
address |
I | 32 | 访问的内存地址 |
WD |
I | 32 | 写入内存的数据 |
WE |
I | 1 | 写使能 |
clk |
I | 1 | 时钟信号 |
byte |
I | 1 | 是否对字节进行读取 |
half |
I | 1 | 是否对半字进行读写 |
RD |
O | 32 | 读出内存的数据 |
功能定义
使用logisim
内置的RAM模拟了内存,支持对字、半字、字节的读写操作。
对于读取:
- RAM为按字索引,故将输入的地址右移两位,得到对应在RAM中的数据,进行读写
- 按字读取,则此时
if_half
和if_byte
均为低电平,直接输出结果 - 按字节读取,此时
if_byte
为高电平,操作逻辑为:- 取输入地址低两位,向左偏移3位得到对应的数据偏移量
shift
。即如果低两位为01,代表读写8-15为的数据,需要左移三位。 - 对于读,将从RAM中读出的数据右移
shift
位,再截取低8位,得到结果并输出 - 对于写,按照位运算的逻辑,将该地址在RAM中以存储的值
dm
和输入数据中对应位的数据进行操作,再存入RAM中。能够实现依赖于RAM的阻塞赋值特性
- 取输入地址低两位,向左偏移3位得到对应的数据偏移量
- 按半字读取,此时
if_half
为高电平,实现逻辑与按字节读取类似
Control Unit:控制单元
个人认为是CPU最关键的部分,依靠指令的op
和func
部分生成控制逻辑,控制电路中的MUX单元,实现具体的运算。
端口定义
信号名 | 方向 | 位宽 | 具体翻译 |
---|---|---|---|
op |
I | 6 | 指令中的op 段 |
func |
I | 6 | 指令中的func 段 |
regWrite |
O | 1 | GRF写使能信号 |
regDst |
O | 1 | GRF的写入地址是rt 还是rd |
regSrc |
O | 1 | GRF的写入数据来源是ALU 还是内存 |
regRa |
O | 1 | GRF的写入地址是regSrc 还是31 |
aluSrc |
O | 1 | ALU的输入数据2来自rt还是指令中的offest 字段 |
extendType |
O | 1 | 对指令中offest 字段extand 的方式是sign 还是zero |
shiftSrc |
O | 1 | ALU的shift输入来自rs 还是指令中的shamt 字段 |
memWrite |
O | 1 | DM的写使能信号 |
branch |
O | 1 | NPC的if_branch 信号 |
jump |
O | 1 | NPC的if_jump 信号 |
jumpSrc |
O | 1 | NPC的jumpSrc 输入来自指令中的imm 字段还是rs |
aluControl |
O | 4 | ALU的操作码 |
功能定义
根据指令的o
p字段和func
字段,生成CPU的控制信号。
具体逻辑不表,给出对应的信号表:
0-1 | enable | rt-rd | regDst-31 | alu-mem | regSrc-PC+4 | rt-imm/off | sign-zero | rs-shamt | enable | enable | enable | imm-rs | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
op | regWrite | regDst | regRa | regSrc | regPc | aluSrc | extendType | shiftSrc | memWrite | branch | jump | jumpSrc | aluControl | alu | aluOp | func | |
add | 000000 | 1 | 1 | 0 | 0 | 0 | 0 | x | 0 | 0 | 0 | 0 | x | + | 0000 | 000 | 100000 |
addu | 000000 | 1 | 1 | 0 | 0 | 0 | 0 | x | 0 | 0 | 0 | 0 | x | + | 0000 | 000 | 100001 |
sub | 000000 | 1 | 1 | 0 | 0 | 0 | 0 | x | 0 | 0 | 0 | 0 | x | - | 0001 | 000 | 100010 |
or | 000000 | 1 | 1 | 0 | 0 | 0 | 0 | x | 0 | 0 | 0 | 0 | x | or | 0011 | 000 | 100101 |
sll | 000000 | 1 | 1 | 0 | 0 | 0 | 0 | x | 1 | 0 | 0 | 0 | x | << | 0110 | 000 | 000000 |
sllv | 000000 | 1 | 1 | 0 | 0 | 0 | 0 | x | 0 | 0 | 0 | 0 | x | << | 0110 | 000 | 000100 |
slt | 000000 | 1 | 1 | 0 | 0 | 0 | 0 | x | 0 | 0 | 0 | 0 | x | < | 1010 | 000 | 101010 |
ori | 001101 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | x | or | 0011 | 011 | |
lui | 001111 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | x | <-16 | 1001 | 100 | |
sltu | 000000 | 1 | 1 | 0 | 0 | 0 | 0 | x | 0 | 0 | 0 | 0 | x | < | 1010 | 000 | 101011 |
beq | 000100 | 0 | x | x | x | x | 0 | 0 | 0 | 0 | 1 | 0 | x | - | 0001 | 010 | |
j | 000010 | 0 | x | 0 | x | 0 | x | x | x | 0 | 0 | 1 | 0 | x | x | x | |
jr | 000000 | 0 | x | 0 | x | 0 | x | x | x | 0 | 0 | 1 | 1 | x | x | x | 001000 |
jal | 000011 | 1 | 1 | 1 | 0 | 1 | x | x | x | 0 | 0 | 1 | 0 | x | x | x | |
jalr | 000000 | 1 | 1 | 0 | x | 1 | x | x | x | 0 | 0 | 1 | 1 | x | x | x | 001001 |
sw | 101011 | 0 | x | x | x | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | + | 0000 | 001 | |
sh | 101001 | 0 | x | x | x | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | + | 0000 | 001 | |
sb | 101000 | 0 | x | x | x | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | + | 0000 | 001 | |
lw | 100011 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | + | 0000 | 001 | |
lh | 100001 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | + | 0000 | 001 | |
lb | 100000 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | + | 0000 | 001 |
内部构成
使用了三个子模块:contorl1
、control2
、JumpControl
。其中,各模块功能为:
-
control1
:负责根据op
字段生成和func
无关的信号control1
和control2
之间通信的aluOp
的含义:aluOp 含义 000 依赖 funct
001 A + B 010 A - B 011 A or B 100 B << 16 -
control2
:负责根据op
和func
字段生成其他信号,包括aluControl
、shiftSrc
-
JumpControl
:负责生成和jump相关指令的控制信号,包括regRa
、regPc
、jumpSrc
、jump
CPU测试
主要使用Mars生成测试码,之后手动导入logisim中进行测试,使用了助教提供的jar包测试工具。
自动测试工具
通过python
程序将test.asm
转化为16进制指令码1.txt,并通过正则表达式匹配得到ROM对应的代码区域,并形成新的电路文件cpu_test.circ
。
1 | import os |
测试代码
忘记保存了,总的策略是对$1-$31
为都测试一次,利用python进行批量生产数据。利用lui和ori的组合指令可以实现临界数据的生成。
思考题
-
上面我们介绍了通过 FSM 理解单周期 CPU 的基本方法。请大家指出单周期 CPU 所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能。
状态存储模块:IFU
状态转移模块:NPC
-
现在我们的模块中 IM 使用 ROM, DM 使用 RAM, GRF 使用 Register,这种做法合理吗? 请给出分析,若有改进意见也请一并给出。
不完全合理。
- RAM较大的容量满足了DM对内存的模拟,可以满足读写要求;
- GRF是通用寄存器堆,采用分开的寄存器实现可以满足其读写速度;
- 但是ROM的实现有些奇怪,这样的设计使得CPU称为了“一次性CPU”,只能执行特定的命令,不能在程序执行过程中增删指令。作为学习过程中的初级CPU,这样做是可以的,因为无论编写的mips程序还是测评都是单个程序的测试,但是也只能适用于教学,不是符合真实情景的IM设计。
- 至于改进方案,可以适用RAM实现,但在执行过程中对RAM进行指令读入,在logisim中难以实现,在verilog中可以实现。
-
在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。
实现了,见上。
-
事实上,实现
nop
空指令,我们并不需要将它加入控制信号真值表,为什么?由于我实现
sll
指令,那么nop
指令相当于sll $0,$0,0
,一方面,左移0位不对原有寄存器造成印象,另一方面,不允许对0寄存器的写操作,故没有影响。即使CPU没有设置
sll
指令,nop
指令也不会对电路中任何元件进行操作,对电路没有任何影响。 -
阅读 Pre 的 “MIPS 指令集及汇编语言”一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。
- 测试数据过弱,没有很好地覆盖临界情况。
- 测试寄存器过少,没有测试绝大部分的寄存器,只测试了几个寄存器。
- 逻辑过于简单,没有实现
beq
嵌套等操作。