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

Joseph Halfmoon

加減乗除に最大/最小、比較とやってきましたが、まだ残っている「演算系の浮動小数点命令」がありました。平方根です、Square Root。平方根を計算するだけでは芸が無いので、2次元のnormも計算してみました。x86(というかx87)系のFPU命令であると「超越関数」までありますが、RISC-Vには超越関数不在です。加減乗除とアルゴリズムで後はよしなに、と。

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

いや~別件ですが、ルネサスからRISC-V機登場したようです。コアは台湾のAndes Technology設計みたいです。ルネ様のスペック拝見するにRISC-V機としたら現状ハイエンド機ですな。是非とも使ってみたいもんですが、ルネサス製の評価ボードとかそれなりのお値段だろうからなあ、立派だけれどお高い?それに開発ツールが別売だったりもするし。ラズパイと同程度のお値段で手に入ると良いのだけれど。。。

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

単精度浮動小数の平方根命令

レジスタ一つを引数にとり、ルートを計算して結果をレジスタ一つに書き戻します。

fsqrt.s rd, rs1

今回、負の数を引数にとったらどうなるのか調べてないです。メンドイというか忘れました。後で他の件と合わせて扱うかもしれません。

ルートとるだけではあんまりなので、アイキャッチ画像に掲げたとおり、norm計算(2次元ですけれども)をやってみました。以前やりました積和演算命令を使えば、積和して積和して平方根、というシーケンスで計算できます。勿論、3次元以上になったら次元の数だけ積和算を増やせばよい、と。簡単。ホントか。

被テスト関数のソース

ブツブツいいながらも、「定形」コピペで追加です。fsqrt.s単独テスト用とnorm計算用の2種。今回は実行サイクル数の計測も含めましたです。

void tst_Fsqrt(uint32_t tnum, float arg1)
{
  uint32_t nCyc = 0;
  TestFloat f0S, f1S;

  f0S.fDat = 0.0;
  f1S.fDat = arg1;
  asm volatile("fmv.w.x f0, %[Rd]\n\t"
               "fmv.w.x f1, %[Rs1]\n\t"
               "rdcycle t0\n\t"
               "fsqrt.s f0, f1\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)
              : "t0", "t1");
  Serial.printf("TEST-fsqrt.s #%u\r\n", tnum);
  Serial.printf("CYCLE: %u\r\n", nCyc);
  Serial.printf("%f <- fsqrt.s(%f)\r\n", f0S.fDat, f1S.fDat);
  Serial.printf("%e <- fsqrt.s(%e)\r\n", f0S.fDat, f1S.fDat);
  Serial.printf("  arg1 HEX: %08x\r\n", f1S.u32Dat.LowW);
  Serial.printf("\r\n");
}

void tst_Fnorm(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"
               "fmadd.s f0, f2, f2, f0\n\t"
               "fmadd.s f0, f1, f1, f0\n\t"
               "fsqrt.s f0, f0\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-fnorm.s #%u\r\n", tnum);
  Serial.printf("CYCLE: %u\r\n", nCyc);
  Serial.printf("%f <- norm(%f, %f)\r\n", f0S.fDat, f1S.fDat, f2S.fDat);
  Serial.printf("%e <- norm(%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");
}
テストケース

今回のテストケースは以下ですが、「ベタ」なものばかり。

    • ルート2、ひとよひとよにひとみごろ
    • ルート3、ひとなみにおごれや
    • ピタゴラスの定理、古代メソポタミア以来の3,4,5!

遥かな太古の時代、中学の数学の先生に「ひとよひとよにひとみごろ」みたいなことを習ったような気がします。最近はそんな語呂合わせはしないのかな。スマホで一発。でも平方根は、開平すれば手計算でも求まるのだけれど。開平こそ今時計算しないか。だいたい手計算ができずにMaxima様にお願いしている自分が何を言う。

tst_Fsqrt(0, 2.0f);
tst_Fsqrt(1, 3.0f);
tst_Fnorm(2, 3.0f, 4.0f);
実機上での実行結果

実機上での実行結果は、予定どおりでありますな。単精度浮動小数点数なので、桁数的に語呂合わせより短いッスけど。normも、ちゃんと三平方の定理の代表例どおりみたいだし。

TEST-fsqrt.s #0
CYCLE: 32
1.414214 <- fsqrt.s(2.000000)
1.414214e+00 <- fsqrt.s(2.000000e+00)
arg1 HEX: 40000000

TEST-fsqrt.s #1
CYCLE: 32
1.732051 <- fsqrt.s(3.000000)
1.732051e+00 <- fsqrt.s(3.000000e+00)
arg1 HEX: 40400000

TEST-fnorm.s #2
CYCLE: 41
5.000000 <- norm(3.000000, 4.000000)
5.000000e+00 <- norm(3.000000e+00, 4.000000e+00)
arg1 HEX: 40400000
arg2 HEX: 40800000

平方根、計算できるですね。よかった。何が?

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

ぐだぐだ低レベルプログラミング(55) RISC-V、sign-injection命令「群」へ進む