RISC-Vに限らず、大概の64ビットプロセッサには64ビット同士の乗算を行う命令が存在する。RISC-Vでは、以下の命令が定義されている。
- MUL : 64bit signed × 64bit signed → 下位64ビットを結果として格納
- MULH : 64bit signed × 64bit signed → 上位64ビットを結果として格納
- MULHU : 64bit unsigned × 64bit unsigned → 上位64ビットを結果として格納
- MULHSU : 64bit signed × 64bit unsigned → 上位64ビットを結果として格納
このような命令を実現するためには、乗算の結果を128ビット分保持しておかなければならず、C++の標準の型では実現不可能だ。
このような命令に対応するために、多倍長演算のライブラリを利用して128ビットの演算を実現しよう。
GMPは、GNUの多倍長演算を実現するためのライブラリだ。GCCをコンパイルするときにも良く使われる。 これのC++バージョンがあって、GMPXXというライブラリを使えばよい。多倍長のクラスなども揃っていて、簡単に使用できる。
基本的な多倍長演算の実装
まずは以下のサイトを見てどのように使うのかを確認した。
あーなるほど、単純な演算子を挿入するだけで四則演算は実現できるのか。
という訳で、MULH命令のために128ビットの演算結果を保持するためには以下のようにすれば良い。
DWord_t rs1_val = m_env->GRegRead<DWord_t> (rs1_addr); DWord_t rs2_val = m_env->GRegRead<DWord_t> (rs2_addr); DWord_t res_hi, res_lo; std::stringstream rs1_str, rs2_str; rs1_str << rs1_val; rs2_str << rs2_val; mpz_class rs1_mpz, rs2_mpz; rs1_mpz.set_str(rs1_str.str(), 10); rs2_mpz.set_str(rs2_str.str(), 10); mpz_class res_mpz = rs1_mpz * rs2_mpz;
なるほど、これでちゃんと128ビットまで演算結果が保たれている。
上位の結果を取り出すためのシフトの実現
実は、GMPXXの資料を読んでも上位64ビットを右シフトして取り出す方法が良く分からなかった。 そこで、とりあえず実装のしやすさを重視し、必要な分母の数値を作成し、除算した。
mpz_class xlen_mpz (1); mpz_class mpz_2 (2); for (int i = 0; i < 64; i++) { xlen_mpz = xlen_mpz * mpz_2; } mpz_class res_mpz = (rs1_mpz * rs2_mpz) / xlen_mpz;
定数を作るのが格好悪い。しかしとりあえずこれで動くようになる。 最後に、必要な値を取り出して格納する。
res_hi = res_mpz.get_si (); m_env->GRegWrite (rd_addr, res_hi);
これでMULHの完成だ。
今後の課題
signed × unsigned は、単純な方法では実現できない。これは、やりかたを考える必要がありそうだ。