FPGA開発日記

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

Verilatorを使って独自CPUコアのシミュレーション環境を構築

久しぶりに自作RISC-Vコアのメンテナンスを行おうと思っているが、個人が使用できるSystemVerilogのシミュレーション環境ってどれも中途半端で困っている。VCSやXceliumは使えないし、VerilatorはC++による独自のドライブが必要、おそらくSystemVerilogを個人がまじめに使うためにはVivado Simulatorが現実的だろうが、今度は波形ビューワが非常にしょぼい。業務でVerdiなどを使っていれば、GTKWaveなどは使い悪すぎて死にそうだし、Vivadoの波形ビューワもしかりだ。

MentorのModelSim Intel Starter Editionも考えられるが、そもそも32ビットバイナリしか配布されていないし、WSLと相性が最悪なので良くない。結局どのRTLシミュレータも決定打が無いわけだ。

と言っても文句ばかり言っていても仕様がないので、独自CPUコアのシミュレーション環境をVerilatorで構築することにする。Verilatorの使い方は私の過去のブログで復習する。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

シミュレーション用のTBファイルとしては以下を用意する。

module mrh_tb
  (
   input logic i_clk,
   input logic i_reset_n
   );

logic                                w_ic_req_valid;
mrh_pkg::mem_cmd_t                   w_ic_req_cmd;
/* 中略 */

mrh_tile_wrapper
  u_mrh_tile_wrapper
    (
    .i_clk     (i_clk    ),
    .i_reset_n (i_reset_n),

    // L2 request from ICache
    .o_ic_req_valid   (w_ic_req_valid ),
    .o_ic_req_cmd     (w_ic_req_cmd   ),
...
    .i_ic_resp_data   (w_ic_resp_data ),
    .o_ic_resp_ready  (w_ic_resp_ready)
     );


tb_l2_behavior_ram
  #(
    .DATA_W    (mrh_pkg::ICACHE_DATA_W),
    .TAG_W     (mrh_pkg::L2_CMD_TAG_W),
    .ADDR_W    (riscv_pkg::PADDR_W),
    .BASE_ADDR ('h8000_0000),
    .SIZE      (4096),
    .RD_LAT    (10)
    )
u_tb_l2_behavior_ram
  (
   .i_clk     (i_clk    ),
   .i_reset_n (i_reset_n),
...
      

TBファイルをドライブするためのC++ファイルとしては以下のようなものを作った。FSTファイルを出力するように修正している。

int main(int argc, char** argv) {

  Verilated::commandArgs(argc, argv);

  // Instantiate DUT
  Vmrh_tb *dut = new Vmrh_tb();

  // Trace DUMP ON
  Verilated::traceEverOn(true);
  VerilatedFstC* tfp = new VerilatedFstC;

  dut->trace(tfp, 100);  // Trace 100 levels of hierarchy
  tfp->open("simx.fst");

/* 中略 */
  int cycle = 0;
  while (time_counter < 500) {
    if ((time_counter % 5) == 0) {
      dut->i_clk = !dut->i_clk; // Toggle clock
    }
    if ((time_counter % 10) == 0) {
      // Cycle Count
      cycle ++;
    }

    // Evaluate DUT
    dut->eval();
    tfp->dump(time_counter);

    time_counter++;
  }

  dut->final();
  tfp->close();
}

これでプロジェクト全体をコンパイルするように調整した。

$ make
verilator --top-module mrh_tb -o ../mrh_tb_rv32 --Mdir obj_dir_riscv32  --exe ../cpp/tb_mrh.cpp --cc -I../src --trace-fst --trace-params --trace-structs --trace-underscore ../src/riscv_common_pkg.sv ../src/riscv32_pkg.sv -f filelist.f
verilator --top-module mrh_tb -o ../mrh_tb_rv64 --Mdir obj_dir_riscv64  --exe ../cpp/tb_mrh.cpp --cc -I../src --trace-fst --trace-params --trace-structs --trace-underscore ../src/riscv_common_pkg.sv ../src/riscv64_pkg.sv -f filelist.f
make -C obj_dir_riscv32 -f Vmrh_tb.mk

これでとりあえず良いだろう。シミュレーションを実行してみる。

$ ./mrh_tlb_rv64
f:id:msyksphinz:20210131173109p:plain
Verilatorによるシミュレーションの結果

一応上手く行っているようだ。