読者です 読者をやめる 読者になる 読者になる

Scalaでハードウェアを書く

Scalaでハードウェアを書けるChiselについて紹介します。

Chisel: Constructing Hardware in an Scala Embedded Language

Chisel 2.2.0 Manual

Chiselとは

Chiselとはハードウェア記述用のためのライブラリでありScalaDSLとして実装されています。最近巷で話題のRISC-Vというアーキテクチャがありますが、その実装であるRocket CoreがChiselで記述されています。Chiselは単なるScalaのライブラリなので、.scalaなファイルの中でimportして使います。そして回路の仕様をScalaで記述してコンパイルするとその仕様に従ったVerilogを出力する実行ファイルが出来ます。以下のコードはChiselのトップページから引用したものですが、これをscalacでコンパイルすると「GCDの仕様を記述したVerilog」を吐く.classファイルができるわけです。ややこしいですね。実はコンパイル結果の実行ファイルはオプションでC++を出力することもできて、吐かれたC++コードはコンパイルすると回路のシミュレータが出来上がります。出力したVerilogをそのままModelsimに投げるよりも遥かに簡単にシミュレーションができるので開発効率がとても良いです。

import Chisel._

class GCD extends Module {
  val io = new Bundle {
    val a  = UInt(INPUT,  16)
    val b  = UInt(INPUT,  16)
    val e  = Bool(INPUT)
    val z  = UInt(OUTPUT, 16)
    val v  = Bool(OUTPUT)
  }
  val x  = Reg(UInt())
  val y  = Reg(UInt())
  when   (x > y) { x := x - y }
  unless (x > y) { y := y - x }
  when (io.e) { x := io.a; y := io.b }
  io.z := x
  io.v := y === UInt(0)
}

object Example {
  def main(args: Array[String]): Unit = {
    chiselMain(args, () => Module(new GCD()))
  }
}

さて、実際に回路を書くときにはScalaVerilogのことを両方考えながら書いていく必要があります。ある意味Chisel自体が一つのVerilogライクな言語になっていて、ScalaはChiselのメタ言語(=Chisel言語から見たマクロ)と考えることができますね。もちろんChiselを使っているときにそんなややこしいことを意識する必要はなくて構文が若干変わったVerilogと思えばすらすら書けると思います。たまにメタレベルとオブジェクトレベルの区別ができなくなってScalaのifとChiselのwhenをお互い書き間違えたりしますがScalaの型チェッカが弾いてくれるので実用上問題ないでしょう。慣れるまではちょっとストレスが溜まりますが。

Chiselのメリット1. 抽象化

Verilogを直接書かずにChiselを使う最大のメリットは抽象化です。たとえばモジュール間の通信用にValidやDecoupledといったものが標準で用意されています。たとえばシリアル通信の出力モジュールを考えてみると、そのインターフェイスとして

  • rxd: rs232cの信号線
  • bits: 出力するデータ
  • valid: 実際に出力を行うかどうかのイネーブル
  • ready: UartTxモジュールが送信中状態にあるかどうかを示す

という4つのポートが欲しくなります。このようなポートの構成で特に最後の三つの組は頻繁に登場するためChiselではDecoupledというクラスに抽象化されています。実際に僕が作ったUartTxモジュールではIOポートをDecoupledを使って記述して可読性を上げています。下に実際のコードの一部だけを引用しました。特にDecoupledがbitsの型を引数として取っていることに注目してください。VHDLでもポートにrecordを使うことは可能ですがChiselのように多相化するのは簡単ではないはずです(SystemVerilogには詳しくないので実はできるかもしれません、誰か教えて下さい)。

class UartTx(val wtime: Int) extends Module {
  val io = new Bundle {
    val txd = Bool(OUTPUT)
    val enq = Decoupled(UInt(width = 8)).flip
  }
  ...
}

抽象化が最大の利点であると言いましたが、このようにメタ言語ScalaになっているおかげでChiselでは多相化がとても簡単に行えます。Chiselを言語としてみたときの標準ライブラリはあまり充実しているとは言い難いですが、それでも多相的なハードウェアQueueが最初から提供されているのは大きいのではないでしょうか。val queue = new Queue(UInt(width = 32), 16)と一行書くだけでキューが使えるのはとても楽です。マニュアルに載っているもので便利そうなものをピックアップしていくと

ぐらいでしょうか。Chiselの開発はとても活発なので今後充実することに期待します。

Chiselのメリット2. 効率的なデバッグ

Chiselのメリットその2にデバッグのしやすさがあります。人によってはこちらの方がより大きな利点かもしれません。先ほども言及しましたがchiselは直接C++のシミュレータを出力することができます。さらにオプションをつけることによってシミュレータのコンパイルまで行ってくれるのでChiselで回路記述からの実行がワンステップで行えます。また、Chiselにはテスト用の機能が標準で備わっています。テスターもDSLの延長として書かれており、「ポートpにデータを流し時間をkサイクル進めたあとポートqの値がxになっている」という内容を自然な書き方で記述するとテスターがそれを実行してPASS/FAILを教えてくれます。必要ならば実行トレースをVCDファイルとして吐くこともできるので適当な波形ビューワーによってデバッグすることもできます。printlnメソッドも用意されており、必要なら生成したC++シミュレータに実行時の値を標準出力に書き出させることもできます。

さらに大事なことはChiselのテストをScalaで書けるということです。僕は今回作ったChisel-Uart以外にもChisel-BrainfuckというBrainfuckを実行する小さなCPUをchiselで作成しました。このCPUはbrainfuckソースコードを直接はせず一旦バイトコードコンパイルしたものしか実行できないのですが、そのコンパイラをScalaで書き、Chiselのテスターの中にそのまま埋め込むことができましたVerilogで同じような回路を書いたとしてもVerilogコンパイラまで書いてしまおうという人は(もちろんいるかもしれませんが)自然な考えではないですよね。一方そもそもがScalaDSLであるChiselはテストのためにScalaを使うことは当然ですし、同じファイルに書いてsbt runするだけなのでビルド&テストのプロセスがとても単純です。また、Scalaでテストを書けるということはScalaの標準ライブラリをすべて利用することができるということです。複雑なロジックの回路は正常状態かどうかを判定するのにも複雑な条件を指定しなければなりませんが、Scalaという比較的読みやすく関数型の機能も豊富な言語でテスタを記述すれば保守性も上がります。

まとめ

Chiselは回路記述のための強力なツールです。歴史がまだ浅い分ドキュメントの充実度やユーザー数でやや難点はありますがRocketCoreという大きなプロダクトで使われてるため機能は十分揃っています。ScalaDSLという特徴を存分に生かすことでVerilogVHDLの複雑な部分をうまく隠すことに成功しています。Chiselは特にソフトウェア出身の人間がFPGAで遊んでみるのにちょうど良い道具なのではないでしょうか。興味を持たれた方はぜひchiselで遊んでみてください。(そして可能なら再利用可能なchiselのモジュールをどんどん作って回路での遊びをどんどん楽にしましょう:P)