单周期处理器

处理器的设计步骤

通常来说,设计一个处理器,需要经过以下五个步骤:

  • 分析指令系统,得出对数据通路的需求
  • 为数据通路选择合适的组件
  • 连接组件建立数据通路
  • 分析每条指令的实现,以确定控制信号
  • 集成控制信号,形成完整的控制逻辑

我们知道,现代计算机内部的电路都为数字电路,因而处理器的组成则就是这些数字电路经过精妙的组合设计完成的。处理器由控制器和运算器两部分组成。

第一步,分析指令系统,得出对数据通路的需求

以MIPS指令系统简化版本为例,我们想要实现的指令类型有:

  • 无符号加法和减法
  • 立即数的逻辑或
  • 装载和存储一个字
  • 条件分支

因此通过分析,我们的需求有如下几点:

  • 存放指令的存储器,可读,地址和数据均为32位
  • 存放指令地址的32位寄存器
  • 同时读取两个寄存器的内容(rs和rt)
  • 一组存放数据的32位通用寄存器
  • 改写一个寄存器的内容(rd或rt)
  • 将16位立即数扩展到32位(零扩展)
  • 提供加减逻辑或三种功能的计算器
  • 运算的操作数可以是寄存器或者扩展后的立即数
  • 存放数据的存储器,可读写,地址和数据均为32位
  • 将16位立即数扩展到32位(符号扩展)
  • 比较两个数,判断是否相等
  • PC寄存器支持两种自增方式,加4或加一个立即数

第二步,为数据通路选择合适的组件

我们通过以上的分析,不难得出,需要满足上述的需求,则需要以下一些组件:

  • 算数逻辑单元(ALU)
    • 支持运算类型:加,减,或,比较相等
    • 操作数:2个32位的数,来自寄存器或扩展后的立即数
  • 立即数扩展部件
    • 将一个16位立即数扩展为32位数
    • 扩展方式:零扩展,符号扩展
  • 程序计数器(PC)
    • 一个32位的寄存器
    • 支持两种加法:加4或者加一个立即数
  • 寄存器堆
    • 每个寄存器为32位宽,共32个
    • 支持读操作:rs和rt
    • 支持写操作:rt或rd
  • 存储器
    • 一个只读的指令存储器,地址和数据均为32位
    • 一个可读写的数据存储器,地址和数据均为32位

      这里两个存储器实际对应了CPU中的指令和数据高速缓存,而不是内存

我们来详细看一下寄存器堆

register

再来详细看下存储器

memory


第三步,连接组件建立数据通路

建立数据通路的基本原则就是根据指令需求,连接组件,建立数据通路,而指令的需求,则分为两种,第一种是所有指令的共同需求,第二种是不同指令的各自不同的需求。

所有指令的共同需求

首先我们都需要取指令,而程序计数器的(PC)的内容就是指令的地址,用PC的内容作为地址,我们就可以访问指令存储器获得指令编码。

其次,我们需要为下一条取指做好准备,这时我们就需要更新程序计数器。这里又分两种情况,当程序顺序执行时,我们只需要将PC加四(下条指令的长度,MIPS为四字节)就可以获得下个指令的地址;当程序发生分支时,就需要将分支目标的地址更新到PC。

这样,我们将这些共同需求组合在一起,就完成了一个取指部件(IFU),以下是示意图

memory

不同指令的不同需求

加法和减法指令的需求

加减法的格式大致如此R[rd] = R[rs] op R[rt],为R型指令,如下图所示,ALUCtr和RegWr为控制信号

add

逻辑运算指令的需求

R[rt] = R[rs] op ZeroExt[imm16],为I型指令。上图的部件对逻辑运算指令来说,又出现了三个问题:

  • 逻辑运算的目的寄存器是rt而不是rd
  • 立即数是ALU的输入,目前的部件只能从寄存器输入
  • 立即数只有16位

为了解决这些问题,我们进行了:

  • 在目的寄存器上增加了一个二选一的多选器,由RegDst进行控制
  • 在ALU的一个输入上也增加了一个二选一的多选器,由ALUSrc控制
  • 增加一个零扩展部件,接入多选器的输入端

logic

访存指令的需求(Load)

R[rt] = Mem[R[rs] + SignExt[imm16]],为I型指令。上图的部件对访存指令(Load)来说,又出现了两个问题:

  • 如何进行符号扩展?目前还只能进行零扩展
  • 和何处装载数据?

解决的方法也很简单,只需要将原有的零扩展部件增加符号扩展功能;并且加入数据存储器以及相应的多选器。分别由ExtOp和MemtoReg进行控制。如图所示。
load

访存指令的需求(Store)

Mem[R[rs] + SignExt[imm16]] = R[rt],为I型指令。Store指令和Load指令很相似,所以在上述部件,我们又做了如下修改,增加了一条由busB到数据存储器的线路,同时在数据存储器上新增了控制信号MemWr。
store

到现在为止,我们就已经完成了数据通路的基本建立。


第四步,分析每条指令的实现,以确定控制信号

instruction

1、运算指令的控制信号

加法指令的操作步骤

1. 从指令存储器中取回指令,所有指令都有这个步骤
2. addInstruction
3. 更新程序计数器的内容,除了分支指令,都有此步骤

ori指令的操作步骤

一三步和加法指令一致,这里只看第二步。
oriInstruction

2、访存指令的控制信号

一三步和上述指令一致,这里只看第二步。

lw指令的操作步骤

loadInstruction

sw指令的操作步骤

storeInstruction

3、分支指令的控制信号

1. 从指令存储器中取回指令
2. if[R[rs] - R[rt] == 0]判断转移条件是否成立
分支指令有些特殊,因此,我们还需修改相应的部件,在ALU的计算中判断结果是否等于0,将结果送回IFU。
branchInstruction
3. then PC = PC + 4 + SignExt[imm16]*4 else PC = PC + 4计算下一条指令的地址
第三步的处理方式和其他指令是不同的,也新增了很多部件,详细如下图所示
IFUInstruction


第五步,集成控制信号,形成完整的控制逻辑

到现在,我们已经确定了每条指令实现时控制信号的值,但是这八个控制信号(nPC_sel,RegWr,RegDst,ExtOp,ALUSrc,ALUctr,MemWr,MemtoReg),到底是如何实现的呢?

其实就是指令格式中我们还未使用的两部分,opcodefunc域,用它们来产生各种控制信号,实现这部分的电路就叫做控制逻辑,也就是控制器

IFUInstruction

我们将执行各个指令时各个控制信号的值列成一个表格,并且将各指令的func和opc域放在上面,如下所示:
controllerTable

我们以每行看的话,就可以得到一个控制信号的逻辑表达式,以RegDst为例:
RegDst

要想RegDst信号为1,那么就要将add与sub进行或操作,即RegDst = add + sub,可add与sub自身又如何生成呢?表示如下:

1
2
3
4
add = rtype.func5.~func4.~func3.~func2.~func1.~func0
sub = rtype.func5.~func4.~func3.~func2.func1.~func0
rtype = ~op5.~op4.~op3.~op2.~op1.~op0

这样,我们就得到了RegDst的逻辑表达式,这样就可以通过画出与门和非门产生RegDst信号的电路图。同理可得其他控制信号的逻辑表达式。
allControllerSigns

观察上图可以发现,下半部分都是进行与操作,上半部分则是进行或操作,因此我们将两部分电路分别连接起来,当输入opcode和func域后,就能自动输出控制信号了,这也就是控制器的实现。
controllerFin

这就是简化的单周期处理器的整个设计过程,虽然简单,但却也包含了整个主要知识点。

本文总结自coursera 陆俊林教授的《计算机组成》第五讲