今回は前回と以下同文でラクチンなどと呟いていたら、前回コードにしょうもないバグ発見。天網恢恢疎にして漏らさず、違うか。お詫びして前回分をさきほど修正させていただきました。今回は前回一種しかやらなかった積和演算命令、実は全部で4種もあったという話であります。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※以下は単精度浮動小数点演算命令について述べています。なお実機動作は、64bit RISC-V搭載、Kendryte K210上で行っています。
積和演算命令4種
積和演算命令が4種あるというのは何もRISC-Vの特長ではなく、IEEE-754-2008の規定らしいです。規格書読んでないので知らんけど。浮動小数積和演算のあるArmの命令セット(いつもお世話になっている、組み込み用の Cortex-M4のもの)みてもやはり4種類あります。
勝手にザックリまとめると以下のようです。
命令 | 乗算結果の符号 | 加算オペランドの符号 |
---|---|---|
fmadd.s | 反転せず | 反転せず |
fmsub.s | 反転せず | 反転 |
fnmadd.s | 反転 | 反転 |
fnmsub.s | 反転 | 反転せず |
「掛けて、足して、しまう」という動きは積和演算命令全て同じですが、掛けた後の結果の符号を反転するか否か、そして足し算するときのオペランドの符号を反転する(引き算する)か否かで4種に分かれるということです。
掛け算した後の丸めが許せない、ための規定だと思います。しかしね、nが付く方の乗算結果の符号が反転するときは、引き算するのが add で、足し算するのが sub というのはこんがらがらないか。両方マイナスでプラスか。すみません浮動小数点素人の戯言です。
テスト関数のソース
テスト関数のソースを以下に並べますが、ほぼ前回のもののコピペでチョイ直しです。コピペしないでも似たようなコードが生成されるように工夫するべきなのに、芸がない。それに、前回コピペで失敗しているというのに、またコピペ。コピペしないで済むようにするのが立派なプログラマであるとすれば、ダメダメですな。
fmadd.sのテストコードは前回分で掲げたので、fmsub.s fnmadd.s fnmsub.s の3種分。
void tst_Fmsub(uint32_t tnum, float arg1, float arg2, float arg3) { uint32_t nCyc = 0; TestFloat f0S, f1S, f2S, f3S; f0S.fDat = 0.0; f1S.fDat = arg1; f2S.fDat = arg2; f3S.fDat = arg3; asm volatile("fmv.w.x f0, %[Rd]\n\t" "fmv.w.x f1, %[Rs1]\n\t" "fmv.w.x f2, %[Rs2]\n\t" "fmv.w.x f3, %[Rs3]\n\t" "rdcycle t0\n\t" "fmsub.s f0, f1, f2, f3\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), [Rs3] "r" (f3S.u32Dat.LowW) : "t0", "t1"); Serial.printf("TEST-fmsub.s #%u\r\n", tnum); Serial.printf("CYCLE: %u\r\n", nCyc); Serial.printf("%f * %f - %f = %f\r\n", f1S.fDat, f2S.fDat, f3S.fDat, f0S.fDat); Serial.printf("%e * %e - %e = %e\r\n", f1S.fDat, f2S.fDat, f3S.fDat, f0S.fDat); Serial.printf(" HEX: %08x\r\n", f0S.u32Dat.LowW); Serial.printf("\r\n"); } void tst_Fnmadd(uint32_t tnum, float arg1, float arg2, float arg3) { uint32_t nCyc = 0; TestFloat f0S, f1S, f2S, f3S; f0S.fDat = 0.0; f1S.fDat = arg1; f2S.fDat = arg2; f3S.fDat = arg3; asm volatile("fmv.w.x f0, %[Rd]\n\t" "fmv.w.x f1, %[Rs1]\n\t" "fmv.w.x f2, %[Rs2]\n\t" "fmv.w.x f3, %[Rs3]\n\t" "rdcycle t0\n\t" "fnmadd.s f0, f1, f2, f3\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), [Rs3] "r" (f3S.u32Dat.LowW) : "t0", "t1"); Serial.printf("TEST-fnmadd.s #%u\r\n", tnum); Serial.printf("CYCLE: %u\r\n", nCyc); Serial.printf("-%f * %f - %f = %f\r\n", f1S.fDat, f2S.fDat, f3S.fDat, f0S.fDat); Serial.printf("-%e * %e - %e = %e\r\n", f1S.fDat, f2S.fDat, f3S.fDat, f0S.fDat); Serial.printf(" HEX: %08x\r\n", f0S.u32Dat.LowW); Serial.printf("\r\n"); } void tst_Fnmsub(uint32_t tnum, float arg1, float arg2, float arg3) { uint32_t nCyc = 0; TestFloat f0S, f1S, f2S, f3S; f0S.fDat = 0.0; f1S.fDat = arg1; f2S.fDat = arg2; f3S.fDat = arg3; asm volatile("fmv.w.x f0, %[Rd]\n\t" "fmv.w.x f1, %[Rs1]\n\t" "fmv.w.x f2, %[Rs2]\n\t" "fmv.w.x f3, %[Rs3]\n\t" "rdcycle t0\n\t" "fnmsub.s f0, f1, f2, f3\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), [Rs3] "r" (f3S.u32Dat.LowW) : "t0", "t1"); Serial.printf("TEST-fnmsub.s #%u\r\n", tnum); Serial.printf("CYCLE: %u\r\n", nCyc); Serial.printf("-%f * %f + %f = %f\r\n", f1S.fDat, f2S.fDat, f3S.fDat, f0S.fDat); Serial.printf("-%e * %e + %e = %e\r\n", f1S.fDat, f2S.fDat, f3S.fDat, f0S.fDat); Serial.printf(" HEX: %08x\r\n", f0S.u32Dat.LowW); Serial.printf("\r\n"); }
テストケースと実行結果
main関数内に並べたテストケースはこちら。
tst_Fmadd(1, 1.234567, 9.876543, 1.123456); tst_Fmsub(2, 1.234567, 9.876543, 1.123456); tst_Fnmadd(3, 1.234567, 9.876543, 1.123456); tst_Fnmsub(4, 1.234567, 9.876543, 1.123456); tst_Fmadd(5, 1.234567, 1.134567, 1.123451); tst_Fmsub(6, 1.234567, 1.134567, 1.123451); tst_Fnmadd(7, 1.234567, 1.134567, 1.123451); tst_Fnmsub(8, 1.234567, 1.134567, 1.123451);
漫然と実行した結果が以下に。今回こそ、計算間違いは無いハズ。ホントか、大丈夫か。
TEST-fmadd.s #1 CYCLE: 8 1.234567 * 9.876543 + 1.123456 = 13.316710 1.234567e+00 * 9.876543e+00 + 1.123456e+00 = 1.331671e+01 HEX: 4155113f TEST-fmsub.s #2 CYCLE: 8 1.234567 * 9.876543 - 1.123456 = 11.069798 1.234567e+00 * 9.876543e+00 - 1.123456e+00 = 1.106980e+01 HEX: 41311de5 TEST-fnmadd.s #3 CYCLE: 8 -1.234567 * 9.876543 - 1.123456 = -13.316710 -1.234567e+00 * 9.876543e+00 - 1.123456e+00 = -1.331671e+01 HEX: c155113f TEST-fnmsub.s #4 CYCLE: 8 -1.234567 * 9.876543 + 1.123456 = -11.069798 -1.234567e+00 * 9.876543e+00 + 1.123456e+00 = -1.106980e+01 HEX: c1311de5 TEST-fmadd.s #5 CYCLE: 8 1.234567 * 1.134567 + 1.123451 = 2.524150 1.234567e+00 * 1.134567e+00 + 1.123451e+00 = 2.524150e+00 HEX: 40218bad TEST-fmsub.s #6 CYCLE: 8 1.234567 * 1.134567 - 1.123451 = 0.277248 1.234567e+00 * 1.134567e+00 - 1.123451e+00 = 2.772481e-01 HEX: 3e8df375 TEST-fnmadd.s #7 CYCLE: 8 -1.234567 * 1.134567 - 1.123451 = -2.524150 -1.234567e+00 * 1.134567e+00 - 1.123451e+00 = -2.524150e+00 HEX: c0218bad TEST-fnmsub.s #8 CYCLE: 8 -1.234567 * 1.134567 + 1.123451 = -0.277248 -1.234567e+00 * 1.134567e+00 + 1.123451e+00 = -2.772481e-01 HEX: be8df375
おまけ:Raspberry Pi 4から Maix-Bit へのFlash書き込み
最近、Raspberry Pi 4機にK210を搭載したMaix-Bitを接続し、オブジェクトコードを書き込んだり、結果確認を行ったりしています。冒頭のアイキャッチ画像にRaspberry Pi 4からMaxi-Bitへの書き込みの様子を掲げました。
コマンドライン的には、以下の alias のとおりです。
kflash.py は、Pythonスクリプトなので、Raspberry Pi 4(Raspberry Pi OS 32bit)でも問題なく動作してます。