ChiselはScalaをベースとしたハードウェア記述言語で、ソフトウェア開発言語をベースにしている分だけ柔軟に記述できたり、Verilogなのでは不可能な面白い記述ができたりする。
Chisel自体でも十分ハードウェア設計をすることは可能なのだが、よりChiselを柔軟に使うためにはDiplomacyは避けては通れない。
Diplomacyはその名の通り「外交」の意味で、モジュール間を接続するにあたり自動的にパラメータの調整をしてくれたり、自動的に接続を行ってくれる便利なツールだ。
DiplomacyはChisel自体の機能ではない。Chisel(というかScala)の機能を使用して作り上げられた機能だ。だからDiplomacyはChiselと同等に考えるべきではなく、Chiselでのハードウェア設計を行うにあたっての強力な設計手法だと考えなければならない。
そして問題はDiplomacyはドキュメントがとても少ないこと。私自身もいつも四苦八苦しており、Rocket-Chipの実装を見ながら試行錯誤している。そのような場合のためにいくつかテンプレートを持っていた方がいいと思いドキュメントとして残しておくことにする。
開発環境
私はWindows WSL Ubuntu 18.04を使用している。また、いくつか必要なパッケージについてはバージョンを固定している。
- OpenJDK 8。UbuntuにはOpenJDK 11も入っていたがいろいろ試しているとエラーになった。OpenJDK 8の方が安心かな?
必要なもの
ChiselはScalaがベースなので、ビルドにはJavaのエンジンとさらにsbtというビルドシステムが必要となる。sbtを使用するためにはbuild.sbt
という設定ファイルが必要となる。
build.sbt
の詳細を理解する必要はないのだが、必ず設定しなければならないのはどのバージョンのChiselを使用するかということだ。いろいろ試行錯誤した結果、現在は以下のバージョンの組み合わせがうまく行っている。
// Provide a managed dependency on X if -DXVersion="" is supplied on the command line. val defaultVersions = Map( "chisel3" -> "3.2.2", "chisel-iotesters" -> "1.2.+", "rocketchip" -> "1.2-SNAPSHOT", "dsptools" -> "1.1.+", "firrtl" -> "1.2-SNAPSHOT" )
この記事を執筆している時点ではChiselはVersion 3.4-RCまで進んでおり、バージョンを挙げればいいじゃないか!となるが実はそうはいかない。結構マイナーバージョンチェンジで使える構文が違うのだ。とりあえずはこの組み合わせが良い気がする。
なぜrocketchip
とかfirrtl
がパッケージとして入っているのか不思議かもしれない。実はDiplomacyを使ったTileLink実装を活用するのがとても手っ取り早く、そのためにはRocketChipのライブラリを使うのが良い。またFIRRTLのライブラリを入れているのは、外部のFIRRTLバイナリを使うのではなく直接Chiselのコードから
これらのライブラリの最新バージョンは、Mavenからチェックできる。
Diplomacyとは何なのか
私もあまり詳しく説明できないのだが、Diplomacyは「LazyModule」というモジュールを使用する。LazyModuleというのは"Lazy Evaluation"の概念と似ており「使用される時に初めてインスタンス化される」という概念に近い。DiplomacyはChiselのエラボレーション時に動作し、モジュール間のノードを渡りながらパラメータを伝搬させていく。どう頑張っても到達しないモジュールは、Lazyなためインスタンス化されることなく削除される。
このパラメータ伝搬の中で、例えば2つのメモリでアドレスマップが被っているとか、シミュレーションするとバグになりそうな項目を検出してエラーを出してくれる。これはChiselがScala(ソフトウェア記述言語)をベースとしているからなせる業であって、淡白な信号を接続するだけのSystemVerilogではモジュール間の接続関係やアドレスマップなどをチェックすることは絶対にできない。
勘違いしがちなのが、これらのcheckerはDiplomacy自体の機能というより、マルチプレクサ・デマルチプレクサ内部に入っている機能だったりする。だからDiplomacyを使えばすべてが解決、という訳では無く接続モジュールにしっかりそれらの検証機能を付けておかないと意味がない。Diplomacyはあくまでノード間の情報伝達のための機能なのだ。情報伝達をしたうえで、その情報をどのように活用するかは設計者に委ねられている。
Diplomacyは多くの場合TileLinkやAMBAなどのバスプロトコルをベースに構築されていることが多い。しかしもちろんこれらのバスプロトコルを使わずに構築することも可能だ。それについては後で見ていく。
Diplomacyを構築するためのLazyModule
Diplomacyで接続されるモジュールは2つの側面を持つ。1つが「ノード・エッジ」もう一つが「ModuleImpl」だ。
ノード・エッジはDiplomacyによる接続そのものを司る。その名の通りノード間をエッジで結ぶ。
ModuleImplはノードに接続された情報を吸い出していろんな処理をする部分だ。言うなればここがChiselで設計する部分。CPUだったりコントローラが載っている部分である。
概要を示そう。以下はメモリmemory
スレーブ(DiplomacyではSinkと言う)に対して2つのマスター(DiplomacyではSourceと言う)を接続してアクセスしている。このときクロスバとしてTLXbar
を配置し、以下のようにTLXbar
に対して単純にマスターであるloader
とifu
を接続する。
xbar.node := loader.node
xbar.node := TLDelayer(0.0001) := ifu.node
memory.node := xbar.node
Chiselを使ったことがある人なら:=
という演算子を見たことがあるかもしれないが、これは全く別物。Diplomacyのための演算子で、実際は2つのモジュールをDiplomacyを通じて接続する意味を持つ。