RISC-V の実装の一つである Rocket-Chip の解析を行いたいのだが、Chiselの実装が複雑で詳細の解析に阻まれており、なかなか進まない。
やりたいこととしては、現在のRocket-Chip Generatorの環境を利用せずに、Rocket-Chipの生成されたVerilogファイルだけを使用してプログラムを動作させたい。
この環境が構築できれば、Rocket-ChipのRTLだけカスタマイズして自分のオリジナルの環境が構築できるようになるだろう。 そのために、Rocket-Chipがどのように動いているのかを解析して、少なくともLinuxがブートできるような環境を構築する必要がある。
Rocket-Chipがブートするブートシーケンスの仕組みを解析する
Rocket-Chipでプログラムを動かす、例えばDhrystoneを動作させたいときはrocket-chipのリポジトリで以下のように入力する。
cd rocket-chip/emulator make CONFIG=DefaultConfig output/dhrystone.riscv.out
これでRocket-ChipのVerilogコードが生成され、この時の実行結果を解析するためには、output/dhrystone.riscv.out
を見る必要がある。
このログファイルは実際に実行されていない命令の情報も入っているので、その辺りはgrepで削除していく。
grep " \[1\] pc=" output/dhrystone.riscv.out | less -S C0: 21 [1] pc=[0000010040] W[r10=0000000000000000][1] R[r 0=0000000000000000] R[r20=0163790d47bd1fab] inst=[f1402573] csrr a0, mhartid C0: 22 [1] pc=[0000010044] W[r11=0000000000010044][1] R[r 0=0000000000000000] R[r 0=0000000000000000] inst=[00000597] auipc a1, 0x0 C0: 23 [1] pc=[0000010048] W[r11=0000000000010080][1] R[r11=0000000000010044] R[r28=0163790d47bd1fab] inst=[03c58593] addi a1, a1, 60 C0: 24 [1] pc=[000001004c] W[r 0=ffffffd0053e315d][0] R[r 0=0000000000000000] R[r 5=0163790d47bd1fab] inst=[10500073] wfi (args unknown) C0: 29 [1] pc=[0000010050] W[r 0=0000000000010052][1] R[r31=150ae1ddcefdcd8f] R[r29=0163790d47bd1fab] inst=[0000bff5] j pc - 4 C0: 30 [1] pc=[000001004c] W[r 0=ffffffd0053e315d][0] R[r 0=0000000000000000] R[r 5=0163790d47bd1fab] inst=[10500073] wfi (args unknown) C0: 35 [1] pc=[0000010050] W[r 0=0000000000010052][1] R[r31=150ae1ddcefdcd8f] R[r29=0163790d47bd1fab] inst=[0000bff5] j pc - 4 C0: 36 [1] pc=[000001004c] W[r 0=ffffffd0053e315d][0] R[r 0=0000000000000000] R[r 5=0163790d47bd1fab] inst=[10500073] wfi (args unknown) C0: 41 [1] pc=[0000010050] W[r 0=0000000000010052][1] R[r31=150ae1ddcefdcd8f] R[r29=0163790d47bd1fab] inst=[0000bff5] j pc - 4 C0: 42 [1] pc=[000001004c] W[r 0=ffffffd0053e315d][0] R[r 0=0000000000000000] R[r 5=0163790d47bd1fab] inst=[10500073] wfi (args unknown) C0: 47 [1] pc=[0000010050] W[r 0=0000000000010052][1] R[r31=150ae1ddcefdcd8f] R[r29=0163790d47bd1fab] inst=[0000bff5] j pc - 4 ... (以下略)
デバッグ用のVCDファイルを出力しながらシミュレーションを行う
VCDファイルの出力方法は以下だ。さらに生成されたvcdファイルをwlfに変換してQuestaSimで解析する。
make CONFIG=DefaultConfig output/dhrystone.riscv.vcd
vcd2wlf output/dhrystone.riscv.vcd output/dhrystone.riscv.wlf
これを見ていくと、Rocket-Chipのブートシーケンスは以下のように分かれているように思われる。 1. BootROMからブートコードを実行する 2. デバッグモジュールからデバッグコードをフェッチする(おそらく実行バイナリファイルから情報を引き出して、IcacheとDCacheに格納しているものと思われる)。 3. メインプログラムを実行する
ちなみに、Rocket-Chipのアドレスマップは以下のようになっている。このアドレスマップはChisel上でどのように決められているのだ?
Generated Address Map 0 - 1000 ARWX debug-controller@0 3000 - 4000 ARWX error-device@3000 10000 - 20000 R XC rom@10000 2000000 - 2010000 ARW clint@2000000 c000000 - 10000000 ARW interrupt-controller@c000000 60000000 - 80000000 RWX mmio@60000000 80000000 - 90000000 RWXC memory@80000000
まず1.のBootROMだが、これはrocket-chipリポジトリのbootromディレクトリにコードが格納されている。
cd bootrom ... .section .text.hang, "ax", @progbits .globl _hang _hang: csrr a0, mhartid la a1, _dtb 1: wfi j 1b ...
このプログラムを見ればわかるとおり、ブートシーケンスはwfi(wait for interrupt)命令によってこの割り込みが入るのを待っている。この割り込みの挿入によってデバッグコードに移っていくものと思われる。
のデバッグモジュールは、おそらくは実行バイナリファイルを解析してDPIと接続し、ICacheとDCacheに転送しているのではないかと思われるので、ここも要解析だ。
のメインプログラムは実行しているのはすぐにわかるのだが、DCacech/ICacheの位置を見つけて解析する必要がある。
Rocket-ChipのBootROMを改造してプログラムを差し替えてみる
Rocket-ChipのBootROMはどのような構成になっているのだろうか?
まずはVerilogの実装を見て確認してみよう。emulator/generated-src/freechips.rocketchip.system.DefaultConfig.v
を確認してみると、BootROMと言いながら思いっきりハードワイヤードで実装してあった。
- emulator/generated-src/freechips.rocketchip.system.DefaultConfig.v
module TLROM_bootrom( // @[:freechips.rocketchip.system.DefaultConfig.fir@194725.2] input clock, // @[:freechips.rocketchip.system.DefaultConfig.fir@194726.4] input reset, // @[:freechips.rocketchip.system.DefaultConfig.fir@194727.4] output auto_in_a_ready, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] input auto_in_a_valid, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] input [2:0] auto_in_a_bits_opcode, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] input [2:0] auto_in_a_bits_param, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] input [1:0] auto_in_a_bits_size, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] input [8:0] auto_in_a_bits_source, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] input [16:0] auto_in_a_bits_address, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] input [7:0] auto_in_a_bits_mask, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] input auto_in_d_ready, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] output auto_in_d_valid, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] output [1:0] auto_in_d_bits_size, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] output [8:0] auto_in_d_bits_source, // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] output [63:0] auto_in_d_bits_data // @[:freechips.rocketchip.system.DefaultConfig.fir@194728.4] ); wire TLMonitor_clock; // @[Nodes.scala 25:25:freechips.rocketchip.system.DefaultConfig.fir@194735.4] wire TLMonitor_reset; // @[Nodes.scala 25:25:freechips.rocketchip.system.DefaultConfig.fir@194735.4] wire TLMonitor_io_in_a_ready; // @[Nodes.scala 25:25:freechips.rocketchip.system.DefaultConfig.fir@194735.4] ... .io_in_d_bits_size(TLMonitor_io_in_d_bits_size), .io_in_d_bits_source(TLMonitor_io_in_d_bits_source) ); assign index = auto_in_a_bits_address[11:3]; // @[BootROM.scala 50:34:freechips.rocketchip.system.DefaultConfig.fir@195289.4] assign high = auto_in_a_bits_address[15:12]; // @[BootROM.scala 51:68:freechips.rocketchip.system.DefaultConfig.fir@195290.4] assign _T_1167 = high != 4'h0; // @[BootROM.scala 52:53:freechips.rocketchip.system.DefaultConfig.fir@195291.4] assign _GEN_1 = 9'h1 == index ? 64'h597f1402573 : 64'h1f414130010041b; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_2 = 9'h2 == index ? 64'h840207458593 : _GEN_1; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_3 = 9'h3 == index ? 64'h0 : _GEN_2; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_4 = 9'h4 == index ? 64'h0 : _GEN_3; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_5 = 9'h5 == index ? 64'h0 : _GEN_4; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_6 = 9'h6 == index ? 64'h0 : _GEN_5; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_7 = 9'h7 == index ? 64'h0 : _GEN_6; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_8 = 9'h8 == index ? 64'hbcd5051b0000b537 : _GEN_7; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_9 = 9'h9 == index ? 64'h2345859b000015b7 : _GEN_8; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_10 = 9'ha == index ? 64'h1050007300b50633 : _GEN_9; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_11 = 9'hb == index ? 64'hbff5 : _GEN_10; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4] assign _GEN_12 = 9'hc == index ? 64'h0 : _GEN_11; // @[BootROM.scala 52:47:freechips.rocketchip.system.DefaultConfig.fir@195292.4]
これは、ブートプログラムをコピーして編集し、ロードする場所をConfig.scalaで変更した。
- program/simple/simple.S (一部抜粋)
.section .text.hang, "ax", @progbits .globl _hang _hang: /* 新規追加部分 */ li a0, 0x0abcd li a1, 0x01234 add a2, a0, a1 1: wfi j 1b
diff --git a/src/main/scala/coreplex/Configs.scala b/src/main/scala/coreplex/Configs.scala index ab6d6fa..a6d6068 100644 --- a/src/main/scala/coreplex/Configs.scala +++ b/src/main/scala/coreplex/Configs.scala @@ -25,7 +25,7 @@ class BaseCoreplexConfig extends Config ((site, here, up) => { case MemoryBusKey => MemoryBusParams(beatBytes = site(XLen)/8, blockBytes = site(CacheBlockBytes)) // Additional device Parameters case ErrorParams => ErrorParams(Seq(AddressSet(0x3000, 0xfff)), maxAtomic=site(XLen)/8, maxTransfer=4096) - case BootROMParams => BootROMParams(contentFileName = "./bootrom/bootrom.img") + case BootROMParams => BootROMParams(contentFileName = "./program/simple/simple.img") case DebugModuleParams => DefaultDebugModuleParams(site(XLen)) })
ログファイルを確認すると、正しく実行できているようだ。
cd rocket-chip/emulator make CONFIG=DefaultConfig output/dhrystone.riscv.out grep "\[1\] pc" output/dhrystone.riscv.out | less -S C0: 21 [1] pc=[0000010040] W[r10=000000000000b000][1] R[r 1=62bef4b391e515e7] R[r 0=0000000000000000] inst=[0000b537] lui a0, 0xb C0: 22 [1] pc=[0000010044] W[r10=000000000000abcd][1] R[r10=000000000000b000] R[r13=17f201a91c7cccd7] inst=[bcd5051b] addiw a0, a0, -1075 C0: 23 [1] pc=[0000010048] W[r11=0000000000001000][1] R[r 0=0000000000000000] R[r 0=0000000000000000] inst=[000015b7] lui a1, 0x1 C0: 24 [1] pc=[000001004c] W[r11=0000000000001234][1] R[r11=0000000000001000] R[r20=17f201a91c7cccd7] inst=[2345859b] addiw a1, a1, 564 C0: 25 [1] pc=[0000010050] W[r12=000000000000be01][1] R[r10=000000000000abcd] R[r11=0000000000001234] inst=[00b50633] add a2, a0, a1 C0: 26 [1] pc=[0000010054] W[r 0=ffffffe484e63bc8][0] R[r 0=0000000000000000] R[r 5=17f201a91c7cccd7] inst=[10500073] wfi (args unknown) C0: 31 [1] pc=[0000010058] W[r 0=000000000001005a][1] R[r31=000000000000abcf] R[r29=17f201a91c7cccd7] inst=[0000bff5] j pc - 4 C0: 32 [1] pc=[0000010054] W[r 0=ffffffe484e63bc8][0] R[r 0=0000000000000000] R[r 5=17f201a91c7cccd7] inst=[10500073] wfi (args unknown) C0: 37 [1] pc=[0000010058] W[r 0=000000000001005a][1] R[r31=000000000000abcf] R[r29=17f201a91c7cccd7] inst=[0000bff5] j pc - 4
計算により、 0x0abcd + 0x01234 = 0x0be01
という計算が正しく行えていることを確認した。