计算机指令架构与微处理器设计原理

处理器/DSP

891人已加入

描述

1 计算机指令架构

1.1 基本概念

MIPS ——内部无互锁级微处理器( Microprocessor without interlocked piped stages ),采用RISC 指令集,所有的指令长度相同,运行周期也相同。

计算机硬件的基本功能就是执行 指令 ,指令在冯 · 诺伊曼计算机中由二进制数字进行编码。计算机的全部二进制机器指令组成了一种可供人与计算机进行交流的语言,称为 机器语言

助记符形式的指令的集合组成了 汇编语言

编译 ——将高级语言编写的程序翻译成等价的二进制指令序列来代替,计算机执行等价的机器语言程序。 解释 ——以高级语言程序作为输入数据,顺序地检查它的每一条语句,并直接执行等价的机器语言指令序列。

1.2 MIPS指令格式

寄存器

简洁性 ——所有指令长度相同,都是32位

区分性 ——opcode用于区分不同操作

硬件设计角度 ——不同的opcode编码方式硬 件结构会有差异

1.2.1 R型指令

操作码 ——R,I,J都会包含6bit,用于区分最多可以用于区 分 2^6=64 种指令,这个数字并不足够,因此还需要于后面的 6bit 的功能码一起确定不同的指令。

源操作数 1、2 (rs,rt)——R 型指令的两个操作数均来自于寄存器,按照寄存器的编号确定使用哪两个寄存器。因为在 MIPS 当中一共只有 32 个寄存器,所以 5bit 足以编号。

目标寄存器 (rd)——与源操作数一样,按照寄存器的编号确定使用哪个寄存器,并用 5bit 进行编号。

位移量 (shamt)——用于对寄存器内的数字进行位移。

功能码 (funct)——在同一操作码下区分不同的操作

1.2.2 I型指令

6+5+5+16=32bit

操作码 (opcode)——与 R 相同,I 型指令不需要功能码进行辅助 区分。

源操作数 (rs,rt)——I 型指令的源操作数可能有一个,也可能有两个,其中第一个源操作数的寄存器编号存储在 rs 中。

目标寄存器 (rd)——当 I 型指令没有第二个源操作数时,则第二个寄存器编号代表了目标 寄存器。

立即数 (Imm)——16bit 数字,根据操作码的区别对应不同的含义。

1.2.3 J型指令

伪直接寻址 ——在当前指令的一定的范围内进行寻址。跳转地址为指令中的 26 位常数与 PC 中的高位拼接得到,也就是说:新的 PC = { PC[31..28], target address, 00 } (00:+4)

1.3 寻址方式

寄存器

1.4 指令系统

CPU 可以利用RIJ三类指令构成一套指令系统,完成一系列指定的任务。

1.4.1 数据处理指令

算数运算指令:加法与减法

add $t0, $t1, $t2  # $t0 = $t1 + $t2
 sub $t2, $t3, $t4  # $t2 = $t3 - $t4

算数运算指令:立即数加法与减法

addi $t2, $t3, 5  # $t2 = $t3 + 5
 addi $t2, $t3, -5  # $t2 = $t3 - 5

逻辑运算指令:或、与

and $t0, $t1, $t2  # $t0 = $t1 & $t2
 or  $t0, $t1, $t2  # $t0 = $t1 | $t2
 xor $t0, $t1, $t2  # $t0 = $t1$t2
 nor $t0, $t1, $t2  # $t0 = ~($t1 | $t2)
 nor $t0, $t1, $zero  # $t0 = ~$t1

逻辑运算指令:移位运算

sll $t0, $t1, 10   # $t0 = $t1 < < 10,立即数逻辑左移
 srl $t0, $t1, 10  # $t0 = $t1 > > 10,立即数逻辑右移
 sra $t0, $t1, 10  # $t0 = $t1 > > 10,立即数算术右移
 sllv $t0, $t1, $t3  # $t0 = $t1 < < ($t3%32),逻辑左移
 srlv $t0, $t1, $t3  # $t0 = $t1 > > ($t3%32),逻辑右移
 srav $t0, $t1, $t3  # $t0 = $t1 > > ($t3%32),算术右移

比较指令 :u 代表无符号(unsigned), i 代表立即数(immediate)

slt $t1,$t2,$t3   # if ($t2 < $t3) $t1=1;
 slt $t1,$t2,$t3   # else $t1=0
 sltu $t1,$t2,$t3  # 无符号比较
 slti $t1, $t2, 10  # 与立即数比较
 sltui $t1, $t2, 10  # 与无符号立即数比较

1.4.2 数据传送指令

lw 和 sw ——寄存器与存储器之间的数据传输

lw $t1, 30($t2)  # Load worda
 sw $t3, 500($t4)  # Store word

压栈操作 ——根据**sp 指向的存储器的位置,可以将寄存器的数据进行压栈操作,每压栈一次,**sp 的内容就会指向原来位置-4。

addi $sp, $sp, -12
 sw     $s1, 8($sp)
 sw     $s2, 4($sp)
 sw     $s3, 0($sp)

出栈操作 ——将数据出栈并保存到寄存器中,每出栈一次,$sp 的内容就会 指向原来位置+4。

lw     $s1, 8($sp)
 lw     $s2, 4($sp)
 lw     $s3, 0($sp)
 addi    $sp, $sp, 12

装入高位立即数

lui $t1, 0x1234
 ori $t1, $t1, 0xabcd #将32位立即数0x1234abcd装入$t1寄存器

1.4.3 分支与跳转指令

分支指令

beq $t0, $t1, Target  # 如果$t0 =$t1,则分支执行标号为 Target 的指令
 bne $t0, $t1, Target  # 如果$t0!=$t1,则分支执行标号为 Target 的指令

无条件跳转

j Label#无条件跳转到标号 Label 处

这是一条 J 型指令 ,前面 在介绍 J 型指令 也提到过 ,对于 Label 对应地址的方式为伪直接寻址

while循环

Loop: sll $t1, $s3, 2 # 以 4 的倍数寻址,将 i 的值存入 $t1
 add $t1, $t1, $s6 # 将偏移量加上基地址( save )存入 $t1
 lw $t0, 0($t1) #从 $t1 指向的位置读出数据,存入 $t0
 bne $t0, $s5, Exit #判断 $t0 是否等于 k ,若等于则结束
 addi $s3, $s3 #上面一句不等于的话执行这步,i=i+1
 j Loop #继续循环
 Exit:…… #执行其他代码

过程调用

jal Procedure #将返回地址 (PC+ 保存在 $ra 寄存器中,程序跳转到过程 Procedure处执行
 jr $ra #跳转到寄存器指定的地址,子程序返回通过寄存器跳转指令jr进行

叶过程 ——不调用其他过程的过程,仅需要将返回地址寄存器 ra和在被调过程中修改了的保存寄存器s0~~$s7进行压栈操作即可。

主过程 ——调用了叶过程的过程,除了需要将ra和s0~~**s7 压栈外,还需要使用的参数寄存器 **a0~**a3 和临时寄存器 **t0~~$t9 压栈,用于保存当前程序执行的中间变量。

1.5 评价计算机性能的指标

响应延时 ——系统从开始做一项任务到任务完成所需要的总时间,包括CPU 运算,磁盘读写,内存读写等

吞吐量 ——系统单位时间内处理的任务总数,服务器以及工作站更看重这一点,吞吐量更大的计算系统在面对大量任务请求时,能够更快的完成所有任务(这时大部分的任务都在队列中等待完成,等待的时间远大于任务的响应延时)。

指令平均周期数( Clock cycles Per Instruction CPI ) ——平均一条指令所需要的周期数

CPU执行时间 =指令数×CPI×时钟周期。通过优化编译器减少指令总数,或者通过增加 CPU 的复杂性降低 CPI ,或者优化 CPU 的关键路径降低时钟周期。但是对其中任意一项的优化,都有可能导致另外两项的提升,同时也有可能增加成本,功耗等参数。

不影响其他两项的情况下对其中一项进行优化:编译器优化使得同样一段高级语言的代码翻译成汇编后的指令数更少,代价是增加了编译程序的时间;选择 更快的电路实现与生产工艺 ,增加生产成本和功耗;流水线或超标量通过设计处理器的体系架构,在时钟周期变化不大的情况下,让原本只能一个周期完成一条指令的系统变为可以一个周期完成多条指令,也可以成倍的增加系统的性能。

2 存储器

2.5 虚拟内存和外设

面临的问题:

编译时编译器无法获知物理地址 ——由于编译器在编译时无法获知程序运行时使用的地址空间分配情况,程序必须使用逻辑地址寻址,这就需要在逻辑地址和物理地址之间转换。

多个程序需要共享物理内存空间 ——同一系统上可能需要同时运行多个程序,这些程序无法共享各自的逻辑地址,但物理内存空间有限,如果为所有程序都分配独立的物理地址空间将造成严重的浪费。我们希望更高效地共享物理内存,避免某一程序长期独占物理内存,浪费空间。

2.5.1 页式管理

将地址空间划分为小的、等大的页( page ),以页为单位进行分配和共享 。在运行时进行物理地址和逻辑地址之间翻译的硬件称为存储器管理单元( Memory Management Unit, MMU )。操作系统以页为单位管理内存、完成逻辑地址与物理地址之间的转换;操作系统负责将每个程序中活动的页放入物理内存,这样一来各个程序只需要使用逻辑地址,并且逻辑地址空间可以高于其实际使用的物理内存大小,看起来有一种独享内存和地址空间的感觉,大大简化了编程的难度。同时,由于各个程序的页相互独立,页式管理也便于操作系统提供保护机制 ,避免程序之间内存空间的互相访问。有了分页管理,不用的页不必放在内存中,这大大提高了物理内存的使用效率。

页式管理需要维护一个页位置和地址转换关系的数据结构,称为页表。与页式管理相对的还有段式管理,即以不等大的内存段为单位管理内存。这种管理方式目前很少使用。

2.5.2 虚拟存储器

页式管理当中不活动的页不需要放入内存中。实际计算机系统中,不用的页通常是被放入磁盘上的。实际上,磁盘可以看作是主存的一种扩充,可以把主存和磁盘一起看成一个大的虚拟的存储器 。这样就用磁盘来将那些在主存储器内放不下的数据保存起来 ,这样做的好处有 :编程时不必考虑因为内存不足而带来的各种约束可以通过操作系统有效的管理有限的内存从而在各个程序进程 之间共享 。

虚拟存储器(虚拟内存)是建立在主存辅助存储器结构基础之上,由软件操作系统 和硬件 ( 相结合管理的存储系统 。编好的程序由操作系统装入辅助存储器中,程序运行时,操作系统把辅存的程序一块块自动调入主存由 CPU 执行 。当发生主存储器分配溢出时,操作系统透明地将主存储器中的段或页移到辅助存储器中,并将其标记为无效,然后可以将物理内存分配用做其他用途。

引入虚拟存储管理后,程序的逻辑地址也称为虚拟地址。在发生缺页异常(访问的页不在主存中)时,CPU 自动转到缺页中断处理程序进行处理 。缺页中断处理程序由操作系统提供。它通过页故障寄存器中的虚拟内存地址,计算出相应的页表项地址,根据页表项中查得的外存地址,从磁盘中读出新的页到主存中,然后允许程序重新访问。

在实际的虚拟存储器应用中,页的大小通常是4-16 KB 。虚拟存储器是全相联的,一个虚拟的页可以映射到内存中(几乎)任何一个位置。页的替换规则通常采用 : LRU Least Recent Used) 。由于磁盘的写入代价非常大,通常采用写回机制处理脏页。

2.5.3 地址和地址转换

每个进程都有自己的地址空间,操作系统和硬件协同工作,为每个进程 进行各自的 地址转换 。为了完成这一操作,需要维护一个地址转换 表 页表 。只有操作系统才能修改页表 。通过适当操作页表、转换地址,可以限制 一个进程 无法 获得访问其他进程地址空间的权限 。

由于现代存储器技术的发展,页表越来越大,将全部页表存在内存中往往不现实,也不高效。解决这一问题的方法是使用两级页表,第一级页表始终放在内存中,第二级页表的 1024 项有一部分在内存中 另外一部分在磁盘上或者可以不分配。

快表 (TLB)—— 页式管理使每 次存储器访问都带来额外的存储器访问,即在访问所需数据之前要访问 1 次页表 采用多级页表结构时额外的访问次数更多 。这些花销通常可以通过TLB(Translation Look aside Buffer ,地址变换高速缓存 来避免 。TLB 是一种 Cache ,用来存储最近用过的页转换关系,由硬件实现 。TLB 也称为快表,而页表则称为慢表 。

2.5.4 虚拟存储器与Cache

相同之处都把存储器划分为一个个信息块,运行时都能自动地把信息块从慢速存储器向快速存储器调度,信息块的调度都采用一定的替换策略,新的信息块将淘汰最不活跃的旧的信息块,以提高继续运行时的命中率。新调入的信息块需遵守一定的映射关系变换地址后来确定其在存储中的位置。

不同之处Cache 存储器采用与 CPU 速度匹配的快速存储元件来弥补主存和 CPU 之间的速度差距,而虚拟存储器虽然最大限度地减少了慢速辅存对 CPU 的影响,但它的主要目的是为了弥补了主存的容量不足,具有容量大和程序编址方便的优点 。

两个存储体系均以信息块作为存储层次之间基本信息的传递单位,Cache 存储器每次传递是定长的 信息块,长度只有几十字节,而虚拟存储器信息块划分方案很多,有页、段等等,长度均在几百字节至几千字节左右 。

主存——Cache 存储体系中 CPU 与 Cache 和主存都建立了直接访问的通路,一旦在 Cache未命中, CPU 就直接访问主存,并同时向 Cache 调度信息块,从而减少了 CPU 等待的时间;辅助存储器与 CPU 之间没有直接通路,一旦在主存中不命中,则只能从辅存调度信息块到主存.因为辅存的速度与 CPU 的速度差距太大,调度需要毫秒级 时间,因此, CPU 一般将改换执行另一个程序,等到调度完成后再返回原程序继续工作 。

主存——Cache 存储器存取信息的过程、地址变换和替换策略全部用硬件实现,所以对程序员来说是透明的。虚拟存储器则是由硬件 ( 和软件 操作系统 ) 相结合管理的存储系统。地址变换由 MMU 硬件负责,页表的设置由操作系统负责,页面调度由操作系统实现,所以对设计存储管理软件的系统程序员来说,虚拟存储器是不透明的 。

2.5.5 外围设备

首先是访问地址。虚拟内存地址是逻辑的、可以被扩展,不但可以映射到物理内存,还可以映射到外设。

然后是访问时机。CPU 有两种访问外设的策略:轮询和中断。轮询方式是 CPU 每隔一段时间询问外设是否需要 I/O 。这种方式可能会浪费大量的 CPU 时间 在无意义的外设访问上,但其实现简单,不需要复杂的中断处理程序。中断方式是当外设需要 I/O 时, 向 CPU 发送 一个中断 。只有出现中断时, CPU 才停下来与比较慢的外设通信 。这种方式比较经济,但处理中断比较复杂。

3 微处理器设计原理

3.1 单周期MIPS处理器

3.1.1 数据通路设计

不能直接并行连接每种类型需要的数据通路,这样会增加太多的冗余硬件单元。为了复用硬件单元,要用到多路选择器和相应的控制逻辑。增加多路选择器比增加 ALU增加寄存器堆访问端口要更加经济。

指令集子集按照实现的功能分为四类:寄存器-寄存器运算、寄存器-立即数运算、访存操作(寄存器-内存搬运)、分支和跳转操作。若按照指令格式分类,可以分为 R 型、I 型、J 型。

存储单元

存储器 ——内存存储指令和/或数据

寄存器堆 ——R、I需要访问。R需要两个读端口一个写端口;不是所有指令都写所以有写使能信号RegWr。可以为RS和RT提供读取,为RT或RD 提供写入

程序计数器PC ——指向当前正在执行的指令在内存的地址,需要电路根据是否分支跳转更新PC

运算电路

算数/逻辑运算ALU ——支持操作数的加减、移位等运算。

扩展电路 ——I型操作数是16位立即数时根据指令作符号扩展或零扩展

PC更新电路 ——可以加4或立即数扩展

寄存器-寄存器运算数据通路

指令从Rs, Rt 读出两个操作数并送入 ALU 进行某种运算,运算输出存入Rd中。数据通路主要由PC单元、寄存器堆、ALU之间的连接组成。

寄存器-立即数运算数据通路

ALU 操作数一个从寄存器 (Rs)中读出,另一个由指令中给出的立即数(imm16)做扩展得到。结果写回Rt。

访存操作数据通路

LW指令使用ALU用Rs和16位立即数扩展的结果进行加法运算,得到的结果作为内存地址从内存地址中读出数据写回 Rt。

SW指令同样计算出内存地址。从 Rt 中读出数据写入该内存地址,内存的写使能 WrEn应该置1。

分支和跳转操作数据通路

Beq 指令判断 Rs 和 Rt 是否相等,如果相等则修改 PC 至相对当前 PC 位置为 imm16 符号扩展并×4 的字节位置。由于单周期一 个 ALU 模块不能被用于两个计算,这个符号扩展和相加的逻辑将在 Instruction Fetch Unit 这个模块内部由 一个单独的 ALU 解决,但是符号扩展单元可以复用。所以 数据通路只需要将 ALU 减法的输出是否为 0 输入给 PC 模块即可。

跳转指令的数据通路较为简单,只需将 26 位立即数左移两位作为 内存地址 更新 PC 值即可。

3.2 多周期MIPS处理器

单周期处理器的数据通路主要是组合逻辑,时钟同步行为主要是PC更新、写入寄存器和内存。

多周期将指令可能经过的几个阶段拆分开来,不同的指令经过的阶段数不同,执行时间不同,希望减少固定周期带来的时间浪费。

  • 首先在单周期基础上添加寄存器负责在一条指令的几个周期间的信息传递,每一个阶段的输出结果都应该有寄存器保存;
  • 取指令和访问数据内存肯定会分在两个周期进行,所以存储器复用,数据存储和指令存储采用相同的地址空间,用同一套端口访问;
  • 不同阶段之间 ALU 可以共用,不再需要额外为分支指令和顺序PC+4 增加加法器;
  • 由于 一条指令跨多个周期,控制信号逻辑不再是简单的组合逻辑,而是一个根据指令类型进行转移的有限状态机。

3.3 异常和中断

异常和中断是除了分支和跳转指令以外,特殊的改变程序控制流的方式。

中断主要指的是来自处理器外部 ,外设在有事件发生比如新的网络包到来等时向处理器提出中断处理请求。中断产生与程序执行是异步的。

异常是来自处理器内部的被处理器检测到的事件。分为软件导致的异常比如被零除、 访问段错误等,和硬件异常比如MMU检测到非法内存访问时。异常通常与程序执行流是同步的。

为什么需要在硬件上实现中断和异常处理机制

如果我们不使用中断的方式处理异常,还可以让处理器忙轮询外设来和外设交互 。IO设备比处理器速度慢很多数量级,大量浪费了处理器的能力;如果使用中断处理 外部事件,可以让处理器在有外部事件导致中断时再来处理,其他时间都可以继续进行计算。现代操作系统中,当一个进程开始等待外部 IO 事件时,这个进程常被 暂时挂起,处理器开始运行另一个进程,直到该进程等待的中断来临,才将该进程重新标记为可运行,等待被调度 。

很多异常本身就很不容易通过软件检测,比如未知指令错误会导致硬件进入未知状态。另外本来也存在硬件异常,软件很难处理 。同时我们也想要将设计一些异常处理策略的权利交给软件设计者,所以我们不能只在硬件上用单一的方式处理异常,而是要实现一个能提供给软件设计者异常处理弹性的机制。

作为硬件设计者,我们想要实现异常处理的机制,然后把实现机制的具体策略的权利提供给软件开发者,又是一个将机制和策略明确分离的典例 。所以我们将异常处理做成一个隐式 的过程调用,由软件设计者提供异常处理程序,硬件上实现发生异常时调用该程序的机制。

硬件知道某种异常要调用哪个异常处理程序一般有两种方法:

  • (x86) 向量方式 ——每个异常都有自己的异常处理程序,其入口保存在 一个固定地址处。
  • (MIPS) 原因寄存器 ——只使用一 个通用的异常处理程序,每个异常事件必须将足够的信息加载到原因寄存器中,以便异常处理程序知道是何种异常发生并采取相应措施 。

3.4 MIPS处理器流水线设计

硬件设计中,流水线通常是通过在较长的组合逻辑之间添加寄存器来实现的。通过插入寄存器,可以将延时较长的组合逻辑分解为多步来完成,不同的步骤可以在多次计算上并行。由于组合逻辑被拆解,延时降低,从而时钟频率可以提升,达到加速的目的。所有的寄存器采用同一时钟触发。

3.4.1 MIPS处理器流水化

IF ——根据 PC 读取指令寄存器中的指令,并更新 PC 为 PC+4 或 EX 阶段计算出的跳转地址。

ID ——指令的译码,省略了控制信号的解码部分。同时,根据寄存器地址读出两个用于计算的寄存器中的值,完成立即数的扩展。

EX ——扩展的立即数和寄存器读出的第二个数通过多路选择器得到 ALU 需要的一个操作数。另一个操作数固定为从寄存器堆中读出的第一个数。两者通过 ALU 完成计算。同时,由于 j 指令的需要,扩展的立即数经过移位后与 PC+4 的结果进行相加,得到需要跳转到的地址。从寄存器读出的第二个数作为可能的读写数据存储的地址也被单独寄存。

MEM ——根据需要有三种选择:将 ALU 输出结果写入数据存储;根据地址读取数据缓存;将 ALU 输出结果传至下一级。

WB ——将 MEM 阶段寄存的数据通过多路选择器进行选择后,写回寄存器。 写回地址在 ID 阶段即得到,跟随流水线逐级传递到 WB 阶段。为了使数据尽早地写回寄存器以便被后面的指令使用,可以令寄存器组采用时钟下降沿触发写入 ,而流水线的 中间寄存器采用时钟的上升沿触发写入 。这样,相当于在前半个时钟周期写入寄存器组,后半个时钟周期将数据读出。

3.4.2 流水线处理器的性能

吞吐率 ——单位时间内处理的指令数。吞吐率分为实际吞吐率和最大吞吐率。实际吞吐率为系统运行时实际在单位时间内处理的指令数。最大吞吐率则为系统在达到理想的稳定运行状态时的吞吐率。理想吞吐率为 1 指令每周期 。

加速比 ——不使用流水线的执行时间和使用流水线的执行时间之比。类似于吞吐率的定义,我们可以定义实际加速比和最大加速比。k 级流水线可以达到的最大的最大加速比为 k 。

无法达到最大加速比:

  • 最长的一段决定了流水线运行的最小时钟周期,成为流水线运行的 瓶颈
  • 插入寄存器引入额外的建立时间和保持时间,每级流水结算的逻辑路径长度之和超过纯粹的组合逻辑路径长度,最长的会超过原路径的1/k。这说明无限制地增加流水级不能无限制提升性能。

流水线设计对于指令数并没有任何的优化。相比于单周期设计而言,流水线设计可以有效缩短时钟周期;相比于多周期的设计而言,流水线设计主要降低了 CPI 。流水线的不同阶段在执行的是不同的指令,因此是在完成指令级的并行处理。流水线是指令级并行的基本技术之一。其他的指令集并行技术还包括超标量以及超长指令字。

3.4.3 流水线冒险

解决冒险的最简单和直接的方式是阻塞流水线。通过在指令之间插入空指令(bubble)将相邻的指令间隔拉开。直观的认识是,如果插入足够的空指令使得相邻指令在流水线上完全不重叠,那么流水线处理器的运行就和单周期处理器的运行方式一样,也就不会有冒险存在。当然这也严重影响了流水线处理器的吞吐率。因此冒险的解决办法是以尽量不阻塞流水线为目标来制定的。

结构冒险——硬件资源使用冲突

流水线中的两条指令需要同时访问相同的硬件资源。通常出现在某个单元未完全按照流水线方式实现时。比如,对 load 指令采用 5 级流水,第5 级时写回寄存器。而对于 R 型指令,由于 EX 阶段之后即得到结果,可以在第 4 阶段就写回,那么当 load 指令和 R 型指令相邻依次出现时就会出现同时向寄存器写入两个数的情况,发生结构冒险。又比如,当数据存储器和指令存储器共享同一个 RAM 资源时,取指令和数据的读写也可能发生冲突。

为了避免结构冒险,一方面需要将所有硬件资源固定分配到指定的流水级,另一方面需要所有的指令都顺序完成所有的流水级,不能跳过或停留。当资源冲突时,往往需要增加资源来避免冲突。比如将数据存储器和指令存储器分开,将 ALU 与计算 PC 的单元分开。

数据冒险——数据因指令关联不可获得

当前指令读取的寄存器,应该被之前的一条指令写入后再使用。但是之前的指令在流水线中尚未完成。

数据冒险出现在当前指令和其相邻的前两条指令之间。因此,如果不采用任何措施,需要至多插入 2 条空指令来阻塞流水线。采用的解决策略是尽可能将已经得到的结果向前传递(forwarding)至需要的位置,而不等待其写入寄存器堆之后再去使用。

为了缩短代码长度,必要的阻塞我们也采用硬件的方式来完成。

1)ALU输入端数据选择

(00——寄存器堆;10——EX/MEM;01——MEM/WB)

2)无法解决的load-use冒险

(stall=1阻塞流水线,IF 暂停,并且在 ID 插入空指令。只需要将 RegWrite 和 MemWrite 信号置 0 即可。PC 通过 stall 信号禁止其写入)

前一条指令为 load 指令,而当前指令需要用 load 得到的数据进行计算。这种情况我们称之为 load use hazard 。判断需要阻塞流水线的条件为:当 ID/EX 阶段的寄存器表明当前指令需要读内存,而 IF/ID 阶段表明下一条指令需要使用寄存器,且该寄存器将要被当前指令写入

硬件处理load use hazard 会带来性能上的损失。防止流水线阻塞的另一个方法是在代码编译时进行优化。一些编译器通过将无关的指令放在 load 指令和需要使用其数据的指令之间,相当于将本来需要插入空指令的地方利用了起来,从而提高了执行效率。

3)MEM写入端的数据选择

当load-use是由于复制存储器引起的,则可以不必阻塞一个时钟周期,可以选择增加新的 forward 路径来完成这个转发过程,通过判断寄存器 id 和操作码来决定是否转发。

控制冒险——跳转分支指令使PC无法及时确定

控制冒险定义为:下一条需要执行的指令,依赖于当前正在流水线中执行的指令的结果。控制冒险的出现频率通常比数据冒险要低,但是也不容易解决。可能的解决方案包括:阻塞流水线、提前判断、预测、延迟决定。

j和 jr 指令在 ID/Reg 阶段能够确定之后 应该执行的指令。而 beq 指令则需要经过 ALU计算结果之后再判断才能确定是否要跳转。因此,如果纯粹地插入空指令来等待的话, j 和jr 指令要损失一个周期,而 beq 指令则要损失 2 个时钟周期。

1)阻塞流水线

阻塞流水线能够省去无谓的空指令。但是为了支持对 j jr beq 指令的支持, hazard unit 中的逻辑会更复杂一些,需要添加对 IF/ID 寄存器中指令类型以及 ID/EX 寄存器中指令类型的判断。另外,还需要将 j 指令中跳转地址的计算也转移到 ID 阶段来执行。

2)提前判断

由于在指令译码之后我们才能确定指令的类型,因此进一步提前j 和 jr 指令的跳转相对困难。将 beq 指令的判断放在 ID 阶段执行:

  1. 在 ID 阶段的寄存器堆的输出端增加一个比较器用来判断是否分支跳转
  2. 将判断的结果接入跳转地址的多路选择器的选择端
  3. 对于比较器的输入,需要额外的 forward 逻辑来处理

可能从 EX, MEM 或者 WB 阶段转发。从 EX 阶段的转发是不太实际的。因为需要的是 EX 阶段计算的结果而不是输入,如果进行转发,意味着存在着一条新的逻辑路径,从 ID/EX 寄存器出发,经过 ALU ,比较器以及 PC 的选择端到 PC 寄存器。这很可能会延长关键路径,降低处理器性能。从 WB 阶段的转发没有必要,因为我们写入寄存器堆和写入流水线中的寄存器采用不同的时钟沿。因此我们只需要从 MEM 阶段到 ID 阶段的 forward 逻辑。

3)分支预测

一段程序中出现的跳转应该主要来自于循环操作。如果我们可以通过某种比较可靠的方式来猜测一条 beq 指令是否跳转,那么我们就能在大多数时候不阻塞处理器。比如通过一个表来存储 beq 指令的地址和上一次是否跳转,然后每次执行时查表并执行和之前一次同样的行为。

然而这样的预测不可能 100% 准确,因此我们还需要处理预测出错的情况。通常,当我们发现预测跳转出错时,可以将已经在流水线中的指令清空( flush )。相比于提前判断,分支预测对于长流水线的处理器更友好。

4)延迟槽

在采用了提前判断的技术之后,我们已经将分支所需要阻塞的时钟周期降低到1 个。进一步提高处理器性能可以利用这一个周期。具体的做法是在这个周期中插入和分支无关,总是要执行的指令。这便是延迟槽技术。

延迟槽技术属于软件技术,通常由高级语言的编译器或汇编程序的编写者来决定。延迟槽技术通常会打乱原有程序的顺序,造成可读性降低。并且不总是存在可以放到延迟槽中的指令。但是这种方法不需要额外的硬件。

3.4.4 流水线异常和中断

为了在硬件上支持异常和中断处理,我们需要:

  1. 根据异常或中断的具体 情况,将 PC跳转到处理程序的入口;
  2. 将已经在流水线中的指令清空。考虑以下三种情况:溢出,错误的操作码,外部中断。认为前两种情况都在 EX 阶段被发现和处理。所以这里我们需要将 EX 、 ID 以及 IF 阶段的指令清空。清空的操作很简单,只需要在流水线寄存器的写入端添加一个二路选择器,一端为 0 ,另一端为正常输入当需要清空时选 0 ,否则选正常输入。
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分