目录

计组 - 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
add     $rd, $rs, $rt                   # [rd] = [rs] + [rt]
sub     $rd, $rs, $rt                   # [rd] = [rs] - [rt]
and     $rd, $rs, $rt                   # [rd] = [rs] & [rt]
or      $rd, $rs, $rt                   # [rd] = [rs] | [rt]
slt     $rd, $rs, $rt                   # [rd] = [rs] < [rt] ? 1 : 0
sll     $rd, $rt, shamt                 # [rd] = [rt] << shamt
srl     $rd, $rt, shamt                 # [rd] = [rt] >> shamt
sra     $rd, $rt, shamt                 # [rd] = [rt] >>> shamt
addi    $rt, $rs, imm                   # [rt] = [rs] + SignImm
andi    $rt, $rs, imm                   # [rt] = [rs] & ZeroImm
ori     $rt, $rs, imm                   # [rt] = [rs] | ZeroImm
slti    $rt, $rs, imm                   # [rt] = [rs] < SignImm ? 1 : 0
lw      $rt, imm($rs)                   # [rt] = [Address]
sw      $rt, imm($rs)                   # [Address] = [rt]
j       label                           # PC = JTA
jal     label                           # [ra] = PC + 4, PC = JTA
jr      $rs                             # PC = [rs]
beq     $rs, $rt, label                 # if ([rs] == [rt]) PC = BTA
bne     $rs, $rt, label                 # if ([rs] != [rt]) PC = BTA
nop                                     # No operation

其中使用的符号释义如下:

符号释义
[reg]寄存器 $reg 中的内容
immI 类型指令的 16 位立即数字段
addrJ 类型指令的 26 位地址字段
label指定指令地址的文本
SignImm32 位符号扩展的立即数:{{16{imm[15]}}, imm}
ZeroImm32 位零扩展的立即数:{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
add  : 0000 00ss ssst tttt dddd d--- --10 0000
sub  : 0000 00ss ssst tttt dddd d--- --10 0010
and  : 0000 00ss ssst tttt dddd d--- --10 0100
or   : 0000 00ss ssst tttt dddd d--- --10 0101
slt  : 0000 00ss ssst tttt dddd d--- --10 1010
sll  : 0000 00ss ssst tttt dddd dhhh hh00 0000
srl  : 0000 00-- ---t tttt dddd dhhh hh00 0010
sra  : 0000 00-- ---t tttt dddd dhhh hh00 0011
addi : 0010 00ss ssst tttt iiii iiii iiii iiii
andi : 0011 00ss ssst tttt iiii iiii iiii iiii
ori  : 0011 01ss ssst tttt iiii iiii iiii iiii
slti : 0010 10ss ssst tttt iiii iiii iiii iiii
lw   : 1000 11ss ssst tttt iiii iiii iiii iiii
sw   : 1010 11ss ssst tttt iiii iiii iiii iiii
j    : 0000 10aa aaaa aaaa aaaa aaaa aaaa aaaa
jal  : 0000 11aa aaaa aaaa aaaa aaaa aaaa aaaa
jr   : 0000 00ss sss- ---- ---- ---- --00 1000
beq  : 0001 00ss ssst tttt iiii iiii iiii iiii
bne  : 0001 01ss ssst tttt iiii iiii iiii iiii
nop  : 0000 0000 0000 0000 0000 0000 0000 0000

2 部件构成及分析

2.0 总览

/posts/note/comp-org/single-cycle-mips-cpu/assets/cpu.webp
CPU 总览

图示为单周期 MIPS CPU 的整体构造。直观起见,先只展示这几个模块。其中 mips 为 CPU 核心,imem 为指令储存器(Instruction Memory),dmem 为数据储存器(Data Memory)。

2.1 imem

/posts/note/comp-org/single-cycle-mips-cpu/assets/imem.webp
指令储存器

指令储存器内置了 64 个 32 位寄存器,用于储存指令。

使用时从 $\textrm{A}$ 读入指令地址(范围:$[\mathtt{0x0},\mathtt{0x3F}]$),从 $\textrm{RD}$ 输出这个地址中的 32 位指令。

代码见 这里

2.2 dmem

/posts/note/comp-org/single-cycle-mips-cpu/assets/dmem.webp
数据储存器

数据储存器内置了 64 个 32 位寄存器,用于读写大量数据。其特点是容量大、读写速度慢(相较于寄存器)。

当写使能 $\textrm{WE}$ 为 $1$ 时,在时钟上升沿将数据 $\textrm{WD}$ 写入地址 $\textrm{A}$;当写使能 $\textrm{WE}$ 为 $0$ 时,将地址 $\textrm{A}$ 中的数据读入到 $\textrm{RD}$。

代码见 这里

2.3 mips

/posts/note/comp-org/single-cycle-mips-cpu/assets/mips.webp
CPU 核心

CPU 核心可分为两个部分:control_unitdatapath,分别表示控制单元和数据通路。

代码见 这里

2.4 control_unit

/posts/note/comp-org/single-cycle-mips-cpu/assets/control-unit.webp
控制单元

控制单元负责解析输入的指令,决定各个控制信号。

实现中,先通过主译码器 main_dec 解码,对其中类型为 R-type 的指令再通过 ALU 译码器 alu_dec 解码。

代码见 这里。实现中将控制信号集中赋值,省去了书写大量赋值语句的麻烦。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
logic [13:0] bundle;
assign {reg_write_o, reg_dst_o, alu_src_o, alu_op_o,
        jump_o, branch_o, mem_write_o, mem_to_reg_o} = bundle;

always_comb begin
  unique case (op_i)
    6'b001000: bundle = 14'b10010000000000;   // ADDI
    // ...
  endcase
end

2.4.1 main_dec

主译码器,完整真值表如下1

指令opcodefunctrwrdalu_salu_opjbmwmr
add00000010000011001000000000
sub00000010001011001000000000
and00000010010011001000000000
or00000010010111001000000000
slt00000010101011001000000000
sll00000000000011001000000000
srl00000000001011001000000000
sra00000000001111001000000000
addi00100010010000000000
andi00110010010100000000
ori00110110011100000000
slti00101010011110000000
lw10001110010000000001
sw101011001000000001
j00001000010
jal000011101010
jr00100000100000100
beq000100000001000010
bne000101000001000100

其中:

  • opcode 表示指令对应的操作码。
  • funct 表示指令对应的功能码,用于 ALU 区分同一类型的不同指令。
  • rwreg_write,当需要写寄存器时为 1
  • rdreg_dst,当指令类型为 R-type 时为 1,I-type 时为 0
  • alu_salu_srcalu_src[1] 决定 src_a 的取值,alu_src[0] 决定 src_b 的取值。
    • alu_src[1]0 时,src_a 为寄存器文件 RD1 读出值;
    • alu_src[1]1 时,src_ainstr_i[10:6](需 32 位零扩展),用于移位指令 sll 等;
    • alu_src[0]0 时,src_b 为寄存器文件 RD2 读出值;
    • alu_src[0]1 时,src_binstr_i[15:0](需 32 位符号扩展),用于需要立即数计算的指令 addi 等。
  • alu_op 用于和 funct 一起指定 ALU 的操作。指令 beq, bne 需要做减法,因此也有对应的值。
  • jjump,当指令为 j, jal, jr 时分别为 001, 101, 010。这只是我个人的实现方式,其效果在于 datapath 的代码到时候写起来比较方便。
  • bbranch,当指令为 beq, bne 时分别为 01, 10
  • mwmem_write,当需要写内存 dmem 时为 1,用于指令 sw
  • mrmem_ro_reg,当需要将内存 dmem 读出的值写入寄存器时为 1,用于指令 lw

2.4.2 alu_dec

ALU 译码器,完整真值表如下:

指令alu_opfunctalu_control
add1001000000010
sub1001000100110
and1001001000000
or1001001010001
slt1001010100111
sll1000000000011
srl1000000101000
sra1000000111001
addi, lw, sw0000010
beq, bne0010110
andi0100000
ori1100001
slti1110111

2.5 datapath

/posts/note/comp-org/single-cycle-mips-cpu/assets/datapath.webp
数据通路

数据通路的作用就是将所有这些部件连接起来,传递各种信号。

这张图不用细看,下面我会拆解开来讲解其中的每个部件。

代码见 这里

2.6 sign_ext

/posts/note/comp-org/single-cycle-mips-cpu/assets/sign-ext.webp
符号扩展模块

符号扩展模块的作用是将 16 位的立即数符号扩展至 32 位。

使用时从 $\textrm{A}$ 读入待扩展的数据,从 $\textrm{RESULT}$ 输出扩展后的数据。

代码见 这里

2.7 adder

/posts/note/comp-org/single-cycle-mips-cpu/assets/adder.webp
加法器

32 位加法器,用于计算 PC 值及跳转地址。

使用时读入 $\textrm{A}$ 和 $\textrm{B}$,从 $\textrm{RESULT}$ 输出 $\textrm{A}$ 和 $\textrm{B}$ 相加后的值。

代码见 这里

2.8 mux2, mux4

/posts/note/comp-org/single-cycle-mips-cpu/assets/mux2.webp
2:1 多路复用器
/posts/note/comp-org/single-cycle-mips-cpu/assets/mux4.webp
4:1 多路复用器

多路复用器,用于数据多选一,操作数位数可改变。

使用时读入多路 $\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_nextwrite_reg 的选择电路。

对于 pc_next(新的 PC 值),其值的选择逻辑如下(部分符号释义见 1.1 节):

  • 一般情况下,pc_next = PC + 4,由 pc_src 信号控制 pc_branch_next_mux2 选择,此时 pc_src0
  • 对于指令 beq, bnepc_next = BTA,由 pc_src 信号控制 pc_branch_next_mux2 选择,此时 pc_src1jump[1:0]00
  • 对于指令 j, jalpc_next = JTA,由 jump 信号控制 pc_next_mux4 选择,此时 pc_src1jump[1:0]01
  • 对于指令 jrpc_next = [rs],由 jump 信号控制 pc_next_mux4 选择,此时 pc_src1jump[1:0]10

对于 write_reg(写入的目标寄存器),由 reg_dstjump 信号控制 write_reg_mux4 选择,其值的选择逻辑如下:

  • 对于 I-type 指令,write_reg = [rt],此时 reg_dst0jump[2]0
  • 对于 R-type 指令,write_reg = [rd],此时 reg_dst1jump[2]0
  • 对于指令 jalwrite_reg = $ra,此时 jump[2]1

对于 write_reg_data(写入目标寄存器的数据),其值的选择逻辑如下:

  • 一般情况下,write_reg_data = alu_result,其中 alu_result 为 ALU 运算结果,由 mem_to_reg 信号控制 result_mux2 选择,此时 mem_to_reg0jump[2]0
  • 对于指令 lwwrite_reg_data = [Address],由 mem_to_reg 信号控制 result_mux2 选择,此时 mem_to_reg1jump[2]0
  • 对于指令 jalwrite_reg_data = PC + 4;由 jump 信号控制 write_reg_data_mux2 选择,此时 jump[2]1

代码见 这里

2.9 reg_file

/posts/note/comp-org/single-cycle-mips-cpu/assets/reg-file.webp
寄存器文件

寄存器文件内置了 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

/posts/note/comp-org/single-cycle-mips-cpu/assets/flip-flop.webp
触发器

触发器,用于储存 PC。

在时钟上升沿将新的 PC 值 $\textrm{D}$ 写入。当重置信号 $\textrm{RST}$ 为 $1$ 时,将 PC 异步清零。

代码见 这里

2.11 alu

/posts/note/comp-org/single-cycle-mips-cpu/assets/alu.webp
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_controlresult指令
0000a & band, andi
0001a | bor, ori
0010a + badd, addi, lw, sw
0011b << asll
0100a & ~b
0101a | ~b
0110a - bsub, beq, bne
0111a < b ? 1 : 0slt, slti
1000b >> asrl
1001b >>> asra

代码见 这里

3 样例测试

3.1 测试结果

/posts/note/comp-org/single-cycle-mips-cpu/assets/test-1-3.webp
测试 1 ~ 3
/posts/note/comp-org/single-cycle-mips-cpu/assets/test-4-6.webp
测试 4 ~ 6

3.2 测试环境

  • Windows 10 Version 2004 (OS Build 19041.172)
  • Vivado v2019.1

参考资料

  1. David Money Harris, Sarah L. Harris: Digital Design and Computer Architecture Second Edition
  2. MIPS Instruction Set - MIPT-ILab / mipt-mips Wiki - GitHub
  3. 361 Computer Architecture Lecture 9: Designing Single Cycle Control - Northwestern

  1. nop 实际上只是 sll 的特例,这里就省略了。 ↩︎