FPGA開発日記

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

Verilatorを使って独自CPUコア環境にELFをロードする

一応、前回までで、コアとRAMが用意できたので、次にコア実行前にRAMにELFファイルのデータを流し込むための仕組みを作る。これについてはSiFiveのDTMの仕組みをそのまま使用しようと思う。DTMはDebug Transport Module(だったかな?)で、外部からデータをやり取りするためのポートとなっている。今回はそのモジュールの、データを外部から流し込む部分だけ活用する。

ELFファイルを指定するためにはVerilatorが生成した実行バイナリ側で引数解析をする必要があるのだが、この部分はChipyardから頂戴した。-eを指定するとELFのロードを行う仕組みにした。

int main(int argc, char** argv) {

  Verilated::commandArgs(argc, argv);

  while (1) {
    static struct option long_options[] = {
      {"elf",  no_argument, 0, 'e' },
      {"help", no_argument, 0, 'h' }
    };

    int option_index = 0;
    int c = getopt_long(argc, argv, "-e:h", long_options, &option_index);

    if (c == -1) break;
 retry:
    switch (c) {
      // Process long and short EMULATOR options
      case 'h': usage(argv[0]);             return 1;
      case 'e': {
        m_memory   = std::unique_ptr<Memory> (new Memory ());

        m_func_table = std::unique_ptr<FunctionTable> (new FunctionTable ());
        m_gvar_table = std::unique_ptr<VariableTable> (new VariableTable ());

        LoadBinary("", optarg, true);
        break;
      }
    }
  }

ELFの読み込みについては、昔から使っているISS用のELFローダをそのまま活用してくることにする。ELFをロードして、1サイクル毎にデータを流し込むという仕組みだ。このためにtb_elf_loaderというモジュールを作成した。

module tb_elf_loader
(
 input logic                               i_clk,
 input logic                               i_reset_n,

 output logic                                o_req_valid,
 output mrh_pkg::mem_cmd_t                   o_req_cmd,
 output logic [riscv_pkg::PADDR_W-1:0]       o_req_addr,
 output logic [mrh_pkg::L2_CMD_TAG_W-1:0]    o_req_tag,
 output logic [mrh_pkg::ICACHE_DATA_W-1:0]   o_req_data,
 output logic [mrh_pkg::ICACHE_DATA_W/8-1:0] o_req_byte_en,
 input logic                                 i_req_ready
);

logic                __debug_req_valid;
logic [riscv_pkg::PADDR_W-1: 0] __debug_req_bits_addr;
...
int debug_tick_val;

always_ff @(negedge i_clk, negedge i_reset_n) begin
  if (!i_reset_n) begin
  end else begin
    /* verilator lint_off WIDTH */
    debug_tick_val = debug_tick(
      __debug_req_valid,
      i_req_ready,
      __debug_req_bits_addr[31:0],
      __debug_req_bits_data
      );
  end
end

endmodule // tb_elf_loader

最後のdebug_tick()がキモで、これでDPI経由でC言語の関数を呼び出してELFローダと繋げている。

この結果ELFをRAMに書き込むためのフローが完成した。

f:id:msyksphinz:20210203000045p:plain
tb_elf_loaderを通じてELFファイルをロードしている波形

次に、ロードしたらリセットを切り替えてCPU側を動作してフェッチする。とりあえず最初にロードした命令がフェッチできるのかを確認してみる。

f:id:msyksphinz:20210203000113p:plain
ELFをロードした後CPUのリセットを上げて、フェッチが動作していることを確認

一応波形上は正しくフェッチが入っているようだ。ただしCPU側はろくに作り込んでいないので、PCが変なところに飛んでしまっている。ただ一応フレームワークとしては完成した。