ぐだぐだ低レベルプログラミング(53) RISC-V、単精度浮動小数点数の比較

Joseph Halfmoon

前回は浮動小数点数の最大最小を求める命令でした。今回は、浮動小数点数の比較命令です。比較した結果として条件分岐することが多いと思います。RISC-Vには分岐フラグが存在せず、条件分岐は整数レジスタの中の値を分岐命令で判定します。このため浮動小数の比較命令の結果は整数レジスタに書き込みとなります。

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

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

単精度浮動小数点数の比較命令

流石、RISC-Vと言うべきか。単精度の比較命令は以下の3つだけです。

    • feq.s
    • flt.s
    • fle.s

3命令とも、比較対象として浮動小数点数のレジスタを2個ソース引数にとります。そしてデスティネーションは整数レジスタです。feq.sは2つの一致、flt.sはソースその1がその2より「小なり」、fle.sはその1がその2より「小なりイコール」が判定条件です。条件が成立すれば、デスティネーションに真値(1)が書き込まれ、不成立なら偽値(0)を書き込みです。

普通「C言語」などで比較する場合の ==、<、<= は存在しますが、!=、>、>=は存在しません。==をひっくり返せば!=、<の逆は>=、<=の逆は>ということでコンパイラの方で良きに計らって頂戴、ということだと思います。

テスト関数のソース

例によってほぼ「定形」コピペで3命令のテストコードを追加いたしました。コピペはイケないと思ってはいるのですが。テストコードは相互依存の無い独立ではあります。

void tst_Feq(uint32_t tnum, uint32_t src1, uint32_t src2)
{
  TestFloat f1S, f2S;
  uint32_t dst;

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

void tst_Flt(uint32_t tnum, float src1, float src2)
{
  TestFloat f1S, f2S;
  uint32_t dst;

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

void tst_Fle(uint32_t tnum, float src1, float src2)
{
  TestFloat f1S, f2S;
  uint32_t dst;

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

上記のテスト用関数に実際に値を設定してテストしているコードが以下です。

tst_Feq(0, 0x40218bad, 0x40218bac);
tst_Feq(1, 0x40218bad, 0x40218bad);
tst_Flt(2, 1.234567, 5.678901);
tst_Flt(3, 5.678901, 1.234567);
tst_Flt(4, 1.234567, 1.234567);
tst_Fle(5, 1.234567, 5.678901);
tst_Fle(6, 5.678901, 1.234567);
tst_Fle(7, 1.234567, 1.234567);

浮動小数点数のイコール比較の命令は存在しますが、前にも述べたとおり使い方が難しいです。例によって「10進の浮動小数点数表現で一致していると思うなよ」というアリガチな例をやって失敗する例もやっています。

lt=less than、le=less or equal については、小なり成立、小なり不成立、イコール成立の3ケースについて確認しています。

実機上での実行結果

以下の実機上での実行結果は、ターゲットボードから出力される結果をターミナルソフトでキャプチャしたものです。10進表現は同じものを%fと%eの2種類で出力しています。その後のHEX表記が実際の内部表現です。

TEST-feq.s #0
0 <- feq.s(2.524150, 2.524150)
0 <- feq.s(2.524150e+00, 2.524150e+00)
src1 HEX: 40218bad
src2 HEX: 40218bac

TEST-feq.s #1
1 <- feq.s(2.524150, 2.524150)
1 <- feq.s(2.524150e+00, 2.524150e+00)
src1 HEX: 40218bad
src2 HEX: 40218bad

TEST-flt.s #2
1 <- flt.s(1.234567, 5.678901)
1 <- flt.s(1.234567e+00, 5.678901e+00)
src1 HEX: 3f9e064b
src2 HEX: 40b5b98f

TEST-flt.s #3
0 <- flt.s(5.678901, 1.234567)
0 <- flt.s(5.678901e+00, 1.234567e+00)
src1 HEX: 40b5b98f
src2 HEX: 3f9e064b

TEST-flt.s #4
0 <- flt.s(1.234567, 1.234567)
0 <- flt.s(1.234567e+00, 1.234567e+00)
src1 HEX: 3f9e064b
src2 HEX: 3f9e064b

TEST-fle.s #5
1 <- fle.s(1.234567, 5.678901)
1 <- fle.s(1.234567e+00, 5.678901e+00)
src1 HEX: 3f9e064b
src2 HEX: 40b5b98f

TEST-fle.s #6
0 <- fle.s(5.678901, 1.234567)
0 <- fle.s(5.678901e+00, 1.234567e+00)
src1 HEX: 40b5b98f
src2 HEX: 3f9e064b

TEST-fle.s #7
1 <- fle.s(1.234567, 1.234567)
1 <- fle.s(1.234567e+00, 1.234567e+00)
src1 HEX: 3f9e064b
src2 HEX: 3f9e064b

成立する場合は1,不成立なら0が出力されています。全て予定通りの結果を得ました。当然ちゃ、当然。

しかし、浮動小数点数の命令(それも単精度だけで、倍精度やってないのですが)多いね。まだまだ当分終わらなそう。

ぐだぐだ低レベルプログラミング(52) RISC-V、浮動小数のmin/max へ戻る

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