RISC-Vは、RISCの中でもシンプルで「絞り込んだ」命令セットでありますが、浮動小数点数を扱う命令結構多いんじゃないかい。まあ面倒くさいものを扱うので、いたしかたないか。RISC-Vのせいではないですな。前回は積和演算命令でしたが今回は最大、最小求める命令です。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※以下は単精度浮動小数点演算命令について述べています。なお実機動作は、64bit RISC-V搭載、Kendryte K210上で行っています。
アイキャッチ画像につい掲げてしまったのは、最近K210機への書き込みで使っているラズパイ4機にオブジェクトコード(WindowsPCでビルドしたもの)を送るためのVNC viewerのファイル転送画面です。よく忘れるのでメモっておくと以下のようです。
VNC viewerを動かしているWindowsPC上のファイルをVNC接続先のLinux機などに送るときは画面の上部に隠れている「VNC viewerのトレイ」を引き出して「トランスファファイル」アイコンを押すと出てくるアイキャッチ画像に示したウインドウから左下のSend Filesボタンを押し、Windows上のファイルをGUIから指定、するとVNC接続先から転送先を指定するためのウインドウも出ている(しかし、全面にWindows側のウインドウが立ちはだかっていて裏に隠れている、手元の装置ではいつも)ので、被っているウインドウをどかして転送先を指定、で転送できまする。文字で書くとしんどいが、大した手間ではありません。ちょっとクリクリするだけ。
min/max命令
浮動小数点数に関しても比較演算はあり、次回とりあげさせていただく予定です。今回はその前に、大きい方を取る、小さい方を取る、min/max命令を使ってみたいと思います。
-
- fmin.s 二つのオペランドのうち値の小さい方をデスティネーションに書き込む
- fmax.s 二つのオペランドのうち値の大きい方をデスティネーションに書き込む
ま、やっていることは単純ですが、符号、指数、仮数と3部分に分かれる浮動小数点フォーマットなので、こういう命令がないとメンドイことこの上ありませぬ。
テスト関数のソース
また反省もなく、前回のもののコピペで2命令追加してしまいました。
void tst_Fmin(uint32_t tnum, float arg1, float arg2) { uint32_t nCyc = 0; 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" "rdcycle t0\n\t" "fmin.s f0, f1, f2\n\t" "fmv.x.w %[Rd], f0\n\t" "rdcycle t1\n\t" "sub %[RdC], t1, t0\n\t" : [Rd] "=r" (f0S.u32Dat.LowW), [RdC] "=r" (nCyc) : [Rs1] "r" (f1S.u32Dat.LowW), [Rs2] "r" (f2S.u32Dat.LowW) : "t0", "t1"); Serial.printf("TEST-fmin.s #%u\r\n", tnum); Serial.printf("CYCLE: %u\r\n", nCyc); Serial.printf("%f <- min(%f, %f)\r\n", f0S.fDat, f1S.fDat, f2S.fDat); Serial.printf("%e <- min(%e, %e)\r\n", f0S.fDat, f1S.fDat, f2S.fDat); Serial.printf(" arg1 HEX: %08x\r\n", f1S.u32Dat.LowW); Serial.printf(" arg2 HEX: %08x\r\n", f2S.u32Dat.LowW); Serial.printf("\r\n"); } void tst_Fmax(uint32_t tnum, float arg1, float arg2) { uint32_t nCyc = 0; 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" "rdcycle t0\n\t" "fmax.s f0, f1, f2\n\t" "fmv.x.w %[Rd], f0\n\t" "rdcycle t1\n\t" "sub %[RdC], t1, t0\n\t" : [Rd] "=r" (f0S.u32Dat.LowW), [RdC] "=r" (nCyc) : [Rs1] "r" (f1S.u32Dat.LowW), [Rs2] "r" (f2S.u32Dat.LowW) : "t0", "t1"); Serial.printf("TEST-fmax.s #%u\r\n", tnum); Serial.printf("CYCLE: %u\r\n", nCyc); Serial.printf("%f <- max(%f, %f)\r\n", f0S.fDat, f1S.fDat, f2S.fDat); Serial.printf("%e <- max(%e, %e)\r\n", f0S.fDat, f1S.fDat, f2S.fDat); Serial.printf(" arg1 HEX: %08x\r\n", f1S.u32Dat.LowW); Serial.printf(" arg2 HEX: %08x\r\n", f2S.u32Dat.LowW); Serial.printf("\r\n"); }
fmin/fmaxだけでは寂しいというただそれだけの理由で、金言「浮動小数点数のイコール比較は気を付けてね」を実証するための以下関数も追加いたしました。こちらはアセンブラ関数無し、純然?Cのコード。ついこの間、別シリーズにて浮動小数点数のテストどうする、の一件があったためです。次回の浮動小数点数の比較命令が登場すれば嫌でもまたやることになりますが。
void tst_FequalBAD(uint32_t tnum, uint32_t arg1, uint32_t arg2) { TestFloat f1S, f2S; uint32_t flag; f1S.u32Dat.LowW = arg1; f2S.u32Dat.LowW = arg2; flag = f1S.fDat == f2S.fDat; Serial.printf("TEST-fequalBAD #%u\r\n", tnum); Serial.printf("%u <- %f == %f)\r\n", flag, f1S.fDat, f2S.fDat); Serial.printf(" arg1 HEX: %08x\r\n", f1S.u32Dat.LowW); Serial.printf(" arg2 HEX: %08x\r\n", f2S.u32Dat.LowW); Serial.printf("\r\n"); }
テストケース部分
テストケース部分が以下に。
tst_FequalBAD(0, 0x40218bad, 0x40218bac); tst_FequalBAD(1, 0x40218bad, 0x40218bad); tst_Fmin(2, 1.234567, 1.123456); tst_Fmax(3, 1.234567, 1.123456);
実機上での実行結果
実機上で実行した結果が以下に。最初の2つは10進表現で同じ数に見えてもイコールと思うなよ、というアリガチな件。次の2つが、minとmaxをとったところ。
TEST-fequalBAD #0 0 <- 2.524150 == 2.524150) arg1 HEX: 40218bad arg2 HEX: 40218bac TEST-fequalBAD #1 1 <- 2.524150 == 2.524150) arg1 HEX: 40218bad arg2 HEX: 40218bad TEST-fmin.s #2 CYCLE: 7 1.123456 <- min(1.234567, 1.123456) 1.123456e+00 <- min(1.234567e+00, 1.123456e+00) arg1 HEX: 3f9e064b arg2 HEX: 3f8fcd68 TEST-fmax.s #3 CYCLE: 7 1.234567 <- max(1.234567, 1.123456) 1.234567e+00 <- max(1.234567e+00, 1.123456e+00) arg1 HEX: 3f9e064b arg2 HEX: 3f8fcd68
なにやら風雲急な国際情勢ですが、こちらは平穏に済んでよかった。