BUAA_CO_P3
P3
Logisim
实现单周期处理器
目录 :
[toc]
整体结构
- 所要设计的版块:
IFU
,Splitter
,Controller
,GRF
,ALU
,DM
,EXT
- 设计要求:支持的指令集:
add
sub
ori
lw
sw
beq
lui
nop
其中add
和sub
按照无符号处理 - 顶层驱动信号:异步复位reset
指令分析:
- R型:
add
sub
nop
# Opcode + Rs + Rt + Rd +shamt
+Func
- I型:
ori
lw
sw
lui
beq
# Opcode + Rs + Rt + immediate
1 | add $t0, $t1, $t2 # 机器码:000000_01001_01010_01000_00000_100000 |
数据通路
不涉及j
指令:
j
指令:
指令类型分析
思路整理
在搭建完Logisim
实现上述8条指令的单周期处理器后,我们尝试来回溯一下整个工作的思路。
首先,也是最重要的一步,是明确我们要实现的是哪些指令,以及这些指令到底要做一件什么事:
1 | add $t0, $t1, $t2 # Opcode:000000 Func:100000 rd = rs + rt |
接下来我们需要一张由instruction确定的控制信号表,这张表决定了各个部件在什么时候应该做什么操作:
- 我们观察到ALU涉及的计算有加法,减法,或运算,因而我们需要2位
ALUOp
来指示ALU做哪种具体的运算 - 我们观察到ALU的两个输入中的第一个一定是
rs
,而第二个可能是rt
或者imm_ext
,因而我们需要1位ALUSrc
来指示第二位操作数来自哪里 - 我们观察到并非所有的ALU计算结果都要回写到
GRF
,因而我们需要1位RegWrite
来指示是否需要写入GRF
- 我们观察到回写的目标寄存器可以是
rt
或者rd
,因而我们需要1位RegDst
来指示是否回写入rd
- 我们观察到并非所有指令都要访存,因而我们需要1位
MemRead
来指示是否访存指令 - 我们观察到并非所有时候都能写入
DM
,因而我们设计1位MemWrite
来指示能否写入DM
- 我们观察到并非所有时候
DM
的输出都要回写入GRF
,因而我们设计1位MemtoReg
来指示是否将DM
输出写入GRF
- 我们还需要1位
Branch
来指示是否分支指令,因为分支指令会影响NPC
的值
1 | // Controller 模块规格 |
接下来就是具体的将我们要实现的指令对应到这张控制信号表中:
RegDst | Branch | MemRead | MemtoReg | ALUOp_1 | ALUOp_0 | MemWrite | ALUSrc | RegWrite | |
---|---|---|---|---|---|---|---|---|---|
add | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
sub | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
nop | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
ori | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 1 |
lw | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 1 |
sw | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 |
lui | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
beq | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
因此,当我们站在顶层回看,我们发现利用Logisim
构造单周期处理器事实上只需要做三件事:
构造
IFU
,GRF
,ALU
,DM
这四个功能模块,具体的模块规格如下,这是简单的。考虑我们所涉及的所有指令来设计控制信号,并利用控制信号将功能模块组织起来,这可能要求我们灵活地添加一些多路选择器等,这是抽象而复杂的。
(考虑需要哪些控制信号 + 每个信号对应功能模块什么样的表现 + 怎么把信号与模块组织起来才能不冲突)
根据指令生成控制信号,这只需要我们稍微分析一下每条指令的功能,因而也是简单的。
1 | // IFU 模块规格 |
1 | // GRF 模块规格 |
1 | //ALU 模块规格 |
1 | // DM模块规格 |
优化
在我们第一版的设计中,Branch
和Zero
信号直接接入了EXT
这样做不符合我们“高内聚低耦合”的设计理念,我们不希望EXT
还要承担判断拓展类型的功能,而是有一个EXTOp
专门告诉EXT
该做怎样的拓展。
在我们目前实现的8指令中:
lw
,sw
,beq
需要将16位imm符号拓展为32位ori
需要将16位imm零拓展为32位lui
需要将16位imm右补16位0add
,sub
,nop
不需要拓展
1 | // EXTOp模块规格 |
这样,EXT
就只需要按照EXTOp
告诉它的方式将16位的输入imm
转化为32位的输出imm_ext
1 | // EXT模块规格 |
第二个优化是关于WriteBack
WriteBack
是将要写回寄存器的值,在8指令中有以下可能:
add
,sub
,ori
写回的数据来自ALU_anssw
写回的数据来自DM
的输出WD
lui
写回的数据来自imm_ext
nop
,sw
,beq
不需要写回
同样的思想,我们设计一个WBSource
来说明WD
的取值来自哪里
1 | // WBSource模块规格 |
思考题
Q:上面我们介绍了通过
FSM
理解单周期CPU
的基本方法。请大家指出单周期CPU
所用到的模块中,哪些发挥状态存储功能,哪些发挥状态转移功能?A:将
IFU
看作一个Moore_FSM
,状态储存是:PC
,状态转移是:NPC
将
GRF+ALU+DM
看作一个Mealy_FSM
,状态储存是:GRF
,状态转移时ALU+DM
Q:现在我们的模块中
IM
使用ROM
,DM
使用RAM
,GRF
使用Register
,这种做法合理吗? 请给出分析,若有改进意见也请一并给出。A:合理。
IM
使用ROM
是因为IM
中要存储大量仅读的数据。DM
使用RAM
是因为DM
涉及大量数据的读写。GRF
涉及读写但数据量不大,使用Register
访问和更改更方便。
- Q:在上述提示的模块之外,你是否在实际实现时设计了其他的模块?如果是的话,请给出介绍和设计的思路。
A:实现了Splitter
模块,将32位instruction
按照R型指令的方式拆分。目的是便于确定具体指令。
Q:事实上,实现
nop
空指令,我们并不需要将它加入控制信号真值表,为什么?A:事实上,
nop
等价于sll $0, $0, $0
,我们只需要在检测到Func
字段为000000时把它也当作加法处理即可。
Q:阅读
Pre
的 “MIPS 指令集及汇编语言” 一节中给出的测试样例,评价其强度(可从各个指令的覆盖情况,单一指令各种行为的覆盖情况等方面分析),并指出具体的不足之处。A:
应当测试
sub
指令,因为sub
指令涉及了特有的Opcode
的处理在测试
add
指令时未考虑操作0号寄存器lw
和sw
指令未涉及负跳转beq
跳转指令的未涉及负跳转未涉及
nop
指令的测试