一応、前回までで、コアと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に書き込むためのフローが完成した。
次に、ロードしたらリセットを切り替えてCPU側を動作してフェッチする。とりあえず最初にロードした命令がフェッチできるのかを確認してみる。
一応波形上は正しくフェッチが入っているようだ。ただしCPU側はろくに作り込んでいないので、PCが変なところに飛んでしまっている。ただ一応フレームワークとしては完成した。