FPGA開発日記

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

RISC-V SpikeシミュレータでC/C++のprintfを実現する仕組み (2. Device Tree Blobと Proxy Kernel)

RISC-Vのシミュレータは、シミュレーション対象のプログラムのElfファイル以外に、いくつかの外部ライブラリをロードしている。

  • RISC-V の Device Tree (SpikeのビルドにDevice Tree Compiler が必要なのはこのためだ)
  • RISC-V の Proxy Kernel (I/Oなどの本来OSが行う仕事を肩代わりしている)
f:id:msyksphinz:20180611010051p:plain
図. RISC-V Spike Instruction Set Simulatorの入出力ファイル

まず、Proxy Kernelについては libpk.a ではなく ${RISCV}/riscv-unknown-elf/bin/pk を観察する。 Proxy Kernel のエントリポイントは0x8000_0000 に設定されているため、この場所からプログラムの実行がスタートする。

の前に、本当に最初に始まるのはデバッグコードの実行だ。これはSpikeに以下のように記述されている。 見てわかる通り、このコードは最初にstart_pcのコードにジャンプする。これが上記の${RISCV}/riscv-unknown-elf/bin/pkに相当する。

  • ./riscv-isa-sim/riscv/sim.cc
  uint32_t reset_vec[reset_vec_size] = {
    0x297,                                      // auipc  t0,0x0
    0x28593 + (reset_vec_size * 4 << 20),       // addi   a1, t0, &dtb
    0xf1402573,                                 // csrr   a0, mhartid
    get_core(0)->get_xlen() == 32 ?
      0x0182a283u :                             // lw     t0,24(t0)
      0x0182b283u,                              // ld     t0,24(t0)
    0x28067,                                    // jr     t0
    0,
    (uint32_t) (start_pc & 0xffffffff),
    (uint32_t) (start_pc >> 32)
  };

次にDevice Treeだが、これは何とSpikeが自身でDevice Tree Compilerを動作させて生成するようになっている。 例えば、デフォルトのSpikeを立ち上げたときに生成されるDTSは以下のようになる。これはSpike の --dump-dts で確認できる。

$ spike --dump-dts test_output_c
/dts-v1/;

/ {
  #address-cells = <2>;
  #size-cells = <2>;
  compatible = "ucbbar,spike-bare-dev";
  model = "ucbbar,spike-bare";
  cpus {
    #address-cells = <1>;
    #size-cells = <0>;
    timebase-frequency = <10000000>;
    CPU0: cpu@0 {
      device_type = "cpu";
      reg = <0>;
      status = "okay";
      compatible = "riscv";
      riscv,isa = "rv64imafdc";
      mmu-type = "riscv,sv48";
      clock-frequency = <1000000000>;
      CPU0_intc: interrupt-controller {
        #interrupt-cells = <1>;
        interrupt-controller;
        compatible = "riscv,cpu-intc";
      };
    };
  };
  memory@80000000 {
    device_type = "memory";
    reg = <0x0 0x80000000 0x0 0x80000000>;
  };
  soc {
    #address-cells = <2>;
    #size-cells = <2>;
    compatible = "ucbbar,spike-bare-soc", "simple-bus";
    ranges;
    clint@2000000 {
      compatible = "riscv,clint0";
      interrupts-extended = <&CPU0_intc 3 &CPU0_intc 7 >;
      reg = <0x0 0x2000000 0x0 0xc0000>;
    };
  };
  htif {
    compatible = "ucb,htif0";
  };
};

これをコンパイルしてDTBを作成し、これをブートコードの後ろに挿入している。

  • ./riscv-isa-sim/riscv/sim.cc
  dts = s.str();
  std::string dtb = dts_compile(dts);

  rom.insert(rom.end(), dtb.begin(), dtb.end());
  const int align = 0x1000;
  rom.resize((rom.size() + align - 1) / align * align);

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