いろんなニュースがあり、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
: 実行した命令のPCrtl_priv
: 現在のCPUの実行モードrtl_mstatus
: 現在のmstatusの状態rtl_exception
: 当該命令で例外が発生したかrtl_exception_cause
: 例外が発生したときの例外要因rtl_cmt_id
: 命令のIDrtl_grp_id
:rtl_insn
: 命令の機械語rtl_wr_valid
: レジスタ書き込みが発生したかrtl_wr_typ
: 書き込みレジスタの方 (XPR / FPR / VPR)rtl_wr_gpr
: 書き込みレジスタのアーキテクチャレジスタIDrtl_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