一名前ARM工程师批评RISC-V

2020-11-01 19:59:10

这份文件最初是几年前写的。当时我在ARM担任执行核心验证工程师。通过在不同处理器的执行核心内和周围工作,以下几点被涂上了浓墨重彩的色彩。加一小撮盐;观点包含不同程度的意见。

我仍然认为RISC-V可以设计得更好;尽管我也会说,如果我现在正在构建一个32位或64位的CPU,我很可能会实现该体系结构以受益于现有的工具。

主要基于RISC-V ISA规范v2.0。对v2.2进行了一些更新。

RISC-V ISA追求极简主义到了极致。对最小化指令计数、标准化编码等有很大的重视。这种对最小化的追求导致了错误的正交性(例如对分支、调用和返回重复使用相同的指令),并且需要多余的指令,这在指令的大小和数量方面都会影响代码密度。

这是一个简单的数组索引示例,这是一个非常常见的操作。请考虑x86_64的编译方式:

#对任何语法错误表示歉意-没有任何在线RISC-v#编译器slli a1,a1,2添加a0,a1,a1lw a0,a0,0jalr r0,r1,0//return。

RISC-V&V;的简化使解码器(即CPU前端)更容易,但代价是执行更多指令。然而,缩放流水线的宽度是一个难题,而对轻微(或高度)不规则指令的解码是很容易理解的(当确定指令的长度不是平凡的时候,主要的困难就出现了-x86在这种情况下尤其糟糕,因为它的前缀很多)。

指令集的简化不应该追求到极限。寄存器+移位寄存器内存操作不是一条复杂的指令,它是程序中非常常见的操作,CPU很容易执行。如果CPU不能直接执行指令,它可以相对容易地将其分解为组成操作;这比融合简单操作序列要容易得多。

我们应该区分CISC CPU的复杂指令-复杂、很少使用和普遍低性能,与CISC和RISC CPU通用的功能丰富的指令(结合了较小的操作序列)是常用的,并且性能很高。

高度不受约束的可扩展性。虽然这是RISC-V的一个目标,但它也是一个支离破碎、不兼容的生态系统的配方,必须极其谨慎地管理。

用于调用、返回和寄存器间接分支的相同指令(JALR)(需要额外解码才能进行分支预测)。

可变长度编码不是自同步的(这很常见--例如x86和Thumb-2都有这个问题--但是它会在实现和安全性方面造成各种问题,例如面向返回的编程攻击)。

RV64I需要所有32位值的符号扩展。这会产生不必要的上半部分切换或需要寄存器上半部分的特殊调节。最好是零扩展(因为它减少了切换,并且一旦上半部分已知为零,通常可以通过跟踪为零的位来优化)。

乘法是可选的-虽然快速乘法器在微型实现中占用不可忽略的面积,但可以创建占用面积很小的小乘法器,并且可以广泛重用现有的ALU进行多周期乘法。

LR/SC对有限的使用子集有严格的最终前进进度要求。虽然此约束相当严格,但对于小型实现(特别是那些没有缓存的实现),它确实可能会带来一些问题。

FP粘滞位和舍入模式在同一寄存器中。如果执行RMW操作来更改舍入模式,这需要对FP管道进行序列化

FP指令编码为32、64和128位精度,但不是16位(这在硬件中比128位要常见得多)更新:V2.2具有十进制FP扩展占位符,但没有半精度占位符。我的头脑有点不对劲。

FP值在FP寄存器文件中的表示方式未指定,但可以观察到(通过加载/存储)。

没有条件代码,而是比较和分支指令。这本身并不是问题,而是它的含义:由于需要编码一个或两个寄存器说明符,减少了条件分支中的编码空间。

(请注意,这仍然比ISA更好,后者将标志写入GPR,然后根据生成的标志进行分支)。

用户级ISA似乎需要高精度计数器。在实践中,将这些应用程序暴露给应用程序是侧通道攻击的一个很好的载体。

乘法和除法是同一扩展的一部分,似乎如果一个实现了,另一个也必须实现。乘法比除法简单得多,在大多数CPU上都很常见,即使在除法不是这样的情况下也是如此。

基本ISA中没有原子指令。多核微控制器越来越普遍,LL/SC型原子芯片价格低廉(最小的单CPU实现只需要1位CPU状态)。

LR/SC与更复杂的原子指令在同一扩展中,这限制了小型实现的实现灵活性

通用(非LR/SC)原子不包括CAS原语动机是为了避免需要读取5个寄存器(addr、CmpHi:CmpLo、SwapHi:SwapLo)的指令,但这对实现造成的开销可能比为替换它而提供的有保证的转发进度LR/SC要少。

提供了对32位和64位数量进行操作的原子指令,而不是对8位或16位数量进行操作的原子指令。

对于RV32I,除非通过内存,否则无法在整数和FP寄存器堆之间传输DP FP值。

例如,RV32I 32位添加和RV64I 64位添加共享编码,而RVI64添加了不同的ADD.W编码。对于同时实现这两条指令的CPU来说,这是不必要的复杂性-最好改为添加新的64位编码。

无MOV说明。MV汇编器别名实现为MV RD,RS->;Addi RD,RS,0。MOV优化通常由高端处理器执行(尤其是无序的);识别RISC-V的规范MV需要在没有MOV指令的情况下或12位立即数,加上RD、RS、R0实际上是较佳的规范MOV,因为它更容易解码,并且CPU通常具有识别零寄存器的特殊情况逻辑。

JAL浪费5位对链接寄存器进行编码,它将始终为R1(或分支为R0)。这意味着RV32I具有21位的分支位移(不足以用于大型应用程序-例如Web浏览器-无需使用多个指令序列和/或分支孤岛)。

尽管在统一编码上花费了大量精力,但加载/存储指令的编码方式不同(寄存器与立即字段交换)似乎首选目标寄存器编码的正交性,而不是编码两条高度相关指令的正交性。考虑到地址生成是计时更为关键的操作,这种选择似乎有点奇怪。

FENCEI.表示指令缓存与所有前面的存储完全同步,无论是栅栏存储还是非栅栏存储。实现将需要刷新围栏上的整个I$,或者同时监听D$和存储缓冲区

在RV32I中,读取64位计数器需要读取上半部分两次,比较和分支以防在读取操作期间在上半部分和下半部分之间发生进位。通常32位ISA包括一对特殊寄存器的读取指令,以避免此问题。

没有架构定义的提示编码空间。提示编码是那些在当前处理器上作为NOP执行,但在以后的变体上有一些行为的编码。还实现了更复杂的提示(即,那些对新处理器有明显副作用的提示;例如,x86边界检查指令在提示空间中编码,以便二进制保持向后兼容)。