ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Chiselで簡単なRISC-Vを作った話

スポンサーリンク

今年のGWの自分宿題としてものすごく簡単なRISC-Vを作っていました。
ツイッター見てる方はご覧になってたかもしれませんが、GW中に一応riscv-tests位ならPASSするものが出来ています。
今回はその後にVivadoで合成してみた結果も取ることが出来たのでそれも含めて紹介をしてみようと思います。

Chiselで作るオレオレRISC-V

そんなわけで作ったRISC-V(適当にdirvという名前になってます)のスペックを。
こんな感じです。(これはgithubのREADMEをほぼそのまま載っけてます)

  • RV32I
  • Machine mode Only
  • User-Level ISA Version 2.2
  • Privileged ISA Version 1.10
  • 割り込みは未サポート
  • 2ステージパイプライン (Fetch - Decode/Execute/Memory/Write back)
  • Interface Protocol - オリジナル
  • Chiselで実装

  • githubリポジトリはここ↓

    • ツッコミどころ満載だと思うので、お気づきの点があればあれば教えていただけるとありがたいですm(_ _)m

github.com

なお今回実装するにあたって、以下の2つのRISC-V実装を大いに参考にさせてもらっています。

  • riscv-sodor : 教育用に作れらたわりかしシンプルな実装

    • 割とシンプルなChiselの記述なので、RISC-Vあんまり知らない/Chiselあんま書けないの自分にはもってこいでした。 github.com
  • SCR1 : syntacore社が開発してgithubに公開しているRISC-Vコア

    • こちらはsystem verilogで書かれていて、構成がしっかりしている&ドキュメントもしっかりしているというとてもありがたいRISC-Vの実装です。
    • riscv-sodorでわからない部分はこちらを参考にしています
      • 実装中でgithubのデータでは未対応だけど割り込み関連とか github.com

ディレクトリの構成

今回の実装にあたってはchisel-templateの構成をそのまま使わせてもらっています。

github.com

そのためdirvのChiselのソースは"src/main"以下のディレクトリに格納されています。

└── main
    ├── resources
    │   └── MemModel.v
    └── scala
        ├── Elaborate.scala             : RTL生成用のメインを実装したファイル
        ├── MemModel.scala              : resources/MemModel.vのBlackBoxラッパー
        ├── SimDtm.scala                : テスト環境のトップモジュール
        └── dirv
            ├── Config.scala            : dirvの設定用クラス及びパラメータ定義
            ├── Dirv.scala              : dirvのトップモジュール
            ├── io
            │   └── Bundles.scala       : 外部インターフェース用のBundleクラス定義
            └── pipeline
                ├── Alu.scala           : ALUの実装部
                ├── Csrf.scala          : CSRの実装部
                ├── Exu.scala           : EXU(Execution Unit)の実装部
                ├── Idu.scala           : IDU(Instruction Decide Unit)の実装部
                ├── Ifu.scala           : IFU(Instruction Fetch Unit)の実装部
                ├── Lsu.scala           : LSU(Load Store Unit)の実装部
                └── Mpfr.scala          : MPFR(Multi Port Register File)の実装部

chisel-templateをIntelliJ IDEA上で扱えるようにして、その上で実装を行いました。
IDEA上でプロジェクトを構築する方法については、以前に記事を書いたのでそちらも合わせてご覧ください。

www.tech-diningyo.info www.tech-diningyo.info

ブロック図

出来上がったdirvコアのブロック図は以下のようになりました。
ブロックの構成がSCR1に何となく似ているのは前述の通り実装の際に参考にさせてもらったからで気のせいではありません。

f:id:diningyo-kpuku-jougeki:20190519221354p:plain

外部インターフェース

最初からAXIとか書くのが面倒になったので適当なメモリインターフェースを作りました。
最初はもっとシンプルな奴にしてたんですが、それだと逆に面倒な部分があったので最低限のアクセス情報(アドレスやサイズ)をready-validのハンドシェイクを使ってやり取りするものになっています。
2019/05/22 波形がおかしかったので修正しました。

リード

f:id:diningyo-kpuku-jougeki:20190522211051p:plain

ライト

f:id:diningyo-kpuku-jougeki:20190522211055p:plain

テスト環境

テスト環境のディレクトリ構成

テスト環境の構成もchisel-template環境に含まれている構成をほぼ同等です。

test/
├── resources
│   ├── riscv-tests               : riscv-testsのgit環境(サブモジュール登録してます)
│   └── riscv-tests.patch         : riscv-testsのdirv向けのパッチファイル
└── scala
    └── dirv
        ├── DirvMain.scala        : (このファイル、今は不要だった。。。)
        ├── DirvTester.scala      : dirvのテストクラスが定義されたファイル(ChiselFlatSpec)
        └── DirvUnitTester.scala  : dirvの単体テスト用クラスが実装されたファイル

違いは、後々RISC-Vの各extensionをサポートした際に、テストの組み合わせを簡単に入れ替えれるような仕組み↓を入れていることくらい。

  • object RisvTestsParams : 各命令用のテストと対応するHEXダンプファイルの管理
  • abstract class DirvBaseTester : テスト時の引数の解析やテストの実行メソッドを備えた抽象クラス
  • class DirvRV32ITester : 現在のdirv用にrv32mi/rv32uiのテストを全て実行するテストクラス

ChiselFlatSpecを使ったテスト環境の構築の話については以前にも記事にしているのでそちらもどうぞ↓。

www.tech-diningyo.info

テストの実行

このあたりはgithubのREADMEに書いたのでそちらを参考にしていただくことにして、テストの際の実行コマンドと結果を。
以下の通り、rv32i/machine modeのサポートに必要(なはずの)のriscv-testsをPASSすることを確認しました。
ただ今の状態だとISAの基本テストがPASSするだけなので、まだ他のプログラム動かしたり、外部インターフェースのテスト追加したり、、といろいろやらないとなぁ、、、と思案中。

$ sbt
sbt:dirv> test
[info] DirvRV32ITester:
[info] Dirv
[info] - must execute RISC-V instruction add        - [riscv-tests:rv32ui-000]
[info] - must execute RISC-V instruction addi       - [riscv-tests:rv32ui-001]
[info] - must execute RISC-V instruction and        - [riscv-tests:rv32ui-002]
[info] - must execute RISC-V instruction andi       - [riscv-tests:rv32ui-003]
[info] - must execute RISC-V instruction auipc      - [riscv-tests:rv32ui-004]
[info] - must execute RISC-V instruction beq        - [riscv-tests:rv32ui-005]
[info] - must execute RISC-V instruction bge        - [riscv-tests:rv32ui-006]
[info] - must execute RISC-V instruction bgeu       - [riscv-tests:rv32ui-007]
[info] - must execute RISC-V instruction blt        - [riscv-tests:rv32ui-008]
[info] - must execute RISC-V instruction bltu       - [riscv-tests:rv32ui-009]
[info] - must execute RISC-V instruction bne        - [riscv-tests:rv32ui-010]
[info] - must execute RISC-V instruction fence_i    - [riscv-tests:rv32ui-011]
[info] - must execute RISC-V instruction jal        - [riscv-tests:rv32ui-012]
[info] - must execute RISC-V instruction jalr       - [riscv-tests:rv32ui-013]
[info] - must execute RISC-V instruction lb         - [riscv-tests:rv32ui-014]
[info] - must execute RISC-V instruction lbu        - [riscv-tests:rv32ui-015]
[info] - must execute RISC-V instruction lh         - [riscv-tests:rv32ui-016]
[info] - must execute RISC-V instruction lhu        - [riscv-tests:rv32ui-017]
[info] - must execute RISC-V instruction lui        - [riscv-tests:rv32ui-018]
[info] - must execute RISC-V instruction lw         - [riscv-tests:rv32ui-019]
[info] - must execute RISC-V instruction or         - [riscv-tests:rv32ui-020]
[info] - must execute RISC-V instruction ori        - [riscv-tests:rv32ui-021]
[info] - must execute RISC-V instruction sb         - [riscv-tests:rv32ui-022]
[info] - must execute RISC-V instruction sh         - [riscv-tests:rv32ui-023]
[info] - must execute RISC-V instruction simple     - [riscv-tests:rv32ui-024]
[info] - must execute RISC-V instruction sll        - [riscv-tests:rv32ui-025]
[info] - must execute RISC-V instruction slli       - [riscv-tests:rv32ui-026]
[info] - must execute RISC-V instruction slt        - [riscv-tests:rv32ui-027]
[info] - must execute RISC-V instruction slti       - [riscv-tests:rv32ui-028]
[info] - must execute RISC-V instruction sltiu      - [riscv-tests:rv32ui-029]
[info] - must execute RISC-V instruction sltu       - [riscv-tests:rv32ui-030]
[info] - must execute RISC-V instruction sra        - [riscv-tests:rv32ui-031]
[info] - must execute RISC-V instruction srai       - [riscv-tests:rv32ui-032]
[info] - must execute RISC-V instruction srl        - [riscv-tests:rv32ui-033]
[info] - must execute RISC-V instruction srli       - [riscv-tests:rv32ui-034]
[info] - must execute RISC-V instruction sub        - [riscv-tests:rv32ui-035]
[info] - must execute RISC-V instruction sw         - [riscv-tests:rv32ui-036]
[info] - must execute RISC-V instruction xor        - [riscv-tests:rv32ui-037]
[info] - must execute RISC-V instruction xori       - [riscv-tests:rv32ui-038]
[info] - must execute RISC-V instruction breakpoint - [riscv-tests:rv32mi-000]
[info] - must execute RISC-V instruction csr        - [riscv-tests:rv32mi-001]
[info] - must execute RISC-V instruction illegal    - [riscv-tests:rv32mi-002]
[info] - must execute RISC-V instruction ma_addr    - [riscv-tests:rv32mi-003]
[info] - must execute RISC-V instruction ma_fetch   - [riscv-tests:rv32mi-004]
[info] - must execute RISC-V instruction mcsr       - [riscv-tests:rv32mi-005]
[info] - must execute RISC-V instruction sbreak     - [riscv-tests:rv32mi-006]
[info] - must execute RISC-V instruction scall      - [riscv-tests:rv32mi-007]
[info] - must execute RISC-V instruction shamt      - [riscv-tests:rv32mi-008]
[info] ScalaTest
[info] Run completed in 1 minute, 40 seconds.
[info] Total number of tests run: 48
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 48, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 48, Failed 0, Errors 0, Passed 48

RTLの生成

最初のディレクトリ構成部分に書いた"Elaborate.scala"にRTL生成処理を入れたメインオブジェクトが定義されています。
以下のsbtコマンドを実行することで、RTLが生成可能です。

sbt "runMain Elaborate"

生成したRTLは"rtl/dirv"以下に生成されます。(現時点では"SimDtm"以下がRTL生成対象) 現時点でScalaソースコードの行数は以下の通りで1597行(コメント込み)。
一方でRTLは4357行でした。

  56 dirv/Config.scala
  39 dirv/Dirv.scala
 148 dirv/io/Bundles.scala
  69 dirv/pipeline/Alu.scala
 422 dirv/pipeline/Csrf.scala
 170 dirv/pipeline/Exu.scala
 328 dirv/pipeline/Idu.scala
 134 dirv/pipeline/Ifu.scala
 174 dirv/pipeline/Lsu.scala
  57 dirv/pipeline/Mpfr.scala
1597 合計

Ultra96上で合成

以下のような形で、IP Integrator側にZynq UltraSscale+ MPSoCを置いてそこから出るクロックを

に入力して、そこからクロック/リセットを生成する形にして、そのクロック/リセットをSimDtmに入れるだけの簡単な構成にしました。

f:id:diningyo-kpuku-jougeki:20190519221414p:plain

合成時の周波数は100MHzです。

f:id:diningyo-kpuku-jougeki:20190519222605p:plain

因みにまだちゃんと実機で動くかは確認してません。。。とりあえずどんな結果になるかが知りたかったので。。。

リソース使用量

以下のような感じです(SimDtmの下のdirv)

f:id:diningyo-kpuku-jougeki:20190519222457p:plain

手元で比較できそうなのが少し前のSCR1の合成環境(今の最新版は少し違うはず、デバッグI/F周りとか)

f:id:diningyo-kpuku-jougeki:20190519222438p:plain

なお、SCR1のほうが今回作ったものに比べていろいろリッチ(圧縮命令サポート/乗除算サポート/デバッガI/Fサポートなどなど、、)なので大きいのは当然です。 SCR1より小さくて、なおかつ小さすぎない、、という感じなので、結果としてはおかしくなさそうかな、と思います。

タイミング

以下のような感じ。

f:id:diningyo-kpuku-jougeki:20190519221501p:plain

前述の通り100MHzで合成してますが、もう少し余裕はあるみたい。
最低限のものしか載せてなくてPL領域スッカスカってのは影響してると思います。

作ってみての感想

とりあえず、CPUについてあんまきちんとした知識が無い自分みたいなエンジニアでもRISC-VのISA見てCPUが作れちゃうということが分かったのはとても大きな収穫でした。これについては

  • 前提条件だけど、ISAが公開されている(やっぱりこれはすごい。)
  • 既にオープンになっているRISC−Vの実装が公開されている
  • 何よりISA用の基本テストが公開されている

というのが大きいです。ISAを読んでもイマイチピンとこない命令もあったりしたのですが、ISA向けのテストがあることで、それに沿う形で実装を進めることができました。またspikeのようなエミュレータもあるのでテストのコードだけではわからない部分もそれを動かすことで挙動を調べることが可能になり、そのタイミングでsodorやSCR1の実装を確認することで徐々にISAの中身を深く知ることが出来たと思います。
改めてソフト/ハードを含めたエコシステムの力を感じることが出来ました。
自分も何か貢献できるようになっていきたい、、、という気持ちも再度確認しました。もっといろいろやっていかないとなぁ。。

-2020/1/5追記- この話の続きは以下の記事になります。ご興味あればご一読ください。