本帖最后由 RISCVLAR 于 2020-12-30 17:12 编辑
【来源:https://www.rvmcu.com/article-show-id-537.html】
自RISC和CISC战争在1990年代后期爆发以来,人们就宣称RISC和CISC不再重要。许多人会指出指令集是无关紧要的。
但是指令集其实很重要,因为他们限制了可以轻松添加到微处理器的优化类型。我最近一直在学习有关RISC-V指令集体系结构(ISA)的更多信息,以下是我对RISC-V ISA最印象深刻的一些方面:
1.这是一个RISC指令集,它很小且易于学习(基础为47个)。对于任何对学习微处理器感兴趣的人都非常友好。 2.大学中用于数字设计教学的主导架构。 3.它经过精心设计,可让CPU制造商使用RISC-V ISA创建高性能微处理器。 4.无需授权费,并且被设计为允许简单的硬件实现,那么专业的业余爱好者原则上就可以在合理的时间内进行自己的RISC-V CPU设计。 5.易于修改和使用的开源设计。
RISC的复仇
正如我开始了解RISC-V的好,我认识到,RISC-V其实是一个根本性的转,因为它让我们回到了那个好多人认为已经过去的计算时代。在设计方面,RISC-V就好像回到了上世纪**十年代的经典RISC 时代。
在随后的几年中,许多人指出RISC和CISC的区别不再重要,因为像ARM这样的RISC CPU添加了很多指令,许多指令相当复杂,以至于今天它比纯RISC CPU更像是一种混合。对于其他RISC CPU(例如PowerPC)也有类似的看法。
相比之下,RISC-V则是RISC CPU中的硬核。实际上,如果您阅读有关RISC-V的讨论,您会发现有人声称RISC-V是由一些拒绝与时俱进的老派RISC激进分子制造的。
前ARM工程师Erin Shepherd几年前对RISC-V发表了有趣的评论:RISC-V ISA追求极简主义,这是一个错误。因为他们过分强调了最小化指令数量,规范化编码等。这种极简主义的追求导致错误的正交性(例如将相同的指令重新用于分支,调用和返回),并且需要多余的指令,这会影响代码密度。指令的大小和数量。
让我快速介绍一下。保持较小的代码对性能有利,因为这样可以更轻松地将正在运行的代码保持在高速CPU缓存中。
这里的批评是RISC-V设计师过于关注使用小的指令集。这毕竟是最初的RISC目标之一。
这样声称的结果是,一个现实的程序将需要更多的指令来完成工作,从而占用更多的内存空间。
多年以来的传统常识是,RISC处理器应添加更多指令并变得更像CISC。这个想法是,更专业的指令可以代替多个通用指令的使用。
压缩指令和宏操作融合
但是,CPU设计中特别存在两项创新,这些创新从许多方面使添加更多复杂指令的策略变得多余:
- 压缩指令-指令在内存中进行压缩,并在CPU的第一阶段进行解压缩。
- 宏操作融合-将CPU读取的两个或更多简单指令融合为一个复杂指令。
ARM实际上已经采用了这两种策略,而x86 CPU则采用了后者,因此这并不是RISC-V的新招。
但是,这里有一个关键点:RISC-V从这些策略中获得了更大的优势,其原因有两个:
1.从一开始就添加了压缩指令。ARM上使用的Thumb2压缩指令格式必须通过将其添加为单独的ISA进行改进。这需要一个内部模式开关和单独的解码器来处理。但在RISC-V方面,压缩指令可以添加到带有最少400个额外逻辑门(AND,OR,NOR,NAND门)的CPU中。
2.RISC对保持唯一指令数量低的痴迷得到了回报。压缩指令带来更多空间。
指令编码
后一部分需要一些阐述。在RISC架构上,指令通常为32位宽。这些位需要用于编码不同的信息。例如,假设有一条这样的指令(hash marks comments):ADD x1,x4,x8#x1←x4 + x8
这的注册内容x4和x8结果存储到x1。我们需要对此进行编码的位数取决于我们拥有的寄存器数量。RISC-V和ARM64具有32个寄存器。数字32可以用5位表示:2^5= 32
由于必须指定3个不同的寄存器,因此总共需要15位(3×5)来编码操作数(用于加法运算的输入)。
因此如果我们希望在我们的指令集支持更多的东西,那么我们小号消耗的32bit位数越多。当然,我们可以使用64位指令,但这将消耗过多的内存,从而降低性能。
通过积极降低指令数量,RISC-V留出了更多空间来添加表示我们正在使用压缩指令的位。如果CPU看到指令中的某些位被设置,则知道应该将其解释为压缩指令。
压缩指令:二合一这意味着,我们可以将两条16位宽的指令放入32位字中,而不必在32位字中插入一条指令。自然,并非所有的RISC-V指令都可以16位格式表示。因此,根据32位指令的效用和使用频率来选择它们的子集。未压缩的指令可以使用3个操作数(输入),而压缩的指令只能使用2个操作数。因此,压缩ADD指令如下所示:C.ADD x4,x8#x4←x4 + x8
RISC-V汇编使用C.前缀来指示汇编器应将指令转换为压缩指令。但是实际上您不需要编写此代码。如果适用,RISC-V汇编程序将能够选择未压缩指令而不是未压缩指令。
基本上压缩的指令减少了操作数的数量。三个寄存器操作数将消耗15位,而只剩下1位来指定操作!因此,通过使用两个操作数,我们剩下了6位来指定操作码(执行操作)。
实际上,这与x86汇编的工作方式非常接近,在x86汇编中,保留的位数不足以拥有3个寄存器操作数。取而代之的是,x86会花费一些位来允许例如一条ADD指令从存储器和寄存器中读取输入。
宏操作融合:一对一
但是,当我们将指令压缩与宏操作融合相结合时,我们才能看到真正的收获。你看,如果CPU得到包含两个压缩的16位指令的32位字,它可以融合这些成一个单一的复杂指令。
听起来像胡说八道,难道我们不是刚回到起点吗?我们不是要避免使用CISC样式的CPU吗?
不会,因为我们避免使用很多复杂的指令,x86和ARM策略来填充ISA规范。相反,我们基本上是通过简单指令的各种组合间接地表达大量复杂指令。
在正常情况下,宏融合存在一个问题:尽管两条指令可以被一条指令代替,但它们仍然消耗两倍的内存空间。但是通过指令压缩,我们不再消耗更多空间。我们两全其美。
让我们看一下Erin Shepherd的例子之一。在对RISC-V ISA的批评中,她展示了一个简单的C函数。为了清楚起见,我重写了一下:int get_index(int * array,int i){
return array ;
}
在x86上,它将编译为:mov eax,[rdi + rsi * 4]
ret
当您以编程语言调用函数时,通常会根据已建立的约定将参数传递给寄存器中的函数,这取决于所使用的指令集。在x86上,第一个参数放置在rdi寄存器,中第二个参数放置在中rsi寄存器中。按照惯例,返回值必须放在eax寄存器中。
第一条指令将rsi中的内容乘以4。它包含我们的i变量。为什么要相乘?由于array都是由整数元素组成,因此它们之间的间隔为4个字节。因此,数组中的第三个元素实际上处于字节偏移量3×4 = 12。
之后,我们将其添加到rdi中,因为它包含了array的基础地址地址。这为我们提供了array中i元素的最终地址。我们读存储单元的内容,并将其存储在eax,任务就此完成了。
在ARM上,它非常相似:LDR r0,[r0,r1,lsl#2]
BX lr; return
在这里,我们不是与4相乘,而是r1寄存器向左移动2位,这等同于与4相乘。这可能也是x86代码中发生情况的更真实的表示。在x86上,您只能乘以2、4或8,所有这些都可以通过左移1、2或3来执行。
无论如何,您几乎可以从我的x86描述中猜测其余的内容。现在让我们进入RISC-V,真正的乐趣开始了!(hash starts comments)
SLLI a1,a1,2#a1←a1 << 2
ADD a0,a0,a1#a0←a0 + a1
LW a0,a0,0#a0←[a0 + 0]
RET
在RISC-V寄存器上,a0,a1仅是x10和x11的别名。这些是放置函数调用的第一个和第二个参数的位置。RET是伪指令(简写):JALR x0,0(ra)#sp←0 + ra
#x0←sp + 4 ingnoring resultJALR跳转到ra引用返回地址的地址。ra是x1的别名。
无论如何,这看起来简直太可怕了吧?这样简单而通用的操作的指令需要在表中进行基于索引的查找并返回。
确实确实看起来很糟。这就是为什么Erin Shepherd高度批评RISC-V团队做出的设计选择的原因。她写道:RISC-V的简化使解码器(即CPU前端)更容易,但以执行更多指令为代价。但是,缩放流水线的宽度是一个难题,而对轻微(或高度)不规则指令的解码已广为人知(当确定一条指令的长度不平凡时,主要的困难就出现了-x86在这种情况下尤其糟糕,因为他们又d众恶多前缀)。
但是,由于指令压缩和宏操作融合,我们可以解决这个问题。C.SLLI a1,2#a1←a1 << 2
C.ADD a0,a1#a0←a0 + a1
C.LW a0,a0,0#a0←[a0 + 0]
C.JR ra现在,这将占用与ARM示例完全相同的内存空间。
好的,接下来让我们做一些Macro-op融合!
RISC-V中允许将操作融合为一个的规则之一是目标寄存器是相同的。ADD和LW(加载字)指令就是这种情况。因此,CPU将这些指令转换为一条指令。
如果SLLI也是如此,我们可以将所有三个指令融合为一个。因此,CPU会看到类似于更复杂的ARM指令的内容:LDR r0,[r0,r1,lsl#2]
为什么我们不能在代码中直接编写这种复杂的宏操作?
因为我们的ISA不包含对它的支持!我们有有限的可用位数。为什么不延长说明时间呢?因为那会消耗太多内存,并更快地填充宝贵的CPU缓存。
但是,如果我们在CPU内部制造这些长的半复杂指令,则无需担心。因为在任何时候,CPU永远不会漂浮数百条指令。因此,在每个指令上浪费128位并不重要。每个人都有很多硅。
因此,当解码器获得正常指令时,通常会将其转换为一个或多个微操作。这些微操作是CPU实际处理的指令。这些可能真的很广泛,并且包含许多额外的有用信息。考虑到它们很宽,将它们称为“微型”可能看起来具有讽刺意味。但是,“微型”是指它们执行的任务数量有限。
Goldie锁定指令的复杂性
宏操作融合使解码器的工作变得微不足道:我们没有将一条指令变成多个微操作,而是采取了多种操作并将它们变成一个微操作。
因此,现代CPU中发生的事情显得有些奇怪:
1.首先,它通过压缩将两条指令组合为一条。2.然后通过解压将其分为两部分。3.通过宏操作融合将它们组合回一个操作中。
相反,其他指令最终可能会分成多个微操作,而不是被融合。为什么有些人会融合而另一些人会分拆呢?关键是最终要进行适当程度的复杂性的微操作:
- 不太复杂,因为否则它无法在为每个指令分配的固定数量的时钟周期内完成。
- 不太简单,因为那样我们就在浪费CPU资源。执行两次微操作所需的时间是执行一次微操作所需时间的两倍。
这一切都始于CISC处理器。英特尔开始将其复杂的CISC指令拆分为微操作,因此它们可以像RISC指令那样更轻松地适应其流水线。但是,在后来的设计中,他们意识到许多CISC指令是如此简单,以至于它们很容易与一种中等复杂的指令融合在一起。如果执行的指令较少,则可以更快地完成。
这样设计的好处
好的,这有很多细节,也许很难弄清重点是什么。为什么要进行所有这些压缩和融合?这听起来像很多额外的工作。
首先,指令压缩与zip压缩完全不同。“压缩”一词有点用词不当,因为立即解压缩已压缩的指令非常简单。这样做不会浪费时间。记住,对于RISC-V来说很简单。仅使用400个逻辑门,即可执行解压缩。
宏操作融合也是如此。尽管这看起来很复杂,但是这些方法已经在现代微处理器中使用。因此,已经支付了这种复杂性的税收或成本。
但是,与ARM,MIPS和x86设计人员不同,RISC-V设计人员在开始设计ISA时就知道指令压缩和宏操作融合。或更准确地说,竞争对手在设计原始ISA时对此一无所知。在设计x86和ARM指令的64位版本时,他们可能已经考虑到了这一点。为什么他们没有,我们只能推测。但是,似乎公司喜欢制作新的ISA,而这些ISA不会偏离更早的版本。通常,这是要消除过去的明显错误,而不是彻底改变哲学。
通过使用第一个最小指令集的各种测试,RISC-V设计人员取得了两个重要发现:
1.RISC-V程序通常会比其他任何CPU体系结构占用或减少内存空间。包括x86,考虑到它是CISC ISA,它本来可以节省空间。2.与其他ISA相比,它需要执行的微操作更少。
基本上,通过设计具有融合功能的基本指令集,他们能够融合足够多的指令,从而使任何给定程序的CPU执行的微操作都比竞争对手少。
这使得RISC-V团队将宏操作融合作为RISC-V的核心策略。您可以在RISC-V手册中看到很多有关可以融合哪些操作的注释。您会看到已对指令进行了修订,以便更轻松地融合以常见模式显示的指令。
将ISA保持较小意味着学生更容易学习。这意味着对于学习CPU架构的学生来说,实际上更容易构建运行RISC-V指令的CPU。
RISC-V具有每个人都必须实现的小型核心指令集。但是,所有其他指令都作为扩展的一部分存在。压缩指令只是一个可选扩展。因此,对于简单设计,可以省略。
宏操作融合只是一种优化。它不会改变整体行为,因此不需要您在特定的RISC-V处理器中实现它。
相反,对于ARM和x86,很多复杂性不是可选的。即使您尝试创建最小的简单CPU内核,也必须实现整个指令集和所有复杂的指令。
RISC-V设计策略
RISC-V吸收了我们对现代CPU的了解,并使其成为设计和ISA的选择。例如,我们知道:
- 今天,CPU内核具有先进的分支预测器。他们的预测可以在90%的时间内纠正。
- CPU内核是超标量的,这意味着它们可以并行执行多个指令。
- 使用乱序执行是超标量的。
- 它们已pipelined。
这意味着不再需要诸如ARM支持的条件执行之类的东西。在ARM上支持以指令格式占用位。RISC-V可以保存这些位。
有条件执行的最初目的是避免分支,因为分支对pipeline不利。为了使CPU快速运行,通常会预取下一条指令,以便在上一条指令完成其第一阶段后立即选择下一条指令。
但是对于条件分支,开始填充pipeline时,您不知道下一条指令在哪里。但是,超标量CPU可以简单地并行执行两个分支。
这也是RISV-C没有状态寄存器的原因。这在指令之间创建了依赖关系。每条指令越独立,与另一条指令并行运行就越容易。
RISC-V策略基本上是,我们如何才能使ISA尽可能简单,并尽可能简化RISC-V CPU的最小实现,而又无需做出使高性能CPU成为可能的设计决策。
|