4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NaN Boxingでハマった話

Posted at

はじめに

結論から書きます。RISC-Vでは(そして一般的にも?)1本の、例えば128bit精度の浮動小数点レジスタに32bit, 64bitの浮動小数点値を書く時には、下位にその値をおいて、利用していない上位を1で埋める作業を行います。これをNaN Boxingと呼びます。NaN Boxingでは、使っていない上位がすべて1で埋まってなくてはなりません。もし1でうまってない数があった場合は、いかに下位が正しく32bit/64bitの浮動小数点値を示していても、それはNaNとして扱われます。

問題の出発点 - fsgnj.s命令が想定と違う動きをする

RISC-VのISS(instruction set simulator)を自作している中で、fsgnj.s命令のテストがどうしてもパスせず困っていました。fsgnj.s f0, f1, f2命令はsign injection命令で、f1のsign bitだけf2のsign bitと入れ替えてf0に格納する、という命令です。精度はfloat(32bit)です。

これのテストがriscv-testsの以下ファイルの58-61行目にあります。

これの58行目のテスト、テスト番号40のテストにおいて、fsgnj.sを使っている箇所はマクロの48行目になります。以下、spikeにてステップ実行した際の上記48行目のfsgnj.s命令実行直後におけるレジスタの値のダンプです。

spike log
core   0: 0x0000000080000608 (0x20208053) fsgnj.s ft0, ft1, ft2
: freg 0 ft0
0xffffffffffffffffffffffff7fc00000
: freg 0 ft1
0xffffffffffffffff7ffffffe12345678
: freg 0 ft2
0xffffffffffffffff0000000000000000

レジスタ自体は128bitあるようですが、この命令では単精度、32bitしか使われないため、下位32bitだけを見ていました。そうすると、ft1は0x12345678, ft2は0x00000000、どちらも浮動小数点の値として正しい値(NaNではない、という意味)です。ft2のサインビットは0のため、ft1のサインビットを0に書き換えて(も元のママ)0x12345678がft0に入るはずだ、と思っていました。ところが、spikeの実行結果では、0x7fc00000がft0に入っていました。Canonical NaNというやつらしいです(quiet NaNでもある)。どうしてNaNになるのか全くわからず、NaNなんだ!NaNでなんだ!とうめいてrisc-vのslackサーバーに質問を投げつけました。

問題のポイント - NaN Boxingは上位が全てall 1で埋まってないとNaNとみなす

NaN Boxingとは、前述したように、利用していない上位bitを1で埋める作業を指します。NaNは、exponentがall 1で、fractionが0以外の場合を指します。上位を1で埋めると、自然とexponentとfractionの上位何ビットかは必ずall 1になります。つまり、32bit幅で浮動小数点レジスタに書き込んだ値は、64bit/128bitで読もうとするとNaNに見えてしまうわけです。詳細はriscvの仕様書p73-74に記載されてあります。ここで、ポイントは以下のパラグラフです

Apart from transfer operations described in the previous paragraph, all other floating-point operations on narrower n-bit operations, n < FLEN, check if the input operands are correctly NaN-boxed, i.e., all upper FLEN−n bits are 1. If so, the n least-significant bits of the input are used as the input value, otherwise the input value is treated as an n-bit canonical NaN.

ここで、FLENとは浮動小数点レジスタの幅で、上記のデフォルトのspikeだと128のようです。これを読むと、input operandは正しくNaN Boxingされているか、つまり、利用されていない上位bitがすべて1かを確認する必要があります。そして、もし正しくNaN Boxingされていない場合は、いくら下位の値が正しく浮動小数点の値を示していても、canonical NaNとして扱え、とあります。Oh, MY! ft1レジスタは上位bitに1ではないものが含まれています。そのため、ft1はfloat精度で読み取った時にもNaNとして、つまり0x7fc00000として扱わなければなりません。0x7fc00000にsign bitをinjectionした結果は0x7fc00000です。このように、spikeの実行結果の説明がつきました。

教訓

仕様書は舐めるように端から端まで読みましょう(難しい!

最後に

そして検索したらFPGA開発日記さんにて同じ問題が指摘されていました。ああ、NaN Boxingという言葉でぐぐる能力があれば...

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?