FPGA開発日記

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

自作CPUのシミュレーション環境に、サイクル精度シミュレータを接続する

自作CPUの環境に、モデルシミュレータのサイクル精度との比較環境を構築した。RTLもモデルシミュレータも波形ビューアで表示可能なログを表示することができ、パイプラインの挙動を比較することができる。

このためには、大まかに言って以下の作業が必要だ。

  • RTLによりパイプライン・ビューアにより可視化可能なフォーマットを出力する(対応済み)
  • RTLシミュレーション時に同時動作するSpikeが、Sniper(サイクル精度モデリングツール)に入力可能なトレースファイルを出力する
  • Sniperにより、トレースファイルを読み込みサイクル計算を行う。
  • Sniperにより、パイプライン・ビューアにより可視化可能なフォーマットを出力する

ということになる。まずは、Spikeを改造し、Sniperが取り扱い可能なSIFTトレースフォーマットを生成するように変更する必要がある。

まず、Spikeのriscv.acを書き換えて、--with-sift=${SNIPER_ROOT}オプションを追加する。この時、RISCV_ENABLE_SIFTのdefineマクロを追加する(この辺のconfigureの作法が全く分からずにかなり迷った…)

  • riscv-isa-sim/riscv/riscv.ac
+AC_ARG_WITH(sift,
+       [AS_HELP_STRING([--with-sift=PATH],
+               [Sets Sniper root directory for SIFT])],
+  [AC_DEFINE([DEFAULT_SIFT], "$withval", [Default sniper location])
+   CPPFLAGS="-I$withval/sift $CPPFLAGS"
+   LDFLAGS="-L$withval/sift $LDFLAGS"
+   LIBS="$LIBS -lsift -lz"],
+
+  AC_DEFINE([DEFAULT_SIFT], ["default path"], [Default sniper location]))
+AC_DEFINE([RISCV_ENABLE_SIFT], [], [Sniper SIFT enable])

基本的には、Spikeに対して命令動作のたびにトレースファイルを出力するようにする。

  • riscv-isa-sim/riscv/execute.cc
+static void log_print_sift_trace(processor_t* p, reg_t pc, insn_t insn)
+{
+#ifdef RISCV_ENABLE_SIFT
+
+  FILE *log_file = p->get_log_file();
+  fprintf(log_file, "log_print_sift_trace called, %08lx\n", pc);
+
+  uint64_t addr = pc;
+  uint64_t size = insn.length();
+  uint64_t num_addresses = p->get_state()->log_addr_valid;
+  uint64_t *addresses = p->get_state()->log_addr;
+  reg_t    *wr_regs = p->get_state()->log_reg_addr;
+  bool     is_branch = p->get_state()->log_is_branch;
+  bool     taken = p->get_state()->log_is_branch_taken;
+
+  auto& reg = p->get_state()->log_reg_write;
+  p->get_state()->log_writer->Instruction(addr, size, num_addresses, addresses, is_branch, taken, 0 /*is_predicate*/, 1 /*executed*/);
+  if (sift_executed_insn == 0x00100013) {  // ADDI
+    p->get_state()->log_writer->Magic (1, 0, 0);   // SIM_ROI_START = 1 at sim_api.h
+  }
+  if (sift_executed_insn == 0x00200013) {  // ADDI
+    p->get_state()->log_writer->Magic (2, 0, 0);   // SIM_ROI_END = 2 at sim_api.h
+  }
+
+  p->get_state()->log_addr_valid = 0;
+  p->get_state()->log_is_branch = false;
+  p->get_state()->log_is_branch_taken = false;
+#endif
+}
@@ -184,22 +237,29 @@ static reg_t execute_insn(processor_t* p, reg_t pc, insn_fetch_t fetch)
     if (npc != PC_SERIALIZE_BEFORE) {

 #ifdef RISCV_ENABLE_COMMITLOG
+      record_executed_insn (fetch.insn.bits());
       if (p->get_log_commits_enabled()) {
         commit_log_print_insn(p, pc, fetch.insn);
       }
 #endif

+      log_print_sift_trace(p, pc, fetch.insn);
+
      }
 #ifdef RISCV_ENABLE_COMMITLOG
   } catch (wait_for_interrupt_t &t) {
+      record_executed_insn (fetch.insn.bits());
       commit_log_print_insn(p, pc, fetch.insn);
+      log_print_sift_trace(p, pc, fetch.insn);
       throw;
   } catch(mem_trap_t& t) {
       //handle segfault in midlle of vector load/store
       if (p->get_log_commits_enabled()) {
         for (auto item : p->get_state()->log_reg_write) {
           if ((item.first & 3) == 3) {
+            record_executed_insn (fetch.insn.bits());
             commit_log_print_insn(p, pc, fetch.insn);
+            log_print_sift_trace(p, pc, fetch.insn);
             break;
           }
diff --git a/riscv/mmu.h b/riscv/mmu.h
index 060552eb..8aac973b 100644
--- a/riscv/mmu.h
+++ b/riscv/mmu.h
@@ -93,9 +93,22 @@ public:
 #define RISCV_XLATE_VIRT (1U << 0)
 #define RISCV_XLATE_VIRT_MXR (1U << 1)

+#ifdef RISCV_ENABLE_SIFT
+# define LOG_ADDR(addr, reg_addr) ({   \
+      if (proc && proc->get_state()) { \
+        proc->get_state()->log_addr[proc->get_state()->log_addr_valid] = addr; \
+        proc->get_state()->log_reg_addr[proc->get_state()->log_addr_valid] = reg_addr; \
+        proc->get_state()->log_addr_valid++; \
+      } \
+    })
+#else
+# define LOG_ADDR(addr) do {} while (false)
+#endif
+
   // template for functions that load an aligned value from memory
   #define load_func(type, prefix, xlate_flags) \
     inline type##_t prefix##_##type(reg_t addr, bool require_alignment = false) { \
+      LOG_ADDR(addr, 0);                                                \
       if ((xlate_flags) != 0) \
         flush_tlb(); \
       if (unlikely(addr & (sizeof(type##_t)-1))) { \
@@ -162,6 +175,7 @@ public:
   // template for functions that store an aligned value to memory
   #define store_func(type, prefix, xlate_flags) \
     void prefix##_##type(reg_t addr, type##_t val) { \
+      LOG_ADDR(addr, 0);                             \
       if ((xlate_flags) != 0) \
         flush_tlb(); \
       if (unlikely(addr & (sizeof(type##_t)-1))) \

ついでに、ちゃんとRTLシミュレーションが終わった後にSpikeを終了させないと、SIFTフォーマットは圧縮されて出力されるので、ちゃんと吐き出し切ってくれなくなるので注意。

diff --git a/cpp/tb_mycpu.cpp b/cpp/tb_mycpu.cpp
index 6cbb9f9..464e654 100644
--- a/cpp/tb_mycpu.cpp
+++ b/cpp/tb_mycpu.cpp
@@ -21,6 +21,7 @@ extern "C" {
   void initial_spike (const char *filename, int rv_xlen, int rv_flen, const char *ext_isa);
   void stop_sim(int code, long long rtl_time);
   void stop_sim_deadlock(long long rtl_time);
+  void delete_spike ();
 }

 extern "C" {
@@ -242,6 +243,8 @@ void stop_sim(int code, long long rtl_time)
   if (dump_fst_enable) tfp->close();
 #endif // DUMP_FST

+  delete_spike();
+
   exit(!(code == 1));
 }

RTLシミュレーションとは別にSniperを駆動すれば、両方のパイプライン・チャートを取得することができるというわけだ。