目录
4.0 预备知识
顺序的处理器(sequential,SEQ)的实现包括组合逻辑电路、存储元件——这样的数字电路系统也被描述为时序电路。
4.0.1 组合逻辑电路
【概念】 由与、或、非三种基本逻辑门电路组成的逻辑门网络。
【性质】 组合逻辑电路本质上不存储任何信息,不需要任何时序或控制,只是简单地响应输入信号,当输入发生变化了,值就通过逻辑门网络传播,输出端会在很短的时间内响应。
组合逻辑电路的输出值,取决于当前的输入值。
然而,组合逻辑电路的输出值可能需要在后续过程中才能使用到(🌰作为另一组合逻辑电路在其他时刻下的输入值),因此需要先把这样的值保存起来。
——于是引入了存储元件👇
4.0.2 存储元件
4.0.2.1 概念
✏️ 存储元件是能够存储二进制信息的电路。
存储元件在某一时刻存储的二进制信息定义为该时刻存储元件的状态。
✏️ 对比于组合逻辑电路:存储元件可以保存逻辑电路过去某一时刻的状态。
4.0.2.2 问题来了,存储元件中的状态值如何改变?
✏️ 在同一数字电路系统中,存储设备都是由同一个时钟控制的,决定了何时要把新值存储(加载)到设备中。
——时钟是一个周期性信号,由晶振实现
4.0.2.3 两类存储器设备
1、时钟寄存器(多称:(硬件)寄存器)
✏️ ——存储单个位或字;
使用时钟信号控制寄存器加载新值(状态)。
✏️ ——寄存器输入端连接电路中的一个组合逻辑电路,接收其输出值;
寄存器输出端连接电路中的一个组合逻辑电路,作为其输入值。
寄存器是如何工作的?
- 时钟信号不变时,寄存器保持在稳定状态(用 x x x 表示),产生的输出等于它的当前状态。
- 时钟信号发生变化时(分为下降沿和上升沿,具体情况具体分析),当一个新的输入(用
y
y
y 表示,见上图左半边)时,输人信号就加载到寄存器中,成为下一个状态
y
y
y;然后直到下一个时钟信号变化时,这个状态就一直是寄存器的新输出。
——上图中,当时钟信号是从低电平变成高电平时(上升沿信号),寄存器加载新值。
事实上,一般也多用上升沿的触发器来设计寄存器。
【结论】
寄存器是电路不同部分的组合逻辑电路之间的屏障。
【补充】硬件寄存器和程序寄存器
硬件寄存器:就是物理意义上的被称之为“寄存器”的时序电路,也即上文的时钟寄存器。
程序寄存器:面向程序而言,运行的程序即进程,进程可能包含很多个线程,这些线程拥有各自的寄存器来保存它们各自的状态信息,这些寄存器称为程序寄存器。这些个程序寄存器的物理实现是内存,它们是操作系统为该进程分配的一块物理内存中的部分区域。
2、随机访问存储器
✏️ ——存储多个字;
用地址来选择该读(写)哪个字。
形式有两种
(1)处理器的虚拟内存系统
✏️ 硬件(如内存条)和操作系统软件结合,实现虚拟内存地址到物理内存的映射。
✏️ 这里给出了可读写存储器和只读存储器(双端口)的示意图。
橙色表示读/写数据信号进行一般数据的读/写;
红色表示只读,一般用来读指令。
虚线部分表示组合逻辑给出的实时信号
✏️ error 信号:如果地址非法,就要设置为 1,报异常!
(实际上内存和CPU之间还有一个MMU,专门用于内存中各个 cell 的访问)
![数据存储器结构](https://i-blog.csdnimg.cn/blog_migrate/18b2cb40575ffd8a3675b01a3033a31e.png)
(2)寄存器文件
又称寄存器堆,是 CPU 中多个硬件寄存器组成的阵列,通常由快速的静态随机读写存储器(SRAM)实现。这种 RAM 具有专门的读端口与写端口,可以多路并发访问不同的寄存器。
就是一个小的、以寄存器ID作为地址的随机访问存储器。
✏️ 下图给出了一个典型的寄存器文件内部结构(简化版)
这个寄存器有两个读端口(蓝色方框),一个写端口(绿色方框)。
- 读端口:外部提供要读取的寄存器 ID,多路选择器(multiplexer)根据 ID 值读取对应寄存器的值,然后输出给外部。
- 写端口:在时钟信号上升沿时,地址解码器根据外部提供的寄存器 ID 确定要写的寄存器,然后将外部提供的数据加载到对应的寄存器中。
4.0.3 时序电路
✏️ 时序电路由组合逻辑电路和存储元件组成。
这就意味着,时序电路某一时刻的输出值取决于当前电路中:
(1)组合逻辑电路的当前输入值,设为
A
=
[
A
1
,
A
2
,
⋅
⋅
⋅
,
A
n
]
A=[A_1,A_2,···,A_n]
A=[A1,A2,⋅⋅⋅,An];
(2)存储元件当前保存的值,设为
B
=
[
B
1
,
B
2
,
⋅
⋅
⋅
,
B
m
]
B=[B_1,B_2,···,B_m]
B=[B1,B2,⋅⋅⋅,Bm]。
该时序电路当前的输出值为
L
o
g
_
C
a
l
(
A
,
B
)
Log\_Cal(A,B)
Log_Cal(A,B)。
4.0.4 RISC 和 CISC 指令集(背景了解)
x86-64 有时称为“复杂指令集计算机”(CISC,读作“sisk"), 与“精简指令集计算机” (RISC,读作“risk")相对。
✏️ 从历史上看,先出现了CISC机器,它从最早的计算机演化而来。
最早的微处理器出现在 20 世纪 70 年代早期。
20 世纪 80 年代早期集成电路发展,指令集复杂度越来越大。
x86 家族发展:到 IA32,再到 x86-64。
继续增加。
✏️ 20世纪80年代早期,RISC 的设计理念是作为上述发展趋势的一种替代而发展起来的,认为更简单的指令集形式可以产生更高效的代码;实际上,许多高级指令很难被编译器产生,因此也很少被用到。
加州大学伯克利分校的David Patterson和斯坦福大学的John Hennessy进一步发展了RISC的概念。Patterson将这种新的机器类型命名为RISC,而将以前的那种称为CISC,因为以前没有必要给一种几乎是通用的指令集格式起名字。
CISC | 早期的RISC |
---|---|
指令数量很多。Intel 描述全套指令的文档有1200多页。 | 指令数量少得多。通常少于100个。 |
有些指令的延迟很长。包括将一个整块从内存的一个部分复制到另一部分的指令,以及其他一些将多个寄存器的值复制到内存或从内存复制到多个寄存器的指令。 | 没有较长延迟的指令。有些早期的RISC机器甚至没有整数乘法指令,要求编译器通过一系列加法来实现乘法。 |
编码是可变长度的。x86-64 的指令长度可以是1~15个字节。 | 编码是固定长度的。通常所有的指令都编码为4个字节。 |
**指定操作数的方式很多样。**在x86-64 中,内存操作数指示符可以有许多不同的组合,这些组合由偏移量、基址和变址寄存器以及伸缩因子组成。 | 简单寻址方式。通常只有基址和偏移量寻址。 |
可以对内存和寄存器操作数进行算术和逻辑运算。 | **只能对寄存器操作数进行算术和逻辑运算。**允许使用内存引用的只有load 和 store 指令,load 是从内存读到寄存器,store 是从寄存器写到内存。这种方法被称为load/store体系结构。 |
**对机器级程序来说实现细节是不可见的。**ISA 提供了程序和如何执行程序之间的清晰的抽象。 | **对机器级程序来说实现细节是可见的。**有些RISC机器禁止某些特殊的指令序列,而有些跳转要到下一条指令执行完了以后才会生效。编译器必须在这些约束条件下进行性能优化。 |
**有条件码。**作为指令执行的副产品,设置了一些特殊的标志位,可以用于条件分支检测。 | **没有条件码。**相反,对条件检测来说,要用明确的测试指令,这些指令会将测试结果放在一个普通的寄存器中。 |
**栈密集的过程链接。**栈被用来存取过程参数和返回地址。 | **寄存器密集的过程链接。**寄存器被用来存取过程参数和返回地址。因此有些过程能完全避免内存引用。通常处理器有更多的(最多的有32个)寄存器。 |
20世纪90年代早期,争论逐渐平息,因为事实已经很清楚了,无论是单纯的 RISC 还是单纯的 CISC 都不如结合两者思想精华的设计。
RISC 机器发展进化的过程中,引入了更多的指令,也不“精简”了;
CISC 也利用了高性能流水线结构。
4.1 Y86-64 指令集体系结构
定义一个指令集体系结构,包括定义各种状态单元、指令集和它们的编码、一组编程规范和异常事件处理。
4.1.1 程序员可见状态
——程序员,既可以是用汇编代码写程序的人,也可以是产生机器级代码的编译器。
✏️ 程序员可见状态,包括:
(1)15个程序寄存器(RF):%rax
、%rcx
、%rdx
、%rbx
、%rsp
、%rbp
、%rsi
、%rdi
、%r8
、%r9
、%r10
、%r11
、%r12
、%r13
、%r14
.
(2)程序状态(Stat):表明程序执行的总体状态,指示正常或异常。
Stat 值 | 名字 | 含义 |
---|---|---|
1 | AOK | 正常操作 |
2 | HLT | 机器执行 halt 指令 |
3 | ADR | 遇到非法地址 |
4 | INS | 遇到非法指令 |
(3)三个条件码(ConditionCode):零标志(ZF)、符号标志(SF)、溢出标志(OF)。
(4)程序寄存器(PC):存放当前正在执行指令的地址。
(5)内存(DMEM):虚拟内存映射物理内存。
4.1.2 Y86-64 指令
——是 x86-64 指令集的一个子集
(1)只包括 8 字节整数操作;
(2)传送指令中的前两个字母的含义:‘ r r r’——寄存器;‘ i i i’——立即数;‘ m m m’——内存;
(3)指令编码长度从 1 个字节到 10 个字节不等;
(4)第零号字节:表明指令的类型
高四位表示代码值(icode
),范围 0~0xB;
低四位表示功能值(ifun
),只有 OPq
、jXX
、cmovXX
三类有功能值,其他指令功能值为 0。
(5)Y86-64 只定义了简单的基址和偏移量形式,不支持第二变址寄存器和任何寄存器值的伸缩。
(6)不允许从一个内存地址直接传送到另一个内存地址;
不允许将立即数直接传送到内存。
(7)halt
指令:停止指令执行(x86-64中与之对应的是 hlt
指令),并将状态码 Stat
设置为 HLT
。
(8)采用小端法编码。
(9)pushq
指令会把栈指针减 8,并且将一个寄存器值写人内存中。因此,当执行 pushq %rsp
指令时,处理器的行为是不确定的,因为要人栈的寄存器会被同一条指令修改。
通常有两种不同的约定:① 压入 %rsp
的原始值;②压入减去 8 的 %rsp
的值。
![](https://i-blog.csdnimg.cn/blog_migrate/97e0edc0fff78424bdc3a9b422ab5f58.png)
✏️ 15个程序寄存器中每个都有一个相对应的范围在 0 到 0xE 之间的寄存器标识符 (register ID)。
Y86-64 中的寄存器编号跟 x86-64 中的相同。
程序寄存器存在CPU中的一个寄存器文件中。
在指令编码中以及在硬件设计中,当需要指明不应访问任何寄存器时,就用 ID 值 0xF 来表示。
数字 | 寄存器名字 | 数字 | 寄存器名字 |
---|---|---|---|
0 | %rax | 8 | %r8 |
1 | %rcx | 9 | %r9 |
2 | %rdx | A | %r10 |
3 | %rbx | B | %r11 |
4 | %rsp | C | %.r12 |
5 | %rbp | D | %r13 |
6 | %rsi | E | %r14 |
7 | %rdi | F | No register |
🌰用十六进制来表示指令 rmmovq %rsp, 0x123456789abcd (%rdx)
的字节编码。
从👆👆图可以看到,rmmovq
的第一个字节为 40
。源寄存器 %rsp
应该编码放在
r
A
rA
rA 字段中,而基址寄存器 %rdx
应该编码放在
r
B
rB
rB 字段中。
根据👆图中的寄存器编号,得到寄存器指示符字节 42
。
偏移量编码放在 8 字节的常数中:首先在 0x123456789abcd
的前面填充上 0 变成 8 个字节,变成字节序列 00 01 23 45 67 89 ab cd
,由于小端法编码,写成按字节反序就是 cd ab 89 67 45 23 01 00
最后,将它们都连接起来就得到指令的编码 4042cdab896745230100
。
🌰已知指令编码:0x100:30f3fcffffffffffffff40630008000000000000
,确定指令序列。
30
→irmovq V,rB
,f3
→
r
B
rB
rB→%rbx
,
V
V
V→fcffffffffffffff
→反序:fffffffffffffffc
→-4
;
40
→rmmovq rA, D(rB)
,63
→
r
A
rA
rA,
r
B
rB
rB→%rsi,%rbx
,
D
D
D→0008000000000000
→反序:00080000000000000800
→0x800
;
最后结果:0x100:irmovq $-4, %rbx
、0x10a:rmmovq %rsi, $0x800(%rbx)
。
4.2 SEQ 硬件结构
4.3 Y86-64 的顺序实现
4.3.1 执行一条指令的六个阶段
处理器每执行一条指令,都要经过这些阶段。
【注】表中的“第零字节、第一字节、……”对应着第 1 小节 Y86-64 指令表中的字节编号。
阶段 | 处理 |
---|---|
取指(fetch) | 根据程序计数器 (PC) 提供的地址,从内存读取指令字节。 指令字节的高八位分成两个四位部分(第零字节),分别称为 icode (指令代码)和 ifun (指令功能),根据它们的值,有三种可能:(1)取出一个寄存器指示符字节(第一字节),指明仅有一个寄存器操作数指示符 rA ; (2)指明两个寄存器操作数指示符 rA 和 rB ;(3)它还可能取出一个四字节常数字 valC。 PC 增加器 (PC incrementer) 按顺序方式计算当前指令的下一条指令的地址 valP ——就是说,valP 等于 PC 的值加上已取出指令的长度。可以发现:取指和译码实际上交互操作的,因为必须先根据指令字节的最零字节判断这条指令是啥,进而知道指令字节到底有多长。 |
译码(decode) | 译码阶段从寄存器文件读入最多两个操作数,得到值 valA 和/或 valB 。通常,寄存器文件读入 rA 和 rB 字段指明的寄存器,不过有些指令是读寄存器 %rsp 的。 |
执行(execute) | 执行阶段根据指令的类型,算术/逻辑单元(ALU) (1)要么执行指令指明的操作 (根据 ifun 的值),计算内存引用的有效地址;(2)要么作为一个加法器:增加或减少栈指针,或者计算有效地址,或者只是简单地加0,将一个输人传递到输出。 由此得到值—— valE 。此时,有些指令可能设置条件码寄存器 (CC) (其有三个条件码位) 如: (1)对一条条件传送指令来说,这个阶段会根据条件码和传送条件 (由 ifun 给出),如果条件成立,则更新目标寄存器。(2)对一条跳转指令来说,这个阶段会根据条件码和跳转类型来计算分支信号 Cnd ,来决定是否应该选择分支。 |
访存(memory) | 访存阶段可以将数据写人内存,或者从内存读出数据。 读出的值为 valM 。指令和数据内存访问的是同一内存设备,但是用于不同的目的。 |
写回(write back) | 写回阶段最多可以写两个结果到寄存器文件。 寄存器文件有两个写端口: (1)端口 E 用来写ALU计算出来的值; (2)端口M用来写从数据内存中读出的值。 |
更新PC(PC update) | 将 PC 设置成下一条指令的地址。 PC 的新值不同阶段不一样,有三种可能: (1)下一条指令的地址 valP ;(2)调用指令或跳转指令指定的目标地址 valC ;(3)从内存读取的返回地址 valM 。 |
【补充】在 Y86-64 的实现中,发生任何异常时,处理器就会停止:它执行 halt 指令或非法指令,或它试图读或者写非法地址。而在实际的设计中,发生异常时,处理器会进人异常处理模式,开始执行由异常的类型决定的特殊代码,且**每一阶段都要进行异常检测!**
4.3.2 👇不同 Y86-64 指令在不同阶段的处理过程
4.4 SEQ 的时序
4.4.1 控制逻辑块的目标
通过控制逻辑,在不同硬件单元之间传送数据,以及操作这些单元,使得对每个不同的指令执行指定的运算。
控制逻辑信号怎么变化?
✏️ 通过时钟信号进行控制。
一个时钟变化会引发一个经过组合逻辑的流,从而执行整个指令。
因此,指令执行的 6 个过程并不是顺序的,而几乎是同时的(时钟信号上升沿),(有点延迟很正常)。
4.4.2 时钟变化引起的组合逻辑“流”经过硬件时的变化
分析
遵循原则:从不回读。(处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态)
举个🌰说明这条原则:
有些指令(整数运算)会设置条件码,有些指令(跳转指令)会读取条件码,但没有指令必须既设置又读取条件码。虽然要到时钟上升沿时,才会设置条件码,但是在任何指令试图读之前,它们都会更新。
SEQ 的实现包括组合逻辑、时钟寄存器(程序计数器和条件码寄存器)、随机访问寄存器(寄存器文件、指令内存、数据内存)。
- 组合逻辑是根据电路的实时状态而实时变化的——与时钟信号没关系。
- 剩余的四个硬件单元(程序计数器、条件码寄存器、数据内存、寄存器文件)使用一个时钟信号来控制,用于触发将新值装载到寄存器或将数据值写到随机访问存储器。
- 每个时钟周期,程序计数器都会装载新的指令地址。
- 只有在执行整数运算指令时,才会装载条件码寄存器。
- 只有在执行
rmmovq
、pushq
或call
指令时,才会写数据内存。 - 寄存器文件的两个写端口允许每个时钟周期更新两个程序寄存器;当只有一个寄存器时,可以用特殊的寄存器 ID
0xF
作为端口地址,来表明在此端口不应该执行写操作。
🌰例子
![image-20211216212119914](https://i-blog.csdnimg.cn/blog_migrate/cade3d0f184406c66eff24d33dc1c90f.png)
👆上图的不同颜色的代码表明电路信号是如何与正在被执行的不同指令相联系的。
✏️ 每个周期开始时,也即处于上升沿时,状态单元(程序计数器、条件码寄存器、寄存器文件和数据内存)均是根据前一条指令执行完的电路状态进行设置的。
✏️ 假设处理是从设置条件码开始的,按照 ZF、SF 和 OF的顺序,设为100。
时钟信号 | 说明 |
---|---|
①,周期 3 开始时 | 状态单元的值是被更新为执行完第二条 irmovq 指令后的电路中的状态值。图中组合逻辑电路部分被用白色表示,表明它在时钟信号刚处于上升沿的瞬间,还没有来得及对新的状态做出反应。 |
①到②,周期 3 的过程 | 地址 0x014 载人程序计数器中,取出并处理 addq 指令。值沿着组合逻辑电路流动,包括读随机访问存储器。 |
②,周期 3 末尾 | 组合逻辑电路已经根据 addq 指令发生了新的变化,图中蓝色区域;其中条件码寄存器有了新值 000、程序寄存器 %rbx 得到了新值、程序计数器也更新为 0x016。但是新值仍然以组合电路的形式保存着,没有被存储起来。 |
③,周期 4 开始时 | 上升沿的瞬间,更新了各个状态单元的值,即由 addq 指令产生的新值。同样地,此时组合逻辑电路尚未响应新值带来的变化。 |
③到④,周期 4 的过程 | 取出并执行 je 指令 |
④,周期 4 末尾 | 同样,程序计数器虽然已经产生了新值 0x01f,但直到下个周期开始之前,状态还是保持着 addq 指令设置的值。 |
【注意】**这里提到的组合逻辑电路指的并不是同一个!**因为寄存器的存在,将整个电路分割为一个个独立的组合逻辑电路。
🌰假设,组合逻辑电路 A 的状态,经过上升沿后,改变了状态单元中的值。然而由于这些状态单元的值又可能是组合逻辑电路 B 的输入,那么下一刻,组合逻辑电路 B 的输出就会因为这些状态单元值的更新而发生新的变化。
由此可以理解👆表中,“组合逻辑电路用白色”表示的正是上升沿过后,组合逻辑电路 B 还未来得及响应新的输入值时的状态(也即图表中的①或③时)。
【SEQ 总结】用组合逻辑电路来传播值,用时钟的上升沿触发来控制状态单元的更新,足够控制 SEQ 实现中每条指令执行的计算了。每次时钟由低变高时,处理器开始执行一条新指令。
4.5 流水线的通用原理
✏️ 延迟(latency)
从头到尾执行一条指令所需要的时间成为延迟。
——组合逻辑进行的逻辑运算会产生延迟;
——寄存器加载值会产生延迟。
✏️ 吞吐量(throughput)
用每秒千兆条指令(GIPS),也即每秒十亿条指令,为单位来描述吞吐量 。
——吞吐量
=
1
=1
=1/延迟,即延迟的倒数。
4.5.1 流水线计算
1、非流水线化的计算硬件
-
数据信号输入组合逻辑,穿过一系列逻辑门,这花费了一定时间的延迟,产生了输出;
-
时钟信号在每个特定的时间间隔,将组合逻辑产生的输出值加载到寄存器。
👆图还给出了“流水线图(pipeline diagram)”:
时间从左向右移动;
从上到下写着一组操作(I1、I2、I3);
蓝色的实心长方形表示这些指令执行的时间。
✏️ 可以发现,这些蓝色方框在垂直方向上没有重叠,在开始下一条指令之前必须先完成前一条指令!
2、流水线化的计算硬件
假设将系统执行的计算分成三个阶段(A、B、C),各个阶段之间放上“流水线寄存器”,如👇图:
——每条指令都会按照三步经过这个系统,从头到尾需要三个完整的时钟周期。
——本🌰中,在时刻 240~360 之间(稳定状态),三个阶段都是活动的,每个时钟周期的上升沿,一条指令离开系统(I1),一条新的进入(I3)。
4.5.2 流水线操作的详细说明
跟踪👆图中时刻 240ps~360ps 之间的电路活动:指令 I1 经过阶段 C,I2 经过阶段 B,而 I3 经过阶段 A。
👇图中:(文字:阶段X的组合逻辑 == 图中:组合逻辑 X)
① 时刻 = 239ps,时钟信号上升之前:
指令 I2—阶段 A 的组合逻辑的输出值已经到达第一个流水线寄存器的输人端,但是还没到上升沿,所以该寄存器的状态和输出仍然保持为指令 I1—阶段 A 中组合逻辑输出的值。
![流水线1](https://i-blog.csdnimg.cn/blog_migrate/6ebf74eb5c4f991a0197c9ea9064e264.png)
【理解】🌰是三阶段流水线,只有三个流水线寄存器。流水线图中表示的好像有 9 个,但这只是为了显式地表示出三个指令执行的时间分布。因此:
在时刻 = (120+20+1)ps = 141ps,上升沿之后,指令I1—阶段A 的组合逻辑产生的输出值已经加载到阶段 A 的流水线寄存器中了。
而时刻 = 239ps,与时刻 = 120ps 处于同一时钟周期,所以对于 指令 I2 ,阶段 A 的流水线寄存器的状态尚未变化。
①-② 时刻 = 240ps,时钟信号上升时:
阶段 A 的流水线寄存器决定加载指令 I2—阶段 A 的组合逻辑产生的值。
② 时刻 = 241ps,时钟信号上升之后:经过 1ps(<20ps)
阶段 A 的流水线寄存器开始加载指令 I2—阶段 A 的组合逻辑产生的值。
<img src=“https://s2.loli.net/2021/12/17/Pay6gcuh8KsZiLX.png” alt="流水线2"width=80% />
③ 时刻 = 300ps,时钟信号上升之后,经过 60ps(>20ps):
阶段 A 的流水线寄存器完成加载指令 I2—阶段 A 的组合逻辑产生的值;
指令 I2—阶段 B 的组合逻辑开始计算输出值,因为信号在组合逻辑中经历的逻辑电路可能各不相同,因此👇图中用曲线来表示。
![流水线3](https://i-blog.csdnimg.cn/blog_migrate/4d9497dd7e72ea991792c3f183c9c47c.png)
④ 时刻 = 359ps,下一个上升沿之前:
指令 I2—阶段 A 的组合逻辑的输出值已经到达第一个流水线寄存器的输人端,但是还没到上升沿,所以该寄存器的状态和输出仍然保持为**指令 I1—阶段 B **中组合逻辑输出的值。
![流水线4](https://i-blog.csdnimg.cn/blog_migrate/d2ab82330937eea5ce693d66d3fe933c.png)
【总结】
时钟周期变大,不会影响流水线的行为,只会增加空闲状态。
时钟周期变小,小至不能满足组合逻辑的逻辑运算的时间(一般组合逻辑需要的时间都大于寄存器的新值加载时间),导致电路中的输入、输出值不是期望值。
4.5.3 流水线的局限性
1、不一致的划分
每个阶段被划分的时间不同,导致时钟周期必须大于最大的阶段延迟时间,这就导致了其他阶段会存在空闲时间,进而一条指令的总延迟会增加,系统的吞吐量下降。
2、流水线过深,收益反而下降
所谓物极必反。
时钟周期虽然变小,但是流水线寄存器的个数增加了,一个导致延迟减小,一个导致延迟增加。划分的阶段过多,流水线寄存器越多,增加的延迟越多了,虽然只有 20ps,但积少成多。
现代处理器采用了较深的(15或更多的阶段)流水线。
4.5.4 带反馈的流水线系统
相邻指令之间可能是相关的:数据相关(左)、控制相关(右,jne)
在我们的 SEQ 设计中,这些相关都是由反馈路径来解决的。这些反馈将更新了的寄存器值向下传送到寄存器文件,将新的PC值向下传送到 PC 寄存器。
将流水线引入含有反馈路径的系统中的危险
在原来的系统 (👇图 a) 中,每条指令的结果都反馈给下一条指令。
流水线图 (图 b) 就说明了这个情况,I1 的结果成为 I2 的输入,依此类推。
图 3 以最直接的方式将它转换成一个三阶段流水线,导致 I1 的结果成为 I4 的输入,导致原本 I4 的输入发生了变化,这改变了系统原本要实现的功能。
因此,引入含有反馈路径的系统,必须要正确的处理反馈带来的影响!
4.6 Y86-64 的流水线实现
4.6.1 SEQ+ 结构
作为实现流水线化设计的一个过渡步骤,我们必须稍微调整一下 SEQ 中阶段的顺序,使得更新 PC 阶段在一个时钟周期开始时执行,而不是结束时才执行。只需要对整体硬件结构做最小的改动,对于流水线阶段中的活动的时序,它能工作得更好。我们称这修改过的设计为 SEQ+。
……未完……