许多处理器具有统一格式和宽度的指令,例如所有指令都是 32 位长的 ARM。其他处理器具有多种宽度的指令,例如 2、3 或 4 字节长,例如 8086。
- 使所有指令具有相同的宽度和统一的格式有什么好处?
- 具有多种宽度的指令有什么好处?
许多处理器具有统一格式和宽度的指令,例如所有指令都是 32 位长的 ARM。其他处理器具有多种宽度的指令,例如 2、3 或 4 字节长,例如 8086。
具有相对统一格式的固定长度指令的优点是获取和解析指令要简单得多。
对于每个周期获取一条指令的实现,固定大小的单个对齐内存(高速缓存)访问保证提供一条(且仅一条)指令,因此不需要缓冲或移位。也无需担心在单个指令中跨越高速缓存行或页面边界。
指令指针以固定的量递增(执行控制流指令时——跳转和分支除外),与指令类型无关,因此下一条顺序指令的位置可以及早使用最少的额外工作(与必须在至少部分解码指令)。这也使得每个周期获取和解析多条指令相对简单。
为每条指令提供统一的格式允许将指令简单地解析为其组件(立即值、操作码、源寄存器名称、目标寄存器名称)。解析源寄存器名称是最关键的时间;将它们放在固定位置,可以在确定指令类型之前开始读取寄存器值。(这个寄存器读数是推测性的,因为操作可能实际上并没有使用这些值,但是这种推测在错误推测的情况下不需要任何特殊的恢复,但确实需要额外的能量。)在 MIPS R2000 的经典 5 级流水线中,这允许在取指令后立即开始读取寄存器值,提供半个周期来比较寄存器值并解析分支的方向;
(解析操作码通常比源寄存器名称对时间的要求要低一些,但越早提取操作码越早开始执行。简单地解析出目标寄存器名称可以更简单地检测指令间的依赖关系;这可能主要是有帮助的尝试在每个周期执行多条指令时。)
除了更快地提供解析之外,更简单的编码还可以减少解析工作(能源使用和晶体管逻辑)。
与典型的可变长度编码相比,固定长度指令的一个次要优势是指令地址(和分支偏移)使用更少的位。这已在一些 ISA 中被利用来为模式信息提供少量的额外存储。(具有讽刺意味的是,在 MIPS/MIPS16 等情况下,表示具有更小或可变长度指令的模式。)
固定长度指令编码和统一格式确实有缺点。最明显的缺点是代码密度相对较低。指令长度不能根据使用频率或需要多少不同的信息来设置。严格的统一格式也倾向于排除隐式操作数(尽管即使 MIPS 使用隐式目标寄存器名称作为链接寄存器)和可变大小的操作数(大多数 RISC 可变长度编码都有短指令,只能访问总数量的子集)寄存器)。
(在面向 RISC 的 ISA 中,这还有一个额外的小问题,即不允许将更多工作捆绑到指令中以均衡指令所需的信息量。)
固定长度指令也使得使用大立即数(指令中包含的常量操作数)更加困难。经典 RISC 将立即数长度限制为 16 位。如果常量较大,则必须将其作为数据加载(这意味着额外的加载指令及其地址计算、寄存器使用、地址转换、标签检查等开销)或第二条指令必须提供常量的其余部分. (MIPS 提供了一个加载高立即数指令,部分假设大常数主要用于加载地址,这些地址稍后将用于访问内存中的数据。PowerPC 提供了几个使用高立即数的操作,例如,允许添加 32 - 两条指令中的立即位。
固定长度指令还使得在保持二进制兼容性(并且不需要添加操作模式)的同时扩展指令集变得更加困难。即使是严格统一的格式也会阻碍指令集的扩展,特别是对于增加可用寄存器的数量。
富士通的 SPARC64 VIIIfx 就是一个有趣的例子。它使用一个两位操作码(在其 32 位指令中)来指示一个特殊寄存器的加载,该寄存器带有两个 15 位指令扩展,用于接下来的两条指令。这些扩展提供额外的寄存器位和 SIMD 操作指示(即扩展应用扩展的指令的操作码空间)。这意味着一条指令的完整寄存器名称不仅不完全在一个固定的位置,甚至不在同一个“指令”中。(可能会注意到与 x86 的 REX 前缀的相似之处——它提供了位来扩展在指令的主要部分中编码的寄存器名称。)
(固定长度编码的一个方面是 2 的幂的暴政。虽然可以使用非 2 的幂的指令长度 [Tensilica 的 XTensa 现在有固定的 24 位指令作为其基本 ISA——使用 16 位短指令支持是一种扩展,以前它们是基本 ISA 的一部分;IBM 有一个带有 40 位指令的实验性 ISA。],这增加了一点复杂性。如果一个大小,例如 32 位,有点太短,下一个可用大小,例如 64 位,可能太长,牺牲了太多的代码密度。)
对于具有深管道的实现,解析指令所需的额外时间不太重要。硬件完成的额外动态工作和额外的设计复杂性对于添加复杂分支预测、乱序执行和其他特性的高性能实现来说意义重大。
对于可变长度指令,权衡基本上是相反的。
更大的代码密度是最明显的优势。更大的代码密度可以提高静态代码大小(给定程序所需的存储量)。这对于某些嵌入式系统尤其是微控制器尤其重要,因为它可能占系统成本的很大一部分并影响系统的物理尺寸(这会影响适用性和制造成本)。
提高动态代码大小会减少用于获取指令(从内存和缓存中)的带宽量。这可以降低成本和能源使用,并可以提高性能。较小的动态代码大小也减少了给定命中率所需的缓存大小;较小的高速缓存可以使用更少的能量和更少的芯片面积,并且可以具有更低的访问延迟。
(在具有窄存储器接口的非流水线或最小流水线实现中,在某些情况下,在一个周期中仅获取指令的一部分不会像在受获取带宽限制较少的流水线设计中那样严重损害性能。)
使用可变长度指令,可以在指令中使用大常量,而无需所有指令都很大。使用立即数而不是从数据存储器中加载常量利用了空间局部性,在流水线中更早地提供了值,避免了额外的指令,并删除了数据缓存访问。(更广泛的访问比相同总大小的多个访问更简单。)
鉴于对可变长度指令的支持,扩展指令集通常也更容易。可以通过使用超长指令来包含附加信息。(在某些编码技术的情况下——特别是使用前缀——,也可以将提示信息添加到现有指令中,从而允许与额外的新信息向后兼容。x86 已经利用这一点不仅提供了分支提示 [它们大多未使用] 还有硬件锁省略扩展。对于固定长度的编码,很难预先选择哪些操作应该保留额外的操作码,以便将来可能添加提示信息。)
可变长度编码显然使寻找下一条顺序指令的开始变得更加困难。对于每个周期只解码一条指令的实现来说,这不是一个问题,但即使在这种情况下,它也会为硬件增加额外的工作(这可能会增加周期时间或流水线长度以及使用更多能量)。对于更广泛的解码,可以使用几种技巧来降低从指令存储器块中解析出单个指令的成本。
一种主要用于微架构的技术(即,不包括在暴露给软件的接口中,而只是一种实现技术)是使用标记位来指示指令的开始或结束。这样的标记位将为指令编码的每个部分设置并存储在指令高速缓存中。这种延迟了有关指令高速缓存未命中的此类信息的可用性,但是与填充高速缓存未命中的普通延迟相比,这种延迟通常很小。仅在缓存未命中时才需要额外的(预)解码工作,因此在缓存命中的常见情况下节省了时间和精力(以一些额外的存储和带宽为代价,这有一些能量成本)。
(一些 AMD x86 实现使用了标记位技术。)
或者,标记位可以包含在指令编码中。由于标记位有效地成为操作码的一部分,因此这对操作码的分配和放置施加了一些限制。
IBM zSeries(S/360 和后代)使用的另一种技术是在第一个包中的操作码中以简单的方式对指令长度进行编码。zSeries 使用两位来编码三种不同的指令长度(16、32 和 48 位),其中两种编码用于 16 位长度。通过将其放置在固定位置,可以相对容易地快速确定下一条顺序指令的开始位置。
(更激进的预解码也是可能的。Pentium 4 使用了一个包含固定长度微操作的跟踪缓存,而最近的英特尔处理器使用了一个带有 [大概] 固定长度微操作的微操作缓存。)
显然,可变长度编码需要以包的粒度进行寻址,这通常小于固定长度 ISA 的指令。这意味着分支偏移要么丢失一些范围,要么必须使用更多位。这可以通过支持更多不同的直接大小来补偿。
同样,获取单个指令可能会更复杂,因为指令的开头可能不会与 2 的较大幂对齐。缓冲指令提取减少了这种影响,但增加了(微不足道的)延迟和复杂性。
对于可变长度指令,统一编码也更加困难。这意味着在开始对指令进行基本解析之前,必须经常解码部分操作码。这往往会延迟寄存器名称和其他不太重要的信息的可用性。仍然可以获得显着的一致性,但需要更仔细的设计和权衡权衡(这可能会在 ISA 的生命周期内发生变化)。
如前所述,随着更复杂的实现(更深的管道、乱序执行等),处理可变长度指令的额外相对复杂性会降低。在指令解码之后,具有可变长度指令的 ISA 的复杂实现往往看起来与具有固定长度指令的 ISA 非常相似。
还需要注意的是,可变长度指令的设计复杂性大部分是一次性成本。一旦一个组织学会了处理这些怪癖的技术(包括开发验证软件),这种复杂性的成本对于以后的实施来说就会降低。
由于许多嵌入式系统的代码密度问题,一些 RISC ISA 提供可变长度编码(例如,microMIPS、Thumb2)。这些通常只有两个指令长度,因此限制了额外的复杂性。
为某些 ISA 选择的一种(一种中间)替代方案是使用具有不同长度指令的固定长度指令束。通过在包中包含指令,每个包都具有固定长度指令的优点,并且每个包中的第一条指令具有固定的、对齐的起始位置。CDC 6600 使用具有 15 位和 30 位操作的 60 位捆绑包。M32R 使用带有 16 位和 32 位指令的 32 位捆绑包。
(Itanium 使用固定长度的二次幂包来支持两个 [41 位] 指令的非幂次幂,并且在少数情况下,两个“指令”被连接以允许 64 位立即数。Heidi Pan 的 [academic] Heads and Tails 编码使用固定长度的包来编码从左到右的固定长度基本指令部分和从右到左的可变长度块。)
一些 VLIW 指令集使用固定大小的指令字,但字中的各个操作槽可以是不同的(但对于特定槽是固定的)长度。因为不同的操作类型(对应时隙)有不同的信息需求,所以对不同的时隙使用不同的大小是明智的。这提供了固定大小指令的优势和一些代码密度优势。(此外,可以分配一个槽来选择性地为指令字中的操作之一提供立即数。)