Chisel の紹介

Chisel(Constructing Hardware in a Scala Embedded Language)は、Scala の内部 DSL として実装されたハードウェア記述言語(開発しているバークレー大 EECS(Electrical Engineering and Computer Sciences)では "hardware construction language" としている)です。Scala によるディジタル回路の記述から、C++ によるシミュレータ、または、Verilog HDL による記述を出力できます(さらに検証を行うこともできるようです)。詳しくは http://chisel.eecs.berkeley.edu/ を参照してください。なお chisel は英語で、たがね(鏨)およびのみ(鑿)を指す語です。
Chisel の導入として、PC-Unix 環境(私の場合は FreeBSD)で簡単な回路記述(4 ビット加算器)の Verilog HDL 記述を得るまでの手順を紹介します。

環境の準備

Scala のビルドツール sbt(Simple Build Tool)が必要ですのでインストールします。私の使っている FreeBSD の場合は ports の devel/sbt にありますので、次のようにしてインストールしました。

$ portinstall -s --batch devel/sbt

Chisel 自体のインストールは、sbt(が使っている Ivy)によって自動的に(というか automagical に)行われますので必要ありません。

ビルドディレクトリ

http://metanest.jp/chisel/adder.zip にサンプルを置きました。中身は以下のようになっています。

sbt/  ← sbt を起動するディレクトリ
  project/
  project/build.scala  ← sbt の記述ファイル(Makefile のようなもの)
src/
  adder.scala  ← ハードウェアを記述したソースコード
verilog/  ← 生成される Verilog HDL 記述の出力先

build.scala

build.scala は次のようになっています。

以下、主要部分を解説します。

val buildOrganization = "org.example"
val buildVersion = "0.0.0"

何らかの設定が必要なようなので、適当に設定しておきます。

val buildScalaVersion = "2.9.2"

使用する Scala のバージョンです。現行バージョンを指定しておけばいいでしょう。

Defaults.defaultSettings ++ Seq (
  ...

設定を定義する本体です。

libraryDependencies += "edu.berkeley.cs" %% "chisel" % "1.0.7"

これがあることで、自動的にライブラリの jar がダウンロードされて、使われるようになります。

object ChiselBuild extends Build {
  import BuildSettings._
  lazy val adder = Project("adder", file("adder"), settings = BuildSettings(".."))
}

1 個目の引数はプロジェクト名、2 個目の引数はコンパイルするファイル名(から拡張子 .scala を除いたもの)です。

ソースファイル

続いて、ハードウェアを記述したソースコードを示します。

package adder

試したところでは、Chisel 1.0.7 では何らかのパッケージの指定が必要でした。これを付けないと合成ができません。

import Chisel._

ライブラリのインポート。

class Adder4 extends Component {

Component クラスを拡張したクラスで、ディジタル回路のコンポーネントを表現します。

val io = new Bundle {
  val a = Bits(INPUT, 4)
  val b = Bits(INPUT, 4)
  val ci = Bits(INPUT, 1)
  val s = Bits(OUTPUT, 4)
  val co = Bits(OUTPUT, 1)
}

入出力端子はこのように記述します。

val fa0 = new FullAdder()
...

利用するコンポーネントは、このようにインスタンスを生成します。

def fullAdder_connect(fa: FullAdder, a: Bits, b: Bits, c: Bits) = {
  fa.io.a := a
  fa.io.b := b
  fa.io.c := c
}

fullAdder_connect(fa0, io.a(0), io.b(0), io.ci)
fullAdder_connect(fa1, io.a(1), io.b(1), fa0.io.co)
fullAdder_connect(fa2, io.a(2), io.b(2), fa1.io.co)
fullAdder_connect(fa3, io.a(3), io.b(3), fa2.io.co)

ベタ書きしても良いのですが、ここでは各桁の加算器に入力を接続する記述を関数に切り出しました。この関数定義と関数呼出は Chisel の機能ではなく Scala の機能です。このようなメタ記述を、実装言語によって簡単に行える点が、内部 DSL の強みでしょう。逆に、内部 DSL というものに慣れていないと戸惑う点でもあるかもしれません。

io.s := fa3.io.s ## fa2.io.s ## fa1.io.s ## fa0.io.s
io.co := fa3.io.co

信号の接続は := で行います。## はビットの連結の演算子です。
FullAdder については省略します。

val tmp1 = io.a | io.b
val tmp2 = io.a & io.b
io.s := tmp1 & ~tmp2
io.co := tmp2

HalfAdder の一部です。ここで | や & や ~ は Scala の普通の演算子ですが、 Chisel で Bits 型に対して、回路を合成する演算子として定義されているため、このような記述で回路記述ができます。

object adder_main {
  def main(args: Array[String]): Unit = {
    val top = args(0)
    val chiselArgs = args.tail
    chiselMain(chiselArgs.toArray, () => Class.forName(top).newInstance.asInstanceOf[Component])
  }
}

Chisel による合成を起動するための main です。sbt から起動(後述)します。

コンパイルと起動

前述したようなビルド用のディレクトリ構成を作り、sbt ディレクトリに移動して、sbt コマンドを起動します。

$ cd sbt
$ sbt
[info] Loading project definition from /home/ksmakoto/chisel-sample/adder/sbt/project
[info] Set current project to adder (in build file:/home/ksmakoto/chisel-sample/adder/sbt/)
>

パス名は私の環境の場合です。最後の > は sbt のプロンプトです。なお、最初に起動した時はもっと他にメッセージが出ます(sbt が環境をキャッシュするため)。
次のようにして、コンパイルし、Chisel を起動します。先頭に > のある行が sbt へのコマンドの入力です。

> compile
[info] Updating {file:/home/ksmakoto/chisel-sample/adder/sbt/}adder...
[info] Resolving edu.berkeley.cs#chisel_2.9.2;1.0.7 ...
[info] Done updating.
[info] Compiling 1 Scala source to /home/ksmakoto/chisel-sample/adder/sbt/adder/target/scala-2.9.2/classes...
[success] Total time: 4 s, completed 2012/12/24 20:48:27
> run adder.Adder4 --backend v --targetDir ../verilog
[info] Running adder.adder_main adder.Adder4 --backend v --targetDir ../verilog
started inference
7
finished inference
start width checking
finished width checking
started flattenning
130
finished flattening
resolving nodes to the components
finished resolving
BEGINNING COMBINATIONAL LOOP CHECKING
BEGINNING SEARCHING CIRCUIT FOR COMBINATIONAL LOOP
FINISHED ANALYZING CIRCUIT
NO COMBINATIONAL LOOP FOUND
// COMPILING Adder4 4 CHILDREN
//   COMPILING FullAdder 2 CHILDREN
//     COMPILING HalfAdder 0 CHILDREN
//     COMPILING HalfAdder 0 CHILDREN
//   COMPILING FullAdder 2 CHILDREN
//     COMPILING HalfAdder 0 CHILDREN
//     COMPILING HalfAdder 0 CHILDREN
//   COMPILING FullAdder 2 CHILDREN
//     COMPILING HalfAdder 0 CHILDREN
//     COMPILING HalfAdder 0 CHILDREN
//   COMPILING FullAdder 2 CHILDREN
//     COMPILING HalfAdder 0 CHILDREN
//     COMPILING HalfAdder 0 CHILDREN
[success] Total time: 1 s, completed 2012/12/24 20:48:45

adder.Adder4 が合成対象の回路の指定、--backend v が Verilog HDL の合成の指定、--targetDir ../verilog が出力先の指定です。次のようにシェルのコマンドから直接起動することもできます(コンパイルは依存関係によって自動的に行われます)。

$ sbt "run adder.Adder4 --backend v --targetDir ../verilog"

合成結果は ../verilog/Adder4.v というファイルに得られます。この程度の規模であれば、十分に読める Verilog HDL 記述が得られますので、読んでみると、どのように変換されるかがわかるかと思います。HalfAdder は以下のように変換されます。

module HalfAdder(
    input  io_a,
    input  io_b,
    output io_s,
    output io_co);

  wire tmp2;
  wire T0;
  wire T1;
  wire tmp1;

  assign io_co = tmp2;
  assign tmp2 = io_a & io_b;
  assign io_s = T0;
  assign T0 = tmp1 & T1;
  assign T1 = ~ tmp2;
  assign tmp1 = io_a | io_b;
endmodule

内部 DSL でありながら、(おそらくリフレクションを使っているのだと思います)変数名が損なわれることなく変換されていることがわかります。
なお、実用的には + 演算子を使ったほうが効率的な加算器が合成される記述になると思われます。ここでは、あくまで回路記述の例として、半加算器・全加算器・多ビット加算器を順に合成して加算器を作りました。