更新于 

单周期MIPS32

单周期MIPS32处理器的设计

接下来我们来设计一个单周期MIPS32位处理器,我们需要考虑一下几个方面:

MIPS指令集

首先我们需要知道MIPS3类指令的主要组成是什么,这里我们只学习最基本的指令集:

  1. R-type指令:and,or,add,sub,slt
  2. 存储器指令:lw,sw
  3. 分支指令:beq
  4. 扩展指令:addi

上面是我们将要实现的MIPS指令(后面我们将学到上面的这种指令分类是根据主译码模块的功能进行分类的)。接下来我们看一下每一个类型指令的功能:

对于R-type类型指令op都是0,并且都对应有三个寄存器类型存储的操作数,并且格式都是

1
助记符 目的操作数 源操作数1 源操作数2

这里实际上只有slt指令我们不太熟悉,他是一个比较两个源操作数大小的指令。

L-type类型指令是立即数类型指令,他的特定是有一个源操作数为立即数常数,我们要注意此时op不再是全0并且形式为:

1
助记符 源操作数1 目的操作数 源操作数2

J-type类型指令就是地址跳转指令,实现起来比较简单。

思考一个问题,op位数是有限的,这也就意味着可以表示的指令类别是有限的,因此我们为了尽可能多的取表示更多的不同类型的指令,我们只对最基础(即其他指令不能组合表示)的指令进行编写并且为其分配Opcode。

设计目标

我们在编写MIPS处理器结构前,我们需要明确一下型号,这里我们设计的MIPS32处理器的配置:

单周期MIPS32处理器

  • 每一条指令必须在一个时钟周期内完成
  • 32个32位寄存器,哈佛结构,小端存储模式,支持23条指令

数据通路+控制通路

  • 数据通路:完成对指令中操作数的操作、存储等处理工作
  • 控制通路:从数据通路接受指令,并对其进行翻译以告知数据通路如何处理,因此控制电路决定数据的某一部分到哪里去接受什么操作后将目的信息传输到哪里
  • 处理器设计相当于在各个记忆部件之间添加组合逻辑电路,在控制单元的控制下根据当前电路的状态计算出电路的新状态

MIPS32位处理器的概念模型

我们简单的来认识一下上面的概念模型的运行方式,首先我们根据指令,对输入信号进行操作,他可能会需要到其他操作数的帮助,因此我们需要从现态的记忆部件中取出需要的信息,然后进行组合逻辑的运算(实际上就是指令控制着数据的处理),指令在一个时钟周期内完成信息的处理后会产生输出数据,此时我们需要将这个新的输出数据存储起来也就是改变了记忆部件的状态,因此也就是现态,同时我们还需要进行输出信号。因此上图实际上可以看成就是一个取数据->处理数据->存放数据的过程,这其中涉及到了组合逻辑电路和时序逻辑电路的合作使用。

记忆部件

分别是存储地址的程序计数器,存储指令的内存块,寄存器以及存储数据内存块他们都是cpu内部的核心部件,需要受到clk时钟周期控制。

在寄存器模块中有六个端口,其中A1对应RD1,A2对应RD2,这两组端口是从寄存器读出数据时需要的,其中A端口用来接收要读出数据到的地址,RD端口使用来输出32位的数据的。而A3是用来接收要从那个地址写入数据的地址,WD3是用来接收要写入的32位数据。WE3是写使能端,控制着此时寄存器是写/读和数据(一次性只能读或写)。

数据存储和寄存器也类似,A和RD是用来读出数据的,WD是用来写入数据的。WE还是控制着内存的读/写。

具体指令的实现

数据通路——LW

此时我们从程序计数器中读出下一个指令的地址,然后到存储指令的存储块中取出指令。我们知道一个指令在MIPS32中也是32位的,但是也可以使用8位16进制码来表示。

这里我们假设的不是J型指令而是I型指令,因此25:21这六位是对应的rs,我们将指令的这六位地址传进去即可获得源操作数rs的值,接下来我们还需要取立即数操作数:

取得立即数后我们得到只是一个16位的,我们需要对齐进行符号扩展为32位立即数

然后我们进行数据的处理,因此将两个源操作数传入到ALU中,同时我们需要告诉ALU进行那种类型的计算,因此传入ALUcontrol信号(实际上这个是由数值计算的指令来操控)。然后这里假设传入的是010,代表是加法,因此ALU此时进行加法,然后要注意我们得到的结果是要到存储数据的内存块中的地址,然后我们将计算结果传送到内存块地址接受端口即可得到我们最终要找到的数。

然后我们将从内存中读出的数据放到寄存器中以便使用,因此使用A3(接受要存放的地址位置)跟WD3(要存入的具体32位数值)端口接受要写入寄存器的数据。我们要注意从存储数据内存块处得到的数据是放到指令的rt目的操作数处,因此我们在写入寄存器时,传进去的是I型指令的20:16区域。

然后我们要进行程序计数器的更新,说来也简单,就是将PC加4就可以得到下一个指令的地址了😃。

思考:为什么要加四?

我们知道MIPS中是按字节编址的,因此一个地址对应一个字节,而一个指令是32位的对应4个字节,因此加4才是下一条指令的地址。

数据通路——SW

因此我们此时是将寄存器读出的数据写入到数据内存中,我们先用20:16目的操作数地址传进去读出这个目的操作数,然后写入到数据内存中(使用WD接受这个数,同时写使能端要为1代表此时内存可写入数据)。前面的过程都不变,还是可能需要LW的操作来将所需要的数据首先保证存储到了寄存器中。

数据通路——R型指令

之前我们所学习的指令是LOAD指令加载一个内存中的数到寄存器或者SAVE指令集将寄存器中的一个数写回到内存中,现在我们要学习一个更加复杂的R型指令,他是将立即数与源操作数或者两个源操作数进行数值计算处理的结果存储到目的操作数寄存器中,因此我们此时需要先获得源操作数,由于我们可能只需要一个或者2个寄存器存储的操作数,然后在进行计算,我们可能会使用一个从寄存器中取出的源操作数与立即数进行ALU计算或者两个从寄存器中取出的源操作数进行数值计算,因此由ALUSrc来决定(首先有一个寄存器操作数由RD1连接的SrcA给出,另一个是使用RD2的寄存器操作数充当SrcB还是使用立即数来作为SrcB由AluSrc决定)。然后我们最终得到的结果并不是内存地址了,而是一个确切的结果数值了,我们决定是否需要将它写回到寄存器中,因此由MemtoReg来决定,如果需要我们再将数值写回到寄存器中,这需要RegDst来决定使用哪个寄存器地址段(RS还是RT?)来存储这个结果数。最后如果我们需要内存存储这个结果数,还需要通过RD2连接的WriteData线进行写内存的操作来存储结果数到内存中。我们思考一下整个的过程,一个R型指令需要两个源操作数首先存在于寄存器中,如果不存在很明显我们需要在前面的步骤中先使用LW将源操作数数据取出放入到寄存器中,因此在R指令中的数据通路也会涉及到LW的过程,而SW也是有可能的,因为很可能最终寄存器存储的结果数值需要写入数据存储块中存储,这就是一个完整的R型指令可能涉及到的所有数据通路。

数据通路——BEQ指令

上面的BCQ指令是根据是否满足条件进行跳转,因此首先我们需要判断是否满足条件,通过branch控制的与aluzero标志位(结果为0是zero为1)取与操作得到的结果作为是否满足条件的判断结果(当rs与rt值相等时满足条件),只有在满足条件即PCSrc为1时才会进行跳转,那么具体跳转到哪里,是由下面的通路给出的,他是用一个立即数偏移量来决定的,最终的跳转的指令地址为:

BTA=(sign_extended)(immediate<<2)+(pc+4)BTA=(sign\_extended)(immediate<<2)+(pc+4)

也就是sign_extended的意思是有符号数位数扩展,我们要注意最终位数扩展后的立即数偏移量是与下一条指令PC+4的值求和得到的跳转地址,而不是当前地址,所以说明跳转前程序计数器已经进行了一次更新指令地址的操作。

思考:上面是如何进行rs与rt是否相等的判断的?

这里的ALU的zero并不是返还0的意思,他是alu的一个零标志位,只有计算结果为0时zero标志位为1,因此很明显此时alu进行的是相减操作让rs和rt相减,当两者相等时结果为0,zero标志位才能置为1,那么此时PCSrc才能变成1代表满足条件需要跳转。

CPU加入到通路中对记忆器件进行控制

根据上面的MIPS概念图,我们还没有加入控制通路,即需要连接控制单元使得他可以控制我们之前连接起来的记忆组件。如下图我们加入控制单元形成控制通路:

控制单元

上面的控制单元又可以拆分成主解码模块,另一个是ALU解码模块,主解码模块输入的是指令的opcode,主解码模块来识别具体是哪一种类型以便控制分配任务。对于R型指令有许多不同的数值计算功能,因此此时需要ALU解码模块来根据FUNC5:0来解码具体的数值ALU计算功能并分配ALUcontrol信号,当然alu解码模块还受到主解码模块的信号控制。

ALU译码

ALU译码是ALU解码模块根据funct和aluop来解码指令的具体功能同时生成ALU控制信号来告诉ALU进行何种数值操作

ALUOP1:0 Meaning
00 Add
加法
01 Subtract
减法
10 Look at Funct
需要根据funct具体分析功能
11 Not Used
不使用
ALUOP1:0 Funct ALUControl2:0
00 X 010(Add)
X1 X 110(Subtract)
1X 100000(add) 010(Add)
1X 100010(sub) 110(Subtract)
1X 100100(and) 000(And)
1X 100101(or) 001(Or)
1X 101010(slt) 111(SLT)
主译码

主译码是主解码模块根据op判断不同类型的主要指令来控制数据通路的控制各种选择器的信号以决定数据通路的走向:

Instruction Op5:0 RegWrite RegDst AluSrc Branch MemWrite MemtoReg ALUOP1:0
R-type 000000 1 1 0 0 0 0 10
lw 100011 1 0 1 0 0 0 00
sw 101011 0 X 1 0 1 X 00
beq 000100 0 X 0 1 0 X 01

思考:addi拓展指令怎样加入上面的表内?

首先我们要知道addi是拓展指令,他独立于其他类型的指令,因此我们需要单独为他分配一个opcode,因此他也需要加入到主译码模块:

Instruction Op5:0 RegWrite RegDst AluSrc Branch MemWrite MemtoReg ALUOP1:0
addi 001000 1 0 1 0 0 0 00

addi指令和lw相比只是没有内存取出的数加入到寄存器的功能,其他和lw相同,但是addi还需要进行一个加法操作,因此最终还需要到alu移码生成加法控制信号。

J型指令数据通路

我们前面已经学些了跳转指令的工作过程了,他的跳转计算公式就是:

BTA=(PC+4)31:28instr_index0BTA=(PC+4)_{31:28}||instr\_index||0

因此实际上后面补两个0的操作就是乘四左移2位,又因为instr_index26位的限制,我们可以知道跳转指令一次跳转的地址量是有限的,他的能跳转的最远地址是

max=(2261)4max=(2^{26}-1)*4

又因为一个地址是4字节,因此能最终跳转的指令个数是

max=2261max=2^{26}-1

一定要注意BEQ指令与J型指令的跳转地址计算公式是不一样的。BEQ跳转地址的计算是先取出instr的26:0位,然后进行有符号数扩展为32位有符号立即数再左移两位与PC+4进行求和得到跳转地址PCBranch,而对于J型指令,则是直接获取instr的26:0位然后左移两位与PC+4的高四位进行拼接得到32位跳转地址。因此两者的计算方式不同,使用的地址计算线路也是不同的。

J指令主译码控制信号

由于J型指令也是单独的一类指令,因此也需要单独分配一个opcode

单周期处理器的性能分析

表示的是一个程序的时间,它是由以下公式原理给出的:

一个程序的时间=一个程序的指令数×一个指令所用的时间周期数×一个时间周期的时间长度一个程序的时间=一个程序的指令数×一个指令所用的时间周期数×一个时间周期的时间长度

我们知道一个程序运行的有效时间是由其关键路径决定的也就是运行时间开销最长的通路,如下图是我们设计出的MIPS处理器的关键路径:

我们可以给出单周期处理器的关键路径时间:

Tc=tpaq_PC+tmem+max{tRFread,tsext,tmux}+tALU+tmem+tmux+tRFsetupT_c=t_{paq\_PC}+t_{mem}+max\{t_{RFread},t_{sext},t_{mux}\}+t_{ALU}+t_{mem}+t_{mux}+t_{RFsetup}

很明显上面的公式难以进行具体的计算,因此我们简化一下上面的公式给出关键路径的计算公式:

Tc=tpcq_PC+2tmem+tRFread+tmux+tALU+tRFsetupT_c=t_{pcq\_PC}+2t_{mem}+t_{RFread}+t_{mux}+t_{ALU}+t_{RFsetup}

从上面的公式我们可以看出关键路径的时间开销主要是由内存的访存时间,ALU计算时延,指令加载和回写寄存器数据以及寄存器的读数据时间组成的。

例题

如下图是部分给出的参数,请给出具体的关键路径的计算公式并计算处结果: