FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

GMPライブラリをつかって64ビット同士の乗算を実装する

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ビットの演算を実現しよう。

The GNU MP Bignum Library

GMPは、GNUの多倍長演算を実現するためのライブラリだ。GCCコンパイルするときにも良く使われる。 これのC++バージョンがあって、GMPXXというライブラリを使えばよい。多倍長のクラスなども揃っていて、簡単に使用できる。

基本的な多倍長演算の実装

まずは以下のサイトを見てどのように使うのかを確認した。

lv4.hateblo.jp

あーなるほど、単純な演算子を挿入するだけで四則演算は実現できるのか。

という訳で、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の完成だ。

github.com

今後の課題

signed × unsigned は、単純な方法では実現できない。これは、やりかたを考える必要がありそうだ。