この記事はRISC-V Advent Calendar 2020の1日目の記事です。
RISC-Vの実機としてHiFive1 Rev Bを勢いで買いました😊
64bitのRISC-V64GCならLinuxも動くんですけど、HiFive UnleashedはディスコンだしHiFive Unmatchedはまだ出てないので手軽に遊べそうなHiFive1 Rev Bで良いじゃんって感じです。手頃なサイズ感のRISC-V64GCが乗ったボードがあれば欲しいかも。
開発環境を整える
それはさておき、うちのメイン環境はfedora 33です。そして開発環境を整えようと思ってこれらをダウンロードしてセットアップします。
- Freedom E SDK
- GNU Embedded Toolchain — v2020.04.1
- OpenOCD — v2020.04.6
- J-Link GDB Server
- About the J-Link GDB Server - SEGGER - The Embedded Experts
Linux向けと言ってもCentOS版、Ubuntu版とありましたのでCentOS版のほうを選びました。あとはrpmじゃなくてtar.gz形式のファイルを選んで/optに置く感じにしました。 で、SDKはビルドが必要なんですが、fedora33環境でSDKをビルドしようとしたんだけどFIX: build on cpython master branch by tacaswell · Pull Request #128 · python/typed_ast · GitHubに当たってtyped_astの新しいバージョンが出るのを待つか、vmかコンテナ使うかという感じだったのですが、第3の選択肢としてSDKを使わない方法で遊んでみました。
コードを動かしてみる
SDKでビルドしないとなると他に適当なものが必要なので探してみたところサイズも手頃なRustのコードをriscv-rust-quickstartを見つけました。
動かし方はREADME.md通りにやれば簡単です。
JLinkGDBServerを起動しておいて、
masami@moon:~/projects/hello$ sudo /opt/risc-v-tools/JLink_Linux_V688a_x86_64/JLinkGDBServer -device FE310 -if JTAG -speed 4000 -port 3333 -nogui
cargoコマンドでビルドして実行
masami@moon:~/projects/hello$ cargo run --example hello_world
そうするとgdb server側ではこんな感じにバイナリがダウンロードされ、
J-Link found 1 JTAG device, Total IRLen = 5 JTAG ID: 0x20000913 (RISC-V) Connected to target Waiting for GDB connection...Connected to 127.0.0.1 Reading all registers Received monitor command: reset halt Expected an decimal digit (0-9) Downloading 15074 bytes @ address 0x20010000 Downloading 3204 bytes @ address 0x20013AF0 Comparing flash [....................] Done. Writing register (pc = 0x20010000) Starting target CPU...
cargoを実行した方ではこんな感じの出力になります。
masami@moon:~/projects/hello$ cargo run --example hello_world Finished dev [unoptimized + debuginfo] target(s) in 0.06s Running `riscv64-unknown-elf-gdb -q -x gdb_init target/riscv32imac-unknown-none-elf/debug/examples/hello_world` /home/masami/.gdbinit:8: Error in sourced command file: No symbol table is loaded. Use the "file" command. Reading symbols from target/riscv32imac-unknown-none-elf/debug/examples/hello_world... 0x20010974 in main () at examples/hello_world.rs:25 25 loop {} Expected an decimal digit (0-9) Loading section .text, size 0x3ae2 lma 0x20010000 Loading section .rodata, size 0xc84 lma 0x20013af0 Start address 0x20010000, load size 18278 Transfer rate: 5949 KB/sec, 9139 bytes/write.
そして、uartでデバイスに接続しといたほうではこんな感じで文字列がでます。
masami@moon:~$ sudo picocom -b 115200 /dev/ttyACM0 -q hello world!
簡単😊
実行の仕組みを調べる
cargo runを
実行すると次のログが出ているのでgdbを起動してるのがわかります。
riscv64-unknown-elf-gdb -q -x gdb_init target/riscv32imac-unknown-none-elf/debug/examples/hello_world
この時にgdb_initファイルを指定しています。gdb_initは次のような内容でgdb serverに接続してバイナリを送って処理を続けるような流れになっていました。
set history save on set confirm off set remotetimeout 240 target extended-remote :3333 set print asm-demangle on monitor reset halt load continue # quit
cargoでrunを指定した時にどう動くのかよくわかってないので、どこでgdbのコマンドライン作ってんだと思って調べて見ると.cargo/にconfigファイルがありそこに書かれてました。リンカーの設定もありますね。
masami@moon:~/projects/hello$ cat .cargo/config [target.riscv32imac-unknown-none-elf] runner = "riscv64-unknown-elf-gdb -q -x gdb_init" rustflags = [ "-C", "link-arg=-Thifive1-link.x", ] [build] target = "riscv32imac-unknown-none-elf"
サンプルコードを読む
hello_world.rsを見てみます。30行もありません。
#![no_std] #![no_main] extern crate panic_halt; use riscv_rt::entry; use hifive1::hal::prelude::*; use hifive1::hal::DeviceResources; use hifive1::{sprintln, pin}; #[entry] fn main() -> ! { let dr = DeviceResources::take().unwrap(); let p = dr.peripherals; let pins = dr.pins; // Configure clocks let clocks = hifive1::clock::configure(p.PRCI, p.AONCLK, 320.mhz().into()); // Configure UART for stdout hifive1::stdout::configure(p.UART0, pin!(pins, uart0_tx), pin!(pins, uart0_rx), 115_200.bps(), clocks); sprintln!("hello world!"); loop {} }
hello worldの出力に必要なことはriscvとかhifeve1のcrateでサポートされてるんですね。コードとしてはクロックの設定とUARTで出力するための設定してから文字列表示させてるだけですね。この単純明快さは😃
bootの仕組みを調べる
.cargo/confgにリンカの指定があってhifive1-link.xが指定されているのでこれを見てみます。
masami@moon:~/projects/hello$ cat ./target/riscv32imac-unknown-none-elf/debug/build/hifive1-d6f67d95492879dd/out/hifive1-link.x INCLUDE hifive1-memory.x INCLUDE link.x
2つのファイルがincludeされてます。
masami@moon:~/projects/hello$ cat ./target/riscv32imac-unknown-none-elf/debug/build/hifive1-d6f67d95492879dd/out/hifive1-memory.x INCLUDE memory-fe310.x MEMORY { FLASH : ORIGIN = 0x20000000, LENGTH = 4M } REGION_ALIAS("REGION_TEXT", FLASH); REGION_ALIAS("REGION_RODATA", FLASH); REGION_ALIAS("REGION_DATA", RAM); REGION_ALIAS("REGION_BSS", RAM); REGION_ALIAS("REGION_HEAP", RAM); REGION_ALIAS("REGION_STACK", RAM); /* Skip first 64k allocated for bootloader */ _stext = 0x20010000;
このファイルも最初にmemory-fe310.xをincludeしてます。これはというと、RAMのアドレスが0x80000000から0x80000000+16Kとなってますね。
masami@moon:~/projects/hello$ cat ./target/riscv32imac-unknown-none-elf/debug/build/e310x-9f201268daf213f7/out/memory-fe310.x MEMORY { RAM : ORIGIN = 0x80000000, LENGTH = 16K }
そして、hifive1-memory.x に戻ると最初のほうはこんな感じで、FLASHは0x20000000から始まってサイズは4Mとなっていて、TEXT領域やRODATA領域はFLASHのアドレス範囲、BSSなどはmemory-fe310.xで設定してたRAMのアドレス範囲に置かれるというのがわかりました。
MEMORY { FLASH : ORIGIN = 0x20000000, LENGTH = 4M } REGION_ALIAS("REGION_TEXT", FLASH); REGION_ALIAS("REGION_RODATA", FLASH); REGION_ALIAS("REGION_DATA", RAM); REGION_ALIAS("REGION_BSS", RAM); REGION_ALIAS("REGION_HEAP", RAM); REGION_ALIAS("REGION_STACK", RAM);
最後のところで0x20010000が出てきています。
/* Skip first 64k allocated for bootloader */ _stext = 0x20010000;
このアドレスはcargo runした時に表示されていたstart addressですね。
Loading section .text, size 0x3ae2 lma 0x20010000 Loading section .rodata, size 0xc84 lma 0x20013af0 Start address 0x20010000, load size 18278 Transfer rate: 5949 KB/sec, 9139 bytes/write.
このアドレスはHiFive1 Rev B Getting Started Guideに次のように書かれています。
bootloarderは0x20000000から始まり、ユーザーのコードは0x20010000から始まると書かれているのでリンカースクリプトのコメントに書いてあった /* Skip first 64k allocated for bootloader */
の意味がわかりますね。
まとめ
Rust良い👍
bootの仕組みもなんとなくわかったので今度はなにか作っていこう…
- 作者:デイビッド・パターソン,アンドリュー・ウォーターマン
- 発売日: 2018/10/18
- メディア: 単行本