FPGA開発日記

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

RocketChipの足回りを理解する(4. RISC-V FrontEnd Server)

ところで、RISC-V Rocket-Chipをシミュレーションするとき、何気なくmakeを叩いて、

make CONFIG=DefaultConfig output/rv64ui-p-add.out

とかして、勝手に実行されているけど、よく考えると具体的にどのようにして動作しているのか分からない。これを理解するためには、RocketChipがどのようにコンパイルされ、実行されているのかについて知らなければならない。

これを理解するためには、RISC-V FrontEnd Server (fesvr)について理解しなければならない。

RISC-V Rocket Chipが動作するまでの仕組み

まず、VCSシミュレータで実行する場合を見てみる。手元にVCSが実行できないのでmakeの中身を参照しながらだが、

cd vsim
make CONFIG=DefaultConfig

とすると、Makefile群として以下が生成されるはずだ。

これらに、RocketChipを理解するための秘密が隠されている。

必要なファイル群

Makefragを参照すると、以下のファイルが必要なことが分かる。jtagはとりあえず無視してもOKだが、SimDTM.v, SimDTM.ccというのはRocketChipの全体制御を管理しているものだ。

bb_vsrcs = \
    $(base_dir)/vsrc/jtag_vpi.v \
    $(base_dir)/vsrc/plusarg_reader.v \
    $(base_dir)/vsrc/ClockDivider2.v \
    $(base_dir)/vsrc/ClockDivider3.v \
    $(base_dir)/vsrc/AsyncResetReg.v \

sim_vsrcs = \
    $(generated_dir)/$(long_name).v \
    $(generated_dir)/$(long_name).behav_srams.v \
    $(base_dir)/vsrc/$(TB).v \
    $(base_dir)/vsrc/SimDTM.v \
    $(bb_vsrcs)

# C sources

sim_csrcs = \
    $(base_dir)/csrc/SimDTM.cc \
    $(base_dir)/csrc/jtag_vpi.c

また、VCSコンパイルオプションに、RISC-Vのライブラリが指定されていることが分かる。これがRISC-Vのフロントエンドサーバfesvrだ。

VCS_OPTS = -notice -line +lint=all,noVCDE,noONGS,noUI -error=PCWM-L -timescale=1ns/10ps -quiet \
    +rad +v2k +vcs+lic+wait \
    +vc+list -CC "-I$(VCS_HOME)/include" \
    -CC "-I$(RISCV)/include" \
    -CC "-std=c++11" \
    -CC "-Wl,-rpath,$(RISCV)/lib" \
    $(RISCV)/lib/libfesvr.so \
    -sverilog \
    +incdir+$(generated_dir) \
    +define+CLOCK_PERIOD=1.0 $(sim_vsrcs) $(sim_csrcs) \
    +define+PRINTF_COND=$(TB).printf_cond \
    +define+STOP_COND=!$(TB).reset \
    +define+RANDOMIZE_MEM_INIT \
    +define+RANDOMIZE_REG_INIT \
    +define+RANDOMIZE_GARBAGE_ASSIGN \
    +define+RANDOMIZE_INVALID_ASSIGN \
    +libext+.v \

RocketChipのシミュレーション時フロー

前回紹介したように、RocketChipの構造は以下のようになっている。

f:id:msyksphinz:20170712013910p:plain

TestDriver.v

シミュレーションのオプションを管理する部分。$value$plusargsの処理などはここで行われている。TestDriver内でTestHarnessがインスタンスされている。

TestHarness

TestHarnessの中にインスタンスされているものを具体的に列挙すると、

  • ExampleRocketTop : RocketChip本体に当たる。
  • SimAXIMem : mem_axi4 が接続されているRAMにあたる。おそらく通常の命令、データアクセスに当たる。
  • SimAXIMem_1 : mmio_axi4が接続されているRAMにあたる。MMIOのデータアクセスに当たる。
  • SimDTM : RTLのコントロール部になる。

ここではとりあえずRocketChipの本体とか、SimAXIMemはほおっておいて、SimDTMについて着目する。

SimDTM.vを参照する

SimDTM.vを参照してみる。

module SimDTM(
  input clk,
  input reset,

...

      __exit = debug_tick(
        __debug_req_valid,
        __debug_req_ready,
        __debug_req_bits_addr,
        __debug_req_bits_op,
        __debug_req_bits_data,
        __debug_resp_valid,
        __debug_resp_ready,
        __debug_resp_bits_resp,
        __debug_resp_bits_data
      );

このdebug_tick()が、毎サイクル実行されるというわけだ。このdebug_tick()

  if (!dtm) {
    s_vpi_vlog_info info;
    if (!vpi_get_vlog_info(&info))
      abort();
      dtm = new dtm_t(filter_argv_for_dtm(info.argc, info.argv));
  }

として定義されており、このdtm_tfesvrに定義されている。このdtmriscv-tools/riscv-fesvr/fesvr/dtm.ccに定義されており、ここではコンストラクタを呼んでいるので、

dtm_t::dtm_t(const std::vector<std::string>& args)
  : htif_t(args)
{
  start_host_thread();
}

...

htif_t::htif_t(const std::vector<std::string>& args)
  : mem(this), entry(DRAM_BASE), sig_addr(0), sig_len(0),
    tohost_addr(0), fromhost_addr(0), exitcode(0), stopped(false),
    syscall_proxy(this)
{
  signal(SIGINT, &handle_signal);
  signal(SIGTERM, &handle_signal);
  signal(SIGABRT, &handle_signal); // we still want to call static destructors

  size_t i;

ここで、最初にload_program()が呼ばれることで、ELFファイルがロードされる。

void htif_t::load_program()
{
  std::string path;
  if (access(targs[0].c_str(), F_OK) == 0)
    path = targs[0];
  else if (targs[0].find('/') == std::string::npos)
  {
    std::string test_path = PREFIX TARGET_DIR + targs[0];
    if (access(test_path.c_str(), F_OK) == 0)
      path = test_path;
  }

  if (path.empty())
    throw std::runtime_error("could not open " + targs[0]);

  std::map<std::string, uint64_t> symbols = load_elf(path.c_str(), &mem, &entry);

  if (symbols.count("tohost") && symbols.count("fromhost")) {
    tohost_addr = symbols["tohost"];
    fromhost_addr = symbols["fromhost"];
  } else {
    fprintf(stderr, "warning: tohost and fromhost symbols not in ELF; can't communicate with target\n");
  }

  // detect torture tests so we can print the memory signature at the end
  if (symbols.count("begin_signature") && symbols.count("end_signature"))
  {
    sig_addr = symbols["begin_signature"];
    sig_len = symbols["end_signature"] - sig_addr;
  }
}