计组 - Lab 1: 单周期 MIPS CPU
32 位单周期 MIPS 指令集 CPU,使用 SystemVerilog 编写。
Introduction to Computer Systems II (H) @ Fudan University, spring 2020.
1 MIPS 指令集
1.1 实现指令集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
其中使用的符号释义如下:
| 符号 | 释义 |
|---|---|
[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 机器码格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
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 解码。
代码见 这里。实现中将控制信号集中赋值,省去了书写大量赋值语句的麻烦。
1 2 3 4 5 6 7 8 9 10 | |
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。
nop 实际上只是 sll 的特例,这里就省略了。
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