プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Chisel入門1~今すぐVerilogを捨てるべき理由~

 日本人だけが知らないScala製のChisel!とか、煽り文章とか考えてみたけど、タイトルだけでギブアップ。別にVerilogもVHDLも捨てなくて良いです。


 今回はChiselというハードウェアを記述するScalaのライブラリ(埋め込み言語)をご紹介します。
 なお、Scalaって単語に釣られたけど、ハードとか知らんし、結論だけで回れ右したい人は、「AltJSの回路版」で全てを理解できるでしょう。お疲れ様でした。


 さて、今世界でブームになりつつある(なってる?)様ですが、例によって日本はいつも通りあまり知名度が無さそうです。そんなChiselを勉強してみて、これスゲーとなったので、まずは何故Chiselを導入すべきなのか、という記事を書いてみたいと思います。



 

そもそもChiselって何?

f:id:nodamushi:20180203135601p:plain
 ChiselはUC Berkeleyが開発している、Scalaというプログラム言語に組み込まれた、簡単に言えばVerilogやVHDLを生成するライブラリです。Chiselの意味は彫刻刀で、読み方はチィーゼルって感じでしょうか。
 身近な例で言えば、topの接続の為に、Verilogを何かの表から自動生成したことはないでしょうか?とーっても雑に言えば、あれです。
 繋ぐだけじゃなくて論理記述やテストの作成とかも出来ます。

 
 こういうライブラリはJavaやらPythonRubyで作成してる人もいたりで、雨後の筍のように乱立しています。それだけVerilog書くのがしんどいと言うことですね。かくいう私も、自作のLisp(Schme)ライブラリでVerilog吐き出してました。

 そんな中、何故Chiselなのかというと、だいたいこいつの所為です。
f:id:nodamushi:20180203145913p:plain
 今世界中で話題のオープンなISA(命令セットアーキテクチャ)のRISC-V。メンバ企業にはGoogleやらIBMやらNVIDIAやらTI,Mentor、MicrosemiにWestern Digital、NXPやらと、もう巨人の集まりです。米やインドは国単位の動きすらもあるようで。脱ARMや脱Intelの動きですかね。
 で、このRISC-V実装のRocketChiselで書かれています。(coreの実装を読むのはしんどいので、AMBAあたりが読むの楽。)RocketはUC Berkeleyで作ってる、RISC-Vの大本みたいな奴です。Chiselも同じバークレイ。なお、RISC-Vの実装は他にもあります
 巨額の金が動いてるので、Chiselが放置されるとか、時代の流れに取り残される心配が少ないのもメリットの一つでしょう。(世の中所詮金!)

 

 

Chiselを導入するメリット

 信用は金の別名ですから、Chiselが十分に信用あると理解して頂けたかと思います。が、だからといって実際に使うかどうかは別問題ですよね。
 そもそもVerilogとかVHDLを書いてる人が、私の様にC++やらJavaやらSchemeやら使える方がキモイのです。普通は精々Tclぐらいしか書けないでしょう。あぁ、スペース地獄のTclとか滅べば良いのに。
 そんな方々に、Chiselで論理組めと言っても拒絶されて当然です。だいたい、ChiselのベースとなってるScalaで検索すると、オブジェクト指向、関数型、Java、モナド、圏論と聞いたこともない単語が殴りかかってきます。なので、最初は検索しないこと推奨です。

 なので、先ずは浅いところだけ、ね、先っちょだけだから、ちょっとだけだから、いいでしょ?って感じでメリットを紹介したいと思います。

基礎:moduleをChiselで書く

 メリットをお話しする前に、最低限の基本である、モジュールの書き方だけ紹介します。細かい理解はしなくて良いです。
 以下の様な、入力が出力に抜けていくモジュールを作ってみましょう。
f:id:nodamushi:20180203162051p:plain

 Verilogだとこうですね。

module Hello( 
  input   i, 
  output  o 
);
  assign o = i;
endmodule

 これをChiselで書くと、こうなります。

class Hello extends UserModule{
  val i = IO(Input(Bool()))
  val o = IO(Output(Bool()))

  o := i
}

 完全に一対一対応ですね。なんとなく、読めるんじゃないでしょうか。Chiselがちょっと冗長な程度です。
 ね?怖くない。


脳汁垂れる入出力の書き方

 先ほどの例だと、Chiselの方が書くの面倒で、何が良いのか分かりませんね。もうちょっと状況を面倒にしてみましょう。
 先にこの節の結論を言っておくと、SystemVerilogが使えない環境でも、SystemVerilogのInterfaceの様な物が使えると言う話です。

 さて、以下の様に、何かのMasterとSlaveがいて、その間を繋ぐ、バスのようなモジュールを考えてみましょう。
f:id:nodamushi:20180203163412p:plain

 Verilogだと例えば、こんな感じですね。(図と同じではありません。)

module Hello(
  input     S_A,
  input     S_B,
  output    S_C,
  input     S_D,
  output    M_A,
  output    M_B,
  input     M_C,
  output    M_D
);
endmodule

 あぁ、面倒くさいんじゃ―
 Chiselではこうなります。

class Hello extends UserModule{
  val S_A = IO( Input(Bool()))
  val S_B = IO( Input(Bool()))
  val S_C = IO(Output(Bool()))
  val S_D = IO( Input(Bool()))
  val M_A = IO(Output(Bool()))
  val M_B = IO(Output(Bool()))
  val M_C = IO( Input(Bool()))
  val M_D = IO(Output(Bool()))
}

 どうですか!?すごいでしょ嬉しいでしょ!?脳汁垂れてきません?え!?嬉しくない!?
 うん、知ってる。何も嬉しくないよね。

 

 このままでは何も嬉しくないので、Bundleという複数の信号を一つのデータとしてまとめる機能を使ってみましょう。以下の様に、入力と出力をまとめることが出来ます。

class Hello extends UserModule{
  val S = IO(new Bundle{
    val A =  Input(Bool())
    val B =  Input(Bool())
    val C = Output(Bool())
    val D =  Input(Bool())
  })
  val M = IO(new Bundle{
    val A = Output(Bool())
    val B = Output(Bool())
    val C =  Input(Bool())
    val D = Output(Bool())
  })
}

 構造化されて、少し見やすくなったでしょうか?
 でも、まだ嬉しくないですよね。

 

 安心してください。更に以下の様に書き換えることが出来ます。

class MyPort extends Bundle{
  val A =  Input(Bool())
  val B =  Input(Bool())
  val C = Output(Bool())
  val D =  Input(Bool())
}
class Hello extends UserModule{
  val S = IO(new MyPort)
  val M = IO(Flipped(new MyPort))
}

 脳汁溢れる!

 どうですか。なんと、マスターポート定義が消えました。
 Chiselでは、Flippedによって入出力を反転したデータを定義することが出来るのです。

 ちなみに、これをVerilogに変換させると、ちゃんと最初のVerilogになります。
 SystemVerilogが使えない環境で、interfaceを利用することが出来るのです。

 

脳汁ブシャーなつなぎ方

 先ほどの例を更に発展させ、マスターポートとスレーブポートを直結しましょう。Verilogならこうなりますね。

module Hello(
  input   S_A, 
  input   S_B, 
  output  S_C,
  input   S_D, 
  output  M_A,
  output  M_B,
  input   M_C,
  output  M_D
);
  assign S_C = M_C;
  assign M_A = S_A;
  assign M_B = S_B;
  assign M_D = S_D;
endmodule

 ま、まぁ、まだ4つしかないから何とかなりますね。しんどいけど。
 じゃぁ、Chiselで書いてみましょう。

class MyPort extends Bundle{
  val A =  Input(Bool())
  val B =  Input(Bool())
  val C = Output(Bool())
  val D =  Input(Bool())
}
class Hello extends UserModule{
  val S = IO(new MyPort)
  val M = IO(Flipped(new MyPort))
  S<>M
}

 脳汁ブシャーーー!

 

 いやー、うん、素晴らしい。まぁ、この位はSystemVerilogのinterfaceでも出来ますが。
 これでも、異なるモジュールのインスタンスの入出力を繋ぐ場合には十分に有用ですが、単一のインスタンスの中で入出力をそのまま直結することはないですよね。次は、ABCは直結するけど、出力Dは入力Aと入力DのORを取ることにしてみましょう。

class MyPort extends Bundle{
  val A =  Input(Bool())
  val B =  Input(Bool())
  val C = Output(Bool())
  val D =  Input(Bool())
}
class Hello extends UserModule{
  val S = IO(new MyPort)
  val M = IO(Flipped(new MyPort))
  S<>M
  M.D := S.A | S.D
}

 脳汁弾けて阿呆になりそう

 実は、S<>Mで一度M.DはS.Dに直結しているのですが、Chiselは最後に書かれた接続が有効になるので、M.D:=S.A | S.Dだけが結果として残ります。他のポートはS<>Mでの接続がそのまま残ります。


 

まとめ

 今回は、文法など、細かいことは気にせず、繋ぐという点だけに絞って紹介してみました。(最後に微妙にORの論理が入りましたが。)

 どうでしたでしょうか?非常に強力なツールだと実感して貰えたでしょうか?今回の内容だけだと、SystemVerilogを使っている人には、あまり響かなかったかも知れませんが。
 論理の実装はVerilogで行い、繋ぐところはChiselでという接着剤的な使い方から初めても良いと思います。

 今熱いChiselを是非初めて見ましょう。

 というわけで、次回から実際の入門記事でも書こうかな。