FPGA開発日記

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

RISC-V. Open Hardware for Your Open Source Software @ FOSDEM17

FOSDEM17 というイベントでRISC-Vについての講演があるらしい。

FOSDEMというイベントはこれまで知らなかったのだが、"FOSDEM is a free event for software developers to meet, share ideas and collaborate"ということで、 主にオープンソースツールについての発表やイベントが行われる場ということらしい。

f:id:msyksphinz:20170206003513p:plain

FOSDEM 2017 - Home

2/5のアーキテクチャのセッションとしてRISC-Vについての講演が行われるようだ。ビデオも公開されたら見てみたい。

FOSDEM 2017 - Architectures

ちなみに、発表の概要については発表前にArun Thomasのインタビューがあるので、大体そこで何を言いたいのかが分かる。 今回は学界的な発表ではなくて、招待講演のような、ざっくりとした概要をしゃべるものと思われるね。

https://fosdem.org/2017/interviews/arun-thomas/

RISC-VのSpike-ISSを使ったFreeRTOSの起動ルーティンの解析(3)

f:id:msyksphinz:20170201002310j:plain

前回の続きで、ROMにはさらにいくつかの情報を挿入しなければならない。0x1020から挿入しなければならないのが、以下のRISC-Vのコンフィグレーションに関する情報だ。

前回の記事で紹介した以下のものになる。

platform {
  vendor ucb;
  arch spike;
};
rtc {
  addr 0x40000000;
};
ram {
  0 {
    addr 0x80000000;
    size 0xaef00000;
  };
};
core {
  0 {
    0 {
      isa rv64imafdc;
      timecmp 0x40000008;
      ipi 0x40001000;
    };
  };
};

これをまずは文字列として定義し、それをROMの初期値としてコピーする。

github.com

  std::stringstream s;
  s << std::hex <<
      "platform {\n" \
      "  vendor ucb;\n" \
      "  arch spike;\n" \
      "};\n" \
      "rtc {\n" \
      "  addr 0x40000000;\n" \
      "};\n" \
      "ram {\n" \
      "  0 {\n" \
      "    addr 0x80000000;\n" \
      "    size 0xaef00000;\n" \
      "  };\n" \
      "};\n" \
      "core {\n" \
      "  0 {\n" \
      "    0 {\n" \
      "      isa rv64ima;\n" \
      "      timecmp 0x40000008;\n" \
      "      ipi 0x40001000;\n" \
      "    };\n" \
      "  };\n" \
      "};\n";

  std::string config_string = s.str();
  memcpy (&m_memory[0x20], config_string.c_str(), config_string.size());

最後にmemcpyをして、ROMの当該位置に値を挿入した。

結構やっつけだが、これでROMについては完成。

さらにSpike-ISSとの不一致部分について調査していたのだが、ここから先はタイマ割り込みに入ってしまった。。。さてどうやって実現しようか。

RISC-VのSpike-ISSを使ったFreeRTOSの起動ルーティン解析(2)

f:id:msyksphinz:20170201002310j:plain

前回の続き。Spike-ISSには通常のDRAM以外に別のメモリが貼り付けられていることが分かった。

今回焦点を当てたいのは0x1000に貼り付けられているROMらしきものだ。sim.ccには、以下のような記述でROMが生成されていた。

  uint32_t reset_vec[8] = {
    0x297 + DRAM_BASE - DEFAULT_RSTVEC, // reset vector
    0x00028067,                         //   jump straight to DRAM_BASE
    0x00000000,                         // reserved
    0,                                  // config string pointer
    0, 0, 0, 0                          // trap vector
  };
  reset_vec[3] = DEFAULT_RSTVEC + sizeof(reset_vec); // config string pointer

  config_string = s.str();

  rom.insert(rom.end(), config_string.begin(), config_string.end());
  rom.resize((rom.size() / align + 1) * align);

  boot_rom.reset(new rom_device_t(rom));
  bus.add_device(DEFAULT_RSTVEC, boot_rom.get());

config_string--dump-config-stringで出てくるアレだ。

platform {
  vendor ucb;
  arch spike;
};
rtc {
  addr 0x40000000;
};
ram {
  0 {
    addr 0x80000000;
    size 0xaef00000;
  };
};
core {
  0 {
    0 {
      isa rv64imafdc;
      timecmp 0x40000008;
      ipi 0x40001000;
    };
  };
};

ただし、その前に4byte x8=32byteほど、特殊な情報が貼り付けられている。reset_vecで定義されているところだ。 ダンプしてみると以下のようになる。

7ffff29f
00028067
00000000
00001020
00000000
00000000
00000000
00000000

なるほど、0x100cを参照したときに返された値はこの0x1020だったのか。これは分からない。。。

という訳でROM領域を急遽実装した。

github.com

  • module_rom.cpp
MemResult ModuleRom::LoadData (Addr_t addr, Size_t size, Word_t *data)
{
  memcpy (data, &m_memory[addr], size);
  m_env->DebugPrint ("<ROM: Load (%08x)=>%08x, %d\n", addr, *data, size);
  m_env->GetTrace()->RecordTraceMemRead (addr, *data, Size_Word);

  return MemResult::MemNoExcept;
}

ModuleRom::ModuleRom (EnvBase *env, FILE *dbgfp)
{
  m_env = env;

  uint32_t init[8] = {0x7ffff29f,
                      0x00028067,
                      0x00000000,
                      0x00001020,
                      0x00000000,
                      0x00000000,
                      0x00000000,
                      0x00000000};
  for (int i = 0; i < 8; i++) {
    memcpy (&m_memory[i*4], &init[i], 4);
  }
}

とりあえず問題の場所は解決しt。次にまだSpikeと一致しないところがあるから直さないと。。。

RISC-VのSpike-ISSを使ったFreeRTOSの起動解析

前回の続きで、どうしても自作ISSとSpike-ISSの動作結果が一致しないところがあり、何が悪いんだろうと調べていた。

まず、spikeにはRISCV_ENABLE_COMMITLOGというログ出力用のフォーマットがある事は紹介した。 これだけでは情報としては不十分で、例えば実際にどのアドレスに対してメモリアクセスを発行したか等の情報を取得したい。 これらの情報はdefineで切ってあるわけではないので、自分で追加する必要がある。

RISC-V Spike-ISS の命令実行の仕組み

まず、RISC-V Spike-ISSはどのようにして命令をシミュレートしているのか、その仕組みを解き明かしてみる。

例えば、lw命令を実行する記述はどこに書いてあるかというと、grepしてみると以下のようなファイルを見つけることが出来る。

riscv/insns/c_lw.h
WRITE_RVC_RS2S(MMU.load_int32(RVC_RS1S + insn.rvc_lw_imm()));

一見、C++には見えないのだが、これはdefineが切りまくってあるC++だ。よく見ると、RSフィールドで指定される汎用レジスタに対して、RS1フィールド+即値フィールドを加算したアドレスでメモリアクセスした結果のデータを格納しているのが分かるだろう。

ここが気になった理由は、RISC-V 自作ISSとSpike-ISSの違いが、アドレス0x0000100cのメモリデータの違いとなって現れたからだ。

  • Spike-ISSの結果
core   0: 0x000000008000117c (0x0007a783) lw      a5, 0(a5)
3 0x000000008000117c (0x0007a783) x15 0x0000000000001020
  • 自作ISSの結果
    210822:M:MBar:[8000117c][P8000117c] 0007a783 : lw         r15,0x000(r15)       r15=>000000000000100c (000000000000100c)=>80001178 r15<=ffffffff80001178

という訳で、ロード結果はどのようにして取り扱われるのかを調べていたのだが、Spikeにおけるメモリアクセスの記述は、以下だ。

  // template for functions that load an aligned value from memory
  #define load_func(type) \
    inline type##_t load_##type(reg_t addr) { \
      if (addr & (sizeof(type##_t)-1)) \
        throw trap_load_address_misaligned(addr); \
      reg_t vpn = addr >> PGSHIFT; \
      if (likely(tlb_load_tag[vpn % TLB_ENTRIES] == vpn)) \
        return *(type##_t*)(tlb_data[vpn % TLB_ENTRIES] + addr); \
      if (unlikely(tlb_load_tag[vpn % TLB_ENTRIES] == (vpn | TLB_CHECK_TRIGGERS))) { \
        type##_t data = *(type##_t*)(tlb_data[vpn % TLB_ENTRIES] + addr); \
        if (!matched_trigger) { \
          matched_trigger = trigger_exception(OPERATION_LOAD, addr, data); \
          if (matched_trigger) \
            throw *matched_trigger; \
        } \
        return data; \
      } \
      type##_t res; \
      load_slow_path(addr, sizeof(type##_t), (uint8_t*)&res); \
      return res; \
    }

  // load value from memory at aligned address; zero extend to register width
  load_func(uint8)
  load_func(uint16)
  load_func(uint32)
  load_func(uint64)

  // load value from memory at aligned address; sign extend to register width
  load_func(int8)
  load_func(int16)
  load_func(int32)
  load_func(int64)

メチャメチャdefineが切ってあるが、C++言語だ。つまり、アクセスサイズごとにメモリ関数が定義されており、

  • アドレスアラインが間違っていれば、トラップ発生
  • TLBに引っかかれば、TLB変換
  • そうでない場合は、通常アクセス (load_slow_path)

load_slow_path()の中を見てみよう。

void mmu_t::load_slow_path(reg_t addr, reg_t len, uint8_t* bytes)
{
  reg_t paddr = translate(addr, LOAD);

  if (sim->addr_is_mem(paddr)) {
    memcpy(bytes, sim->addr_to_mem(paddr), len);
    if (tracer.interested_in_range(paddr, paddr + PGSIZE, LOAD))
      tracer.trace(paddr, len, LOAD);
    else
      refill_tlb(addr, paddr, LOAD);
  } else if (!sim->mmio_load(paddr, len, bytes)) {
    throw trap_load_access_fault(addr);
  }

  if (!matched_trigger) {
    reg_t data = reg_from_bytes(len, bytes);
    matched_trigger = trigger_exception(OPERATION_LOAD, addr, data);
    if (matched_trigger)
      throw *matched_trigger;
  }
}

printfデバッグ(笑)をしていくと、どうやら0x100cはaddr_is_memには引っかからないらしい。つまり特殊アドレスとして処理されるということか?

else節でMMIO_Loadが呼ばれている。

bool sim_t::mmio_load(reg_t addr, size_t len, uint8_t* bytes)
{
  if (addr + len < addr)
    return false;
  return bus.load(addr, len, bytes);
}

これはどこからロードされているんだろう?別のデバイスから引かれているんだろうか。調査要。

次に引っかかった、reg_from_bytes()を調べていくと、

reg_t reg_from_bytes(size_t len, const uint8_t* bytes)
{
  switch (len) {
    case 1:
      return bytes[0];
    case 2:
      return bytes[0] |
        (((reg_t) bytes[1]) << 8);
    case 4:
      return bytes[0] |
        (((reg_t) bytes[1]) << 8) |
        (((reg_t) bytes[2]) << 16) |
        (((reg_t) bytes[3]) << 24);
    case 8:
      return bytes[0] |
        (((reg_t) bytes[1]) << 8) |
        (((reg_t) bytes[2]) << 16) |
        (((reg_t) bytes[3]) << 24) |
        (((reg_t) bytes[4]) << 32) |
        (((reg_t) bytes[5]) << 40) |
        (((reg_t) bytes[6]) << 48) |
        (((reg_t) bytes[7]) << 56);
  }
  abort();
}

なんでこれで、mmioからロードされた値が並べられる、ということか。

RISC-V のISS "Spike"のログフォーマットを拡張する方法

f:id:msyksphinz:20170201002310j:plain

RISC-VにはUC-Berkeleyの開発した命令セットシミュレータであるSpikeが存在する。

github.com

このSpikeというシミュレータ、高速でLinuxもブートできる優れものなのだが、欠点はログがほとんど見れず、プログラムが誤作動を起こしても何が起きているのかさっぱり分からないことだった。

  • 通常起動させると基本的にログを出さない
spike riscv-spike.elf
  • -lオプションを付ける事により、標準出力にログを出すことが可能(ただしPC値と逆アセンブルコード)
spike -l riscv-spike.elf
core   0: 0x0000000080000000 (0x00002fb7) lui     t6, 0x2
core   0: 0x0000000080000004 (0x800f8f9b) addiw   t6, t6, -2048
core   0: 0x0000000080000008 (0x300f9073) csrw    mstatus, t6
core   0: 0x000000008000000c (0x0340006f) j       pc + 0x34
core   0: 0x0000000080000040 (0x00000297) auipc   t0, 0x0
core   0: 0x0000000080000044 (0x12428293) addi    t0, t0, 292
core   0: 0x0000000080000048 (0x30529073) csrw    mtvec, t0
core   0: 0x000000008000004c (0x00000093) li      ra, 0
core   0: 0x0000000080000050 (0x00000113) li      sp, 0
core   0: 0x0000000080000054 (0x00000193) li      gp, 0
core   0: 0x0000000080000058 (0x00000213) li      tp, 0
core   0: 0x000000008000005c (0x00000293) li      t0, 0
core   0: 0x0000000080000060 (0x00000313) li      t1, 0
core   0: 0x0000000080000064 (0x00000393) li      t2, 0
core   0: 0x0000000080000068 (0x00000413) li      s0, 0
core   0: 0x000000008000006c (0x00000493) li      s1, 0
core   0: 0x0000000080000070 (0x00000513) li      a0, 0
core   0: 0x0000000080000074 (0x00000593) li      a1, 0
...

これが無いと、例えば64ビットバイナリを流すときに-isa=RV64IMAをつけるのを忘れたときに、何がおきているのか分からなかったりする。

ただし、これでもレジスタの書き込み値が表示されなかったりして、詳細なデバッグをしたい時に不便だったりする。

–enable-commitlog=yesビルドオプションにてシミュレーションログを拡張する

ソースコードを眺めていると、どうやら詳細なログを出すためのdefineが切ってあるのを発見した。

      STEP_STEPPED
  } single_step;

  reg_t load_reservation;

#ifdef RISCV_ENABLE_COMMITLOG
  commit_log_reg_t log_reg_write;
  reg_t last_inst_priv;
#endif
};
...

これを有効にするのはどうしたらいいのか、いろいろ調査していくと、どうやらconfigure実行時に--enable-commitlog=yesを追加すれば有効になることが分かった。

riscv-toolsリポジトリ中のbuild-spike-only.shを改造して、--enable-commitlog=yesビルドオプションを追加しよう。

  • riscv-tools/build-spike-only.sh
diff --git a/build-spike-only.sh b/build-spike-only.sh
index ac3fda5..2673dc8 100755
--- a/build-spike-only.sh
+++ b/build-spike-only.sh
@@ -7,6 +7,6 @@
 echo "Starting RISC-V Toolchain build process"

 build_project riscv-fesvr --prefix=$RISCV
-build_project riscv-isa-sim --prefix=$RISCV --with-fesvr=$RISCV
+build_project riscv-isa-sim --prefix=$RISCV --with-fesvr=$RISCV --enable-commitlog=yes

 echo -e "\\nRISC-V Toolchain installation completed!"

このオプション付きでSpikeをビルドし直し、実行してみると以下のようにレジスタ書き込みのログが拡張された。

core   0: 0x0000000000001000 (0x7ffff297) auipc   t0, 0x7ffff
3 0x0000000000001000 (0x7ffff297) x 5 0x0000000080000000
core   0: 0x0000000000001004 (0x00028067) jr      t0
3 0x0000000000001004 (0x00028067)
core   0: 0x0000000080000000 (0x00002fb7) lui     t6, 0x2
3 0x0000000080000000 (0x00002fb7) x31 0x0000000000002000
core   0: 0x0000000080000004 (0x800f8f9b) addiw   t6, t6, -2048
3 0x0000000080000004 (0x800f8f9b) x31 0x0000000000001800
core   0: 0x0000000080000008 (0x300f9073) csrw    mstatus, t6
3 0x0000000080000008 (0x300f9073)
core   0: 0x000000008000000c (0x0340006f) j       pc + 0x34
3 0x000000008000000c (0x0340006f)
core   0: 0x0000000080000040 (0x00000297) auipc   t0, 0x0
3 0x0000000080000040 (0x00000297) x 5 0x0000000080000040
core   0: 0x0000000080000044 (0x12428293) addi    t0, t0, 292
3 0x0000000080000044 (0x12428293) x 5 0x0000000080000164
core   0: 0x0000000080000048 (0x30529073) csrw    mtvec, t0
3 0x0000000080000048 (0x30529073)
core   0: 0x000000008000004c (0x00000093) li      ra, 0
3 0x000000008000004c (0x00000093) x 1 0x0000000000000000
core   0: 0x0000000080000050 (0x00000113) li      sp, 0
3 0x0000000080000050 (0x00000113) x 2 0x0000000000000000
core   0: 0x0000000080000054 (0x00000193) li      gp, 0
3 0x0000000080000054 (0x00000193) x 3 0x0000000000000000
core   0: 0x0000000080000058 (0x00000213) li      tp, 0

ちょっと見にくいけれど、、、例えばauipc命令がx5(=t0)に即値の書き込みを発生させていることがわかるようになった。

これで、詳細なレジスタ値の遷移が読み取れるようになった。自作ISSと比較して、今の自作ISSのどこが悪いのかデバッグして行こう(やっと本来の目的に戻ってきた…)

FreeRTOSをRISC-V Spike ISSで動作させるためのオプション

しばらくFreeRTOSをRISC-Vの自作ISSや、Spikeシミュレータで動作させることが出来ず悩んでいたのだが、いつの間にかSpike-ISSに以下のオプションが追加されているのを発見した。

$ spike --help
spike: unrecognized option --help
usage: spike [host options] <target program> [target options]
Host Options:
  -p<n>                 Simulate <n> processors [default 1]
  -m<n>                 Provide <n> MiB of target memory [default 4096]
  -d                    Interactive debug mode
  -g                    Track histogram of PCs
  -l                    Generate a log of execution
  -h                    Print this help message
  -H                 Start halted, allowing a debugger to connect
  --isa=<name>          RISC-V ISA string [default RV32IMA]
  --ic=<S>:<W>:<B>      Instantiate a cache model with S sets,
  --dc=<S>:<W>:<B>        W ways, and B-byte blocks (with S and
  --l2=<S>:<W>:<B>        B both powers of 2).
  --extension=<name>    Specify RoCC Extension
  --extlib=<name>       Shared library to load
  --gdb-port=<port>  Listen on <port> for gdb to connect
  --dump-config-string  Print platform configuration string and exit

-lを追加することで、命令トレースが生成されるらしい。

$ spike -l riscv-spike.elf > riscv-spike.sw.log 2>&1

riscv-spike.sw.logを参照してみると、

warning: only got 2934964224 bytes of target mem (wanted 4026531840)
core   0: 0x0000000000001000 (0x7ffff297) auipc   t0, 0x7ffff
core   0: 0x0000000000001004 (0x00028067) jr      t0
core   0: 0xffffffff80000000 (0x00002fb7) lui     t6, 0x2
core   0: 0xffffffff80000004 (0x800f8f9b) addiw   t6, t6, -2048
core   0: exception trap_illegal_instruction, epc 0xffffffff80000004
core   0: 0x0000000000001010 (0x00000000) addi    s0, sp, 0
core   0: exception trap_illegal_instruction, epc 0x0000000000001010
...

ん?すぐに例外に飛んでしまった。しかもaddiwということは実行モードが間違っているのか?

  --isa=<name>          RISC-V ISA string [default RV32IMA]

そうだ!デフォルトでは32ビットモードになっているのか!という訳で--isa=RV64IMAを追加してみた。

$ spike --isa=RV64IMA riscv-spike.elf
warning: only got 2934964224 bytes of target mem (wanted 4026531840)
PASS!

おー、ちゃんとパスした!ということは、Makefileが間違っているんじゃん!

sim:
-    spike $(PROG).elf
+    spike --isa=RV64IMA $(PROG).elf

資格試験(TOEIC)の勉強をRedmineで管理してみる試行

TOEIC受けてきた。毎年1度は受験するようにしているのだが、今回は比較的大きな会場だったのだが、音響もしっかりしている分、聞き取りにくいということは無かった。

去年の春からTOEICは新形式になっており、一応勉強はしていたのだがやはり時間配分や問題の難易度が変わっており、少し対応が難しく感じた。 まあこれも慣れかな。新形式になって10年、また少しずつ対応していけばよいと思う。 まあ今回の試験は黒歴史として葬り去られるかもしれない(笑)。

さて、今回はちょっとした挑戦として、TOEICの試験勉強をRedmineで管理してみた。 目的としては、複数の教科書で漏れが無いように勉強するためだ。

f:id:msyksphinz:20170129205639p:plain

少し感覚を空けて問題集をリトライする時でも、どこまでやっていたかを記録しているし、進捗もガントチャートで表示できるので、まあまあ良いんじゃないかな。 ほかの勉強でも、Redmineで管理してみよう。