微处理器设计/指令集架构
指令集或指令集架构 (ISA) 是处理器理解的基本指令集。指令集是构成架构的一部分。
历史上,指令集的最初两种哲学是:简化 (RISC) 和复杂 (CISC)。两种哲学的优点和争论的性能优势一直受到深入的讨论。
复杂指令集计算机 (CISC) 来源于计算的历史。最初没有编译器,程序必须手动逐条指令编码。为了简化编程,增加了越来越多的指令。其中许多指令是复杂的组合指令,例如循环。一般来说,更复杂或更专门的指令在硬件中效率低下,在典型的 CISC 架构中,可以使用 ISA 中最简单的指令才能获得最佳性能。
最知名/商品化的 CISC ISA 是 Motorola 68k 和 Intel x86 架构。
精简指令集计算机 (RISC) 是由 IBM 在 1970 年代后期实现的。研究人员发现,大多数程序都没有利用所有可用于指令的各种寻址模式。通过减少寻址模式的数量并将多周期指令分解为多个单周期指令,可以实现以下几个优势
- 编译器更容易编写(更容易优化)。
- 对于执行简单操作的程序,性能得到提升。
- 由于最小循环时间由运行时间最长的指令决定,因此可以提高时钟频率。
最知名/商品化的 RISC ISA 是 PowerPC、ARM、MIPS 和 SPARC 架构。
我们将在后面的章节中讨论VLIW 处理器。
我们将在后面的章节中讨论向量处理器。
计算 RAM (C-RAM) 是将处理器集成到设计的半导体随机存取存储器,用于构建廉价的大规模并行计算机。
指令通常在内存中按顺序排列。每个指令占用 1 个或多个计算机字。程序计数器 (PC) 是微处理器内部的一个寄存器,它包含当前指令的地址。[1] 在取指周期中,从程序计数器指示的地址读取内存中的指令到指令寄存器 (IR) 中,程序计数器增加 n,其中 n 是机器的字长(以字节为单位)。
除了可执行指令的取指之外,许多(但不是全部)指令还会从内存中取指数据值(“加载”)到数据寄存器,或者将数据值从数据寄存器写入内存(“存储”)。在这样的加载或存储指令中访问的特定内存字的地址称为“有效地址”。在最简单的指令集中,有效地址始终包含在某个地址寄存器中。其他指令集具有更复杂的“有效地址”计算 - 我们将在后面讨论这些“寻址模式”。
移动指令导致将一个寄存器中的数据移动或复制到另一个寄存器。加载指令将数据从外部源(例如内存)放入寄存器。存储指令将数据从寄存器移动到外部目标。
将数据从一个地方移动(或复制)到另一个地方的指令是大多数程序中使用频率最高的指令。[2]
分支和跳转是将 PC 寄存器加载到一个新的地址的能力,这个地址不是下一个顺序地址。通常,"跳转"或"调用"无条件发生,而"分支"在给定条件下发生。在这本书中,我们将一般地将两者都称为分支,其中“跳转”是无条件分支。
“调用”指令是分支指令,其额外作用是将当前地址存储到特定位置(例如将其压入堆栈),以便于轻松返回以继续执行。“调用”指令通常与“返回”指令匹配,该指令检索存储的地址并恢复执行。
“中断”指令是调用预设位置的指令,通常是某种方式编码在指令本身中的指令。这通常用于访问常用的资源,例如操作系统。通常,通过中断指令进入的例程通过中断返回指令退出,该指令与返回指令类似,它检索存储的地址并恢复执行。
在许多程序中,“调用”是第二常用的指令(仅次于“移动”)。[2]
算术逻辑单元 (ALU) 用于执行算术和逻辑指令。ALU 的功能通常随着更高级的中央处理器而增强,但 RISC 机器的 ALU 故意保持简单,因此只具有一些功能。ALU 至少会执行加法、减法、非、与、或和异或,通常也会执行单比特旋转和移位。许多 CISC 机器 ALU 还可以执行多比特旋转和移位(使用桶形移位器)以及整数乘法和除法。虽然许多现代 CPU 也可以执行浮点数学运算,但这些通常由 FPU 处理,FPU 是机器的不同部分。我们在ALU 设计章节中更详细地描述了 ALU。
输入指令从指定的输入端口获取数据,而输出指令将数据发送到指定的输出端口。输入/输出空间和内存空间之间的区别很小,微处理器提供一个地址,然后从数据总线接收数据或向数据总线发送数据,但是输入/输出空间中可用的操作类型通常比内存空间中可用的操作类型更有限。
NOP,是“无操作”的缩写,是一种不产生结果也不产生副作用的指令。NOP 在计时和防止危险方面很有用。
人们平衡各种指令长度的各种优缺点,有几种不同的方法。
对于 CPU 来说,固定长度指令比可变宽度指令更容易处理,原因有以下几点,因此在一定程度上更容易优化速度。例如:具有可变长度指令的 CPU 必须检查每个指令是否跨越缓存行或虚拟内存页边界;具有固定长度指令的 CPU 可以跳过所有这些。[3]
在 16 位指令中,没有足够的位来容纳 32 个通用寄存器,而且还做“Ra = Rb (op) Rc”Template:Snd,即独立地从 32 个通用寄存器组中选择 2 个源寄存器和 1 个目标寄存器,并独立地选择几种 ALU 操作之一。
因此,设计指令集的人员必须做出以下一项或多项折衷
- 牺牲代码密度,使用更长的固定宽度指令,通常为 32 位,例如 MIPS、DLX 和 ARM。
- 牺牲固定宽度指令,需要更复杂的解码器来处理短的 16 位指令和更长的 3 操作数指令,例如 ARM Thumb。
- 牺牲 3 操作数,在所有指令中使用不超过 2 个操作数,例如 Atmel AVR。3 操作数指令可以更好地重用数据;[3]没有 3 操作数指令,程序偶尔需要额外的复制指令,当某些 ALU 操作的两个可变输入操作数都需要为某些后面的指令保留时。
- 牺牲寄存器,因此只有 16 或 8 个程序员可见的寄存器。
- 牺牲通用寄存器的概念——也许只有 16 或 8 个“数据寄存器”对 3 操作数 ALU 指令可见,就像在 68000 中一样,或者目标寄存器仅限于一个或两个“累加器”,但其他寄存器(例如“地址寄存器”)对其他指令可见。
对于任何一个特定的 CPU,任何一个特定的机器语言指令通常可以细分为字段。例如,在“ADD”指令中的某些位指示操作——这实际上是一个“ADD”而不是一个“XOR”或“减法”指令。其他位指示哪个寄存器是源寄存器,其他位指示哪个寄存器是目标寄存器,等等。
一些处理器不仅具有固定的指令宽度,而且还具有单一指令格式——一组固定的字段,对于每个指令都是相同的。
许多处理器具有固定的指令宽度,但具有多种指令格式。存储在特定固定位置“指令类型”字段中的实际位(该字段在该 CPU 的每个指令中都位于相同的位置)指示该特定指令使用哪种指令格式——该指令使用哪种特定字段布局。例如,MIPS 处理器具有 R 型、I 型、J 型、FR 型和 FI 型指令格式。[4] 例如,J1 处理器具有 3 种指令格式:Literal、Branch 和 ALU。[5] 例如,Microchip PIC 中档具有 4 种指令格式:字节定向寄存器操作、位定向寄存器操作、8 位字面量操作和具有 11 位字面量的分支指令。[6]
偶尔,一些新的 CPU 会与其他 CPU 具有不同的指令集格式,使其与其他 CPU“不兼容”。但是,有时可以设计这种新的 CPU 使其与其他 CPU 具有“源代码向后兼容性”——它与为其他 CPU 编写的程序“兼容汇编语言,但不兼容二进制”。(例如,8080 与 8008 源代码兼容,但不兼容二进制,或者 8086 与为 8085、8080 和 8008 编写的程序源代码兼容,但不兼容二进制 [需要引用])。
- ↑ 实际上,所有现代 CPU 都保持程序计数器顺序遍历代码一次执行一条指令的假象。但是,一些复杂的现代 CPU 在内部同时执行多个指令(超标量),或乱序执行指令,甚至推测性地预执行“错误”路径上的指令,然后备份并采取正确的路径。在设计和测试这种内部结构时,“PC”的概念有点模糊。一些处理器架构,例如 CDP1802,没有单个程序计数器;相反,通用寄存器之一用作程序计数器,而哪个寄存器可以由程序控制更改。
- ↑ a b Peter Kankowski。 "x86 机器码统计"
- ↑ a b IBM 的 RISC 技术发展——IBM 研究与开发杂志,第 44 卷,第 1/2 期,第 48 页 (2000)
- ↑ MIPS 汇编/指令格式
- ↑ Victor Yurkovsky。 "自制 CPU:使用 J1 胡闹"
- ↑ "PICmicro 中档 MCU 系列:第 29 节 指令集" 第 29.2 节 指令格式。