FPGA開発日記

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

RTLの情報をC++プログラムにバックポートする方法の調査

自作CPUとかをやっていて、RTLとISS(命令セットシミュレータ)でどうしても誤差が生じる部分と言えば、真っ先に思いつくのがサイクル計測部分だ。 RISC-VのCPUとかを設計していると、ベンチマークを流している場合に必ずMCYCLEが一致しない問題にぶちあたる。

MCYCLEの比較を無視すればなんとかなるが、さらにMCYCLEの値を汎用レジスタに格納してメモリにストアしてしまったら大変。そこから先はロード命令や通常の命令ですら一致が取れなくなる。 そこでRTL側のMCYCLEの情報を抜き出してISSにバックポートするという方法を取る。RTLのMCYCLEの値をISSに転送して上書きすることでISSとRTLのつじつまを合わせるという作戦だ。

この方法はベンチマークソフトウェアなどでの検証を継続できるというメリットはあるものの、RTLのMCYCLEの検証はISSとの比較では決してできないという問題がある。常にRTL側を信頼するという前提に立っているので、一致検証以外の方法でMCYCLEの値は検証する必要がある。

それはさておき、SystemVerilogで書いている自作CPUと、SpikeなどのISSを接続する方法について調査した。

SystemVerilog側ではコミットが発生する度にDPI-Cを経由してC++コードを呼び出しており、Spikeを1ステップ実行しては一致比較する。

 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_gpr,
    input int     rtl_wr_rnid,
    input longint rtl_wr_val
    );

/* ... 中略 ... */
             /* verilator lint_off WIDTH */
             step_spike ($time, longint'(committed_rob_entry.inst[grp_idx].pc_addr),
                         int'(u_msrh_tile_wrapper.u_msrh_tile.u_msrh_csu.u_msrh_csr.r_priv),
                         u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_sim_mstatus[u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_out_cmt_entry_id][grp_idx],
                         u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_valid_except_grp_id[grp_idx],
                         u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_except_type_selected,
                         u_msrh_tile_wrapper.u_msrh_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].rd_valid,
                         committed_rob_entry.inst[grp_idx].rd_regidx,
                         committed_rob_entry.inst[grp_idx].rd_rnid,
                         w_physical_gpr_data[committed_rob_entry.inst[grp_idx].rd_rnid]);

引数は長いがとりあえず無視。コミットIDとPC、命令ビット、書き込み汎用レジスタアドレス、書き込み値などの情報が転送できていればよろしい。

Spike側だが、汎用レジスタなどの情報を取得して更新することのできるAPIが一応そろっているので、それを活用する。

   if (rtl_wr_valid) {
     int64_t iss_wr_val = p->get_state()->XPR[rtl_wr_gpr_addr];
     if ((((iss_insn.bits() & MASK_CSRRW) == MATCH_CSRRW) ||
          ((iss_insn.bits() & MASK_CSRRS) == MATCH_CSRRS) ||
          ((iss_insn.bits() & MASK_CSRRC) == MATCH_CSRRC) ||
          ((iss_insn.bits() & MASK_CSRRWI) == MATCH_CSRRWI) ||
          ((iss_insn.bits() & MASK_CSRRSI) == MATCH_CSRRSI) ||
          ((iss_insn.bits() & MASK_CSRRCI) == MATCH_CSRRCI))) {
       if (((iss_insn.bits() >> 20) & 0x0fff) == CSR_MCYCLE) {
         p->set_csr(static_cast<int>(CSR_MCYCLE), static_cast<reg_t>(rtl_wr_val));
         p->get_state()->XPR.write(rtl_wr_gpr_addr, rtl_wr_val);
         fprintf(compare_log_fp, "==========================================\n");
         fprintf(compare_log_fp, "RTL MCYCLE Backporting to ISS.\n");
         fprintf(compare_log_fp, "ISS MCYCLE is updated to RTL = %0*llx\n", g_rv_xlen / 4, rtl_wr_val);
         fprintf(compare_log_fp, "==========================================\n");

雑な実装ではあるが、iss_insn.bits()というのはISSの実行した命令ビット、そしてMASKMATCHの定数はSpikeがビルド時に勝手に用意してくれているので活用させて頂く。

そして対象となるCSRがMCYCLEの場合n以上県が成立し、p->get_state()->XPR.write()レジスタアドレスを更新するという訳だ。さらに元となるMCYCLEのシステムレジスタp->set_csr()で更新する。 これをriscv-testsベンチマークで実行すると例えば以下のようなログが得られる。

142535 : RTL(23,1) Exception Cause = 27
142535 : 24552 : PC=[00000000800023b4] (23,01) b00027f3 csrr    a5, mcycle
==========================================
RTL MCYCLE Backporting to ISS.
ISS MCYCLE is updated to RTL = 0000000000010e2d
==========================================
142563 : 24553 : PC=[00000000800023b8] (24,01) 00001717 auipc   a4, 0x1
GPR[14](73) <= 00000000800033b8
142563 : 24554 : PC=[00000000800023bc] (24,02) ab870713 addi    a4, a4, -1352
GPR[14](127) <= 0000000080002e70

RTLのMCYCLEの情報0x10e2dISSにバックポートして、MCYCLEの値とGPRの値が更新されたというログが得られた。 これで一応ベンチマークの継続実行ができるようになった。