今回取り上げさせていただくのは、浮動小数点数の符号を操作するための命令「群」です。3命令「も」存在します。倹約を旨としてオペコード空間を割り当てているRISC-Vにしたら大盤振舞いにも見えましたが、例によって疑似命令という別名をみると「無いと困る」命令群でした。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※以下は単精度浮動小数点演算命令について述べています。なお実機動作は、64bit RISC-V搭載、Kendryte K210上で行っています。
sign-injection命令
3命令ある sign-injection命令は、どれも3つの浮動小数点レジスタをオペランドにとります。2つがソース、1つがデスティネーションです。基本、第一のソースオペランドの符号ビットを操作して、操作した結果の値をデスティネーションに転送します。違いは符号操作に関わる部分です。
-
- fsgnj.s 第2ソースオペランドの符号ビットをデスティネーションにコピー
- fsgnjn.s 第2ソースオペランドの符号ビット反転をデスティネーションに
- fsgnjx.s 第2ソースオペランドの符号ビットと第1ソースオペランドの符号ビットのXORをとったものをデスティネーションに
上記を見ると、細々したことをやっておるだけの命令にも見えますが、以下の疑似命令の実体であると分かると、俄然大事に思えてきます。
-
- fmv.s 浮動小数点転送命令(疑似命令)の実体は、fsgnj.sである
- fneg.s 浮動小数点数の正負反転命令(疑似命令)の実体は、fsgnjn.sである
- 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 の実体だと思えば感慨もひとしお。大袈裟だな、自分。