HiFive1 Rev Bを買ったのでRISC-V実機に入門する

この記事はRISC-V Advent Calendar 2020の1日目の記事です。

RISC-Vの実機としてHiFive1 Rev Bを勢いで買いました😊 

f:id:masami256:20201126200139j:plain
HiFive1 Rev B

64bitのRISC-V64GCならLinuxも動くんですけど、HiFive UnleashedはディスコンだしHiFive Unmatchedはまだ出てないので手軽に遊べそうなHiFive1 Rev Bで良いじゃんって感じです。手頃なサイズ感のRISC-V64GCが乗ったボードがあれば欲しいかも。

開発環境を整える

それはさておき、うちのメイン環境はfedora 33です。そして開発環境を整えようと思ってこれらをダウンロードしてセットアップします。

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を見つけました。

github.com

動かし方は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に次のように書かれています。

f:id:masami256:20201203000340p:plain
9.1 Bootloader recovery

bootloarderは0x20000000から始まり、ユーザーのコードは0x20010000から始まると書かれているのでリンカースクリプトのコメントに書いてあった /* Skip first 64k allocated for bootloader */ の意味がわかりますね。

まとめ

Rust良い👍

bootの仕組みもなんとなくわかったので今度はなにか作っていこう…