关于RISC和CISC处理器的区别,少量指令并不意味着RISC

关于RISC和CISC处理器的区别,大部分人会认为是一些特性、指令,或者晶体管数量的区别。但实际上两者的区别不能简单概括。

很少的指令并不意味着 RISC

首先,我们需要消除一些非常明显的误解。因为 RISC 的意思是精简指令集计算机,所以很多人认为 RISC 处理器只是一个没有很多指令的 CPU。如果是这样,6502 处理器将是有史以来最 RISCy 处理器之一,只有 56 条指令。甚至 Intel 8086 也算作 RISC 处理器,因为它只有 81 条指令。即使是后来的 Intel 80286 也只有大约 100 条指令。

像 AVR 这样的简单 8 位 RISC 处理器有 78 条指令。如果您查看最早的 32 位 RISC 处理器之一,例如 PowerPC 601(1993 年发布),它有 273 条指令。

MIPS32 指令集源自伯克利最初的 RISC 处理器,该处理器也有超过 200 条指令。

我们可以将其与 CISC 32 位处理器进行比较,例如 80386),它只有 170 多条指令。大约在同一时间推出的 MIPS R2000 处理器有大约 92 条指令。

对于古玩:

x86 指令列表

奔腾指令集

6502指令集

MIPS R2000 指令集

古董:

x86 指令列表

奔腾指令集

6502指令集

MIPS R2000 指令集

也就是说,诸如 x86 指令集、Pentium 指令集、6502 指令集、MIPS R2000 指令集之类的指令集都很少,但它们都不是 RISC 处理器。

几个晶体管并不意味着 RISC

CISC 和 RISC 处理器之间晶体管数量的分界点是多少?绝对不。6502 有 4528 个晶体管。第一个 ARM 处理器有 25,000 个晶体管。

或者这个有趣的小事实。摩托罗拉 68060 被认为是那个时代最具标志性的 CISCy 处理器之一,它只有 250 万个晶体管,少于 1994 年发布的 IBM PowerPC 601 的 280 万个晶体管。

如果您查看大约同时发布的 RISC 和 CISC 处理器,则没有明显的趋势表明 RISC 处理器的晶体管和指令比 CISC 处理器更少。

1990 年代初期流行的 RISC 和 CISC 处理器的晶体管和指令比较

因此,让我们得出结论,我们无法根据晶体管或指令数来区分 RISC 或 CISC 芯片。但问题仍然存在,究竟什么是 RISC 微处理器或 CISC 微处理器?

RISC 和 CISC 是不同的晶体管预算理念

当你的老板告诉你“这里有一百万个晶体管,让我成为一个快速处理器!”,那么你有很多方法可以实现它。对于相同数量的晶体管,RISC和CISC的设计者会做出不同的选择。

伯克利大学的 David A. Patterson 最出名的可能是他在 1980 年的论文《简化指令集计算机的案例》中普及了 RISC 处理器的思想。

帕特森在本文中概述的并不是芯片制造的详细蓝图,而更像是一种哲学指导方针。

在现实世界的程序中,添加这条指令会提高多少性能?硬件影响是什么?我们是否需要存储大量复杂的状态,导致上下文切换和乱序执行变得更加复杂,因为要存储大量的状态?

精心设计的简单指令组合能否以相当的性能完成相同的工作?

我们可以使用现有的算术逻辑单元 (ALU) 和 CPU 上的其他资源添加这条指令,还是需要添加很多新的东西?

如果不添加这条指令,这些晶体管还能用来做什么?更多缓存?更好的分支预测?

重要的是要了解这些规则适用于给定的晶体管预算。如果你有更多的晶体管,你可以添加更多的指令,甚至更复杂的指令。

然而,RISC 哲学优先考虑保持指令集简单。这意味着 RISC 设计人员将首先尝试通过其他方式提高性能,而不是添加如下指令:

使用晶体管添加更多缓存

更多 CPU 寄存器

更好的管道

更好的分支预测

超标量架构的架构

添加更多指令解码器

乱序执行

宏观操作融合

压缩说明

因此,设计一个好的 RISC 指令集 (ISA) 的一个关键目标是使设计不受未来微架构优化的阻碍。

这与 CISC 设计人员设计 CPU 的方式不同。为了能够提供更好的性能,CISC 设计人员将添加复杂的指令,例如引入更多状态来跟踪状态寄存器。

CISC 设计概念的问题

问题是CISC的设计者没有提前考虑。您的晶体管预算将来可能会增加。突然之间,您拥有了所有这些优秀的晶体管,您可以使用它们来创建乱序 (OoO) 超标量处理器逻辑。这意味着您在每个时钟周期解码多条指令并将它们放入指令队列中。OoO 逻辑然后找出哪些指令不相互依赖,以便它们可以并行运行。

如果您是软件开发人员,您可能会考虑函数式编程和命令式编程之间的区别。更改全局数据以获取短期性能提升可能很诱人。但是,一旦您并行运行,并且全局状态被多个函数更改,这可能会跨多个线程并行运行,这绝对是一场噩梦。

函数式编程喜欢只依赖于输入而不依赖于全局数据的纯函数。这些功能可以很容易地并行运行。同样的机制也适用于 CPU。不依赖于全局状态(例如状态寄存器)的汇编代码指令可以更容易地并行或流水线运行。

RISC-V 就是这种思维的一个很好的例子。RISC-V 没有状态寄存器。比较和跳转指令合二为一。除非运行额外的计算以确定是否发生了溢出,否则状态寄存器无法捕获整数溢出。

这应该会给你一些关于 RISC 和 CISC 之间区别的线索。

RISC 处理器设计的优先级

如果 10 条新指令对微架构没有显着影响,那么 RISC 设计人员不一定会遇到添加 10 条新指令的问题。如果需要在 CPU 中表示更多全局状态,RISC 设计人员将非常不愿意添加指令。

这种理念的最终结果是,从历史上看,向 RISC 处理器添加流水线和超标量架构比向 CISC 处理器添加更容易,因为人们避免添加会引入状态管理或控制逻辑的指令,这使得添加这些微架构创新变得困难。

这就是为什么 RISC-V 团队更喜欢做宏操作融合,而不是添加支持复杂寻址模式或整数溢出检测的指令。

RISC 的理念导致了 ad hoc 设计选择的出现,使我们能够讨论比较 RISC 和 CISC 处理器时观察到的更具体的差异。让我们看看这些。

现代 RISC 和 CISC 处理器的特点

某些设计选择不断出现在许多不同的 RISC 处理器上。通常,RISC 处理器倾向于使用固定长度的 32 位指令。也有一些例外,例如 AVR,它使用固定长度的 16 位指令。相比之下,Intel x86 处理器的指令长度为 1 到 15 个字节。摩托罗拉 68k 处理器是另一个著名的 CISC 设计,其指令长度为 2 到 10 个字节(16 位到 80 位)。

对于汇编程序员来说,可变长度指令实际上非常方便。我的第一台电脑是 Amiga 1000,它有一个摩托罗拉 68k 处理器,所以它向我介绍了 68k 组件,坦率地说,它非常整洁。它具有将数据从一个内存位置移动到另一个内存位置的指令,或者它可以将数据从寄存器 A1 给出的地址移动到寄存器 A2 给出的另一个内存位置。

; 68k 汇编代码

移动.B 4, 12 ; 内存[4] → 内存[12]

MOVE.B (A1), (A2) ; mem[A1] → mem[A2]

这样的指令使 CPU 易于编程,但这意味着无法将支持的每条指令都放入 32 位,因为表示完整的源地址和目标地址只会消耗 64 位。因此,通过使用可变长度指令8位复杂指令集cpu设计,我们可以轻松地将完整的 32 位内存地址包含在任何指令中。

然而,这种便利是有代价的。可变长度指令更难以流水线化,如果您希望超标量处理器并行解码两条或更多条指令,则很难做到这一点,因为您不知道每条指令的开始和结束位置,直到您对其进行解码。

使用超标量处理器,可以有多个指令解码器并行工作。

RISC 处理器倾向于避免使用可变长度指令,因为这不符合 RISC 不添加指令的理念,这也使得添加更高级的微架构优化变得更加困难。

定长指令不方便。您不能将内存地址放入任何操作中,只能放入特定操作,例如加载和存储指令。

RISC 处理器中的算术逻辑单元 (ALU) 只能从寄存器而不是内存中获取输入。

图片[1]-关于RISC和CISC处理器的区别,少量指令并不意味着RISC-老王博客

加载/存储架构

机器代码指令必须对正在执行的信息进行编码,例如它是在执行 ADD、SUB 还是 MUL。它还必须对传入的信息进行编码。什么是输入寄存器和输出寄存器。一些指令需要对要加载数据的地址进行编码。这是用 RISC-V 指令编码的:

上图显示了如何使用 32 位字的每一位为 RISC-V 指令集编码指令

我们执行的具体指令称为操作码(上图中的黄色),它消耗 7 位。我们指定的每个寄存器输入或输出都需要 5 位。从这里应该清楚,不可能挤入32位地址。即使是较短的地址也很困难,因为您需要位来指定操作中使用的寄存器。对于 CISC 处理器,这不是问题,因为您可以自由使用超过 32 位的指令。

这种紧凑的空间要求允许 RISC 处理器拥有我们所说的加载/存储架构。只有专用的加载和存储指令,例如 RISC-V 上的 LW 和 SW,才能用于访问内存。

对于像 68k 这样的 CISC 处理器,几乎任何像 ADD、SUB、AND 和 OR 这样的操作都可以使用内存地址作为操作数(参数)。在下面的示例中, 4(A2) 计算为内存地址,我们用它来读取 ADD 指令的操作数(参数)。最终结果也存储在那里(目标就在 68k 参数上)。

; 68k 组件

ADD.L D3, 4(A2) ; D3 + mem[4 + A2] → mem[4 + A2]

典型的 RISC 处理器,例如基于 RISC-v 指令集的处理器,需要将加载 (LW) 和存储 (SW) 作为单独的指令存储。

#RISC-V 程序集

长宽 x4, 4(x2) # x4 ← mem[x2+4]

添加 x3, x4, x3 # x3 ← x4 + x3

SW x3, 4(x2) # x3 → mem[x2+4]

不需要通过组合地址寄存器(A0到A7)来计算地址。可以直接指定一个内存地址,比如400:

; 68k 组件

ADD.L 400, D4 ; 内存[400] + D4 → D4

但即使是这样一个看似简单的操作,也需要多条 RISC 指令。

#RISC-V 程序集

长宽 x2, 400(x0) # x3 ← mem[x0 + 400]

添加 x4, x4, x3 # x4 ← x4 + x3

在许多 RISC 设计中,x0 寄存器始终为 0,这意味着即使您只对绝对内存地址感兴趣,也始终可以使用偏移加基址的形式。虽然这些偏移看起来与您在 68k 上所做的非常相似,但它们的限制要大得多,因为您总是需要适应 32 位字。对于 68k,可以给 ADD.L 一个完整的 32 位地址。您不能使用 RISC-V LW 和 SW。获取完整的 32 位地址相当麻烦。假设您希望从 32 位地址:0x00042012 加载数据,则必须分别加载高 20 位和低 12 位以形成 32 位地址。

#RISC-V 程序集

LUI x3, 0x42 # x3[31:12] ← 0x42 放入高 20 位

添加 x3, x3, 0x12 # x3 ← x3 + x3 + 0x12

长宽 x4, 0(x3) # x4 ← mem[x3+0]

实际上这可以简化为: LUI x3, 0x42 LW x4, 0x12(x3)

我记得当我从 68k 组件转移到 PowerPC(Apple 过去使用的 RISC 处理器)时,这让我很恼火。当时我认为 RISC 意味着一切都会变得更容易。我发现 x86 很麻烦而且很难处理。但是,对于汇编编码器,RISC 不像 68k 那样方便地使用 CISC 指令集。幸运的是,有一些简单的技巧可以让 RISC 处理器上的这个过程更容易。RISC-V 定义了一些伪指令来简化汇编代码的编写。使用 LA(加载地址)指令,我们可以像这样编写前面的代码:

# 带有伪指令的 RISC-V 汇编

LI x3, 0x00042012 # 扩展为 LUI 和 ADDI

长宽 x4, 0(x3)

总结一下:虽然加载/存储架构使编写汇编代码更加麻烦,但它允许我们将每条指令保持 32 位长。这意味着创建一个可以并行解码多条指令的超标量微架构需要更少的晶体管来实现。对每条指令进行流水线化变得更加容易,因为它们中的大多数都可以放入经典的 5 步 RISC 流水线中。

RISC处理器有很多寄存器

使用像 68k 这样的高级 CISC 处理器,您可以用一条指令完成很多工作。假设您要将数字从一个数组复制到另一个数组。以下是 C 语言中使用指针的示例:

// C 代码

整数数据[4] = {4, 8, 1, 2, -1};

int *src = 数据;

而 (*xs > 0)

*dst++ = *src++;

如果在 68k 处理器上将指针 src 存储在地址寄存器 A0 中,将指针 dst 存储在地址寄存器 A1 中,则可以在一条指令中将每个指针复制并向前移动 4 个字节:

; 68k 组件

MOVE.L (A0)+,(A1)+ ; mem[A1++] → mem[A2++]

这只是一个示例8位复杂指令集cpu设计,但通常您可以使用 CISC 指令执行更多操作。这意味着您需要更少的代码。结果,RISC 设计人员意识到他们的代码会变得臃肿。因此,RISC 的设计者对真实代码进行了分析,并提出了一种可以在不使用复杂指令的情况下减小代码大小的方案。他们发现很多代码只是为了在内存中加载和存储数据。通过添加大量寄存器,可以将临时结果保存在寄存器中,而无需将它们写入内存。这将减少需要执行的加载和存储指令的数量,从而减小代码的 RISC 代码大小。

因此,MIPS、SPARC、Arm(64 位)和 RISC-V 处理器有 32 个通用寄存器。我们可以比较原始的 x86,它只有 8 个通用寄存器。

复杂性的 RISC/CISC 视角

我在这个故事中试图说明的是 RISC 处理器并不比 CISC 处理器差。不同之处在于 RISC 和 CISC 的设计者选择了增加复杂性。

CISC 设计人员将复杂性置于指令集架构 (ISA) 中,RISC 设计人员宁愿将复杂性添加到他们的微架构中,但正如我一直强调的那样,他们不希望微架构中的指令集强加复杂性。

让我比较 MIPS R4000、摩托罗拉 68040 和英特尔 486 以强调哲学上的差异。它们都有大约 120 万个晶体管,并且几乎同时发布(1989 年到 1991 年)。

RISC 处理器(R4000) 为 64 位,其他为 32 位。

R4000 有一个 8 级流水线,允许比 68040 用于 6 级流水线和 486 用于 5 级流水线更高的时钟频率。

较长的管道使 100-200 Mhz 的 R4000 比 68040 的 40Mhz 和 486DX2 获得 66Mhz(更高型号的 100Mhz)多得多。

最终更快的 CISC 处理器(如 68060 和 Pentium)出现在 1993/1994 年。但同时出现了 MIPS R8000,它是一种超标量结构,可以并行解码 4 条指令。奔腾处理器每个时钟周期只能解码 2 条指令。

所以我们可以看到 RISC 设计者更喜欢花哨的微架构而不是花哨的指令。

“但现代 CISC 处理器具有复杂的微架构!”

您可能会抗议今天的 CISC 处理器具有复杂的微体系结构。他们做什么。现代 Intel 或 AMD 处理器具有多个解码器、微操作缓存、高级分支预测器和乱序 (OoO) 执行引擎。然而,这并不奇怪。记住我关于晶体管预算理念的关键点:今天每个人都有很多晶体管可供使用,所以所有高端芯片都将具有很多先进的微架构特性。他们可以负担得起他们的预算。

关键是:许多这些复杂的微架构特性是由复杂的 CISC 指令集强加的。例如,为了使管道工作,x86 处理器将其长而复杂的指令分解为微操作。微操作很简单,行为更像 RISC 操作,因此它们可以更容易地流水线化。

问题在于将 CISC 指令分解为更简单的微操作并不容易。因此,许多现代超标量 x86 处理器具有 3 个用于简单指令的指令解码器和 1 个用于复杂指令的解码器。由于您不知道每条指令的开始和结束位置,CISC 处理器必须玩一个涉及许多晶体管的复杂猜谜游戏。

RISC 处理器避免了这种复杂性,并且可以使用所有浪费的晶体管来添加更多解码器或进行其他优化,例如使用压缩指令或宏操作融合(将非常简单的指令组合成更复杂的指令)。

在不同的 CPU 设计中,指令可以以不同的方式组合或拆分。

如果您将 Apple 的 M1 处理器(基于 RISC 的处理器)与 AMD 和 Intel 的处理器进行比较,您会注意到它具有更多的指令解码器。CISC 的设计者试图通过添加微操作缓存来缓解这个问题。借助微操作缓存,CISC 处理器不必克服障碍或一遍又一遍地解码相同的复杂指令。但是,增加这个特性显然会消耗晶体管的成本。它不是免费的。因此,由于 ISA 的复杂性,您将晶体管预算浪费在微架构的复杂性上。

Arm 与 RISC-V 设计理念

比较现代 RISC 和 CISC 处理器的一个问题是 RISC 基本上胜出。再也没有人从头开始设计 CISC 处理器了。Intel 和 AMD 的 x86 处理器今天很受欢迎,主要是因为向后兼容。

如果你今天坐下来一个设计团队并告诉他们从头开始设计一个高性能处理器,你最终不会得到传统的 CISC 设计。

然而,这并不意味着在 RISC 社区中,有多少设计师倾向于 CISC 或 RISC 没有区别。现代 Arm 处理器和基于 RISC-V 的处理器是这​​种对比的一个有趣例子。

Arm 的设计师更喜欢添加复杂的指令来提高性能。请记住,不是 Arm 不是 RISC 设计。随着晶体管预算的增长,添加更复杂的指令是公平的。.

RISC-V 的设计者更感兴趣的是将 ISA 的复杂性保持在最低限度,而不是增加微架构的复杂性,从而通过使用压缩指令和宏操作融合等技巧来提高性能。我在这里讨论这些设计选择:RISC-V 微处理器的天才。

Arm 和 RISC-V 的不同选择不是随意的,而是受到非常不同的目标和市场的严重影响。Arm正在越来越多地进入高端市场。请记住,Apple 的 Arm 芯片正在与 x86 芯片正面交锋,Nvidia 很快也会这样做。

RISC-V 旨在成为一个更广泛的架构,适用于从键盘到 AI 加速器、从 gGPU 到专业超级计算机的任何事物。这意味着 RISC-V 意味着灵活性,您添加的指令越复杂,您施加的复杂性就越低,从而降低了为特定用例定制芯片的自由度。

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论