FPGA開発日記

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

自作RISC-V CPUコアの検証用にDPI-Cを使用してSpikeを接続するための試行(Spikeの仕組み調査)

CPUコアの検証には多くの場合にはテストパタンを流して検証する方法が用いられるが、CPUの動作をチェックするために命令セットシミュレータ(Instruction Set Simulator: ISS)を使用して、RTLの動作と命令セットシミュレータの動作が一致するかを確認する手法が用いられる。

RISC-Vの場合には標準的なISSとしてriscv-isa-simが公開されているので、これを使うのが最も現実的だろう。そしてC++で記述されているのでDPI-Cを使用してRTLと接続することができる。このためにSpikeの挙動を制御するための方法について調査していく。

Spikeの起動方法

Spikeはいくつかのライブラリから構成されており、最も中心になるのがriscv.aというライブラリであろう。それ以外にもsoftfloatとかfesvrなどのライブラリも用意されているが、これらは最終的にすべてリンクすれば良い。riscv.aにはsim_tというSpikeのコアになるクラスが定義されており、これを制御することでSpikeを制御する。

このsim_tのためにはいくつかオプションが必要なのだが、spike.ccを参考にしながらDPI-CでのSpikeの初期化方法を調査する。

void initial_spike (const char *filename)
{
  argv.push_back("./spike_dpi");
  argv.push_back("--isa=rv64gc");
  argv.push_back(filename);
...

さらに、いくつかのオプションについて設定を行う。これは本来はSpikeの引数として設定されるところであるが、基本的にDPI-Cの場合は特に設定しない限りデフォルト値を使えば良かろう。

  bool debug = false;
  bool halted = false;
  bool histogram = false;
  bool log = false;
  bool dump_dts = false;
  bool dtb_enabled = true;
  bool real_time_clint = false;
  size_t nprocs = 1;
  const char* kernel = NULL;
  reg_t kernel_offset, kernel_size;
  size_t initrd_size;
  reg_t initrd_start = 0, initrd_end = 0;
  const char* bootargs = NULL;
  reg_t start_pc = reg_t(-1);
  std::vector<std::pair<reg_t, mem_t*>> mems;
  std::vector<std::pair<reg_t, abstract_device_t*>> plugin_devices;
  std::unique_ptr<icache_sim_t> ic;
  std::unique_ptr<dcache_sim_t> dc;
  std::unique_ptr<cache_sim_t> l2;
  bool log_cache = false;
  bool log_commits = false;
  const char *log_path = nullptr;
  std::function<extension_t*()> extension;
  const char* initrd = NULL;
  const char* isa = DEFAULT_ISA;
  const char* priv = DEFAULT_PRIV;
  const char* varch = DEFAULT_VARCH;
  const char* dtb_file = NULL;
  uint16_t rbb_port = 0;
  bool use_rbb = false;
  unsigned dmi_rti = 0;
  debug_module_config_t dm_config = {
    .progbufsize = 2,
    .max_bus_master_bits = 0,
    .require_authentication = false,
    .abstract_rti = 0,
    .support_hasel = true,
    .support_abstract_csr_access = true,
    .support_haltgroups = true,
    .support_impebreak = true
  };
  std::vector<int> hartids;

そしてSpikeが使用しているオプションParserを使用する。最終的にこれらの文字列がhtifなどに渡されるので、コード量は増えるがとりあえずspike.ccからほとんどの記述を取り出してくる。

  parser.help(&suggest_help);
  parser.option('h', "help", 0, [&](const char* s){help(0);});
  parser.option('d', 0, 0, [&](const char* s){debug = true;});
  parser.option('g', 0, 0, [&](const char* s){histogram = true;});
  parser.option('l', 0, 0, [&](const char* s){log = true;});
  // parser.option('p', 0, 1, [&](const char* s){nprocs = atoul_nonzero_safe(s);});
  parser.option('m', 0, 1, [&](const char* s){mems = make_mems(s);});

そしてやっとsim_tインスタンス化する。これにより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);

上記の初期化ルーチンを一つにまとめたinitial_spike()externし、RTL側から読みだせるようにする。いろいろ方法はあると思うが、今回はVerilatorを使うのでC++側でinitial_spike()を呼び出そう(Vivado Simの場合はRTL側でDPI-CをImportして呼び出すことになるだろう)。

extern "C" {
  void initial_spike (const char *filename);
}
int main(int argc, char** argv) {
...
  fprintf(stderr, "initial_spike opening %s ...\n", filename);
  initial_spike(filename);

次は、命令がコミットされる毎にSpikeを動かして動作を検証する方法を確立しよう。