FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://sites.google.com/site/fpgadevelopindex/

RISC-V命令セットシミュレータの途中状態を保存する(セッション保存)機能の検討と実装

f:id:msyksphinz:20160520003556j:plain

RISC-V/MIPS対応ISSを作っているのだが、これを使ってxv6などの比較的長いシミュレーション実行時間が必要なプログラムを動作させようとしている。 これらのOSの問題点は、ブートするまでが非常に長く、せっかくブートしたと思っても検証プログラムを流すのにずいぶんと時間がかかってしまう。

この問題は、ISSが途中の状態を保存して、そこから再開できるように出来れば、解決できると考えている。たとえば、次のような機能だ。

  • 特定の関数まで到達すると、CPUの内部状態およびメモリの状態を全てファイルに書き出す (セッション保存)
  • セッションを復元したいときは、CPUの内部状態とメモリの状態をファイルから読み出し、それを全て設定してからシミュレーションを再開する

この機能が実現できれば、(正常に)OSが起動した状態のセッションを保存しておき、アプリケーションの検証したいときは、そのセッションをまずは復元してから、プログラムの実行開始させれば良い。

主に保存しなければならないのは、以下のような情報と考えた。

メモリの値だが、全ての領域の情報を保存しようとすると莫大になるので、差分のみ保存できれば良いと考えている。つまり、ISSがブートを開始した状態に対して、書き込みが行われた履歴のみ保存できていれば良い、ということになる。 しかも、同一アドレスに書き込んだ情報については、最後に書き込んだ情報のみ必要である。

CPUの内部状態を保存する機能の検討と実装

メモリの状態保存については、ちょっとだけ他と比較して重そうなため、まずはプログラムカウンタやCPUの汎用レジスタを保存および復元する機能を考えてみよう。

ちなみにこれらを実現するインタフェースとしては、ISSLuaプログラミング言語のインタフェースを備えているので、Luaの関数としてセッションを保存する関数、セッションを復元する関数を追加する。

save_session (riscv, "session.log")      # セッションをsession.logに保存する
restore_session (riscv, "session.log")   # セッションをsession.logから復元する

Luaのインタフェースの追加

上記の関数を追加するためには、Luaのインタフェースに新しい関数を追加しておく。

github.com

  lua_register (lua_state, "save_session", Lua_SaveSession);
  lua_register (lua_state, "restore_session", Lua_RestoreSession);

...

/*!
 * Save Simulation Session
 */
int Lua_SaveSession (lua_State *L)
{
  // Getting Arguments
  int arg_size = lua_gettop (L);

#ifdef ARCH_MIPS64
  Mips64Env *env = static_cast<Mips64Env *>(lua_touserdata (L, 1));
#else  // ARCH_MIPS64
#ifdef ARCH_MIPS
  MipsEnv *env = static_cast<MipsEnv *>(lua_touserdata (L, 1));
#else  // ARCH_MIPS
#ifdef ARCH_RISCV
  RiscvEnv *env = static_cast<RiscvEnv *>(lua_touserdata (L, 1));
#endif // ARCH_RISCV
#endif // ARCH_MIPS
#endif // ARCH_MIPS64

  std::string save_filename = lua_tostring (L, 2);

  if (arg_size != 2) {
    std::cerr << "<Error: invalid operands. save_sesion(env, save_file)>\n";
    exit (EXIT_FAILURE);
  }

  std::cout << "Save Filename = " << save_filename;
  env->SaveSession (save_filename);

  return 0;
}

セッションを保存する関数本体 (CPUの内部状態を保存する)

非常にありきたりな実装だが、まずはCPUの内部のありとあらゆる状態をファイルに書き出すようにした。

/*!
 * SaveSession saves current context as session
 */
void RiscvEnv::SaveSession (std::string filename)
{
  std::ofstream ofs_session;
  ofs_session.open (filename, std::ios::out);

  // PC
  ofs_session << std::hex << GetPC() << '\n';

  // General Registers
  for (RegAddr_t i = 0; i <= REG_R31; i++) {
    UDWord_t reg_val = GRegRead<UDWord_t>(i);
    ofs_session << std::hex << reg_val << '\n';
  }
  // Floating Point Registers
  for (RegAddr_t i = 0; i <= REG_R31; i++) {
    UDWord_t reg_val = GetFReg<UDWord_t>(i);
    ofs_session << std::hex << reg_val << '\n';
  }

  UDWord_t csr_val;
  m_csr_env->RISCV_Read_CSR (SYSREG_ADDR_FFLAGS,   &csr_val, PrivMode::PrivMachine); ofs_session << std::hex << csr_val << '\n';
  m_csr_env->RISCV_Read_CSR (SYSREG_ADDR_FRM ,     &csr_val, PrivMode::PrivMachine); ofs_session << std::hex << csr_val << '\n';
  m_csr_env->RISCV_Read_CSR (SYSREG_ADDR_FCSR,     &csr_val, PrivMode::PrivMachine); ofs_session << std::hex << csr_val << '\n';
  m_csr_env->RISCV_Read_CSR (SYSREG_ADDR_CYCLE,    &csr_val, PrivMode::PrivMachine); ofs_session << std::hex << csr_val << '\n';
  m_csr_env->RISCV_Read_CSR (SYSREG_ADDR_TIME,     &csr_val, PrivMode::PrivMachine); ofs_session << std::hex << csr_val << '\n';
  m_csr_env->RISCV_Read_CSR (SYSREG_ADDR_INSTRET,  &csr_val, PrivMode::PrivMachine); ofs_session << std::hex << csr_val << '\n';
  m_csr_env->RISCV_Read_CSR (SYSREG_ADDR_CYCLEH,   &csr_val, PrivMode::PrivMachine); ofs_session << std::hex << csr_val << '\n';
  m_csr_env->RISCV_Read_CSR (SYSREG_ADDR_TIMEH,    &csr_val, PrivMode::PrivMachine); ofs_session << std::hex << csr_val << '\n';
...

システムレジスタの保存については、もうちょっと賢い書き方が無いものかと思ったが、まずはこんなもんだろう。

セッションを復元する関数本体 (CPUの内部状態を復元する)

これも逆のことをするだけだ。

/*!
 * SaveSession saves current context as session
 */
void RiscvEnv::RestoreSession (std::string filename)
{
  std::ifstream ifs_session;
  ifs_session.open (filename, std::ios::in);

  // PC
  UDWord_t reg_val;
  ifs_session >> std::hex >> reg_val;
  SetPC (reg_val);

  // General Registers
  for (RegAddr_t i = 0; i <= REG_R31; i++) {
    UDWord_t reg_val;
    ifs_session >> std::hex >> reg_val;
    GRegWrite<UDWord_t>(i, reg_val);
  }
  // Floating Point Registers
  for (RegAddr_t i = 0; i <= REG_R31; i++) {
    UDWord_t reg_val;
    ifs_session >> std::hex >> reg_val;
    WriteFReg<UDWord_t>(i, reg_val);
  }

  UDWord_t csr_val;
  ifs_session >> std::hex >> csr_val; m_csr_env->RISCV_Write_CSR (SYSREG_ADDR_FFLAGS,   csr_val, PrivMode::PrivMachine);
  ifs_session >> std::hex >> csr_val; m_csr_env->RISCV_Write_CSR (SYSREG_ADDR_FRM ,     csr_val, PrivMode::PrivMachine);
  ifs_session >> std::hex >> csr_val; m_csr_env->RISCV_Write_CSR (SYSREG_ADDR_FCSR,     csr_val, PrivMode::PrivMachine);
  ifs_session >> std::hex >> csr_val; m_csr_env->RISCV_Write_CSR (SYSREG_ADDR_CYCLE,    csr_val, PrivMode::PrivMachine);
  ifs_session >> std::hex >> csr_val; m_csr_env->RISCV_Write_CSR (SYSREG_ADDR_TIME,     csr_val, PrivMode::PrivMachine);

まずはこれで、CPUの内部状態は保存・復元できるようになった。 次は、メモリの状態保存について、実装を進めていきたい。