Running RISC-V (picorv32) on TinyFPGA BX.
Yosys、arachne-pnr の動かし方の基本を理解し、RISC-V のアーキテクチャも少し分かってきたので、当初の目標「FPGA で RISC-V を動かす」にチャレンジしてみようと思います。実はチャレンジも何も、ネットでたくさんの事例が紹介されているのです。ただし、私は FPGA についてはほとんど素人ですので、基本を理解する良いテーマだと思いました。
RISC-V の HDL 実装については、既に優秀な技術者の方が GitHub で公開なさっています。有名なところとしては、次の 2つがあるようでした。
- PicoRV32
- VexRiscv
今回は、Project IceStorm で有名な Clifford Wolf さんの GitHub で公開されている、picorv32 を勉強してみることにしました。picorv32 は、RV32E, RV32I, RV32IC, RV32IM, および RV32IMC に対応した Verilog 実装です。特徴(設計の方針)としては、実装がコンパクトであると同時に、f_MAX(最高動作周波数)を大きく取れることを目標としているようです。
私は当初、f_MAX がいくら高くても CPI (Cycles Per Instruction) が大きくては意味がないのではないか、と思ったのですが、Clifford さんによると
“Due to its high fmax it can be integrated in most existing designs without crossing clock domains. When operated on a lower frequency, it will have a lot of timing slack and thus can be added to a design without compromising timing closure.”
ということで、意味のある設計なのだそうです。友人の高速ロジック設計者さんが、「クロックドメイン間のデータの渡しかたは奥が深いんだよ」と言っていたことを思い出し、なるほどー、そういうことなんだ、と納得した次第です。
picorv32 のテストベンチシミュレーション
まずは picorv32 をダウンロード(git clone)し、シミュレータでテストベンチを動かしてみることにしました。picosoc の README.md を見ても、そのような評価順序が説明されています。今回試したのは 12月14日時点の master ブランチ(コミット e308982)です。
Icarus Verilog
評価に必要なツールは、Icarus Verilog(iverilog)の新しいバージョンと、RISC-V 用の GNU toolchain です。前者としては、apt-get などで入る 0.9.7 等ではダメで、GitHub から最新の iverilog を入手する必要があります。私は今回、v10_3 版を使うことにしました。iverilog のビルドには、bison、flex、autoconf 等が必要になりますので、README.txt をよく読んでインストールしてください。
GNU toolchain
次に、GNU toolchain です。以前に SiFive 社のウェブサイトからバイナリ版を入手したことがあるのですが、折角ですので、勉強を兼ねてソースからビルドしてみることにしました。ちなみに、GNU toolchain のビルドには時間がかかります。(試行錯誤を重ねていると、2〜3時間はすぐに経ってしまいます。時間に余裕のあるときに試すのが良いでしょう。) toolchain の入手先はこちらです。実は Clifford さんの picorv32 のサイトには toolchain のビルド方法が説明されているのですが、私はそちらを見ずにインストールしてしまい、勉強にはなりましたがいろいろとハマりました。あ、忘れてました。今回使用した toolchain のバージョンは v20171231 です。それより新しいバージョンもあるのですが、GitHub のリリースタブをクリックすると最初に表示されるのが v20171231 なのです。
GNU toolchain のビルドには、Linux 上で動作するバイナリを生成する make linux と、OS 上ではなく組込用として動作するバイナリを生成する、make newlib があります。make に引数を渡さないと後者がビルドされます。
その前に、configure にも注意が必要です。configure では –with-arch と –with-abi が重要なのです。デフォルトでは、それぞれ rv64imafdc と lp64d になっており、64ビット版 RISC-V の M, A, F, D, C オプション全部付きとなります。ただし実際は、gcc などを実行するときのデフォルト(引数を省略したときの)アーキテクチャと ABI が -march=rv64imafdc -mabi=lp64d になるだけであり、引数で指定すればちゃんと動作します。私はうまく説明できていないと思いますので、こちらも参考にしてください。
ただしもう一つ重要なこととして、toolchain のビルド時に生成される newlib ライブラリのアーキテクチャと ABI という問題があります。何も指定しないで configure すると、–with-arch と –with-abi で指定した newlib しか生成されないようなのです。(間違っていたらすみません。) つまり、デフォルトでは rv64imafdc と lp64d 用の newlib しか生成されないようです。いろいろな組み合わせの newlib を生成するには、たとえば
./configure --prefix=/opt/riscv --enable-multilib make newlib
とすると良いようです。(prefix は、RISC-V GNU toolchain の標準に合わせました。)
上記のビルドですと、例えば R32IMC のために GCC を走らせるためには
/opt/riscv/bin/riscv64-unknown-elf-gcc -c -march=rv32imc -mabi=ilp32 foo.c
のようにしないといけません。RISC-V 系プロジェクトの Makefile では -march と -mabi を省略しているものが多いですが、GCC の configure 時にどのようなオプションが与えられているか分からない場合には、きちんと指定したほうが良いようです。
さて、ここまで無事に準備できましたので、picorv32 のシミュレーションテストベンチを走らせてみましょう。Makefile を少しだけ修正しました。
diff --git a/picosoc/Makefile b/picosoc/Makefile index f600062..27a77be 100644 --- a/picosoc/Makefile +++ b/picosoc/Makefile @@ -1,5 +1,5 @@ -CROSS=riscv32-unknown-elf- +CROSS=riscv64-unknown-elf- CFLAGS= # ---- iCE40 HX8K Breakout Board ----
make test を実行してみましょう。すごいですね。いろんなインストラクションの動きをチェックし、エラトステネスのふるい法で素数を計算し、動作チェックしているようです。最後に次が表示されれば OK です。
Cycle counter ......... 429159 Instruction counter .... 93608 CPI: 4.58 DONE ------------------------------------------------------------ EBREAK instruction at 0x000006CC pc 000006CF x8 00000000 x16 00000000 x24 00000000 x1 0000069C x9 00000000 x17 E2E2B92B x25 00000000 x2 00020000 x10 20000000 x18 00000000 x26 00000000 x3 DEADBEEF x11 075BCD15 x19 0000559C x27 00000000 x4 DEADBEEF x12 0000004F x20 00000000 x28 00000020 x5 00000F4C x13 0000004E x21 00000000 x29 00000001 x6 00000000 x14 00000045 x22 00000000 x30 38BAA671 x7 1B639DFB x15 0000000A x23 00000000 x31 00000000 ------------------------------------------------------------ Number of fast external IRQs counted: 54 Number of slow external IRQs counted: 6 Number of timer IRQs counted: 22 TRAP after 471267 clock cycles ALL TESTS PASSED.
picosoc
Clifford さんの picorv32 ディレクトリの下を覗いてみると、picosoc というものが見つかります。これは、上述の picorv32 に周辺ロジックを追加し、実際の FPGA ボードの上で容易に picorv32 を試してみることができるようになっています。
残念ながら、標準では iCE40-HX8K Breakout Board と iCEBreaker Board にしか対応しておらず、私の持っている TinyFPGA BX には未対応です。本来であれば、自力で TinyFPGA BX 用に top module や周辺ロジックを書き直せば勉強になるのでしょうが、TinyFPGA BX さんの GitHub に移植済みのプロジェクトがあるので、そちらを試してみます。もちろん、後で差分が何かを調べてみましょう。
まずは動かしてみます。ここからプロジェクトを git clone します。(今回試したバージョンはコミット 9dbf1a2 です。) 続いて、必要なツールをいくつかインストールします。
tinyprog
TinyFPGA を動かしている方なら既にインストール済みだと思いますが、こちらを参考にしてインストールします。なお、iverilog はインストール済みですし、icestorm は別途最新版をインストールしたいので、ここでは省略します。(apio を使いたいかたは、この指示通りにインストールし、ただし最新版の iverilog 等はディレクトリを分けてインストールすると良いでしょう。)
Yosys, Arachne-pnr, IceStorm
Yosys、Arachne-pnr、IceStorm は、こちらにある通りにインストールしました。バージョンはそれぞれ、以下の通りです。
- Yosys: コミット 9ab1fee
- Arachne-pnr: コミット c40fb22
- IceStorm: コミット 0ec00d8
さっそくビルド
それでは make してみましょう。
cd TinyFPGA-BX/examples/picosoc make
なんとエラーが出ます。
(略) pass 199, 1 shared. pass 200, 1 shared. fatal error: failed to route Makefile:13: recipe for target 'hardware.asc' failed make: *** [hardware.asc] Error 1
調べてみると、どうやら arachne-pnr に問題があり、乱数の seed によっては稀にルーティングに失敗するようです。とりあえず seed を変えるとうまくいくようですが、David Shah さんによると nextpnr では修正されているので、そちらを使うのが推奨とのことです。Arachne-pnr の開発は既に終わっている(基本的に今後の修正はなし)ので、あとで nextpnr も試してみましょう。
はい、seed を 2 に変えてみます。
(略) pass 17, 0 shared. After routing: span_4 12958 / 29696 span_12 2285 / 5632 (略)
今度は無事にルーティングが完了し、tinyprog でフラッシュへの書き込みができました。TinyFPGA BX のユーザー LED がチカチカしていれば成功です。
まずは、私が試した Makefile への差分を示します。
diff --git a/examples/picosoc/Makefile b/examples/picosoc/Makefile index d174349..0dc9073 100644 --- a/examples/picosoc/Makefile +++ b/examples/picosoc/Makefile @@ -1,4 +1,7 @@ +CC=riscv64-unknown-elf-gcc +OBJCOPY=riscv64-unknown-elf-objcopy + upload: hardware.bin firmware.bin tinyprog -p hardware.bin -u firmware.bin @@ -7,7 +10,7 @@ hardware.blif: hardware.v spimemio.v simpleuart.v picosoc.v picorv32.v yosys -ql hardware.log -p 'synth_ice40 -top hardware -blif hardware.blif' $^ hardware.asc: hardware.pcf hardware.blif - arachne-pnr -d 8k -P cm81 -o hardware.asc -p hardware.pcf hardware.blif + arachne-pnr -s 2 -d 8k -P cm81 -o hardware.asc -p hardware.pcf hardware.blif hardware.bin: hardware.asc icetime -d hx8k -c 12 -mtr hardware.rpt hardware.asc @@ -15,10 +18,10 @@ hardware.bin: hardware.asc firmware.elf: sections.lds start.S firmware.c - riscv32-unknown-elf-gcc -march=rv32imc -nostartfiles -Wl,-Bstatic,-T,sections.lds,--strip-debug,-Map=firmware.map,--cref -ffreestanding -nostdlib -o firmware.elf start.S firmware.c + $(CC) -march=rv32imc -mabi=ilp32 -nostartfiles -Wl,-Bstatic,-T,sections.lds,--strip-debug,-Map=firmware.map,--cref -ffreestanding -nostdlib -o firmware.elf start.S firmware.c firmware.bin: firmware.elf - riscv32-unknown-elf-objcopy -O binary firmware.elf /dev/stdout > firmware.bin + $(OBJCOPY) -O binary firmware.elf /dev/stdout > firmware.bin clean:
修正は、
- GNU tool チェインの prefix を riscv32- から riscv64- に変更
- arachne-pnr に seed 2 を使うように指示
- GCC オプションに -mabi=ilp32 を追加
です。
ロジック規模の確認
最後に yosys と arachne-pnr の出力を確認しておきましょう。まずは、yosys が出力した BLIF を覗き、使っている LUT や D-FF の数を調べておきましょう。
for i in `egrep '^\.gate' hardware.blif | awk '{print $2}' | sort | uniq` do echo -n $i; egrep '^\.gate *'$i' ' hardware.blif | wc -l done SB_CARRY 992 SB_DFF 175 SB_DFFE 621 SB_DFFESR 571 SB_DFFESS 61 SB_DFFNSR 4 SB_DFFSR 216 SB_DFFSS 6 SB_IO 4 SB_LUT4 4629 SB_RAM40_4K 20
こんな感じです。LUT4 が 4629個、DFF が計 1654個、RAM40_4K が 20個になりました。
次に、arachne-pnr の出力です。
After packing: IOs 13 / 63 GBs 0 / 8 GB_IOs 0 / 8 LCs 5548 / 7680 DFF 1437 CARRY 793 CARRY, DFF 217 DFF PASS 520 CARRY PASS 67 BRAMs 20 / 32 WARMBOOTs 0 / 1 PLLs 0 / 1
使用した logic cell(LC) は 5548個。これは LP8K が持っている LC 総数の 72% にあたります。
icetime も実行してみましょう。
icetime -m -dlp8k hardware.asc // Reading input .asc file.. // Reading 8k chipdb file.. // Creating timing netlist.. // Timing estimate: 54.72 ns (18.27 MHz)
TinyFPGA BX のクロック 16MHz ならば問題なく動作しそうです。(あとで、nextpnr と比較してみたいと思います。)
最後に、お約束のレイアウトビューです。
今後の宿題
宿題を忘れないようにまとめておきます。
- Arachne-pnr に代えて nextpnr を試す(完了)
- picorv32 の生成オプションを変えて、ロジック規模へのインパクトを確認する
- オリジナルの picosoc と TinyFPGA BX 版 picosoc の差分を確認する
- VexRiscv を評価する(評価中)
今日はここまで。
お気軽に御相談ください
私もまだまだ勉強中です。ぜひ、一緒に勉強しましょう!