浮点乘加器 FMA(Fused Multiply-Add,融合乘加)

$R=Round(A\times B+C)$

乘积不先舍入,先以足够高的内部精度与 (C) 相加,最后只舍入一次。

与依次执行浮点乘法、浮点加法不同 $R_{\text{非融合}}=Round\big(Round(A\times B)+C\big)$

因此,FMA 具有更好的数值精度、更少的指令数和更高的计算吞吐率

RISC-V 的 FMADDFMSUBFNMSUBFNMADD 都采用这种一次舍入语义,并使用三个源寄存器和一个目的寄存器的 R4 型指令格式


基本算法流程

设三个输入为:

$$
A=(-1)^{S_A}M_A2^{E_A}\
B=(-1)^{S_B}M_B2^{E_B}\
C=(-1)^{S_C}M_C2^{E_C}
$$

其中 M 是包含隐藏位的有效数

整体流程可以概括为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
A、B、C

├── 解包和特殊值判断

├── A、B 尾数相乘,指数相加

├── 乘积与 C 指数对齐

├── 根据符号执行加法或减法

├── 规格化

├── 一次最终舍入

└── 打包并产生异常标志

1. 输入处理

  1. 从 IEEE 754 编码中提取:

    • 符号位
    • 指数字段
    • 小数字段
    • 隐藏的最高有效位
  2. 判断操作数属于:

    • 规格化数;
    • 非规格化数;
    • 正零或负零;
    • 正无穷或负无穷;
    • quiet NaN;
    • signaling NaN。

高性能实现通常让“普通数数据通路”和“特殊值判断通路”并行运行,最后由选择器决定输出,避免特殊值判断拉长主数据通路。

2. 计算乘积

  • 乘积符号: $S_P=S_A\oplus S_B$
  • 乘积指数:$E_P=E_A+E_B-\text{Bias}$
  • 尾数乘积:$M_P=M_A\times M_B$

对于不同格式,精确乘积宽度为有效数精度 (p) 的 2 倍

3. 指数比较与对齐

接下来

  1. 比较乘积 $P=A\times B$ 与加数 $C$ 的指数 $D=E_P-E_C$
  2. 将指数较小的操作数右移,使两者的小数点对齐, 右移过程保留 GRS 位

4. 尾数加减

  • 如果乘积与加数符号相同,则执行加法:$M_R=M_P’+M_C’$
  • 如果符号不同,则执行减法:$M_R=\left|M_P’-M_C’\right|$

5. 规格化和舍入

操作处理和浮点加法一致

6. 输出检查

FMA可能设置:

  • NV:Invalid Operation;
  • OF:Overflow;
  • UF:Underflow;
  • NX:Inexact。

FMA本身不产生除零异常 DZ。典型无效运算包括:$\infty\times0, \infty\times -\infty$

RISC-V 规范要求即使加数是 quiet NaN,$\infty\times0$ 仍应设置无效操作标志。


现代 FMA 的核心硬件结构

1. 乘法器部分

只针对尾数部分,同整数乘法器


2. 将加数直接注入压缩树

朴素实现是:尾数乘法器 → 完整乘积 → 大加法器加 C, 但这种结构需要先完成一次宽进位传播,再做第二次宽加法,延迟较大。

主流融合结构:

graph LR
   S1[A×B 部分积]
   S2[对齐后的 C]
   S3[Carry-Save 压缩树]
   S4[最终 CPA]
   S1 --> S3
   S2 --> S3
   S3 --> S4

把对齐后的 C 直接作为额外一行操作数注入乘法部分积压缩树,避免先形成完整乘积再相加

不同 FMA 架构通常围绕部分积压缩、加数注入、规格化和舍入的组合方式展开([TechRxiv][1])


3. Close Path 与 Far Path

高性能浮点加法器和 FMA 经常采用双路径或多路径结构。

Far Path

当指数差很大时:$|E_P-E_C|\gg0$

同双通路浮点加法器的 far path

Close Path

当指数接近且符号相反时,可能发生严重相消:$E_P\approx E_C, S_P\neq S_C$

同双通路浮点加法器的 close path