ぐだぐだ低レベルプログラミング(55) RISC-V、sign-injection命令「群」

Joseph Halfmoon

今回取り上げさせていただくのは、浮動小数点数の符号を操作するための命令「群」です。3命令「も」存在します。倹約を旨としてオペコード空間を割り当てているRISC-Vにしたら大盤振舞いにも見えましたが、例によって疑似命令という別名をみると「無いと困る」命令群でした。

※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら

※以下は単精度浮動小数点演算命令について述べています。なお実機動作は、64bit RISC-V搭載、Kendryte K210上で行っています。

sign-injection命令

3命令ある sign-injection命令は、どれも3つの浮動小数点レジスタをオペランドにとります。2つがソース、1つがデスティネーションです。基本、第一のソースオペランドの符号ビットを操作して、操作した結果の値をデスティネーションに転送します。違いは符号操作に関わる部分です。

    1. fsgnj.s 第2ソースオペランドの符号ビットをデスティネーションにコピー
    2. fsgnjn.s 第2ソースオペランドの符号ビット反転をデスティネーションに
    3. fsgnjx.s 第2ソースオペランドの符号ビットと第1ソースオペランドの符号ビットのXORをとったものをデスティネーションに

上記を見ると、細々したことをやっておるだけの命令にも見えますが、以下の疑似命令の実体であると分かると、俄然大事に思えてきます。

    1. fmv.s 浮動小数点転送命令(疑似命令)の実体は、fsgnj.sである
    2. fneg.s 浮動小数点数の正負反転命令(疑似命令)の実体は、fsgnjn.sである
    3. fabs.s 浮動小数点数の絶対値命令(疑似命令)の実体は、fsgnjx.sである

上記は、どれもソース1個、デスティネーション1個の疑似命令ですが、実際には同じソース2個に対してsign-injection命令を適用すれば上記の機能が実現できると。

考えてみれば当たり前なのだけれど、凡人は言われるまで気づきませぬ。。。

被テスト関数のソース

懲りもせず「定形」コピペで以下追加いたしました。

void tst_Fsgnj(uint32_t tnum, float arg1, float arg2)
{
  TestFloat f0S, f1S, f2S;

  f0S.fDat = 0.0;
  f1S.fDat = arg1;
  f2S.fDat = arg2;
  asm volatile("fmv.w.x f0, %[Rd]\n\t"
               "fmv.w.x f1, %[Rs1]\n\t"
               "fmv.w.x f2, %[Rs2]\n\t"
               "fsgnj.s f0, f1, f2\n\t"
               "fmv.x.w %[Rd], f0\n\t"
              : [Rd] "=r" (f0S.u32Dat.LowW)
              : [Rs1] "r" (f1S.u32Dat.LowW), [Rs2] "r" (f2S.u32Dat.LowW)
              : );
  Serial.printf("TEST-fsgnj.s #%u\r\n", tnum);
  Serial.printf("%f <- fsgnj(%f, %f)\r\n", f0S.fDat, f1S.fDat, f2S.fDat);
  Serial.printf("%e <- fsgnj(%e, %e)\r\n", f0S.fDat, f1S.fDat, f2S.fDat);
  Serial.printf("  HEX: %08x\r\n", f0S.u32Dat.LowW);
  Serial.printf("\r\n");
}

void tst_Fsgnjn(uint32_t tnum, float arg1, float arg2)
{
  TestFloat f0S, f1S, f2S;

  f0S.fDat = 0.0;
  f1S.fDat = arg1;
  f2S.fDat = arg2;
  asm volatile("fmv.w.x f0, %[Rd]\n\t"
               "fmv.w.x f1, %[Rs1]\n\t"
               "fmv.w.x f2, %[Rs2]\n\t"
               "fsgnjn.s f0, f1, f2\n\t"
               "fmv.x.w %[Rd], f0\n\t"
              : [Rd] "=r" (f0S.u32Dat.LowW)
              : [Rs1] "r" (f1S.u32Dat.LowW), [Rs2] "r" (f2S.u32Dat.LowW)
              : );
  Serial.printf("TEST-fsgnjn.s #%u\r\n", tnum);
  Serial.printf("%f <- fsgnjn(%f, %f)\r\n", f0S.fDat, f1S.fDat, f2S.fDat);
  Serial.printf("%e <- fsgnjn(%e, %e)\r\n", f0S.fDat, f1S.fDat, f2S.fDat);
  Serial.printf("  HEX: %08x\r\n", f0S.u32Dat.LowW);
  Serial.printf("\r\n");
}

void tst_Fsgnjx(uint32_t tnum, float arg1, float arg2)
{
  TestFloat f0S, f1S, f2S;

  f0S.fDat = 0.0;
  f1S.fDat = arg1;
  f2S.fDat = arg2;
  asm volatile("fmv.w.x f0, %[Rd]\n\t"
               "fmv.w.x f1, %[Rs1]\n\t"
               "fmv.w.x f2, %[Rs2]\n\t"
               "fsgnjx.s f0, f1, f2\n\t"
               "fmv.x.w %[Rd], f0\n\t"
              : [Rd] "=r" (f0S.u32Dat.LowW)
              : [Rs1] "r" (f1S.u32Dat.LowW), [Rs2] "r" (f2S.u32Dat.LowW)
              : );
  Serial.printf("TEST-fsgnjx.s #%u\r\n", tnum);
  Serial.printf("%f <- fsgnjx(%f, %f)\r\n", f0S.fDat, f1S.fDat, f2S.fDat);
  Serial.printf("%e <- fsgnjx(%e, %e)\r\n", f0S.fDat, f1S.fDat, f2S.fDat);
  Serial.printf("  HEX: %08x\r\n", f0S.u32Dat.LowW);
  Serial.printf("\r\n");
}
テストケース

テストケースは以下のとおりです。

tst_Fsgnj(0, 1.234567f, -1.0e-10);
tst_Fsgnjn(1, 1.234567f, 1.0e-10);
tst_Fsgnjx(2, 1.234567f, 1.0e-10);
tst_Fsgnjx(3, -1.234567f, 1.0e-10);
tst_Fsgnjx(4, 1.234567f, -1.0e-10);
tst_Fsgnjx(5, -1.234567f, -1.0e-10);
実機上での実行結果

ソース上では-1.0e-10などと記述しておるのですが、%f変換でprintfすると-0.000000などと表示されてしまいますな。2行目%e変換でも表示しているので許してくだされや。

TEST-fsgnj.s #0
-1.234567 <- fsgnj(1.234567, -0.000000)
-1.234567e+00 <- fsgnj(1.234567e+00, -1.000000e-10)
HEX: bf9e064b

TEST-fsgnjn.s #1
-1.234567 <- fsgnjn(1.234567, 0.000000)
-1.234567e+00 <- fsgnjn(1.234567e+00, 1.000000e-10)
HEX: bf9e064b

TEST-fsgnjx.s #2
1.234567 <- fsgnjx(1.234567, 0.000000)
1.234567e+00 <- fsgnjx(1.234567e+00, 1.000000e-10)
HEX: 3f9e064b

TEST-fsgnjx.s #3
-1.234567 <- fsgnjx(-1.234567, 0.000000)
-1.234567e+00 <- fsgnjx(-1.234567e+00, 1.000000e-10)
HEX: bf9e064b

TEST-fsgnjx.s #4
-1.234567 <- fsgnjx(1.234567, -0.000000)
-1.234567e+00 <- fsgnjx(1.234567e+00, -1.000000e-10)
HEX: bf9e064b

TEST-fsgnjx.s #5
1.234567 <- fsgnjx(-1.234567, -0.000000)
1.234567e+00 <- fsgnjx(-1.234567e+00, -1.000000e-10)
HEX: 3f9e064b

予定どおり、符号操作出来とります。

まあ、こいつらが浮動小数点数の mov、neg、abs の実体だと思えば感慨もひとしお。大袈裟だな、自分。

ぐだぐだ低レベルプログラミング(54) RISC-V、単精度平方根で2次元normを計算 へ戻る

ぐだぐだ低レベルプログラミング(56) RISC-V、浮動小数点数のClassify命令 へ進む