FPGA開発日記

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

商用RISC-V命令セットシミュレータが使えない人のためのオープンソース命令セットシミュレータSpike入門 (2. DPI-Cを用いたC++との通信)

いろんなニュースがあり、Imperasの検証環境が使えなくなる可能性があるので、オープンソース(そしてほぼRISC-Vシミュレータとしてはデファクトスタンダード)のRISC-V命令セットシミュレータSpikeを使ってどのようにハードウェアを検証するかどうかについて、まとめていきたいと思う。

前回はこちら: msyksphinz.hatenablog.com

RTL一致比較に必要な情報

Spikeは内部にCPUコアの情報を保持しています。 RTLとの一致比較を行う場合、基本的にはRTL側でCPUコアで1命令実行されるたびにSpikeでも1命令を実行し、その直後に一致比較を行うのが基本的な形だと思います。

このためには、VerilogとC++などのソフトウェアを接続するためのインタフェースであるDPI-Cを使います。 DPI-Cの詳細については、ここではあまり紹介しませんが、VerilogとC++を接続して情報を交換することができるのがDPI-Cだと考えればよいです。

例えば、RTL側のCPUの動作で、命令のコミットが発生したら以下のstep_spike()を呼び出します。step_spike()はDPI-Cで定義されており、この関数を呼び出すと、C++側のコードが動作します。

step_spike ($time / 4, longint'(committed_rob_entry.inst[grp_idx].pc_addr),
            int'( .u_tile.u_csu.u_scariv_csr.r_priv),
             .u_tile.u_rob.w_sim_mstatus[ .u_tile.u_rob.w_out_cmt_entry_id][grp_idx],
             .u_tile.u_rob.w_valid_except_grp_id[grp_idx],
             .u_tile.u_rob.w_except_type_selected,
             .u_tile.u_rob.w_out_cmt_id,
            1 << grp_idx,
            committed_rob_entry.inst[grp_idx].rvc_inst_valid ? committed_rob_entry.inst[grp_idx].rvc_inst : committed_rob_entry.inst[grp_idx].inst,
            committed_rob_entry.inst[grp_idx].wr_reg.valid,
            committed_rob_entry.inst[grp_idx].wr_reg.typ,
            committed_rob_entry.inst[grp_idx].wr_reg.regidx,
            committed_rob_entry.inst[grp_idx].wr_reg.rnid,
            committed_rob_entry.inst[grp_idx].wr_reg.typ == scariv_pkg::GPR ?
            w_physical_int_data[committed_rob_entry.inst[grp_idx].wr_reg.rnid] :
            w_physical_fp_data [committed_rob_entry.inst[grp_idx].wr_reg.rnid]);

step_spike()の定義は以下です。上記のインタフェースの信号の詳細は理解必要ありませんが、とにかくVerilog側、C++側で関数の宣言をしておく、というわけです。

  • Verilog側のDPI-C関数宣言
import "DPI-C" function void step_spike
  (
   input longint rtl_time,
   input longint rtl_pc,
   input int     rtl_priv,
   input longint rtl_mstatus,
   input int     rtl_exception,
   input int     rtl_exception_cause,
   input int     rtl_cmt_id,
   input int     rtl_grp_id,
   input int     rtl_insn,
   input int     rtl_wr_valid,
   input int     rtl_wr_typ,
   input int     rtl_wr_gpr,
   input int     rtl_wr_rnid,
   input longint rtl_wr_val
   );
  • C++側のDPI-Cの関数宣言
  void step_spike(long long time, long long rtl_pc,
                  int rtl_priv, long long rtl_mstatus,
                  int rtl_exception, int rtl_exception_cause,
                  int rtl_cmt_id, int rtl_grp_id,
                  int rtl_insn,
                  int rtl_wr_valid, int rtl_wr_type, int rtl_wr_gpr_addr,
                  int rtl_wr_gpr_rnid, long long rtl_wr_val);

ここで最も重要なのは、上記の引数が何であるかということなのですが、以下のような情報があれば十分だと思います。

  • rtl_time : RTLの現在のシミュレーション時間 (デバッグ表示用)
  • rtl_pc : 実行した命令のPC
  • rtl_priv : 現在のCPUの実行モード
  • rtl_mstatus : 現在のmstatusの状態
  • rtl_exception : 当該命令で例外が発生したか
  • rtl_exception_cause : 例外が発生したときの例外要因
  • rtl_cmt_id : 命令のID
  • rtl_grp_id :
  • rtl_insn : 命令の機械語
  • rtl_wr_valid : レジスタ書き込みが発生したか
  • rtl_wr_typ : 書き込みレジスタの方 (XPR / FPR / VPR)
  • rtl_wr_gpr : 書き込みレジスタのアーキテクチャレジスタID
  • rtl_wr_rnid : 書き込みレジスタの物理レジスタID
  • `rtl_wr_val : 書き込みレジスタ値

これらは、Spike側のCPU内部状態で以下から取得できます。これらを使って、RTLとSpikeの一致比較を行っていきます。続く。

  auto iss_pc   = p->get_state()->prev_pc;
  auto iss_insn = p->get_state()->insn;
  auto iss_priv = p->get_state()->last_inst_priv;
  auto iss_mstatus = p->get_state()->mstatus;
  p->get_state()->log_reg_write