FPGA開発日記

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

RISC-V Vector命令をサポートした自作命令セットシミュレータの実装検討

RISC-V Vector命令の仕様がかなり固まってきた。いつの間にかVer 0.8が公開され、命令のニーモニックも安定しつつある。

  • RISC-V "V" Vector Extension
    • Version 0.8-draft-20191117

https://riscv.github.io/documents/riscv-v-spec/riscv-v-spec.pdf

ついでに、自分で日本語訳したRISC-V Vector命令仕様書がまだVer.0.7のままなのでアップデートしなければ。

msyksphinz-self.github.io

RVV (RISC-V)の最も基本的なパラメータ

RISC-Vベクトル拡張の最も基本的なパラメータは以下の3つである。

  1. 1ベクトル要素の最大ビットサイズELEN。2の累乗の必要がある。
  2. ベクトルレジスタのビットサイズVLEN ≥ ELEN, 2の累乗の必要がある。
  3. ストリップビット距離SLEN。VLEN ≥ SLEN ≥ 32 である必要がある。それぞれは2の累乗の必要がある。

それぞれ、自作RISC-Vシミュレータでそれぞれ以下のように表現した。

class RiscvPeThread : public EnvBase
{
...
  /*
   * Vector Constant Parameters
   */
  const int32_t RVV_ELEN = 32;  // The maximum size of a single vector element in bits, ELEN, which must be a power of 2.
  const int32_t RVV_VLEN = 512; // The number of bits in a vector register, VLEN ≥ ELEN, which must be a power of 2.
  const int32_t RVV_SLEN = 32;  // The striping distance in bits, SLEN, which must be VLEN ≥ SLEN ≥ 32, and which must be a power of 2.
...
 public:
  int32_t get_ELEN() { return RVV_ELEN; }
  int32_t get_VLEN() { return RVV_VLEN; }
  int32_t get_SLEN() { return RVV_SLEN; }
...

RVVサポートに必要なレジスタ

RVVのサポートには以下のシステムレジスタが新たに追加される。

Address Privilege Name Description
0x008 URW vstart ベクトルスタート位置を示すレジスタ
0x009 URW vxsat 浮動小数点飽和フラグレジスタ
0x00A URW vxrm 浮動小数点丸めモードレジスタ
0xC20 URO vl ベクトル長レジスタ
0xC21 URO vtype ベクトルデータ型レジスタ

それぞれ自作RISC-Vシミュレータに以下のように実装した。これらのCSRはすべてRubyスクリプトで自動生成するように設定している。

$sysreg_table.push(Array[0x008,  'URW',     'vstart'       , Array[Array[xlen-1, 0, 'vstart'        , 'RW', 0]]])
$sysreg_table.push(Array[0x009,  'URW',     'vxsat'        , Array[Array[xlen-1, 0, 'vxsat'         , 'RW', 0]]])
$sysreg_table.push(Array[0x00A,  'URW',     'vxrm'         , Array[Array[xlen-1, 0, 'vxrm'          , 'RW', 0]]])
...
$sysreg_table.push(Array[0xC20, 'URO', 'vl',    Array[Array[xlen-1, 0, 'vl',    'RO', 0]]])
$sysreg_table.push(Array[0xC21, 'URO', 'vtype', Array[Array[xlen-1, xlen-1, 'vill', 'RO', 0],
                                                      Array[6, 5, 'vediv', 'RO', 0],
                                                      Array[4, 2, 'vsew',  'RO', 0],
                                                      Array[1, 0, 'vlmul', 'RO', 0]]])
$sysreg_table.push(Array[0xC22, 'URO', 'vlenb', Array[Array[xlen-1, 0, 'vlenb', 'RO', 0]]])

上記の記述で、例えばvtypeについては以下のような構造体が自動的に生成される。

  union {
    struct {
      uint64_t vlmul : 2;
      uint64_t vsew : 3;
      uint64_t vediv : 2;
      uint64_t dummy_0 : 56;
      uint64_t vill : 1;
    } bit_vtype;
    uint64_t vtype;
  } vtype;

これに対して、Read/Writeの実装を付け加えていく。

template <typename Xlen_t>
CsrAccResult CsrEnv::Read_VTYPE (Xlen_t *data, PrivMode mode)
{
  *data = vtype.vtype;
  return CsrAccResult::Normal;
}

template <typename Xlen_t>
CsrAccResult CsrEnv::Write_VTYPE (Xlen_t data, PrivMode mode)
{
  vtype.vtype = data;
  DWord_t vl = m_pe_thread->get_VLEN() * vtype.bit_vtype.vlmul / vtype.bit_vtype.vsew;
  Write_VL (vl, mode);

  return CsrAccResult::Normal;
}

vsetvli, vsetvl命令の初期実装

まずは、RVVの初期設定を行うための基本的な命令を作り込んで行こう。 それぞれ、vsetvli, vsetvlという命令が定義されている。

f:id:msyksphinz:20200110015855p:plain
vsetvli, vsetvl命令の定義。

それぞれ、JSONファイルを定義して命令のデコーダとテンプレートを作成する。

    //
    // RVV : Vector Extension
    //
    {
        "name":"vsetvli r[11:7],r[19:15],h[30:20]",
        "length":"32", "dlength":"32",
        "field":["0XXXX", "XX", "XXXXX", "XXXXX", "111", "XXXXX", "10101", "11"],
        "category":"VECTOR",
        "func_suffix":"",
        "impl":[""],
        "inst_ctrl":[]
    },
    {
        "name":"vsetvl  r[11:7],r[19:15],r[24:20]",
        "length":"32", "dlength":"32",
        "field":["10000", "00", "XXXXX", "XXXXX", "111", "XXXXX", "10101", "11"],
        "category":"VECTOR",
        "func_suffix":"",
        "impl":[""],
        "inst_ctrl":[]
    },

さらに、実際の実装を作り込んで行く。

void InstEnv::RISCV_INST_VSETVLI (InstWord_t inst_hex)
{
  if (!m_pe_thread->IsVECAvailable ()) {
    m_pe_thread->GenerateException (ExceptCode::Except_IllegalInst, 0);
    return;
  }

  RegAddr_t rs1_addr = ExtractR1Field (inst_hex);
  RegAddr_t rd_addr  = ExtractRDField (inst_hex);

  DWord_t rs1_val = m_pe_thread->ReadGReg<DWord_t> (rs1_addr);

  m_pe_thread->CSRWrite (static_cast<Addr_t>(SYSREG_ADDR_VTYPE), ExtractBitField (inst_hex, 26, 20));
}

とりあえず、これで基本的な命令の初期実装は完了だ。