RISC-Vの自作エミュレータを作成している。
テスト自体はriscv-testsリポジトリを使えばよいのだが、パタンが落ちた時の解析がなかなか大変だ。少しずつテストパタンを確認しながら進めている。
特に浮動小数点命令について、 単純にsoftfloatライブラリを使えばよいと思っていたのだがそうでもないらしい。
RISC-V の浮動小数点命令についてしっかり理解しておかなければ、例えば単純な四則演算については簡単に実装できるかもしれないが、単精度と倍精度での移動や、変換命令などのルール、そして比較のルールなどについて引っかかる可能性がある。
これらについてまとめておく必要があるだろう。
RISC-Vは仕様上32ビット、64ビット、128ビットの浮動小数点命令をカバーしている。
ちなみに、半精度(16ビット)についてはカバーしていないのだが、それは今後サポートされるかどうかはわからない。
RISC-Vの実装やツール群について、カバーしている仕様を表現する表記があるのだが(例えば、RV64IMACだと、64ビットアドレッシングモード、I:整数命令、M:ハードウェア浮動小数点命令、A:アトミック命令、C:16ビット長命令 をサポートしていることを意味する)、
をサポートしていることを意味する。例えば、RV64IMACFDだと、64ビットのアドレッシングモードを持っている命令で、整数命令などに含めて単精度浮動小数点命令と倍精度浮動小数点命令がサポートされていることを意味する。
ちなみに、RV32 / RV64などで示されるアドレッシングモードの数字だが、これは単精度・倍精度などの情報とは全く関係ない。
RISC-Vではハードウェアとしてレジスタが何ビットの長さを持っているのかを示す情報としてXLEN / FLENという情報を持っている。
XLENは整数レジスタのビット幅、FLENは浮動小数点レジスタのビット幅を意味する。
なので、例えば、RV32のRISC-V実装であっても倍精度の浮動小数点命令をサポートできるし、逆に注意したいのはFLEN=64でも単精度浮動小数点命令はサポートできる(この場合は、前のエントリで記述した NaN Boxingを参照のこと。
msyksphinz.hatenablog.com
サポートしているシステムレジスタ
RISC-Vの浮動小数点命令をサポートするシステムレジスタとして、frm / fflags / fcsr が用意されている。
基本的にfcsrレジスタを参照しておけばよいだろう。 fflags と frm はそのコピーに過ぎない。
RISC-V の浮動小数点命令をエミュレートするためのsoftfloatの改造
ここが一番キモになるポイントだ。RISC-Vの浮動小数点命令をエミュレートするために、エミュレータである riscv-isa-sim ではsoftfloatを活用している。
ただし、デフォルトのsoftfloatに比べて、微妙な改造をしている。それがsoftfloatに含まれているspecialize.h だ。
この部分を、デフォルトで搭載されているX86のものから改造しなければならない。
riscv-isa-sim を参考に、specialize.h を改造した。
ベースは、riscv-isa-sim を参照すること。
github.com
デフォルトの specialize.h に対して、主に以下の部分を改造した。
diff --git a/vendor/softfloat/SoftFloat-3d/source/riscv/specialize.h b/vendor/softfloat/SoftFloat-3d/source/riscv/specialize.h
index cfcbd31..ed293ce 100644
--- a/vendor/softfloat/SoftFloat-3d/source/riscv/specialize.h
+++ b/vendor/softfloat/SoftFloat-3d/source/riscv/specialize.h
@@ -99,13 +99,16 @@ struct commonNaN {
| location pointed to by `zPtr'. If the NaN is a signaling NaN, the invalid
| exception is raised.
*----------------------------------------------------------------------------*/
-void softfloat_f16UIToCommonNaN( uint_fast16_t uiA, struct commonNaN *zPtr );
+#define softfloat_f16UIToCommonNaN( uiA, zPtr ) if ( ! ((uiA) & 0x0200) ) softfloat_raiseFlags( softfloat_flag_invalid )
/*----------------------------------------------------------------------------
| Converts the common NaN pointed to by `aPtr' into a 16-bit floating-point
| NaN, and returns the bit pattern of this value as an unsigned integer.
*----------------------------------------------------------------------------*/
-uint_fast16_t softfloat_commonNaNToF16UI( const struct commonNaN *aPtr );
+#define softfloat_commonNaNToF16UI( aPtr ) ((uint_fast16_t) defaultNaNF16UI)
/*----------------------------------------------------------------------------
| Interpreting `uiA' and `uiB' as the bit patterns of two 16-bit floating-
@@ -134,13 +137,15 @@ uint_fast16_t
| location pointed to by `zPtr'. If the NaN is a signaling NaN, the invalid
| exception is raised.
*----------------------------------------------------------------------------*/
-void softfloat_f32UIToCommonNaN( uint_fast32_t uiA, struct commonNaN *zPtr );
+#define softfloat_f32UIToCommonNaN( uiA, zPtr ) if ( ! ((uiA) & 0x00400000) ) softfloat_raiseFlags( softfloat_flag_invalid )
/*----------------------------------------------------------------------------
| Converts the common NaN pointed to by `aPtr' into a 32-bit floating-point
| NaN, and returns the bit pattern of this value as an unsigned integer.
*----------------------------------------------------------------------------*/
-uint_fast32_t softfloat_commonNaNToF32UI( const struct commonNaN *aPtr );
+#define softfloat_commonNaNToF32UI( aPtr ) ((uint_fast32_t) defaultNaNF32UI)
/*----------------------------------------------------------------------------
| Interpreting `uiA' and `uiB' as the bit patterns of two 32-bit floating-
@@ -169,13 +174,15 @@ uint_fast32_t
| location pointed to by `zPtr'. If the NaN is a signaling NaN, the invalid
| exception is raised.
*----------------------------------------------------------------------------*/
-void softfloat_f64UIToCommonNaN( uint_fast64_t uiA, struct commonNaN *zPtr );
+#define softfloat_f64UIToCommonNaN( uiA, zPtr ) if ( ! ((uiA) & UINT64_C( 0x0008000000000000 )) ) softfloat_raiseFlags( softfloat_flag_invalid )
/*----------------------------------------------------------------------------
| Converts the common NaN pointed to by `aPtr' into a 64-bit floating-point
| NaN, and returns the bit pattern of this value as an unsigned integer.
*----------------------------------------------------------------------------*/
-uint_fast64_t softfloat_commonNaNToF64UI( const struct commonNaN *aPtr );
+#define softfloat_commonNaNToF64UI( aPtr ) ((uint_fast64_t) defaultNaNF64UI)
それ以外にも、Max/Min, 比較命令についてはいくつか条件があるので気を付けなければならない。
res = (isFloatNaN (op1) && isFloatNaN (op2)) ? FLOAT_STD_QNAN :
(f32_lt_quiet (f_op2, f_op1) || isFloatNaN (op2) || (f32_eq(f_op1, f_op2) && (op2 & FLOAT_SIGN_BIT))) ? op1 :
op2;
これ以外にも注意事項については随時追加していく。