TinyFPGA BX で RISC-V を動かしてみる(その1)

(更新

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 と比較してみたいと思います。)

最後に、お約束のレイアウトビューです。

今後の宿題

宿題を忘れないようにまとめておきます。

今日はここまで。

お気軽に御相談ください

私もまだまだ勉強中です。ぜひ、一緒に勉強しましょう!

お問い合わせを頂いた後、継続して営業活動をしたり、ニュースレター等をお送りしたりすることはございません。
御返答は 24時間以内(営業時間中)とさせて頂いております。もし返答が届かない場合、何らかの事情でメールが不達となっている可能性がございます。大変お手数ですが、別のメールアドレス等で督促頂けますと幸いです。