2.7 无条件码执行
很多早期的RISC架构发明了带条件码的指令,譬如在指令编码的头几位表示的是条件码(Conditional Code),只有该条件码对应的条件为真时,该指令才被真正执行。
这种将条件码编码到指令中的形式可以使得编译器将短小的循环编译成带条件码的指令,而不用编译成分支跳转指令。这样便减少了分支跳转的出现,一方面减少了指令的数目;另一方面也避免了分支跳转带来的性能损失。然而,这种“条件码”指令的弊端同样会使得CPU的硬件设计变得复杂,增加硬件的开销,也可能损伤时序使得CPU的主频无法提高,笔者在曾经设计此类处理器时便深受其苦。
RISC-V架构则放弃使用这种带“条件码”指令的方式,对于任何的条件判断都使用普通的带条件分支跳转指令。此选择再次印证了RISC-V追求硬件简单的哲学,因为放弃带“条件码”指令的方式可以大幅简化CPU的硬件设计,对于低功耗小面积的CPU可以选择非常简单的电路进行实现,而高性能超标量处理器由于硬件动态调度能力很强,可以有强大的分支预测电路保证CPU能够快速的跳转执行达到高性能。
2.8 无分支延迟槽
很多早期的RISC架构均使用了“分支延迟槽(Delay Slot)”,最具有代表性的便是MIPS架构,在很多经典的计算机体系结构教材中,均使用MIPS对分支延迟槽进行过介绍。分支延迟槽就是指在每一条分支指令后面紧跟的一条或者若干条指令不受分支跳转的影响,不管分支是否跳转,这后面的几条指令都一定会被执行。
早期的RISC架构很多采用了分支延迟槽诞生的原因主要是因为当时的处理器流水线比较简单,没有使用高级的硬件动态分支预测器,所以使用分支延迟槽能够取得可观的性能效果。然而,这种分支延迟槽使得CPU的硬件设计变得极为的别扭,CPU设计人员对此往往苦不堪言。
RISC-V架构则放弃了分支延迟槽,再次印证了RISC-V力图简化硬件的哲学,因为现代的高性能处理器的分支预测算法精度已经非常高,可以有强大的分支预测电路保证CPU能够准确的预测跳转执行达到高性能。而对于低功耗小面积的CPU,由于无需支持分支延迟槽,硬件得到极大简化,也能进一步减少功耗和提高时序。
2.9 无零开销硬件循环
很多RISC架构还支持零开销硬件循环(Zero Overhead Hardware Loop)指令,其思想是通过硬件的直接参与,通过设置某些循环次数寄存器(Loop Count),然后可以让程序自动地进行循环,每一次循环则Loop Count自动减1,这样持续循环直到Loop Count的值变成0,则退出循环。
之所以提出发明这种硬件协助的零开销循环是因为在软件代码中的for 循环(for i=0; i<N; i++)极为常见,而这种软件代码通过编译器编译之后,往往会编译成若干条加法指令和条件分支跳转指令,从而达到循环的效果。一方面这些加法和条件跳转指令占据了指令的条数;另外一方面条件分支跳转如存在着分支预测的性能问题。而硬件协助的零开销循环,则将这些工作由硬件直接完成,省掉了这些加法和条件跳转指令,减少了指令条数且提高了性能。
然有得必有失,此类零开销硬件循环指令大幅地增加了硬件设计的复杂度。因此,零开销循环指令与RISC-V架构简化硬件的哲学是完全相反的,在RISC-V架构中自然没有使用此类零开销硬件循环指令。
2.10 简洁的运算指令
在本章第2.1节中曾经提到RISC-V架构使用模块化的方式组织不同的指令子集,最基本的整数指令子集(I字母表示)支持的运算包括加法、减法、移位、按位逻辑操作和比较操作。这些基本的运算操作能够通过组合或者函数库的方式完成更多的复杂操作(譬如乘除法和浮点操作),从而能够完成大多数的软件操作。
整数乘除法指令子集(M字母表示)支持的运算包括,有符号或者无符号的乘法和除法操作。乘法操作能够支持两个32位的整数相乘得到一个64位的结果;除法操作能够支持两个32位的整数相除得到一个32位的商与32位的余数。
单精度浮点指令子集(F字母表示)与双精度浮点指令子集(D字母表示)支持的运算包括浮点加减法,乘除法,乘累加,开平方根和比较等操作,同时提供整数与浮点,单精度与双精度浮点彼此之间的格式转换操作。
很多RISC架构的处理器在运算指令产生错误之时,譬如上溢(Overflow)、下溢(Underflow)、非规格化浮点数(Subnormal)和除零(Divide by Zero),都会产生软件异常。RISC-V架构的一个特殊之处是对任何的运算指令错误(包括整数与浮点指令)均不产生异常,而是产生某个特殊的默认值,同时,设置某些状态寄存器的状态位。RISC-V架构推荐软件通过其他方法来找到这些错误。再次清楚地反映了RISC-V架构力图简化基本的指令集,从而简化硬件设计的哲学。
2.11 优雅的压缩指令子集
基本的RISC-V基本整数指令子集(字母I表示 )规定的指令长度均为等长的32位,这种等长指令定义使得仅支持整数指令子集的基本RISC-V CPU非常容易设计。但是等长的32位编码指令也会造成代码体积(Code Size)相对较大的问题。
为了满足某些对于代码体积要求较高的场景(譬如嵌入式领域),RISC-V定义了一种可选的压缩(Compressed)指令子集,由字母C表示,也可以由RVC表示。RISC-V具有后发优势,从一开始便规划了压缩指令,预留了足够的编码空间,16位长指令与普通的32位长指令可以无缝自由地交织在一起,处理器也没有定义额外的状态。
RISC-V压缩指令的另外一个特别之处是,16位指令的压缩策略是将一部分普通最常用的的32位指令中的信息进行压缩重排得到(譬如假设一条指令使用了两个同样的操作数索引,则可以省去其中一个索引的编码空间),因此每一条16位长的指令都能一一找到其对应的原始32位指令。因此,程序编译成为压缩指令仅在汇编器阶段就可以完成,极大的简化了编译器工具链的负担。
RISC-V架构的研究者进行了详细的代码体积分析,如图3所示,通过分析结果可以看出,RV32C的代码体积相比RV32的代码体积减少了百分之四十,并且与ARM,MIPS和x86等架构相比都有不错的表现。
图3 各指令集架构的代码密度比较(数据越小越好)
“大道至简——RISC-V架构之魂”——分成上中下三篇,本文是中篇。关注文末公众号后可查询上中下三篇的内容。
本文上接《大道至简——RISC-V架构之魂(上)》
(未完待续:请参见《大道至简——RISC-V架构之魂(下)》)