ぐだぐだ低レベルプログラミング(58) RISC-V、整数と浮動小数の変換命令

Joseph Halfmoon

今回は浮動小数と整数の間で変換を行う命令fcvtを動かしてみたいと思います。16種類もあります。オペコード空間を節約しているRISC-V的にはかなり大きな一族です。それは、単精度と倍精度の浮動小数、32ビット幅と64ビット幅の整数、符号付と無、変換の方向の組み合わせがあるからです。今回は端折って4命令だけ動かしてみます。

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

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

変換命令群のうち今回動かしてみる4命令

いままで単精度浮動小数命令のみを取り扱ってきたので、今回は単精度浮動小数に限りました。また、整数も64ビット命令はまだ手を付けていないので省いてしまうと。これにより16種類が4種類に激減。手抜きだな、もう。

一覧が以下に。ソースレジスタからデスティネーションへの転送時に変換が起こります。整数の場合は整数レジスタであるx、単精度浮動小数の場合は浮動小数レジスタfが使われます。

命令 デスティネーション ソース
fcvt.s.w 単精度浮動小数 符号付き整数
fcvt.s.wu 単精度浮動小数 符号無整数
fcvt.w.s 符号付きワード整数 単精度浮動小数
fcvt.wu.s 符号無ワード整数 単精度浮動小数

4つだけならチョロイもんだ。ホントか。

テスト用関数

毎度のごとく、これまた手抜きなコピペで各命令のテスト用の関数を作成いたしました。今回は型の変換命令とて、10進の数字だけを見ているとどこが変わったんだかサッパリです。ビット列をみておかないとホントのところが分からない、ということで変換前、変換後の値をHEXでも印字しています。

void tst_Fcvt_s_w(uint32_t tnum, int32_t arg1)
{
  TestFloat f0S;

  f0S.fDat = 0.0f;
  asm volatile("fmv.w.x f0, %[Rd]\n\t"
               "fcvt.s.w f0, %[Rs1]\n\t"
               "fmv.x.w %[Rd], f0\n\t"
              : [Rd] "=r" (f0S.u32Dat.LowW)
              : [Rs1] "r" (arg1)
              : );
  Serial.printf("TEST-Fcvt_s_w #%u\r\n", tnum);
  Serial.printf("%f <- %d\r\n", f0S.fDat, arg1);
  Serial.printf("  S.HEX: %08x <- W.HEX: %08x \r\n", f0S.u32Dat.LowW, arg1);
  Serial.printf("\r\n");
}

void tst_Fcvt_s_wu(uint32_t tnum, uint32_t arg1)
{
  TestFloat f0S;

  f0S.fDat = 0.0f;
  asm volatile("fmv.w.x f0, %[Rd]\n\t"
               "fcvt.s.wu f0, %[Rs1]\n\t"
               "fmv.x.w %[Rd], f0\n\t"
              : [Rd] "=r" (f0S.u32Dat.LowW)
              : [Rs1] "r" (arg1)
              : );
  Serial.printf("TEST-Fcvt_s_wu #%u\r\n", tnum);
  Serial.printf("%f <- %u\r\n", f0S.fDat, arg1);
  Serial.printf("  S.HEX: %08x <- WU.HEX: %08x \r\n", f0S.u32Dat.LowW, arg1);
  Serial.printf("\r\n");
}

void tst_Fcvt_w_s(uint32_t tnum, float arg1)
{
  TestFloat f0S;
  int32_t result = 0;

  f0S.fDat = arg1;
  asm volatile("fmv.w.x f0, %[Rs1]\n\t"
               "fcvt.w.s %[Rd], f0\n\t"
              : [Rd] "=r" (result)
              : [Rs1] "r" (f0S.u32Dat.LowW)
              : );
  Serial.printf("TEST-Fcvt_w_s #%u\r\n", tnum);
  Serial.printf("%d <- %f\r\n", result, f0S.fDat);
  Serial.printf("  W.HEX: %08x <- S.HEX: %08x \r\n", result, f0S.u32Dat.LowW);
  Serial.printf("\r\n");
}

void tst_Fcvt_wu_s(uint32_t tnum, float arg1)
{
  TestFloat f0S;
  uint32_t result = 0;

  f0S.fDat = arg1;
  asm volatile("fmv.w.x f0, %[Rs1]\n\t"
               "fcvt.wu.s %[Rd], f0\n\t"
              : [Rd] "=r" (result)
              : [Rs1] "r" (f0S.u32Dat.LowW)
              : );
  Serial.printf("TEST-Fcvtu_w_s #%u\r\n", tnum);
  Serial.printf("%u <- %f\r\n", result, f0S.fDat);
  Serial.printf("  WU.HEX: %08x <- S.HEX: %08x \r\n", result, f0S.u32Dat.LowW);
  Serial.printf("\r\n");
}
テストケース

main()関数内でテストしているテストケースも手抜き、4つだけです。

tst_Fcvt_s_w(0, -1);
tst_Fcvt_s_wu(1, 4294967295);
tst_Fcvt_w_s(2, -1.0f);
tst_Fcvt_wu_s(3, 128.5f);
実機動作結果

実機動作結果が以下に。

#0)整数 -1 を fcvt.s.w 命令で浮動小数に変換しています。結果は-1.00000です。当然。しかし、HEXで見てみるとオール1の整数表現(2の補数)から、単精度浮動小数でビットパターンはまったく異なるものになっていることが分かります。

#1)整数は上記とおなじくオール1なのですが、符号無です。そのため、10進表現では、4294967295 という巨大な数です。これを fcvt.s.wu 命令で浮動小数に変換しています。結果は、4294967296.0 と整数からすると1.0多いです。しかし、単精度浮動小数の仮数部は24ビット(隠れた1ビットを加えて25ビット)しかないので、全32ビット幅をフルに使って表現した整数と1しか違っていない、というのは「良い近似」かもしれませんで。

#2)今度は float型の-1.0を整数型に fcvt.w.s 命令で変換しています。こういう切りの良い数字であれば完全に元に戻りますな。

#3)つづいて整数にならない、小数点以下をふくむ float 型を fcvt.wu.s 命令で変換してみます。128.5は128と小数点以下は切り捨てられてしまいました。

TEST-Fcvt_s_w #0
-1.000000 <- -1
S.HEX: bf800000 <- W.HEX: ffffffff

TEST-Fcvt_s_wu #1
4294967296.000000 <- 4294967295
S.HEX: 4f800000 <- WU.HEX: ffffffff

TEST-Fcvt_w_s #2
-1 <- -1.000000
W.HEX: ffffffff <- S.HEX: bf800000

TEST-Fcvtu_w_s #3
128 <- 128.500000
WU.HEX: 00000080 <- S.HEX: 43008000

通り一遍以下の少なすぎるテストケースじゃな。手抜きだね、自分。

ぐだぐだ低レベルプログラミング(57) RISC-V、浮動小数点数のロード/ストア命令 へ戻る

ぐだぐだ低レベルプログラミング(59) ARM64(AArch64)、スマホで lldb へ進む