6. FMA 指令集架构简介


无疑,FMA 指令是脱胎于 AMD 的 SSE5 指令集,这在上一篇文章关于《XOP 指令集架构简介》已经介绍过,SSE5 提出了 multiply-add/subtract 类运算,AMD 方面 SSE5 指令集最后形成了 XOP 指令集(提供整数 multiply-add/subtract 处理)和 FMA4 指令集(提供 multiply-add/subtract 浮点数处理)。

Intel 正式化了 FMA 指令集并随着 AVX 指令集一起发布,AMD 随后废除了 SSE5 指令集,也正式使用 FMA 指令名称。Intel 和 AMD 的 FMA 指令集在规格和功能上完全是相同的(兼容),可是在指令名称上存在一些分歧。

6.1 FMA3 与 FMA4 指令

事实上 FMA 指令集存在两种区别:

一般地 FMA 指令指的是 FMA3 指令,也就是由 Intel 发布的 FMA 指令集,虽然存在这样的区别,但是实质上这种版本是完全一样的,下面我们来探讨一下这些区别和实质。

6.1.1 FMA3 指令

Intel 阵营中的 FMA 指令,在指令 mnemonic(助记符)方面采用了数字 123 的修饰方案,mnemonic 的数字意义有两方面:

一方面表示 operands 的寻址方式,如下:

二方面根据数字的排列顺序来表示 operands 的职责作用,如下:

下面看看这 3 条指令例子:

它们实质上是同一条指令的不同表现形式。

vfmadd132pd  ymm0, ymm1, ymm2
             ----  ----  ----
              |      |     |
              +------+-----+-------> ymm0:由 ModRM.reg 提供寻址,它的作用是被乘数
                     |     |
                     +-----+-------> ymm1:由 VEX.vvvv 提供寻址,它的作用是加数
                           |
                           +-------> ymm2:由 ModRM.r/m 提供寻址,它的作用是乘数


ymm0 = ymm0 * ymm2 + ymm1

在排列顺序上,第 1 个 operand 排在前面,因此它是被乘数,第 3 个 operand 排在中间,因此它是乘数(也就是 ymm2 是乘数的原因),第 2 个 operand 排在最后,因此它是加数(即:ymm1

vfmadd213pd  ymm0, ymm1, ymm2
             ----  ----  ----
              |      |     |
              +------+-----+-------> ymm0:由 ModRM.reg 提供寻址,它的作用是乘数
                     |     |
                     +-----+-------> ymm1:由 VEX.vvvv 提供寻址,它的作用是被乘数
                           |
                           +-------> ymm2:由 ModRM.r/m 提供寻址,它的作用是加数


ymm0 = ymm1 * ymm0 + ymm2

在排列顺序上,第 2 个 operand 排在前面,因此它是被乘数(即:ymm1),第 1 个 operand 排在中间,因此它是乘数(即:ymm0),第 3 个 operand 排在最后,因此它是加数(即:ymm2

vfmadd231pd  ymm0, ymm1, ymm2
             ----  ----  ----
              |      |     |
              +------+-----+-------> ymm0:由 ModRM.reg 提供寻址,它的作用是加数
                     |     |
                     +-----+-------> ymm1:由 VEX.vvvv 提供寻址,它的作用是被乘数
                           |
                           +-------> ymm2:由 ModRM.r/m 提供寻址,它的作用是乘数


ymm0 = ymm1 * ymm2 + ymm0

在排列顺序上,第 2 个 operand 排在前面,因此它是被乘数(即:ymm1),第 3 个 operand 排在中间,因此它是乘数(即:ymm2),第 1 个 operand 排在最后,因此它是加数(即:ymm0

这里我们看到一个规则是:FMA 指令的 operands 寻址是固定的,同时第 3 个操作数不可能是被乘数,也就是说不可能是 memory 操作数,我们可以通过改变操作数的顺序,使用两条不同的指令形式达到同样的结果,像下面的例子:

6.1.2 FMA4 指令

FMA4 是 AMD 方面的 FMA 实现方案,支持 4 个操作数,与 Intel 的 FMA 设计思路存在分歧。

AMD 的方案是,实现了一个指令 mnemonic,存在不同的 operands 排列,下面指令例子:

这条指令功能对应 Intel 的 vfmadd132pdvfmadd213pdvfmadd231pd)系列指令,执行的运算是:

由于 FMA4 支持 4 个操作数,目标操作数与源操作数可以是不同的寄存器,FMA4 指令的 mnemonic 显得简单明了。

6.2 FMA 指令的运算

FMA 指令执行的是:fused-multiply-add(融合乘加)类运算,包括一些其他的变种版本。

6.2.1 fused-multiply-add 运算

这类运算执行的是:

6.2.2 fused-multiply-subtract 运算

这类运算执行的操作是:

6.2.3 fused-multiply-Alternating Add/Subtract 运算

这类运算中,乘运算融合了加减交互运算,怎样个加减并互,看看下面的式子:

double a[4], b[4], c[4];                        // packed double
double d[4];                                    // packed double


d[0] = (a[0] * b[0]) - c[0];                   // subtract
d[1] = (a[1] * b[1]) + c[1];                   // add
d[2] = (a[2] * b[2]) - c[2];                   // subtract
d[3] = (a[3] * b[3]) + c[3];                   // add

以处理的数据是 pd(packed double)为例, 指令 vfmaddsubpd 所做运算正如上面的式子,依次低 double 做 fused-multiply-subtract 运算,接着高 double 做 fused-multiply-add 运算。

6.2.4 fused-multiply-Alternating Subtract/add 运算

这类运算和上一类运算相似,所不同的是 subtract 和 add 运算的次序,它所做的操作如下式子:

double a[4], b[4], c[4];                        // packed double
double d[4];                                    // packed double


d[0] = (a[0] * b[0]) + c[0];                   // add
d[1] = (a[1] * b[1]) - c[1];                   // subtract
d[2] = (a[2] * b[2]) + c[2];                   // add
d[3] = (a[3] * b[3]) - c[3];                   // subtract

可以看出这里是:依次低 double 做 fused-multiply-add 运算,接着高 double 做 fused-multiply-subtract 运算。

6.2.5 fused-negative multiply-add 运算

这类运算是将乘积取负,然后相加,它所做的操作是:

6.2.6 fused-negative multiply-subtract 运算

和上一类运算所不同的是:融合 subtract 运算,它所做的操作是:

可以看到,整个 FMA 指令集包括这 6 类运算,处理的数据类型包括:packed double,packed single,scalar double 以及 scalar single,它们能处理 128 位及 256 位的数据大小。

 


版权所有 mik 2009-2011