除法

RV32M 具有有符号和无符号整数的除法指令:divide(div)和 divide unsigned(divu),它们将商放入目标寄存器。

在少数情况下,程序员需要余数而不是商,因此 RV32M 提供 remainder(rem)和 remainder unsigned(remu),它们在目标寄存器写入余数,而不是商。

乘法

乘法比除法要更为复杂,是因为积的长度是乘数和被乘数长度的和。将两个 32 位数相乘得到的是 64 位的乘积。为了正确地得到一个有符号或无符号的 64 位积,RISC-V 中带有四个乘法指令。

  • 要得到整数 32 位乘积(64 位中的低 32 位)就用 mul 指令。
  • 要得到高 32 位,如果操作数都是有符号数,就用 mulh 指令;
  • 如果操作数都是无符号数,就用 mulhu 指令;
  • 如果一个有符号一个无符号,可以用 mulhsu 指令。

在一条指令中完成把 64 位积写入两个 32 位寄存器的操作会使硬件设计变得复杂,所以 RV32M 需要两条乘法指令才能得到一个完整的 64 位积。 对许多微处理器来说,整数除法是相对较慢的操作。如前述,除数为 2 的幂次的无符号除法可以用右移来代替。事实证明,通过乘以近似倒数再修正积的高 32 位的方法,可以优化除数为其它数的除法。例如,图 4.3 显示了 3 为除数的无符号除法的代码。

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

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

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

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