DPI-Cを用いてSpikeとVerilator上のCPUを接続する構成の検討、実装を進めていった。
まず、RTL側だが、コミットが発生した時点でその情報をDPI-Cを経由してSpikeを制御するC++のコードに伝えなければならない。適当に以下のようにした。
always_ff @ (negedge i_clk, negedge i_msrh_reset_n) begin if (!i_msrh_reset_n) begin end else begin if (|(u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_entry_all_done)) begin for (int grp_idx = 0; grp_idx < msrh_pkg::DISP_SIZE; grp_idx++) begin if (committed_rob_entry.grp_id[grp_idx]) begin /* verilator lint_off WIDTH */ step_spike ($time, longint'((committed_rob_entry.pc_addr << 1) + (4 * grp_idx)), $clog2(u_msrh_tile_wrapper.u_msrh_tile.u_rob.w_entry_all_done), 1 << grp_idx, committed_rob_entry.inst[grp_idx].inst, committed_rob_entry.inst[grp_idx].rd_valid, committed_rob_entry.inst[grp_idx].rd_regidx, w_physical_gpr_data[committed_rob_entry.inst[grp_idx].rd_rnid]); end end end end end
本当はVerilatorのC++で作ったTB側で管理してもいいかもしれないが、Vivado SImと環境を共有したいためこのような実装にしておいた(後で考えてみるとVerilogのTB側をリコンパイルする必要があるので修正がかなり面倒くさいということが分かったが...)。
ここではstep_spike()
という関数を用意しており、これがDPI-CでC++とVerilogを接続するインタフェースとなる。
import "DPI-C" function void step_spike ( input longint rtl_time, input longint rtl_pc, 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 longint rtl_wr_val );
extern "C" { void initial_spike (const char *filename); void step_spike(long long time, long long rtl_pc, int rtl_cmt_id, int rtl_grp_id, int rtl_insn, int rtl_wr_valid, int rtl_wr_gpr_addr, long long rtl_wr_val); }
initial_spike()
はシミュレーション前にSpikeのインスタンスを作成するためのもので、spike.cc
をベースにいろいろと移植して作っている。sim_t
というのがSpikeのシミュレーション環境本体のようなので、これをC++制御側でインスタンス化してSpikeのオブジェクトを作成した。
spike_core = new sim_t(isa, priv, varch, nprocs, halted, real_time_clint,
initrd_start, initrd_end, bootargs, start_pc, mems, plugin_devices, htif_args,
std::move(hartids), dm_config, log_path, dtb_enabled, dtb_file);
で、かなり悩んだのだがこれだけではSpikeはシミュレーションを実行してくれない。どうも命令キャッシュの構成などが特殊になっておりHTIF経由でプログラムをロードしないとRAMが全く空の状態で実行されてしまう。多少汚いがSpike側のコードを改造し、初期化だけを行う以下のルーチンを作成してプログラムをロードすることにした。
spike_core->spike_dpi_init();
// this class encapsulates the processors and memory in a RISC-V machine. class sim_t : public htif_t, public simif_t { public: ... void spike_dpi_init() { htif_t::start();}
なんでこんなのが必要なのかというと、これは以下のコード断片からパクってきたものなのだが、load_program()
を呼び出したりするのにいろいろと手順が必要らしい。private()
のメソッドだったりして面倒だったので、結局手っ取り早くhtif::start()
を実行するために独自の公開メソッドを作成したという訳だ。
int sim_t::run() { host = context_t::current(); target.init(sim_thread_main, this); return htif_t::run(); }
さらに空の命令を5回実行する。Spikeは本来は0x10000から実行が始まって(ここはROM)、そこからPayloadに飛ぶので最初の5命令は無視してよい。このため一致検証を始める前に5命令を飛ばしている。
spike_core->get_core(0)->reset(); spike_core->get_core(0)->step(5);
ここまでできれば、あとはRTL側がコミットを発生させるたびにSpikeを実行して結果を比較すればよい。そのためにstep_spike()
を作成し、RTL側からレジスタの書き込み地やPCアドレスなどの情報を引き込んでくるようにした。
void step_spike(long long time, long long rtl_pc, int rtl_cmt_id, int rtl_grp_id, int rtl_insn, int rtl_wr_valid, int rtl_wr_gpr_addr, long long rtl_wr_val) { processor_t *p = spike_core->get_core(0); p->step(1); fprintf(stderr, "%lld : PC=[%016llx] %s\n", time, rtl_pc, disasm->disassemble(rtl_insn).c_str()); auto iss_pc = p->get_state()->prev_pc; if (iss_pc != rtl_pc) { fprintf(stderr, "==========================================\n"); fprintf(stderr, "Wrong PC: RTL = %016llx, ISS = %016lx\n", rtl_pc, iss_pc); fprintf(stderr, "==========================================\n"); } if (rtl_wr_valid) { int64_t iss_wr_val = p->get_state()->XPR[rtl_wr_gpr_addr]; if (iss_wr_val != rtl_wr_val) { fprintf(stderr, "==========================================\n"); fprintf(stderr, "Wrong GPR[%02d]: RTL = %016llx, ISS = %016lx\n", rtl_wr_gpr_addr, rtl_wr_val, iss_wr_val); fprintf(stderr, "==========================================\n"); } else { fprintf(stderr, "GPR[%02d] <= %016llx", rtl_wr_gpr_addr, rtl_wr_val); } } fprintf (stderr, "\n"); }
Spike側はp->step(1)
で1命令ずつ実行して、その結果のPCと汎用レジスタ値を取り出して一致比較を行っている。
実行結果。4命令しか実行していないが、どうにか加算結果と同じ命令を実行したSpikeで書き込んでいる汎用レジスタ値が一致することが確認できた。とりあえずまずは第1段階は終了かな。
10585 : PC=[0000000080000000] li a0, 1 GPR[10] <= 0000000000000001 10585 : PC=[0000000080000004] addi a1, a0, 2 GPR[11] <= 0000000000000003 10585 : PC=[0000000080000008] addi a2, a1, 3 GPR[12] <= 0000000000000006 10585 : PC=[000000008000000c] addi a3, a2, 4 GPR[13] <= 000000000000000a