RISC-V 技术细节
ISA基础和扩展
RISC-V采用模块化设计,由可选的基础部件组成,并增加了可选扩展。 ISA基础及其扩展是在工业界,研究界和教育机构之间共同开发的。基数指定指令(及其编码),控制流,寄存器(及其大小),存储器和寻址,逻辑(即整数)操作和辅助设备。仅基础可以实现简化的通用计算机,具有完整的软件支持,包括通用编译器。
标准扩展名被指定为与所有标准基础一起使用,并且彼此之间没有冲突。
许多RISC-V计算机可能会实现紧凑扩展,以降低功耗,代码大小和内存使用。还有未来计划支持虚拟机管理程序和虚拟化。与管理程序指令集扩展S一起,RVGC定义了方便地支持Unix风格的可移植操作系统接口(POSIX)操作系统所需的所有指令。
为了驯服可能实现的功能组合,定义命名法来指定它们。首先指定指令集基,编码RISC-V,寄存器位宽和变量; 例如,RV64I或RV32E。 然后遵循以规范顺序指定实现的扩展的字母(如上所述)。 基本,扩展整数和浮点计算以及用于多核计算的同步原语(基本和扩展MAFD)被认为是通用计算所必需的,因此具有简写,G。
用于嵌入式系统的小型32位计算机可能是RV32EC。一台大型64位计算机可能是RV64GC;即RV64IMAFDC的简写。
已经提出了针对标准扩展的Zxxx和针对非标准(供应商特定)扩展的Yxxx的命名方案。例如,正在讨论用于总存储排序的Ztso扩展,一种替代弱内存排序的内存一致性模型。[59]
寄存器集
RISC-V具有32个(或嵌入式变体中的16个)整数寄存器,并且在实现浮点扩展时,有32个浮点寄存器。除存储器访问指令外,指令仅对寄存器进行寻址。
第一个整数寄存器是零寄存器,其余是通用寄存器。零寄存器的存储无效,读取始终为0.使用零寄存器作为占位符可以实现更简单的指令集。例如,将rx移动到ry变为将r0添加到rx并存储在ry中。
存在控制和状态寄存器,但用户模式程序只能访问用于性能测量和浮点管理的寄存器。
没有指令来保存和恢复多个寄存器。那些被认为是不必要的,太复杂的,也许是太慢了。
存储器访问
与许多RISC设计一样,RISC-V是一种加载存储架构:指令仅对寄存器进行寻址,其中加载和存储指令传送到存储器和从存储器传送。
大多数加载和存储指令包括12位偏移和两个寄存器标识符。一个寄存器是基址寄存器。另一个寄存器是源(用于写入)或目标(用于读取)。
将偏移量添加到基址寄存器以获取地址。将地址形成为基址寄存器加偏移允许单个指令访问数据结构。例如,如果基址寄存器指向堆栈的顶部,则单个指令可以访问堆栈中的子例程的局部变量。同样,加载和存储指令可以访问记录样式结构或内存映射I / O设备。使用常数零寄存器作为基址允许单个指令访问地址零附近的存储器。
内存被解析为8位字节,字是小端序。可以使用加载和存储指令访问最多为寄存器大小的字。
访问的存储器地址不需要与它们的字宽对齐,但对对齐的地址的访问可能更快;例如,简单的CPU可以通过从对齐失败中断驱动的慢速软件仿真实现未对齐访问。
RISC-V通过确保执行线程始终按编程顺序查看其内存操作来管理CPU或线程之间共享的内存系统。但是在线程和I / O设备之间,RISC-V被简化:除了特定指令(例如fence)之外,它不保证内存操作的顺序。
fence指令保证先前操作的结果对其他线程或I / O设备的后续操作可见。 fence可以保证内存和内存映射I / O操作的组合顺序。例如。它可以分离内存读写操作,而不会影响I / O操作。或者,如果系统可以与内存并行运行I / O设备,则fence不会强制它们彼此等待。一个带有一个线程的CPU可以将fence解码为nop。
与许多RISC指令集(以及一些复杂的指令集计算机(CISC)指令集,如x86和IBM System / 360系列)一样,RISC-V缺少写回寄存器的地址模式。例如,它不会自动递增。
RISC-V与其他熟悉的成功计算机(例如x86)类似。这也降低了CPU的复杂性和成本,因为它以相同的顺序读取所有大小的单词。例如,RISC-V指令集从指令的最低寻址字节开始解码。该规范留下了非标准大端或双端系统的可能性。
一些RISC CPU(例如MIPS,PowerPC,DLX和Berkeley的RISC-I)在加载和存储中放置16位偏移量。它们通过加载高位字指令设置高16位。这允许容易地设置上半字值而不移位。但是,大多数使用上半字指令会产生32位常数,如地址。 RISC-V使用类似SPARC的12位偏移和20位上位指令组合。较小的12位偏移有助于紧凑的32位加载和存储指令选择32个寄存器中的两个,但仍有足够的位来支持RISC-V的可变长度指令编码。
立即数
RISC-V处理32位常数和地址,其指令设置32位寄存器的高20位。加载立即lui加载20位到31到12位。然后第二个指令如addi可以设置底部的12位。
该方法通过添加指令来扩展以允许与位置无关的代码,auipc通过向程序计数器添加偏移量并将结果存储到基址寄存器中来生成20个高位地址位。这允许程序生成相对于程序计数器的32位地址。
基址寄存器通常可以与负载和存储的12位偏移一起使用。如果需要,addi可以设置寄存器的低12位。在64位和128位ISA中,lui和auipc对结果进行符号扩展以获得更大的地址。
一些快速CPU可以将指令的组合解释为单个融合指令。 lui或auipc可能是与addi,货物或商店融合的好候选人。
子程序调用,跳转和分支
RISC-V的子程序调用jal(跳转和链接)将其返回地址放在寄存器中。这在许多计算机设计中更快,因为与直接在内存中的堆栈上推送返回地址的系统相比,它节省了内存访问。 jal具有20位带符号(2的补码)偏移量。偏移量乘以2,然后添加到PC以生成32位指令的相对地址。如果结果不在32位地址(即,可被4整除),则CPU可能会强制执行异常。
RISC-V CPU使用跳转和链接寄存器jalr指令跳转到计算的地址。 jalr类似于jal,但通过向基址寄存器添加12位偏移量来获取其目标地址。 (相比之下,jal为PC增加了更大的20位偏移量。)
jalr的位格式就像寄存器相对的加载和存储。与它们一样,jalr可以与指令一起使用,该指令将基址寄存器的高20位设置为32位分支,可以是绝对地址(使用lui),也可以是PC相对的(使用auipc作为位置无关代码) )。 (使用常量零基址允许单指令调用小的(偏移),固定的正或负地址。)
RISC-V循环使用jal和jalr来获得无条件的20位PC相对跳转和无条件基于寄存器的12位跳转。跳转只是使链接寄存器0,以便不保存返回地址。
RISC-V还循环使用jalr从子程序返回:为此,jalr的基址寄存器设置为由jal或jalr保存的链接寄存器。 jalr的偏移量为零,链接寄存器为零,因此没有偏移量,也没有保存返回地址。
与许多RISC设计一样,在子程序调用中,RISC-V编译器必须使用单独的指令在开始时将寄存器保存到堆栈,然后在退出时从堆栈中恢复这些指令。 RISC-V没有保存多个或恢复多个寄存器指令。这些被认为会使CPU过于复杂,而且可能会变慢。这可能需要更多的代码空间。设计人员计划通过库例程来减少代码大小,以保存和恢复寄存器。
RISC-V没有条件代码寄存器或进位。设计人员认为条件代码通过在不同的执行阶段强制执行指令之间的交互来使快速CPU更加复杂。这种选择使得多精度算术更加复杂。此外,一些数字任务需要更多的能量。
相反,RISC-V具有执行比较的短分支:等于,不等于,小于,无符号小于,大于或等于,无符号大于或等于。通过反转汇编程序中操作数的顺序,仅使用六条指令实现十个比较分支操作。例如,如果大于可以通过小于操作数的反转顺序来完成分支。[2]
比较分支具有12位有符号范围,并相对于PC跳转。[2]
RISC-V的ISA需要CPU的默认分支预测:应该预测后向条件分支。转发条件分支预测未采取。这些预测很容易在流水线CPU中解码:分支地址是添加到PC的带符号数字。后向分支具有负二的补码地址,因此在地址的最高有效位中具有一个。前向分支为零。最重要的位在操作代码中的固定位置以加速流水线。复杂的CPU可以添加分支预测器,即使在异常数据或情况下也能正常工作。
ISA手册建议通过使用默认分支预测来优化软件以避免分支停顿。这将重新使用签名的相对地址的最高位作为提示位来判断是否采用条件分支。因此,RISC-V分支的操作代码中不需要其他提示位。这使得分支操作代码中有更多位可用。简单,廉价的CPU只能遵循默认预测
算术和逻辑集
RISC-V将数学分离为最小的整数指令集(集合I),具有加,减,移位,逐位逻辑和比较分支。这些可以用软件模拟大多数其他RISC-V指令集。 (原子指令是一个值得注意的例外。)RISC-V目前缺少通常用于加速纯整数处理器中软件浮点运算的零和位域操作的计数。
整数乘法指令(集合M)包括有符号和无符号乘法和除法。包括双精度整数乘法和除法,作为产生结果的高位字的乘法和除法。 ISA文档建议CPU和编译器的实现者将标准化的高低和乘法指令序列融合为一个操作,如果可能的话。
浮点指令(集合F)包括单精度算术以及类似于整数运算的比较分支。它需要一组额外的32个浮点寄存器。它们与整数寄存器分开。双精度浮点指令(集合D)通常假设浮点寄存器是64位(即,双宽度),并且F子集与D集合协调。还定义了四精度128位浮点ISA(Q)。没有浮点的RISC-V计算机可以使用浮点软件库。
RISC-V不会导致算术错误的异常,包括溢出,下溢,低于正常和除以零。相反,整数和浮点运算都会生成合理的默认值并设置状态位。除法后,一个分支可以发现除零。状态位可以通过操作系统或周期性中断进行测试。
原子记忆操作
RISC-V支持在多个CPU和线程之间共享内存的计算机。 RISC-V的标准内存一致性模型是发布一致性。也就是说,加载和存储通常可以重新排序,但是一些加载可以被指定为必须在稍后的存储器访问之前的获取操作,并且一些存储可以被指定为必须遵循先前的存储器访问的释放操作。
基本指令集包括以fence指令形式的最小支持以强制执行存储器排序。虽然这已足够(fence r,rw提供获取和围栏rw,w提供释放),但组合操作可以更有效。
原子内存操作扩展支持两种类型的原子内存操作以实现发布一致性。首先,它提供通用的load-reserved lr和store-conditional sc指令。 lr执行加载,并尝试为其线程保留该地址。只有当预留未被来自其他来源的介入商店破坏时,才会执行稍后存储条件的sc到保留地址。如果存储成功,则将零置于寄存器中。如果失败,则非零值表示软件需要重试该操作。在任何一种情况下,预订都会被释放。
第二组原子指令执行读 - 修改 - 写序列:到目标寄存器的加载(可选地是加载 - 获取),然后是加载的值和源寄存器之间的操作,然后是结果的存储(可选地可以是商店释放)。使内存屏障可选,允许组合操作。可选操作由每个原子指令中存在的获取和释放位启用。 RISC-V定义了九种可能的操作:swap(直接使用源寄存器值);加;按位和,或,和 - 或 - ;签名和无签名的最低和最高。
系统设计可以优化这些组合操作而不是lr和sc。例如,如果交换的目标寄存器是常数零,则可以跳过加载。如果存储的值在加载后未经修改,则可以跳过存储。
IBM System / 370及其后续产品(包括z / Architecture和x86)都实现了比较和交换(cas)指令,该指令测试并有条件地更新内存中的位置:如果位置包含预期的旧值,则cas替换它具有给定的新价值;然后它返回是否进行更改的指示。但是,通常在cas之前执行简单的加载类型指令以获取旧值。经典的问题是,如果一个线程读取(加载)一个值A,计算一个新值C,然后使用(cas)将A替换为C,它就无法知道另一个线程中的并发活动是否已替换为一些其他值B然后恢复之间的A.在一些算法中(例如,其中存储器中的值是指向动态分配的块的指针),该ABA问题可能导致不正确的结果。最常见的解决方案是使用双宽度cas指令来更新指针和相邻的计数器;遗憾的是,这样的指令需要特殊的指令格式来指定多个寄存器,执行多次读写操作,并且可以进行复杂的总线操作。
lr / sc替代方案更有效。它通常只需要一个内存负载,并且最小化慢速内存操作是可取的。它也是精确的:它控制对存储器单元的所有访问,而不仅仅是确保位模式。但是,与cas不同,它可以允许活锁,其中两个或多个线程反复导致彼此的指令失败。如果代码遵循有关指令的时序和顺序的规则,则RISC-V保证前进(无活锁):1)它必须仅使用I子集。 2)为了防止重复的高速缓存未命中,代码(包括重试循环)必须占用不超过16个连续指令。 3)它必须不包括系统或围栏指令,或者在lr和sc之间采用反向分支。 4)重试循环的反向分支必须是原始序列。[2]
该规范给出了如何使用该子集来锁定数据结构的示例。
压缩子集
标准RISC-V ISA规定所有指令均为32位。这使得实现特别简单,但与具有这种指令编码的其他RISC处理器一样,导致代码大小比其他指令集更大。[2] [60]为了补偿,RISC-V的32位指令实际上是30位; 3/4的操作码空间保留用于可选(但推荐)的可变长度压缩指令集RVC,其包括16位指令。与ARM的Thumb和MIPS16一样,压缩指令只是较大指令子集的别名。与ARM的Thumb或MIPS压缩集不同,空间从一开始就被保留,因此没有单独的操作模式。标准和压缩指令可以自由混合。(字母C)
因为(像Thumb-1和MIPS16)压缩指令只是选择的较大指令子集的备用编码(别名),压缩可以在汇编程序中实现,编译器甚至不需要知道它。
RVC的原型在2011年进行了测试。原型代码比x86 PC和MIPS压缩代码小20%,比ARM Thumb-2代码大2%。它还大大减少了所需的高速缓冲存储器和存储器系统的估计功耗。
研究人员打算减少小型计算机,特别是嵌入式计算机系统的代码二进制大小。原型包括33个最常用的指令,使用以前为压缩集保留的操作代码重新编码为紧凑的16位格式。压缩是在汇编程序中完成的,编译器没有任何更改。压缩指令省略了通常为零的字段,使用了小的立即值或访问寄存器的子集(16或8)。 addi非常常见并且通常是可压缩的。
由于RISC-V和原型没有保存和恢复多个寄存器的指令,因此与Arm的Thumb集相比,大小差异很大。相反,编译器生成了访问堆栈的传统指令。然后原型RVC汇编器经常将这些转换为压缩形式的一半大小。但是,这仍然需要比保存和恢复多个寄存器的ARM指令更多的代码空间。研究人员建议修改编译器以调用库例程来保存和恢复寄存器。这些例程往往会保留在代码缓存中,因此运行速度很快,但可能不如保存多条指令快。
嵌入式子集
用于最小嵌入式CPU(集合E)的指令集以其他方式减少:仅支持16位32位整数寄存器。不应支持浮点指令(规范禁止它是不经济的),因此必须使用浮点软件库。建议使用压缩集C.特权指令集仅支持使用基址绑定地址重定位的机器模式,用户模式和内存方案。
针对RISC-V的微控制器配置文件进行了讨论,以简化深度嵌入式系统的开发。它集中在对中断的更快,简单的C语言支持,简化的安全模式和简化的POSIX应用程序二进制接口。
记者还提出了更小的非标准16位RV16E ISA:一个使用16×16位整数寄存器,使用标准的EIMC ISA(包括32位指令)。另一个提议只使用16具有8×16位寄存器的位C指令。据说完全重新编码的ISA可以使用完整的RV16EG。
特权指令集
RISC-V的ISA包含一个单独的特权指令集规范。截至2017年7月,这是初步的。
规范1.10版支持几种类型的计算机系统:
- 只有机器模式的系统,可能用于嵌入式系统,
- 具有机器模式(用于管理程序)和用户模式的系统,可能用于实现Linux。
- 每个主管下都有机器模式,虚拟机管理程序,多个主管和用户模式的系统。
这些大致对应于具有最多四个特权和安全环的系统,最多:机器,管理程序,主管和用户。每层还应该有一层薄薄的标准化支持软件,可与更具特权的层或硬件进行通信。
此ISA的总体计划是使管理程序模式与用户和管理员模式正交。基本功能是配置位,允许管理程序级代码访问管理程序寄存器,或者在访问时导致中断。该位允许管理程序模式直接处理管理程序所需的硬件。这简化了由操作系统托管的类型2管理程序。这是运行仓库规模计算机的流行模式。为了支持类型1,非托管虚拟机管理程序,该位可以使这些访问中断到管理程序。该位简化了虚拟机管理程序的嵌套,其中虚拟机管理程序在虚拟机管理程序下运行。它还被称为通过让内核使用自己的虚拟机管理程序功能和自己的内核代码来简化管理程序代码。因此,ISA的管理程序形式支持五种模式:机器,管理程序,用户,管理程序管理程序以及用户管理程序。
特权指令集规范明确定义了硬件线程或harts。多个硬件线程是功能更强大的计算机中的常见做法。当一个线程停滞,等待内存时,其他线程通常会继续。硬件线程有助于更好地利用快速无序CPU中的大量寄存器和执行单元。最后,硬件线程可以是处理中断的简单,强大的方法:不需要保存或恢复寄存器,只需执行不同的硬件线程。但是,RISC-V计算机中唯一需要的硬件线程是零线程。
现有的控制和状态寄存器定义支持RISC-V的错误和内存异常,以及少量中断。对于具有更多中断的系统,规范还定义了中断控制器。中断总是从最高权限的机器级别开始,每个级别的控制寄存器都有明确的转发位,以便将中断路由到权限较低的代码。例如,管理程序不需要包括在每个中断上执行的软件以将中断转发到操作系统。相反,在设置时,它可以设置位以转发中断。
规范中支持几种存储器系统。仅物理适用于最简单的嵌入式系统。对于大容量存储系统中缓存的内存,还有三种UNIX样式的虚拟内存系统。虚拟内存系统有三种尺寸,地址大小分别为32,39和48位。所有虚拟内存系统都支持4 KiB页面,多级页表树,并使用非常相似的算法来遍历页表树。所有这些都是为硬件或软件页面行走而设计的。为了可选地降低页表行走的成本,超大页面可以是系统页表树的更高级别中的叶页。 SV32有一个双层页表树,支持4个MiB超级页面。 SV39有一个三级页表,支持2个MiB超级页面和1个GiB gigapages。 SV48需要支持SV39。它还有一个4级页表,支持2个MiB超级页面,1个GiB gigapages和512个GiB terapages。超级页面在页面边界上对齐,以获得下一个最小页面大小。
位操作
为RISC-V制作了一个初步但未经批准的位操纵(B)ISA的大量工作。做得好,位操作子集可以帮助加密,图形和数学运算。草案中记录的包含标准是符合RV5哲学和ISA格式,代码密度或速度的实质性改进(即,指令至少减少3对1),以及大量的实际应用程序,包括预先存在的编译器支持。版本0.36包括无争议指令,用于计数前导零,计数一位,执行和补码,移位,旋转,广义位反转和随机,字节交换,位提取和存储,以及一些位操作添加压缩集(不是,否定和反向)。它还包括一个有争议的比特字段提取和放置方案,使用非标准的48位指令格式。
打包SIMD
对于简单,成本降低的RISC-V系统,建议使用浮点寄存器的位来执行并行单指令,多数据(SIMD)子字算法。这被广泛用于加速多媒体和其他数字信号处理。[2]截至2016年,这个ISA未定义,但可能类似于PA-RISC的多媒体指令:多媒体加速扩展。除了原生的64位数学运算外,PA-RISC MAX2 CPU还可以同时对四个16位子字进行算术运算,并采用多种溢出方法。它还可以将子词移动到不同的位置。 PA-RISC的MAX2有意简化。它缺乏对8位或32位子字的支持。选择16位子字大小以支持大多数数字信号处理任务。这些说明的设计和构建成本低廉。但是,它们将CPU在数字信号处理任务上的性能提高了48倍或更多,从而在1995年实现了实用的实时视频编解码器。[67] [68]
矢量集
所提出的矢量处理指令集可以使打包的SIMD集过时。设计人员希望具有足够的灵活性,CPU可以在标准处理器的寄存器中实现向量指令。如上所述,这将实现具有与多媒体ISA类似性能的最小实现。但是,真正的向量协处理器可以执行具有更高性能的相同代码。
截至2015年6月29日,矢量处理提议是一种保守,灵活的通用混合精度矢量处理器设计,适用于执行计算内核。代码可以轻松移植到具有不同向量长度的CPU,理想情况下无需重新编译。
相反,短矢量SIMD扩展不太方便。这些用于x86,ARM和PA-RISC。在这些中,字宽的变化强制改变指令集以扩展向量寄存器(在x86的情况下,从64位MMX寄存器到128位流式SIMD扩展(SSE))到256位高级矢量扩展(AVX)和AVX-512)。结果是一个不断增长的指令集,并需要将工作代码移植到新指令。
在RISC-V向量ISA中,不是固定架构中的向量长度,而是使用指令(setvl),该指令采用所请求的大小并将向量长度设置为硬件限制和所请求大小的最小值。因此,RISC-V提案更像是Cray的长矢量设计。也就是说,最多32个向量中的每个向量具有相同的长度。
应用程序指定它所需的总矢量宽度,处理器确定它可以提供的可用片上资源的矢量长度。这采用具有四个立即操作数的指令(vsetcfg)的形式,指定所需的每个可用宽度的向量寄存器的数量。总数不得超过32的可寻址限制,但如果应用程序不需要全部限制,则可以更少。矢量长度受可用片上存储器的限制除以每个条目所需的存储字节数。 (也可能存在增加的硬件限制,这反过来可能允许SIMD风格的实现。)
在向量循环之外,应用程序可以请求零向量寄存器,从而节省操作系统在上下文切换时保留它们的工作。
向量长度不仅在架构上可变,而且设计为在运行时也变化。为了实现这种灵活性,指令集可能使用可变宽度数据路径和使用多态重载的变量类型操作。计划是这些可以减少ISA和编译器的大小和复杂性。
最近的具有可变宽度数据路径的实验矢量处理器还显示出每秒操作的有利增加:秒(速度),面积(更低的成本)和瓦特(更长的电池寿命)。
与典型的现代图形处理单元不同,没有计划提供特殊硬件来支持分支预测。相反,将使用较低成本的基于编译器的预测。
外部调试系统
RISC-V的硬件辅助调试器有一个初步规范。调试器将使用传输系统(如联合测试操作组(JTAG)或通用串行总线(USB))来访问调试寄存器。标准的硬件调试接口可以支持标准化的抽象接口或指令馈送。
截至2017年1月,抽象接口的确切形式仍未定义,但提案包括一个内存映射系统,其中包含调试设备寄存器的标准化地址或命令寄存器和通信系统可访问的数据寄存器。客户声称飞思卡尔的后台调试模式接口(BDM)对某些CPU,ARM,OpenRISC和Aeroflex的LEON使用了类似的系统。
在指令馈送中,CPU将处理调试异常以执行写入寄存器的各个指令。这可以通过数据传递寄存器和直接访问存储器的模块来补充。指令馈送使调试器能够像软件一样访问计算机。它还可以最大限度地减少CPU的变化,并适应多种类型的CPU。据说这对RISC-V特别适用,因为它是为许多类型的计算机明确设计的。数据传递寄存器允许调试器将数据移动循环写入RAM,然后执行循环以便以接近调试系统数据通道最大速度的速度将数据移入或移出计算机。客户表示,MIPS科技的MIPS,英特尔的Quark,Tensilica的Xtensa以及飞思卡尔Power ISA CPU的后台调试模式接口(BDM)使用了类似的系统。