解码
解码阶段的左右就是让处理器知道指令的作用进而知道如何处理该指令。在这一个阶段,处理器需要确定:
- 指令的类型是什么: 控制,访存,算术等。
- 指令需要执行何种操作,比如当指令是一条算术指令时,ALU将会执行操作,当是条件分支指令的时候,what condition should be evaluated,等等。
- 指令需要何种资源,比如对于一条算术指令,那些寄存器将会被读,那些寄存器会被写。
通常来说,解码阶段的输入是原始的字节流。解码单元需要确定指令的边界,然后将字节流划分为指令,然后输入各种控制信号。解码单元的复杂程度取决于指令集和并发度。
第一节将会简要的介绍RISC机器上解码工作是如何进行的。在第二节中将会描述X86的解码工作方式,并且会解释为什么ISA是如何影响解码单元的复杂性的。接下来,我们将会讲解当代的X86处理器中是如何使用动态翻译技术(dyanmic translate technique)将x86指令翻译成为类似RISC的指令。最后,我们将讲解现代的x86乱序处理器的解码流水线如何动态的将x86指令翻译为类RISC的指令。
为什么感觉最后两节没有什么区别啊
RISC的解码
一个典型的RISC解码流水线如图4.1所示。图中展示了可以同时解码四条指令的RISC超标量处理器,接下来的讨论以此为基础。

通常来说,RISC指令的解码很容易。大多数的RISC指令集的长度都是固定的, 所以在fetch buffer中间确定指令的边界以及将原始的字位传递到解码器很trivial。由于第一条指令未必和fetch buffer的开始位置对其,所以需要使用PC的低字段来确定。
RISC指令集只有少量的编码方式,也就是说操作码和操作数在指令中间的位置含有很少的变动。加之RISC指令更加”简单”,也就是他们会为流水线产生很少的控制信号,这都导致RISC的解码器更加简单。
The simplicity of RISC instructions enables high-performance processor implementations to have single-cycle decoding, using simple PLA circuits and/or small look-up tables
PLA是什么,为什么需要含有look-up table
X86指令集的解码方式
X86的指令长度是变长的,其格式如图4.2所示。

一条x86指令包含: 多达四个前缀字节(prefix bytes)(可选的),一个长度可以为1到3字节的mandatory操作码,一个包含ModR/M字节和SIB(可选)的寻址码(addressing specifier).有的指令可能需要displacement(多达四字节)或者immediate field(多达四字节).
指令前缀有好几个作用,比如, a prefix can modify the instruction operands: the segment override prefix changes the segment register of addresses, while the operand-size override prefix changes between 16- and 32-bit register identifiers.
x86解码器遇到的第一个问题就是确定指令的长度。如果想要并发的解码多个指令,我们必须知道每条指令的起始位置。由于指令长度是变化的,导致这项任务是顺序性,也就是说只有第i条指令的起始位置被确定,第i+1指令的位置才可以被确定。所以,快速的计算出来指令的长度对于性能的提升至关重要。
通过解码操作码(opcode)可以知道是否含有ModR/M,displacement 或者 immediate是否存在,通过解码ModR/M可以确定是否含有SIB。
解码操作码含有两个问题,第一个问题是,没有办法判断操作码的位置,由于操作码前面含有前缀码,所以操作码可能开始于前五个操作码中间任何一个。第一个问题是,没有办法确定操作数的长度,操作数的长度可能1到3,而且ModR/M中间的3到5位还可能作为操作数的扩展位。
x86解码器遇到的第第二个问题就是确定指令的操作数。比如,一条简单的寄存器-寄存器操作,操作数可能编码在操作数中间或者ModR/M中间。ModR/M根据其6,7位,可能包含两个或者一个操作数。
在寄存器-寄存器指令的例子中间,一个3位的操作数可以定位一个通用寄存器,但是知道真正的使用哪一个,还需要从操作数中间件获取信息(当前执行模式(current execution mode)), 某些情况下,需要从前缀码中间获取这信息(如果含有操作数长度覆盖前缀(oprand-size override prefix))。只是由于仅仅靠3位,仅仅可以知道编码8个通用寄存器,但是无法确定是哪一个架构寄存器(architectural registers)。在32位的x86中间,操作数的值为0可以表示为AL,AX,EAX,MM0或者XMM0.
通过上面的讨论可以看出来,x86的编码绝不简单。在现代的x86处理器中间,解码需要花费数个周期,解码是复杂性的重要来源。接下来,我们将会探讨高性能,乱序,超标量的处理器中的编码如何实现的。
动态翻译
一条x86指令中间包含很多语义信息,而且可能要求执行单元的多个操作。omit
omit所以,在一个乱序执行单元上高效的执行复杂指令不是一个简单的事情。
另一方面,RISC ISA的编译器会将复杂的操作分解成为多个简单的操作。omit
由于x86 CISC指令会导致执行单元的复杂化,所以从很早以前,x86处理器就决定在解码阶段动态的将x86指令翻译成为类RISC指令。最先实现设一个设计的是Intel P6和AMD K5。这样的话,既可以保证指令集的兼容,也可以让执行单元大大的简化。
如今,所有的x86处理器都动态的翻译x86指令成内部的类RISC指令格式。特别的,Intel称这些内部的指令为微操作(micro-opreation). P6的未操作含有118位,编码了一个操作码和三个操作数, 使用load/store模型。
从P6微操作的长度,我们可以推测,一个微操作的并不是对应于一条RISC指令,而是一条已经解码的指令,也就是类RISC操作的流水线控制信号。显然,当代的处理器中间的微操作和P6中间已有不同,但是我们相信理念是相同。
高性能x86解码
图4.3是Intel Nehalem架构的解码图,从中可以看出x86的解码是一个多周期的行为。在该实现中间,解码被划分为两个部分:指令长度解码器(ILD)和动态翻译器。两者被指令队列分开(IQ), 这是为了将指令长度解码器遇到复杂指令是产生的气泡隐藏起来。IQ也是使用简单的锁存器来替代。

指令长度解码器(ILD)
ILD每个周期从prefetch buffer中间读取16字节,给出每条指令的长度,分析它们的前缀,并且标识出指令的各种有助于第二阶段翻译的属性。
Instruction length decoding is sequential by nature, so it must be as fast as possible if we want to be able to predecode many instructions at high frequency
大多数编码形式的指令都是可以被ILD单周期处理,但是在Intel Core和Core 2微架构(Nehalem应该也是如此),有两种情况不可以被通用方法处理,需要使用一个6周期数据通路来处理:omit
动态翻译器
这一个阶段将从指令队列中间的指令转化为微操作。大多数的寄存器-寄存器指令被翻译为一条微操作,有些操作数为内存单元的指令被翻译多条微操作。
图4.3中间设计实现了三个简单的解码器来处理那些被翻译为单条微操作的指令,只有一个复杂解码器来处理那些可以被翻译为多条微操作的指令。如果大多数指令都是被简单解码器处理并且没有损失解码的带宽,那么这种设计是可以降低功耗的。
图4.3中MSROM是为了处理字符串指令。
本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。