计组 - Lab 1: 单周期 MIPS CPU
32 位单周期 MIPS 指令集 CPU,使用 SystemVerilog 编写。
Introduction to Computer Systems II (H) @ Fudan University, spring 2020.
1 MIPS 指令集
1.1 实现指令集
|
|
其中使用的符号释义如下:
符号 | 释义 |
---|---|
[reg] | 寄存器 $reg 中的内容 |
imm | I 类型指令的 16 位立即数字段 |
addr | J 类型指令的 26 位地址字段 |
label | 指定指令地址的文本 |
SignImm | 32 位符号扩展的立即数:{{16{imm[15]}}, imm} |
ZeroImm | 32 位零扩展的立即数:{16'b0, imm} |
Address | [rs] + SignImm |
[Address] | 存储器单元 Address 地址中的内容 |
JTA | 跳转目标地址:{(PC + 4)[31:28], addr, 2'b0} |
BTA | 分支目标地址:PC + 4 + (SignImm << 2) |
1.2 机器码格式
|
|
2 部件构成及分析
2.0 总览
图示为单周期 MIPS CPU 的整体构造。直观起见,先只展示这几个模块。其中 mips
为 CPU 核心,imem
为指令储存器(Instruction Memory),dmem
为数据储存器(Data Memory)。
2.1 imem
指令储存器内置了 64 个 32 位寄存器,用于储存指令。
使用时从 $\textrm{A}$ 读入指令地址(范围:$[\mathtt{0x0},\mathtt{0x3F}]$),从 $\textrm{RD}$ 输出这个地址中的 32 位指令。
代码见 这里。
2.2 dmem
数据储存器内置了 64 个 32 位寄存器,用于读写大量数据。其特点是容量大、读写速度慢(相较于寄存器)。
当写使能 $\textrm{WE}$ 为 $1$ 时,在时钟上升沿将数据 $\textrm{WD}$ 写入地址 $\textrm{A}$;当写使能 $\textrm{WE}$ 为 $0$ 时,将地址 $\textrm{A}$ 中的数据读入到 $\textrm{RD}$。
代码见 这里。
2.3 mips
CPU 核心可分为两个部分:control_unit
和 datapath
,分别表示控制单元和数据通路。
代码见 这里。
2.4 control_unit
控制单元负责解析输入的指令,决定各个控制信号。
实现中,先通过主译码器 main_dec
解码,对其中类型为 R-type 的指令再通过 ALU 译码器 alu_dec
解码。
代码见 这里。实现中将控制信号集中赋值,省去了书写大量赋值语句的麻烦。
2.4.1 main_dec
主译码器,完整真值表如下1:
指令 | opcode | funct | rw | rd | alu_s | alu_op | j | b | mw | mr |
---|---|---|---|---|---|---|---|---|---|---|
add | 000000 | 100000 | 1 | 1 | 00 | 100 | 000 | 00 | 0 | 0 |
sub | 000000 | 100010 | 1 | 1 | 00 | 100 | 000 | 00 | 0 | 0 |
and | 000000 | 100100 | 1 | 1 | 00 | 100 | 000 | 00 | 0 | 0 |
or | 000000 | 100101 | 1 | 1 | 00 | 100 | 000 | 00 | 0 | 0 |
slt | 000000 | 101010 | 1 | 1 | 00 | 100 | 000 | 00 | 0 | 0 |
sll | 000000 | 000000 | 1 | 1 | 00 | 100 | 000 | 00 | 0 | 0 |
srl | 000000 | 000010 | 1 | 1 | 00 | 100 | 000 | 00 | 0 | 0 |
sra | 000000 | 000011 | 1 | 1 | 00 | 100 | 000 | 00 | 0 | 0 |
addi | 001000 | 1 | 0 | 01 | 000 | 000 | 00 | 0 | 0 | |
andi | 001100 | 1 | 0 | 01 | 010 | 000 | 00 | 0 | 0 | |
ori | 001101 | 1 | 0 | 01 | 110 | 000 | 00 | 0 | 0 | |
slti | 001010 | 1 | 0 | 01 | 111 | 000 | 00 | 0 | 0 | |
lw | 100011 | 1 | 0 | 01 | 000 | 000 | 00 | 0 | 1 | |
sw | 101011 | 0 | 01 | 000 | 000 | 00 | 1 | |||
j | 000010 | 0 | 001 | 0 | ||||||
jal | 000011 | 1 | 0 | 101 | 0 | |||||
jr | 001000 | 001000 | 0 | 010 | 0 | |||||
beq | 000100 | 0 | 00 | 001 | 000 | 01 | 0 | |||
bne | 000101 | 0 | 00 | 001 | 000 | 10 | 0 |
其中:
opcode
表示指令对应的操作码。funct
表示指令对应的功能码,用于 ALU 区分同一类型的不同指令。rw
即reg_write
,当需要写寄存器时为1
。rd
即reg_dst
,当指令类型为 R-type 时为1
,I-type 时为0
。alu_s
即alu_src
,alu_src[1]
决定src_a
的取值,alu_src[0]
决定src_b
的取值。alu_src[1]
为0
时,src_a
为寄存器文件RD1
读出值;alu_src[1]
为1
时,src_a
为instr_i[10:6]
(需 32 位零扩展),用于移位指令sll
等;alu_src[0]
为0
时,src_b
为寄存器文件RD2
读出值;alu_src[0]
为1
时,src_b
为instr_i[15:0]
(需 32 位符号扩展),用于需要立即数计算的指令addi
等。
alu_op
用于和funct
一起指定 ALU 的操作。指令beq
,bne
需要做减法,因此也有对应的值。j
即jump
,当指令为j
,jal
,jr
时分别为001
,101
,010
。这只是我个人的实现方式,其效果在于datapath
的代码到时候写起来比较方便。b
即branch
,当指令为beq
,bne
时分别为01
,10
。mw
即mem_write
,当需要写内存dmem
时为1
,用于指令sw
。mr
即mem_ro_reg
,当需要将内存dmem
读出的值写入寄存器时为1
,用于指令lw
。
2.4.2 alu_dec
ALU 译码器,完整真值表如下:
指令 | alu_op | funct | alu_control |
---|---|---|---|
add | 100 | 100000 | 0010 |
sub | 100 | 100010 | 0110 |
and | 100 | 100100 | 0000 |
or | 100 | 100101 | 0001 |
slt | 100 | 101010 | 0111 |
sll | 100 | 000000 | 0011 |
srl | 100 | 000010 | 1000 |
sra | 100 | 000011 | 1001 |
addi , lw , sw | 000 | 0010 | |
beq , bne | 001 | 0110 | |
andi | 010 | 0000 | |
ori | 110 | 0001 | |
slti | 111 | 0111 |
2.5 datapath
数据通路的作用就是将所有这些部件连接起来,传递各种信号。
这张图不用细看,下面我会拆解开来讲解其中的每个部件。
代码见 这里。
2.6 sign_ext
符号扩展模块的作用是将 16 位的立即数符号扩展至 32 位。
使用时从 $\textrm{A}$ 读入待扩展的数据,从 $\textrm{RESULT}$ 输出扩展后的数据。
代码见 这里。
2.7 adder
32 位加法器,用于计算 PC 值及跳转地址。
使用时读入 $\textrm{A}$ 和 $\textrm{B}$,从 $\textrm{RESULT}$ 输出 $\textrm{A}$ 和 $\textrm{B}$ 相加后的值。
代码见 这里。
2.8 mux2
, mux4
多路复用器,用于数据多选一,操作数位数可改变。
使用时读入多路 $\textrm{DATA}$,从 $\textrm{RESULT}$ 输出 $\textrm{SELECT}$ 选择的那一路的数据。以 mux4
为例,$\textrm{SELECT}$ 为 $00$, $01$, $10$, $11$ 时分别输出 $\textrm{DATA}_0$, $\textrm{DATA}_1$, $\textrm{DATA}_2$, $\textrm{DATA}_3$ 的值。
图中 mux4
只输入了 3 个 $\textrm{DATA}$,是因为这里只需要用到 3 个。教材的电路设计中并没有用到 mux4
,我引入 mux4
的目的是为了简化 pc_next
和 write_reg
的选择电路。
对于 pc_next
(新的 PC 值),其值的选择逻辑如下(部分符号释义见 1.1 节):
- 一般情况下,
pc_next
=PC + 4
,由pc_src
信号控制pc_branch_next_mux2
选择,此时pc_src
为0
; - 对于指令
beq
,bne
,pc_next
=BTA
,由pc_src
信号控制pc_branch_next_mux2
选择,此时pc_src
为1
,jump[1:0]
为00
; - 对于指令
j
,jal
,pc_next
=JTA
,由jump
信号控制pc_next_mux4
选择,此时pc_src
为1
,jump[1:0]
为01
; - 对于指令
jr
,pc_next
=[rs]
,由jump
信号控制pc_next_mux4
选择,此时pc_src
为1
,jump[1:0]
为10
。
对于 write_reg
(写入的目标寄存器),由 reg_dst
和 jump
信号控制 write_reg_mux4
选择,其值的选择逻辑如下:
- 对于 I-type 指令,
write_reg
=[rt]
,此时reg_dst
为0
,jump[2]
为0
; - 对于 R-type 指令,
write_reg
=[rd]
,此时reg_dst
为1
,jump[2]
为0
; - 对于指令
jal
,write_reg
=$ra
,此时jump[2]
为1
。
对于 write_reg_data
(写入目标寄存器的数据),其值的选择逻辑如下:
- 一般情况下,
write_reg_data
=alu_result
,其中alu_result
为 ALU 运算结果,由mem_to_reg
信号控制result_mux2
选择,此时mem_to_reg
为0
,jump[2]
为0
; - 对于指令
lw
,write_reg_data
=[Address]
,由mem_to_reg
信号控制result_mux2
选择,此时mem_to_reg
为1
,jump[2]
为0
; - 对于指令
jal
,write_reg_data
=PC + 4
;由jump
信号控制write_reg_data_mux2
选择,此时jump[2]
为1
。
代码见 这里。
2.9 reg_file
寄存器文件内置了 32 个 32 位寄存器,用于读写临时数据。
使用时从 $\textrm{RA}_1$ 和 $\textrm{RA}_2$ 分别读入地址(范围:$[\mathtt{0x0},\mathtt{0x1F}]$)以指定寄存器,然后从 $\textrm{RD}_1$ 和 $\textrm{RD}_2$ 分别输出对应寄存器中的 32 位数据。其中 0 号寄存器的值始终为 $0$,因此在实现中直接返回 $0$。当写使能 $\textrm{WE}_3$ 为 $1$ 时,在时钟上升沿将数据 $\textrm{WD}_3$ 写入地址 $\textrm{WA}_3$ 指定的寄存器。当重置信号 $\textrm{RST}$ 为 $1$ 时,清空所有寄存器中的数据。
代码见 这里。
2.10 flip_flop
触发器,用于储存 PC。
在时钟上升沿将新的 PC 值 $\textrm{D}$ 写入。当重置信号 $\textrm{RST}$ 为 $1$ 时,将 PC 异步清零。
代码见 这里。
2.11 alu
算术逻辑单元(ALU),用于加减、位运算等算术操作。
ALU 根据 $\textrm{ALU\_CONTROL}$ 信号决定对操作数 $\textrm{A}$ 和 $\textrm{B}$ 进行何种运算,从 $\textrm{RESULT}$ 输出运算结果,从 $\textrm{ZERO}$ 输出结果是否为 $0$。其中 $\textrm{ALU\_CONTROL}$ 由控制单元根据 $\textrm{ALU\_OP}$ 和 $\textrm{FUNCT}$ 决定(详见 2.4.2 节)。具体映射表如下:
alu_control | result | 指令 |
---|---|---|
0000 | a & b | and , andi |
0001 | a | b | or , ori |
0010 | a + b | add , addi , lw , sw |
0011 | b << a | sll |
0100 | a & ~b | |
0101 | a | ~b | |
0110 | a - b | sub , beq , bne |
0111 | a < b ? 1 : 0 | slt , slti |
1000 | b >> a | srl |
1001 | b >>> a | sra |
代码见 这里。
3 样例测试
3.1 测试结果
3.2 测试环境
- Windows 10 Version 2004 (OS Build 19041.172)
- Vivado v2019.1
参考资料
- David Money Harris, Sarah L. Harris: Digital Design and Computer Architecture Second Edition
- MIPS Instruction Set - MIPT-ILab / mipt-mips Wiki - GitHub
- 361 Computer Architecture Lecture 9: Designing Single Cycle Control - Northwestern
nop
实际上只是sll
的特例,这里就省略了。 ↩︎