单周期 MIPS 处理器的设计

⚠ 转载请注明出处:作者:ZobinHuang,更新日期:Dec.17 2021


知识共享许可协议

    本作品ZobinHuang 采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,在进行使用或分享前请查看权限要求。若发现侵权行为,会采取法律手段维护作者正当合法权益,谢谢配合。


目录

有特定需要的内容直接跳转到相关章节查看即可。

    Section 1. 基本思路:介绍了本文讨论的对单周期 MIPS 处理器进行设计的思路;

    Section 2. 准备工作:在进行设计之前,讨论了基本的设计原则和评估原则;
        2.1 逻辑设计惯例
        2.2 State Element
        2.3 MIPS 微架构
        2.3 性能评估方法

    Section 3. 数据通路:介绍了单周期 MIPS 处理器的数据通路的构建过程;
        3.1 读取指令
        3.2 读取源操作数
        3.3 拓展立即数宽度
        3.4 计算内存地址
        3.5 写数据回 (Write Back) Register File
        3.6 决定下一条指令的地址
        3.7 写数据到内存单元
        3.8 对 R-Type 指令进行支持的数据通路
        3.9 beq 指令进行支持的数据通路
        3.10 总结

    Section 4. 控制单元:介绍了单周期 MIPS 处理器的 Control Unit 的构建过程;
        4.1 控制单元设计
        4.2 指令执行实例

    Section 5. 对更多指令的支持:拓展了 MIPS 处理器对 addij 指令的支持,以演示如何拓展处理器对更多指令的支持;
        5.1 增加对指令 addi 的支持
        5.2 增加对指令 j 的支持

    Section 6. 单周期处理器的性能分析:以 lw 指令为例,分析了单周期 MIPS 处理器的性能;

1. 基本思路

    在上一篇文章中,我们看到了作为 MIPS 程序员的最后一道工具 —— MIPS 架构。在这篇文章中,我们将看到如何构建出一个最简单的能够运行 MIPS 指令集子集的处理器 —— 单周期 MIPS 处理器。

    本文描述的 MIPS 处理器能运行的 MIPS 程序子集如下所示:

  1. R-Type 用于算术/逻辑运算的指令: add, sub, and, or, slt
  2. 内存操作指令: lw, sw
  3. 分支指令 beq

    在设计思路上,我们首先会构建处理器的 数据通路 (datapath),数据通路指的是数据在处理器中流经的各个组合/时序逻辑单元,包括内存、寄存器、ALU 和 多路选择器 (Multiplexer) 等。然后我们会关心根据不同指令以控制指令数据在 datapath 上的流动的 控制单元 的构建,最终形成一个完整的基础处理器。

2. 准备工作

2.1 逻辑设计惯例

    首先,我们必须清楚时序逻辑电路设计的一般惯例。如上图所示,在一个时钟周期内,我们可以认为有两个时钟的上升沿,一个在周期开始时,一个在周期结束时。在第一个上升沿到来时,输出寄存器会把它在上一个时钟周期结束前收到的输入值输出到输出端,该输出值会在当前时钟周期内流经连接在该输出端口上的组合逻辑电路,并且基于一定的时序约束在当前时钟周期结束之前到达输入寄存器端口。在第二个时钟上升沿到来时,输入寄存器就会保存组合逻辑电路的运算结果,并且把该结果传递给下一级电路。

2.2 State Element

    通用处理器设计的一般方法是,首先准备一套 State Elements,包括各种 Memory 和 Architectural State (i.e. 寄存器) 等,然后在这些 State Elements 之间插入各种组合逻辑,以基于当前的 State 计算出下一个 State。如此反复循环,以使得通用计算系统得以运转。

    如上图所示是四个基本的 State Elements 组件,我们下面分别进行讨论。

    Program Counter (PC) 是一个用于存储 32-bits 指令地址的寄存器,其输出 PC 指向了当前执行的指令的地址,其输入 PC' 指向了下一条要执行的指令的地址。

    Instruction Memory 用于存储当前处理器运行的指令序列。它有一个 32-bits 的指令地址端口 A 以及一个 32-bits 的指令输出端口 RD。在一个时钟周期内,A 的输入是我们上面所说的程序计数器的输出 PC,也即当前时钟周期处理器要运行的指令的地址,Instruction Memory 会在 RD 上输出相应的指令。

    eitser File 是一个 32-element `\times` 32-bit 的寄存器堆。Regitser File 有两个 5-bits 的寄存器地址输入 A1A2,寄存器堆会根据 A1A2 的地址值在输出端口 RD1RD2 上输出相应寄存器的存储值,也即读取选择的寄存器中存储的值,以作为一些指令中源寄存器的来源。同时 Regitser File 还有一个 5-bits 的寄存器地址输入端口 A3,以及一个 32-bits 的写入数据值 WD3。当控制信号 WE3 被使能时,Regitser File 会将 WD3 上的值写入由 A3 选取的寄存器中。

    Data Memory 用于存储数据。输出端口 RD 会根据地址输入端口 A 的输入值输出相应的数据。当 WE 引脚为高时,代表此时为写操作,故此时会向 A 所指向的内存单元中写入 WD 上的数据。

    值得注意的是,在我们下面的分析中,我们要做这样一个假设:对于 State Element 的读取操作并不需要时钟来进行驱动,在送入地址值之后,State Element 会在一定的传播延迟 (propagation delay) 之后立即输出相关的值。而对于写入操作,State Elements 只有在时钟上升沿到来时才会将数据写入相关的存储单元中

2.3 MIPS 微架构

    在本文和后续的文章中,我们将探讨三种 MIPS 微架构:

    Single-cycle Microarchitecture,单周期微架构,是本文讨论的主要内容,它在一个时钟周期内就执行一条完整的指令。由于在一个时钟周期内就可以完成所有的指令计算工作,因此它并不需要存储任何 Non-architectural State。其缺点是整个系统的最小时钟周期 (i.e. 最高频率) 受限于运行时间最长的指令。

    Multi-cycle Microarchitecture,多周期微架构,是下一篇文章讨论的内容。

    Pipelined-cycle Microarchitecture,流水线微架构,是再下一篇文章讨论的内容。

2.4 性能评估方法

    为了评估处理器的处理性能,我们使用测量一个程序的执行时间的方法来进行评估。计算公式如下所示:

`Execution Time = #Instructions \cdot (\frac{Cycl es}{Instruction}) \cdot (\frac{S e c o n ds}{Cycl e})`

    其中,#Instructions 即指令的条数,对于同一个高级程序,在不同的架构下进行编译之后产生的指令条数各不相同。在一些架构下 (e.g. x86),会有一些比较复杂执行逻辑的指令,这样一来就减少了程序编译后产生的指令数目,然而这样的指令通常会有较高的硬件执行时间。

    Cycles per Instruction,即 CPI,指的是在当前被分析的架构下,一条指令的平均执行时间。平时我们还会看到它的倒数形式 —— Instructions per Cycle,即处理器执行带宽。不同的架构会有不同的 CPI 值。值得注意的是在本系列对 MIPS 微架构的分析文章中,我们假设 Memory System 对 CPI 是不构成影响的,也即访存没有时延的理想内存模型。

    Seconds per Cycle,即 CPU 时钟周期的长度 `T_c`。`T_c` 取决于处理器逻辑中的 关键路径 (Critical Path) 的运行时间。不同的微架构有不同的时钟周期。由于芯片制造商的技术能是的半导体的速度不断的提高,所以即使电路设计没有发生改变,处理器的运行频率也在不断发展。当然,对于现在来说,生产工艺的发展速度已经逼近瓶颈,摩尔定律的失效使得处理器频率的提升速度也逐渐趋缓。

    下面我们正式开启对单周期处理器的设计和思考。

3. 数据通路

    在本节中,我们将看到,在一个时钟周期开始后 (i.e. 上升沿结束后),单周期的 MIPS 处理器是如何构建数据通路 State Element 之间的组合逻辑电路来实现各种功能的。

3.1 读取指令

    首先,PC 寄存器中存储了当前时钟周期处理器要执行的指令在 Instruction Memory 中存储的地址。因此如上图所示,PC 的输出端口 PC 直接接向了 Instruction Memory 的输入地址端口,以在其输出端口 RD 上输出相应指令 `Instr_{[31:0]}`。

    下面,我们假定读取出来的指令是一条 lw 指令 (i.e. I-Type 指令),来开展后续的分析。I-Type 指令的指令格式如上图所示。我们在 I-Type 中曾经介绍过该类型的指令。lw 指令允许程序员使用一个寄存器 (rs 字段指定, `Instr_{[25:21]}`) 和一个立即数 (imm 字段指定, `Instr_{[15:0]}`) 来定位内存单元的位置,并且将该内存单元的值读取到一个指定的寄存器 (rt 字段指定, `Instr_{[20:16]}`) 中。

3.2 读取源操作数

    我们现在拿到了 lw 指令。对于 lw 指令的编码来说,其源寄存器在 rs 字段 (i.e. `Instr_{[25:21]}`),该字段用于指定内存单元基地址。因此如上图所示,我们把从 Instruction Memory 读取到的指令的 `Instr_{[25:21]}` 字段指向了 Register File 的 5-bits 的寄存器地址输入 A1,以在其输出 RD1 获取到相应的寄存器值。

3.3 拓展立即数宽度

    对于 lw 指令来说,其允许程序员使用立即数来计算内存单元的偏移量,因此我们还必须从指令中提取立即数值,也即 `Instr_{[15:0]}`。由于后续 ALU 的运算输入是 32 位数据,所以我们还必须把读取到的 16-bits 立即数拓展到 32 位,以便送入 ALU 以进行运算。此时我们得到:`SignImm_{15:0} = Imm_{15:0}`,`SignImm_{31:16} = Imm_{15}`。

3.4 计算内存地址

    在拿到 32-bits 的内存单元基地址 `SrcA_{31:0}` 和 32-bits 的偏移量 `SrcB_{31:0}` 后,我们就可以利用 ALU 来计算出内存单元的最终位置了。如上图所示,注意到我们使用了 3-bits 的控制信号 `ALUControl_{2:0}` 来控制 ALU 执行的是加法操作。我们最终能在 ALU 的输出 `ALURes u l t_{31:0}` 上获取到最终的内存单元的位置。

    在获取到内存单元地址之后,我们就可以从 Data Memory 中将数据读取出来了,可以看到我们把 ALU 的计算结果送上了 Data Memory 的地址线。

3.5 写数据回 (Write Back) Register File

    在读取到相关的数据后,Data Memory 会将相关的结果进行输出。对于 lw 指令,我们需要把相关的数据写回到寄存器中去,因此 Data Memory 的输出端口接到了 Register File 的 WD3 端口,以在下一个时钟上升沿到来时完成写入。同时,我们还需要选择要写入的寄存器的位置,对于 lw 指令编码来说,这是在 rt 字段 (i.e. `Instr_{[20:16]}`) 指定的,因此如上图所示,我们把 `Instr_{[20:16]}` 接向了 Register File 的 A3 寄存器选择端口。

    值得注意的是,由于要对寄存器进行写入操作,所以必须使能 `RegWrite` 控制信号,我们在后面将会看到产生该信号的 Control Unit 的设计。

3.6 决定下一条指令的地址

    对于单周期处理器的 PC 来说,它需要连续输出处理器将要执行的指令的内存地址,因此我们必须给 PC 设计外围组合逻辑来实现这一功能。如上图所示,我们在从 PC 的输出端口 PC 端口拿到当前周期执行的指令地址之后,将该值使用全加器加 4 之后由输向了 PC 的输入端口 PC',我们在这里考虑了相邻指令连续被执行的情况,一条指令的大小为 32 bits,也即 4 bytes。而对于跳转指令的情况,我们在后面会再予以考虑。

    这样一来,我们就完成了针对 lw 指令的基本数据通路的构建。

3.7 写数据到内存单元

    在完成了 lw 指令的执行逻辑后,现在让我们把设计拓展到 sw 指令。相比较于 Lw 指令,sw 指令的功能是相反的:把寄存器的值保存到指定的内存单元去。与 lw 指令使用 rt 字段 (i.e. `Instr_{[20:16]}`) 来指定读取的寄存器类似,sw 指令使用 rt 字段 (i.e. `Instr_{[20:16]}`) 来指定要保存值到内存去的寄存器的编号,因此如上图所示,我们把 Instr_{[20:16]}` 同时接到了 Register File 的地址选择输入端口 A2 上去,以在输出端口 RD2 上获取到相应寄存器的值,并且将输出 RD2 接到 Data Memory 的 WD 数据输入端口,这样在下一个时钟上升沿到来时,就能将数据保存到 Data Memory 中去。

    值得注意的是,对于 sw 指令,由于要对 Data Memory 进行写操作,那么我们需要使能控制信号 `MemWrite`; 并且由于不需要对任何寄存器进行写入操作,因此 `RegWrite` 控制信号将被关闭; 同时 `ALUControl_{2:0}` 控制信号将保持为加法控制信号不变。我们在后面将会产生这些控制信号的 Control Unit 的设计。

3.8 对 R-Type 指令进行支持的数据通路

    上面我们完成了对 I-Type 指令 lwsw 指令的数据通路的设计。基于上面的设计,我们本节将继续把数据通路拓展到 R-Type 指令,如 add, sub, and, orslt。注意到上面罗列的 R-Type 指令的运行规律都是从 Register File 中读取两个寄存器值,然后使用 ALU 对这两个寄存器值进行一些运算之后,再把该值写回到相应的寄存器中去。不同的是,它们使用的是不同的 ALU 操作,因此事实上它们的不同点就在于 Control Unit 会产生不同的 `ALUControl_{2:0}` 控制信号。

    下面,我们将看到,为了在 I-Type 指令的基础上执行 R-Type 指令,我们需要对数据通路所做的修改。

    在上面的数据通路的设计中,ALU 的 SrcB 是来自于符号拓展器输出的 `SignImm_{31:0}` 值。在 R-Type 指令中,它应该来自于 Register File 输出的第二个寄存器值 RD2。因此,我们首先在 ALU 的 SrcB 输入之前加入了一个多路选择器,并且受到 `ALUSrc` 控制信号的控制:当控制信号为 0 时,ALU 接受来自 Regitser File 输出的寄存器值; 当控制信号为 1 时,ALU 接受来自符号拓展器的 `SignImm_{31:0}` 值。

    在上面的数据通路的设计中,Register File 的写入数据输入 WD3 来自于 Data Memory 的输出,然而对于 R-Type 指令来说,Register File 写入的数据来自于 ALU 的输出 `ALUResu l t`。因此同样地,对于 Register File 的写入数据输入端口 WD3 的来源,我们同样需要一个多路选择器来进行区分,如上图所示。该多路选择器受到 `Memt oReg` 控制信号的控制:当控制信号为 0 时,Resgiter File 接受来自 ALU 输出的值作为寄存器写入值; 当控制信号为 1 时,Register File 接受来自 Data Memory 的输出值作为寄存器的输入值。

    在上面的数据通路的设计中,Register File 的写入寄存器的选择信号 A3 来自于读取的指令的 rt 字段 (i.e. `Instr_{[20:16]}`),而对于 R-Type 指令来说,写入的寄存器的选择信号则来自于读取的指令的 rd 字段 (i.e. `Instr_{[15:11]}`)。因此同样地,对于 Register File 的写入寄存器的选择信号 A3 的来源,我们同样需要一个多路选择器来进行区分,如上图所示。该多路选择器受到 `RegDst` 控制信号的控制:当控制信号为 0 时,Resgiter File 接受来自 rt 字段 (i.e. `Instr_{[20:16]}`) 的值作为写入寄存器的选择值; 当控制信号为 1 时,Register File 接受来自 rd 字段 (i.e. `Instr_{[15:11]}`) 作为写入寄存器的选择值。

    这样一来,在增加了三个多路选择器后,我们的数据通路现在就开始支持 R-Type 指令的执行了。

3.9 对 beq 指令进行支持的数据通路

    在完成对 I-Type 和 R-Type 指令的运行支持后,我们现在继续优化数据通路,使其支持 beq 指令的运行。

    beq 指令使用的是 I-Type 指令的编码格式,其对两个寄存器的值 (由 rs (i.e. `Instr_{[25:21]}`) 和 rt (i.e. `Instr_{[20:16]}`) 指定)进行比较,如果它们是相等的,则会在 PC 输出的当前指令地址 PC 自增 4 后 再加上相应的偏移值,以实现程序跳转的效果。注意到这个偏移值可为正,也可为负。

    值得注意的是,偏移值在指令编码中的 imm (i.e. `Instr_{[16:0]}`) 处指定,由于是一个 16-bits 有符号数 (two's complement),并且需要进行 32-bits 加法运算,因此我们需要将它送入符号扩展器。对立即数的宽度扩展我们在上面已经完成了相关硬件的设计,因此我们在此处无需再对硬件进行修改。

    另外,由于偏移值只是指定要 跳过/跳回 的指令的数量,由于我们要在 Instruction Memory 中进行寻址,所以我们还必须把该偏移值乘以 4 以得到要 跳过/跳回 的字节的数量,所以我们在符号扩展器输出的位置还加上了一个移位器,左移 2 位的操作即乘 4 操作,以得到程序的跳转字节数,我们将该值与 `PCPlus4` 值进行相加,最终得到跳转的指令的目的地址 `PCBranch`。

    我们需要将运行 beq 指令得到的下一指令地址 PC' 和其它非跳转指令得到的下一指令地址 PC'区分开,因此此处我们在 PC 的输入端口 PC' 之前加上了一个多路选择器,受到 `PCSrc` 控制信号的控制: 当 `PCSrc` 为 0 时,下一指令的地址是常规的当前地址加 4 得到的下一条连续指令地址; 当 `PCSrc` 为 1 时,下一指令的地址是基于跳转指令计算得到的下一指令地址。值得注意的是,观察上图可以发现,`PCSrc` 控制信号又收到两个信号的控制: 控制信号 `Branch` 和 ALU 输出信号 Zero,前者用于指示当前运行的指令是一条分支指令,后者用于输出比较结果。如果这两者都为真,那么就需要进行跳转。

3.10 总结

    至此,我们就完成了针对 MIPS 指令子集的单周期处理器的数据通路的设计。我把单周期 MIPS 处理器的设计思路总结为:

  1. State Elements 的串联;
  2. 多路选择器的引入以控制 数据的写入/地址的选择;
  3. 控制信号对数据通路的作用;

    我们首先明确了组合出基本数据通路的 State Elements:PC、Instruction Memory、Register File 以及 Data Memory。当我们对单周期处理器进行分析时,在它们其中,Instruction Memory 是只读器件,PC、Register File 和 Data Memory 是可写器件。对于可写器件,我们必须注意两个点:写入数据的来源写入地址的来源。我们下面对在数据通路中引入的多路选择器进行总结。

    我们使用上图进行说明,Control Unit 部分的线路可以先忽略,我们在后面会进行介绍。

    对于 PC,我们使用一个多路选择器 `MUX_{1}` 以对写入的下一条指令地址进行选择。

    对于 Register File,我们使用多路选择器以对写入的 地址数据 的来源进行选择: 对于地址来说,我们在 Register File 的写寄存器地址输入口 A3 前面加入了一个多路选择器 `MUX_{2}`,以区分 R-Type 指令和 I-Type 指令对目标写入寄存器的指示字段; 对于数据来说,我们在 Register File 的数据写入口 WD3 前面加入了一个多路选择器 `MUX_{4}`,以区分写入结果来自于 ALU 输出和来自于 Data Memory 的指令。

    对于 Data Memory 来说,它的写入数据 WD 恒定来自于 Register File 的第二个寄存器输出端口 RD2,因此我们并不需要加上多路选择器来选择 Data Memory 的输入数据来源; 对于内存单元地址输入端口 A 来说,其输入值恒定来源于 ALU 的计算结果,因此我们同样也不需要加入多路选择器。

    另外,我们还在 ALU 的第二个输入源操作数之前引入了一个多路选择器,以区分数据来源于 R-Type 指令指定的寄存器或者是 I-Type 指令指定的立即数 (p.s. 完成符号扩展后)。

    我们在本文后面的一节 增加对指令 j 的支持 中还会看到为了增加对 J-Type 指令的支持我们引入的新的多路选择器。

    有了数据通路,有了多路选择器,现在我们欠的东风就是设计一个控制单元,用于控制各个多路选择器和使能信号,以实现在数据通路上的不同运行流程,我们在下一节中将看到控制单元的设计。

4. 控制单元

4.1 控制单元设计

    在上面对 MIPS 处理器的数据通路的设计中,我们观察到了要正确地完成一条指令的执行,必须引入大量的控制信号。我们在本节中将看到 MIPS 处理器中是如何完成这些控制信号的提取和集成的。如上图所示,我们将引入一个组合逻辑电路模块 —— Control Unit 来完成整个 MIPS 单周期处理器的设计。

    首先我们先对 MIPS 指令的编码格式作出分析。本质上来说,指令执行的时候数据通路上的控制信号,来自于 MIPS 指令中的 Opcode (i.e. `Instr_{[31:26]}`) 字段。另外,R-Type 指令同时还使用 Funct (i.e. `Instr_{[5:0]}`) 字段来指定 ALU 的运行方式。因此,我们设计的 Control Unit 本质上就是要利用好这两部分的控制信息。

    如上图所示,我们把 Control Unit 整体分为两部分: Main DecoderALU Decoder。首先,Main Decoder 负责解析 Opcode (i.e. `Instr_{[31:26]}`) 字段,同时它还会决定输出给 ALU Decoder 的 `ALUop_{1:0}` 控制信号。我们把 Main Decoder 输出的控制信号整理如下:

控制信号
含义
`Memt oReg`
选择写回 Register File 寄存器的来源:为 0 则来自于 ALU 的输出; 为 1 则来自于 Data Memory 的读取值;
`MemWrite`
写 Data Memory 的使能信号:为 0 则不使能写入; 为 1 则使能写入;
`Branch`
分支信号,与 ALU 输出信号 `Zero` 共同决定控制信号 `PCSrc`:为 0 时,下一指令的地址是常规的当前地址加 4 得到的下一条连续指令地址; 为 1 时,下一指令的地址是基于跳转指令计算得到的下一指令地址;
`ALUSrc`
选择 ALU 第二个输入的 32-bits 数的来源:为 0 则来源于 Register File 的输出; 为 1 则来源于符号扩展器的输出;
`RegDst`
选择写入寄存器的选择信号的来源:为 0 则来自于读取的指令的 rt 字段 (i.e. `Instr_{[20:16]}`); 为 1 则来自于读取的指令的 rd 字段 (i.e. `Instr_{[15:11]}`);
`RegWrite`
写寄存器的使能信号:为 0 则不使能写入; 为 1 则使能写入;

    对于这些控制信号来说,它们是直接受到当前周期读取到的指令的控制的,具体如下:

指令
Opcode
`RegWrite`
`RegDst`
`ALUSrc`
`Branch`
`MemWrite`
`Memt oReg`
`ALUOp`
R-Type
000000
1
1
0
0
0
0
10
lw
100011
1
0
1
0
0
1
00
sw
101011
0
X
1
0
1
X
00
beq
000100
0
X
0
1
0
X
01

    对于 ALU Decoder 来说,它根据 Funct (i.e. `Instr_{[5:0]}`) 字段和 Main Decoder 输出的 `ALUOp_{1:0}` 控制信号负责控制 ALU 计算模块的行为。如下所示,是 `ALUop_{1:0}` 控制信号的真值表。

`ALUOp`
Funct
`ALUControl` (含义)
00
X
010 (add)
X1
X
110 (sub)
1X: 由 Funct 字段决定 ALU 功能
1X
100000 (add)
010 (add)
1X
100010 (sub)
110 (sub)
1X
100100 (and)
000 (and)
1X
100101 (or)
001 (or)
1X
101010 (slt)
111 (set less than)

4.2 指令执行实例

    至此我们已经基本完成了单周期 MIPS 处理器的设计,我们下面展示几条代表性指令在处理器中的执行过程。

    一条 R-Type 指令的执行过程如下所示:

    一条 lw 指令的执行过程如下所示:

    一条 beq 指令的执行过程如下所示:

5. 对更多指令的支持

5.1 增加对指令 addi 的支持

    在上面实现的处理器中,针对 I-Type 指令,我们实现了 lwsw 指令的数据通路设计。现在让我们考虑另一个 I-Type 指令 addi: 它实现了把一个寄存器值 (i.e. 由 rs 字段指定) 和一个立即数 (i.e. 由 imm 字段指定) 的相加值存储到一个寄存器 (i.e. 由 rt 字段指定) 中的功能。

    由于数据通路上已经有实现寄存器值和立即数值相加的基本能力,所以要实现对 addi 的支持实际上很简单,我们只需要在 Control Unit 上加上对 addi 的解码的组合逻辑即可。我们向 Main Decoder 的增值表中增加如下表项:

指令
Opcode
`RegWrite`
`RegDst`
`ALUSrc`
`Branch`
`MemWrite`
`Memt oReg`
`ALUOp`
addi
001000
1
0
1
0
0
0
00

5.2 增加对指令 j 的支持

    j 指令是 J-Type 指令,其用于向 PC 寄存器中写入一个新的地址值。基于指令 j 进行跳转时,PC 中存储的 32-bits 地址值的最低 2 位 `PC_{1:0}` 恒为 0 (i.e. 存储的指令是 32-bits 对齐的),接下来的 26 bits `PC_{[27:2]}` 是从指令 jimm 字段 (i.e. `Instr_{[25:0]}`) 中进行提取的,最高的 4 bits `PC_{[31:28]}` 保留的是 PC 寄存器中原先的最高 4 位。由于在我们上面设计的数据通路中并没有这种逻辑的计算连接,因此当我们加入对 j 指令的支持的时候,我们需要对数据通路做出相应的修改。

    如上图所示,我们额外增加了一条数据通路分支和一个控制信号 Jump。我们首先提取出 imm 字段 (i.e. `Instr_{[25:0]}`),向左移动 2 位后,得到一个 28-bits 长的值,然后我们将该值与 `PCPlus4` 的高 4 位进行合成,最终得到一个新的 32-bits 地址值。注意到在送入 PC 的下一指令地址输入端口 PC' 之前,我们使用了一个新的多路选择器来选择送入的地址值,并使用 Jump 信号来加以控制: 当信号为 0 时,送入的地址值是我们上面分析过的对 beq 指令和其它常规指令产生得到的地址值; 当信号为 1 时,送入的地址值是我们刚刚基于指令 j 得到的新的地址值。

    另外,由于引入了新的控制信号 Jump,Control Unit 中的 Main Decoder 的真值表更新如下:

指令
Opcode
`RegWrite`
`RegDst`
`ALUSrc`
`Branch`
`MemWrite`
`Memt oReg`
`Jump`
`ALUOp`
R-Type
000000
1
1
0
0
0
0
0
10
lw
100011
1
0
1
0
0
1
0
00
sw
101011
0
X
1
0
1
X
0
00
beq
000100
0
X
0
1
0
X
0
01
addi
001000
1
0
1
0
0
0
0
00
j
000010
0
X
X
0
0
X
1
XX

6. 单周期处理器的性能分析

    在本节中,我们以指令 lw 为例,来分析单周期 MIPS 处理器的性能。

    我们通过指令 lw 运行的时间来得到对处理器性能的评估。为了得到运行时间,我们首先必须抓出运行某条指令在运行过程中的 关键路径 (Critical Path),这是制约该条指令运行时间的因素。

    如上图所示,我们现在对运行 lw 的数据通路进行分析。在时钟上升沿到来的时候,就标志着当前指令周期的开始:

  1. PC 寄存器需要根据上一个时钟周期输入的 PC' 值来输出新的指令地址,记该延迟为 `t_{pcqPC}`。这里的 "`pcq`" 的概念可以回顾我们在 时序逻辑电路基础 中的相关分析: `t_{pcq}` 是第一个时钟上升沿到所有触发器输出达到预期值的延迟;
  2. Instruction Memory 需要根据从 PC 寄存器中读取到的指令地址输出相应的指令,记该延迟为 `t_{mem}`;
  3. Register File 需要根据 rs 字段读取出相关寄存器的值,记该延迟为 `t_{RFread}`, 与此同时 lw 指令的 imm 字段还会经过符号扩展器,并且经由一个多路选择器到达 ALU 的 SrcB 输入端口,记该时延为 `t_{sext} + t_{m ux}`;
  4. ALU 需要对 SrcA 上的寄存器值和 SrcB 上的立即数拓展符号值进行加法运算,记该时延为 `t_{ALU}`;
  5. Data Memory 需要根据 ALU 运算后输出的结果作为地址值,输出相应的内存单元中存储的值,记该时延为 `t_{mem}`;
  6. 在把 Data Memory 输出的值保存到 Register File 中的寄存器之前,还必须经过一个多路选择器,记该时延为 `t_{m ux}`;
  7. 回顾我们在 时序逻辑电路基础 中分析过的 建立时间 的概念,我们还需要考虑 Register File 的相关触发器在下一个时钟上升沿到来之前的建立时间,以使得这些触发器能够在时钟上升沿到来时修改并输出正确的值,我们记该时延为 `t_{RFsetup}`

    综上,我们可以把 lw 指令的关键路径的运行时延整理为:

`T_c = t_{pcqPC} + t_{mem} + max(t_{RFread}, t_{sext} + t_{m ux}) + t_{ALU} + t_{mem} + t_{m ux} + t_{RFsetup}`

    由于从 PC、Memory 和 Register File 中读取指令的时间相比于其它操作都要长的多,所以我们可以把上面的式子简化为:

`T_c = t_{pcqPC} + 2t_{mem} + t_{RFread} + t_{ALU} + t_{m ux} + t_{RFsetup}`