ARMv8.3-CompNum FCMLA命令の動作

FCMLA命令の動作について書く。

FCMLA <Zda>.<T>, <Pg>/M, <Zn>.<T>, <Zm>.<T>, <const>

複素数の乗算はFCMLA命令2つで書ける。 ベクトルレジスタには複数の複素数を格納できる。 偶数番目が実部で、奇数番目が虚部。 ベクトルレジスタが512ビットの場合、倍精度(double, 64ビット)の複素数を4つ格納できる。

  • 複素数1: 実部が0番目, 虚部が1番目
  • 複素数2: 実部が2番目, 虚部が3番目
  • 複素数3: 実部が4番目, 虚部が5番目
  • 複素数4: 実部が6番目, 虚部が7番目

この前提のもと、FCMLA命令の動作は以下の表のようになる。

const 実部 虚部
0 Re(Zda)+=Re(Zn)*Re(Zm) Im(Zda)+=Re(Zn)*Im(Zm)
90 Re(Zda)+=Im(Zn)*(-Im(Zm)) Im(Zda)+=Im(Zn)*Re(Zm)
180 Re(Zda)+=Re(Zn)*(-Re(Zm)) Im(Zda)+=Re(Zn)*(-Im(Zm))
270 Re(Zda)+=Im(Zn)*Im(Zm) Im(Zda)+=Im(Zn)*(-Re(Zm))

確認してみる。

#include <stdio.h>
#include <arm_sve.h>

int main() {
  double a[8];
  double b[8];
  double c[8];

  for (int i=0; i<8; i++)
    a[i] = i % 2 == 0 ? -i : i;

  for (int i=0; i<8; i++)
    b[i] = i * 2.0;

  for (int i=0; i<8; i++)
    c[i] = 0.0;

  svbool_t pg = svptrue_b64();

  svfloat64_t avec = svld1(pg, a);
  svfloat64_t bvec = svld1(pg, b);
  svfloat64_t cvec = svld1(pg, c);

  puts("*** rotate == 0 ***");
  svfloat64_t result = svcmla_x(pg, cvec, avec, bvec, 0);
  svst1(pg, c, result);
  for (int i=0; i<8; i++)
    printf("c[%d]=%f\n", i, c[i]);

  puts("check:");
  for (int i=0; i<8; i+=2) {
    // re
    c[i] = a[i] * b[i];
    // im
    c[i+1] = a[i] * b[i+1];
  }
  for (int i=0; i<8; i++)
    printf("c[%d]=%f\n", i, c[i]);

  puts("*** rotate == 90 ***");
  result = svcmla_x(pg, cvec, avec, bvec, 90);
  svst1(pg, c, result);
  for (int i=0; i<8; i++)
    printf("c[%d]=%f\n", i, c[i]);

  puts("check:");
  for (int i=0; i<8; i+=2) {
    // re
    c[i] = a[i+1] * -b[i+1];
    // im
    c[i+1] = a[i+1] * b[i];
  }
  for (int i=0; i<8; i++)
    printf("c[%d]=%f\n", i, c[i]);

  puts("*** rotate == 180 ***");
  result = svcmla_x(pg, cvec, avec, bvec, 180);
  svst1(pg, c, result);
  for (int i=0; i<8; i++)
    printf("c[%d]=%f\n", i, c[i]);

  puts("check:");
  for (int i=0; i<8; i+=2) {
    // re
    c[i] = a[i] * -b[i];
    // im
    c[i+1] = a[i] * -b[i+1];
  }
  for (int i=0; i<8; i++)
    printf("c[%d]=%f\n", i, c[i]);

  puts("*** rotate == 270 ***");
  result = svcmla_x(pg, cvec, avec, bvec, 270);
  svst1(pg, c, result);
  for (int i=0; i<8; i++)
    printf("c[%d]=%f\n", i, c[i]);

  puts("check:");
  for (int i=0; i<8; i+=2) {
    // re
    c[i] = a[i+1] * b[i+1];
    // im
    c[i+1] = a[i+1] * -b[i];
  }
  for (int i=0; i<8; i++)
    printf("c[%d]=%f\n", i, c[i]);
}
$ aarch64-linux-gnu-gcc-10 fcmla.c -march=armv8.3-a+sve -static
$ qemu-aarch64 -cpu max,sve-max-vq=4 a.out
*** rotate == 0 ***
c[0]=0.000000
c[1]=0.000000
c[2]=-8.000000
c[3]=-12.000000
c[4]=-32.000000
c[5]=-40.000000
c[6]=-72.000000
c[7]=-84.000000
check:
c[0]=0.000000
c[1]=0.000000
c[2]=-8.000000
c[3]=-12.000000
c[4]=-32.000000
c[5]=-40.000000
c[6]=-72.000000
c[7]=-84.000000
*** rotate == 90 ***
c[0]=-2.000000
c[1]=0.000000
c[2]=-18.000000
c[3]=12.000000
c[4]=-50.000000
c[5]=40.000000
c[6]=-98.000000
c[7]=84.000000
check:
c[0]=-2.000000
c[1]=0.000000
c[2]=-18.000000
c[3]=12.000000
c[4]=-50.000000
c[5]=40.000000
c[6]=-98.000000
c[7]=84.000000
*** rotate == 180 ***
c[0]=0.000000
c[1]=0.000000
c[2]=8.000000
c[3]=12.000000
c[4]=32.000000
c[5]=40.000000
c[6]=72.000000
c[7]=84.000000
check:
c[0]=-0.000000
c[1]=-0.000000
c[2]=8.000000
c[3]=12.000000
c[4]=32.000000
c[5]=40.000000
c[6]=72.000000
c[7]=84.000000
*** rotate == 270 ***
c[0]=2.000000
c[1]=0.000000
c[2]=18.000000
c[3]=-12.000000
c[4]=50.000000
c[5]=-40.000000
c[6]=98.000000
c[7]=-84.000000
check:
c[0]=2.000000
c[1]=-0.000000
c[2]=18.000000
c[3]=-12.000000
c[4]=50.000000
c[5]=-40.000000
c[6]=98.000000
c[7]=-84.000000

0.0-0.0でずれているところがあるのはなぜ?