RV32F 和 RV32D单精度和双精度浮点数,乘法和除法指令分析

RV32F 和 RV32D单精度和双精度浮点数,乘法和除法指令分析

RV32F 和 RV32D:单精度和双精度浮点数

5.1 导言

尽管 RV32F 和 RV32D 是分开的,单独的可选指令集扩展,他们通常是包括在一起的。 为简洁起见,我们在一章中介绍了几乎所有的单精度和双精度(32 位和 64 位)浮点指令。 图 5.1 是一个 RV32F 和 RV32D 扩展指令集的图形表示。图 5.2 列出 RV32F 的操作码,图 5.3 列出了 RV32D 的操作码。和几乎所有其他现代 ISA 一样,RISC-V 服从 IEEE 754-2008 浮点标准[IEEE 标准委员会 2008]。 5.2 浮点寄存器 RV32F 和 RV32D 使用 32 个独立的 f 寄存器而不是 x 寄存器。使用两组寄存器的主要 原因是:处理器在不增加 RISC-V 指令格式中寄存器描述符所占空间的情况下使用两组寄存 器来将寄存器容量和带宽是乘 2,这可以提高处理器性能。使用两组寄存器对 RISC-V 指令 集的主要影响是,必须要添加新的指令来加载和存储数据 f 寄存器,还需要添加新指令用于 在 x 和 f 寄存器之间传递数据。图 5.4 列出了 RV32D 和 RV32F 寄存器及对应的由 RISC-V ABI 确定的寄存器名称。 如果处理器同时支持 RV32F 和 RV32D 扩展,则单精度数据仅使用 f 寄存器中的低 32 位。与 RV32I 中的 x0 不同,寄存器 f0 不是硬连线到常量 0,而是和所有其他 31 个 f 寄存 器一样,是一个可变寄存器。 IEEE 754-2008 标准提供了几种浮点运算舍入的方法,这有助于确定误差范围和编写数 值库。最准确且最常见的舍入模式是舍入到最近的偶数(RNE)。舍入模式可以通过浮点控 制和状态寄存器 fcsr 进行设置。图 5.5 显示了 fcsr 并列出了舍入选项。它还包含标准所需 的累积异常标志。 有什么不同之处? ARM-32 和 MIPS-32 都有 32 个单精度浮点寄存器但都只有 16 个双 精度寄存器。它们都将两个单精度寄存器映射到双精度寄存器的左右两半。x86-32 浮点数算 术没有任何寄存器,而是使用堆栈代替。堆栈条目是 80 位宽度提高精度,因此浮点数负载将 32 位或 64 位操作数转换为 80 位,对于存储指令,反之亦然。x86-32 的一个后续版本增 加了 8 个传统的 64 位浮点寄存器以及相关的操作指令。与 RV32FD 和 MIPS-32 不同,ARM32 和 x86-32 忽视了在浮点和整数寄存器之间直接移动数据的指令。唯一的解决方案是先将 浮点寄存器的内容存储在内存中,然后将其从内存加载到整数寄存器,反之亦然。

 

图 5.1:RV32F 和 RV32D 的指令图示。

5.3 浮点加载,存储和算术指令

对于 RV32F 和 RV32D,RISC-V 有两条加载指令(flw,fld)和两条存储指令(fsw, fsd)。他们和 lw 和 sw 拥有相同的寻址模式和指令格式。添加到标准算术运算中的指令有: (fadd.s,fadd.d,fsub.s,fsub.d,fmul.s,fmul.d,fdiv.s,fdiv.d),RV32F 和 RV32D 还包括平方根(fsqrt.s,fsqrt.d)指令。它们也有最小值和最大值指令(fmin.s,fmin.d, fmax.s,fmax.d),这些指令在不使用分支指令进行比较的情况下,将一对源操作数中的较 小值或较大值写入目的寄存器。

许多浮点算法(例如矩阵乘法)在执行完乘法运算后会立即执行一条加法或减法指令。 因此,RISC-V 提供了指令用于先将两个操作数相乘然后将乘积加上(fmadd.s,fmadd.d) 或减去(fmsub.s,fmsub.d)第三个操作数,最后再将结果写入目的寄存器。它还有在加 上或减去第三个操作数之前对乘积取反的版本:fnmadd.s,fnmadd.d,fnmsub.s,fnmsub.d。 这些融合的乘法 - 加法指令比单独的使用乘法及加法指令更准确,也更快,因为它们只(在 加法之后)舍入过一次,而单独的乘法及加法指令则舍入了两次(先是在乘法之后,然后在 加法之后)。这些指令需要一条新指令格式指定第 4 个寄存器,称为 R4。图 5.2 和 5.3 显示 了 R4 格式,它是 R 格式的一个变种。

RV32F 和 RV32D 没有提供浮点分支指令,而是提供了浮点比较指令,这些根据两个浮 点的比较结果将一个整数寄存器设置为 1 或 0:feq.s,feq.d,flt.s,flt.d,fle.s,fle.d。这 些指令允许整数分支指令根据浮点数比较指令设置的条件进行分支跳转。例如,这段代码在 f1<f2时,则分支跳转到 Exit:

flt x5,f1,f2 #如果 f1 < f2,则 x5 = 1;

否则 x5 = 0 bne x5,x0,Exit #如果 x5!= 0,则跳转到 Exit

 

图 5.2:RV32F 操作码表包含了指令布局,操作码,格式类型和名称。这张表与下一张表在编码上的主要 区别是:对于这张表,前两个指令第 12 位是 0,并且对于其余指令,第 25 位为 0,而在下一张表中, RV32D 中的这两个位均为 1。

 

图 5.3:RV32D 操作码表包含了指令布局,操作码,格式类型和名称。这两个图中的一些指令并不仅仅是 数据宽度不同。只有这张表有 fcvt.s.d 和 fcvt.d.s 指令,而只有另一张表有 fmv.x.w 和 fmv.w.x.指令。

 

图 5.4:RV32F 和 RV32D 的浮点寄存器。单精度寄存器占用了 32 个双精度寄存器中最右边的一半。第 3 章解释了 RISC-V 对于浮点寄存器的调用约定,阐述了 FP 参数寄存器(fa0-fa7),FP 保存寄存器(fs0- fs11)和 FP 临时寄存器(ft0-ft11)背后的基本原理。

 

图 5.5:浮点控制和状态寄存器。它保存舍入模式和异常标志。舍入模式包括向最近的偶数舍入(frm 中 的 rte,000); 向零舍入(rtz,001); 向下(−∞)舍入(rdn,010); 向上(+∞)舍入(rup,011); 以及 向最近的最大值舍入(rmm,100)。 五个累积异常标志表示自上次由软件重置字段以来在任何浮点运算 指令上出现的异常条件:NV 表示非法操作; DZ 表示除以零; OF 表示上溢; UF 表示下溢; NX 表示不精确。

 

图 5.6:RV32F 和 RV32D 转换指令。在列中列出了源数据类型,在行中列出转换的目标数据类型。

5.4 浮点转换和搬运

RV32F 和 RV32D 支持在在 32 位有符号整数,32 位无符号整数,32 位浮点和 64 位之 间浮点进行所有组合的转换(只要这个转换是有用,有意义的)。图 5.6 按源数据类型以及 转换后的目的数据类型,罗列了这 10 条指令。 RV32F 还提供了将数据从 f 寄存器(fmv.x.w)移动到 x 寄存器的指令,以及反方向移 动数据的指令(fmv.w.x)。

5.5 其他浮点指令

RV32F 和 RV32D 提供了不寻常的指令,有助于编写数学库以及提供有用的伪指令。 (IEEE 754 浮点标准需要一种复制并且操作符号并对浮点数据进行分类的方式,这启发我 们添加了这些指令。) 第一个是符号注入指令,它从第一个源操作数复制了除符号位之外的所有内容。符号位 的取值取决于具体是什么指令:

1. 浮点符号注入(fsgnj.s,fsgnj.d):结果的符号位是 rs2 的符号位。

2. 浮点符号取反注入(fsgnjn.s,fsgnjn.d):结果的符号位与 rs2 的符号位相反。

3. 浮点符号异或注入(fsgnjx.s,fsgnjx.d):结果符号位是 rs1 和 rs2 的符号位异或的 结果。 除了有助于数学库中的符号操作,基于符号注入指令我们还提供了三种流行的浮点伪指令。

1. 复制浮点寄存器:

fmv.s rd,rs 事实上是 fsgnj.s rd,rs,rs

fmv.d rd,rs 事实上是 sgnj.d rd,rs,rs。

 

图 5.7:用 C 编写的 浮点运算密集型的 DAXPY 程序

 

图 5.8:DAXPY 在四个 ISA 上生成的指令数和代码大小。它列出了每个循环的指令数量以及指令总数。 第 7 章介绍 ARM Thumb-2,microMIPS 和 RV32C 指令集。

2. 否定: fneg.s rd,rs 映射到 fsgnjn.s rd,rs,rs fneg.d rd,rs 映射到 fsgnjn.d rd,rs,rs。

3. 绝对值(因为 0⊕0= 0 且 1⊕1= 0): fabs.s rd,rs 变成了 fsgnjx.s rd,rs,rs fabs.d rd,rs 变成了 sgnjx.d rd,rs,rs。

第二个不常见的浮点指令是 classify 分类指令(fclass.s,fclass.d)。分类指令对数学库 也很有帮助。他们测试一个源操作数来看源操作数满足下列 10 个浮点数属性中的哪些属性 (参见下表),然后将测试结果的掩码写入目的整数寄存器的低 10 位。十位中仅有一位被设 置为 1,其余为都设置为 0。

 

5.6 使用 DAXPY 程序比较 RV32FD,ARM-32,MIPS-32 和 x86-32 我们现在将使用 DAXPY 作为我们的浮点基准对不同 ISA 进行比较(图 5.7)。它以双精 度计算𝑌 = 𝑎 × 𝑋 + 𝑌,其中 X 和 Y 是矢量,a 是标量。图 5.8 总结了 DAXPY 在四个不同 的 ISA 下对应的指令数和字节数。他们的代码如图 5.9 至 5.12 所示。

尽管 RISC-V 指令集强调本身的简单性,RISC-V 版本的 不管是指令数量还是代码大小,都接近或者优于其他 ISA。在此示例中,RISC-V 的比较和 执行分支指令和 ARM-32 和 x86-32 中更复杂的寻址模式,以及入栈、退栈指令节省了差不 多数量的指令。

补充说明:16 位,128 位和十进制浮点运算 修订后的 IEEE 浮点标准(IEEE 754-2008)除了单精度和双精度之外,还描述了几种新的浮 点数格式,它们称为 binary32 和 binary64。不出意料,修订后,新增了四倍精度,名为 binary128。 RISC-V 暂时计划用 RV32Q 扩展来支持新的浮点数格式。该标准还为二进制数据交换提供了两种新的数据尺寸,程序员可以会将这些浮点数以特定宽度存 储在内存或存储中,但是或许不能以这种宽度进行计算计算。它们分别是半精度(binary16) 和八元精度(binary256)。尽管标准对这两种新宽度的存储使用定义的,但 GPU 确实以半 精度计算并将它们保存在内存中。 RISC-V 的计划在向量指令RV32V中包 含半精度计算,但是前提是处理器如果支持向量半精度指令,则也必须支持半精度标量指令。令人惊讶的是,修订后标准还添加了十进制浮点数,新增的三种十进制格式分别是 decimal32,decimal64 和 decimal128。RISC-V 预留 RV32L 指令集扩展用于支持它。

IEEE 754-2008 浮点标定义了浮点数据类型,计算精 度和所需操作。它的广泛流行大大降低了移植浮点程序的难度,这也意味着不同 ISA 中的浮 点数部分可能比其他章节中描述的其他部分的指令更一致。

 

图 5.9:图 5.7 中 DAXPY 的 RV32D 代码。十六进制的地址位于机器的左侧,接下来是十六进制的语言代 码,然后是汇编语言指令,最后是注释。比较和分支指令避免了 ARM-32 和 X86-32 代码中的两条比较指令。

 

图 5.10:图 5.7 中 DAXPY 的 ARM-32 代码。 与 RISC-V 相比,ARM-32 的自动增量寻址模式可以节省 两条指令。与插入排序不同,DAXPY 在 ARM-32 上不需要压栈和出栈寄存器。

 

图 5.11:图 5.7 中 DAXPY 的 MIPS-32 代码。三个分支延迟槽中的两个填充了有用的指令。检查两个寄存 器之间是否相等的指令避免了 ARM-32 和 x86-32 中的两条比较指令。与整数加载不同,浮点加载没有延 迟槽。

 

 图 5.12:图 5.7 中 DAXPY 的 x86-32 代码。在这个例子中,x86-32 缺少寄存器的劣势在这里表现得很明 显——有四个变量被分配到了内存,而在其他 ISA 中,这些变量是被存放在寄存器中的。它展示了 x86- 32 中,如何将寄存器与零比较(test ecx,ecx)以及如何将一个寄存器清零(xor eax,eax)。

 乘法和除法指令分析

4.1 导言 RV32M 向 RV32I 中添加了整数乘法和除法指令。图 4.1 是 RV32M 扩展指令集的图形 表示,图 4.2 列出了它们的操作码。 除法是直截了当的。可以回想起如下的式子: 商 = (被除数 − 余数) ÷ 除数

或者

被除数 = 除数 × 商 + 余数 余数 = 被除数 − (商 × 除数)

RV32M 具有有符号和无符号整数的除法指令:divide(div)和 divide unsigned(divu),它们将 商放入目标寄存器。在少数情况下,程序员需要余数而不是商,因此 RV32M 提供 remainder(rem)和 remainder unsigned(remu),它们在目标寄存器写入余数,而不是商。

 

图 4.1:RV32M 指令的图示

 

图 4.2:RV32M 操作码映射包含指令布局,操作码,指令格式类型和它们的名称的表 19.2 是此图的基础。) 乘法的式子很简单:

积 = 被乘数 × 乘数 它比除法要更为复杂,是因为积的长度是乘数和被乘数长度的和。将两个 32 位数相乘得到 的是 64 位的乘积。为了正确地得到一个有符号或无符号的 64 位积,RISC-V 中带有四个乘 法指令。要得到整数 32 位乘积(64 位中的低 32 位)就用 mul 指令。要得到高 32 位,如果 操作数都是有符号数,就用 mulh 指令;如果操作数都是无符号数,就用 mulhu 指令;如 果一个有符号一个无符号,可以用 mulhsu 指令。在一条指令中完成把 64 位积写入两个 32 位寄存器的操作会使硬件设计变得复杂,所以 RV32M 需要两条乘法指令才能得到一个完整 的 64 位积。

对许多微处理器来说,整数除法是相对较慢的操作。如前述,除数为 2 的幂次的无符号 除法可以用右移来代替。事实证明,通过乘以近似倒数再修正积的高 32 位的方法,可以优 化除数为其它数的除法。例如,图 4.3 显示了 3 为除数的无符号除法的代码。

 

图 4.3:RV32M 中用乘法来实现除以常数操作的代码。要证明该算法适用于任何除数需要仔细的数值分析, 而对于其它除数,其中的修正步骤更为复杂。算法正确性的证明以及产生倒数和修正步骤的算法中可以找到。

有什么不同之处? 长期以来,ARM-32 只有乘法而无除法指令。直到第一台 ARM 处理器 诞生的大约 20 年后(2005 年),除法指令才成为 ARM 的必要组成部分。MIPS-32 使用特殊 寄存器(HI 和 LO)作为乘法和除法指令的唯一目标寄存器。虽然这种设计降低了早期 MIPS 处理器实现的复杂性,但它需要额外的移动指令以使用乘法或除法的结果,这可能会降低性 能。HI 和 LO 寄存器也会增加架构状态,使得在任务之间切换的速度稍慢。

补充说明:mulhsu 对于多字有符号乘法很有用 当乘数有符号且被乘数无符号时,mulhsu 产生乘积的上半部分。当乘数的最高有效字(包 含符号位)与被乘数的较低有效字(无符号)相乘时,它是多字有符号乘法的子步骤。该指 令将多字乘法的性能提高了约 15%。

补充说明:检查是否除零也很简单 要测试除数是否为零,只需要在除法操作之前加入一条用于测试的 beqz 指令。RV32I 不会 因为除零操作而 trap,因为极少数程序需要这种行为,而且在那些软件中可以很容易地检查 是否除零。当然,除以其它常数永远不需要检查。

补充说明:mulh 和 mulhu 可以检查乘法的溢出 如果 mulhu 的结果为零,则在使用 mul 进行无符号乘法时不会溢出。类似地,如果 mulh 结果中的所有位与 mul 结果的符号位匹配(即当 mul 结果为正时 mulh 结果为 0,mul 结 果为负时 mulh 结果为十六进制的 ffffffff),则使用 mul 进行有符号乘法时不会溢出。

为了为嵌入式应用提供最小的 RISC-V 处理器,乘法和除法被归入 RISC-V 的第一个可 选标准扩展的一部分 RV32M。许多 RISC-V 处理器将包括 RV32M。

 

posted @   吴建明wujianming  阅读(169)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2022-11-16 晶体管-ADAS-氢能技术分析
2021-11-16 自研GPU之火(续)
2021-11-16 Dorado用法与示例
2021-11-16 如何在CPU上优化GEMM矩阵乘法
2021-11-16 GPU创业之火
点击右上角即可分享
微信分享提示