前回はGW中に作ったRISC-Vの紹介をしてみたが、その記事ではそのRISC-Vプロセッサの概要と作った所感についてをまとめただけでした。
まず最初の目標としていたのがこの取り組みだったため、ひとまず最初の目標は達成したというところ言う感じです。
そんなわけもあり一区切りという意味も込めて、ここまでに勉強してきたChiselの文法的な話をついてまとめてみようと思います。
ある意味Chiselの文法の入門編的な感じになる、、はず。
Chisel入門編〜その1:モジュールの定義とエラボレート〜
”文法まとめ”の位置づけなので、基本的な部分から。
なお、Chiselの開発環境は既に構築されている前提で書きます。
Chiselの環境についてはgithubに公開されている"chisel-template"を使うのがオススメ。
この辺の開発環境構築については以下の記事でまとめているのでこちらも良ければ参考になさってください。
Chiselのモジュール
Chiselのモジュールは以下の形で記載するのが基本形となります。
import chisel3._ class <モジュール名> extends Module { // 以下のval ioは必須 val io = IO(<IOの宣言>) // モジュールの実装 }
上記に記載したとおり、io
変数は実装必須。これは継承したModule
内で以下のように抽象メソッドとして宣言されているからです。
因みにScalaのメソッドを()
なしで宣言すると変数でのオーバーライドが可能になるので、上記のようにval io
での宣言が可能になっているからです。
// IO for this Module. At the Scala level (pre-FIRRTL transformations), // connections in and out of a Module may only go through `io` elements. def io: Record
上記のモジュール宣言を実際に書いてみると以下のようになります。
import chisel3._ /** * Chiselのモジュール基本形 * このモジュールは入力"in"を出力"out"に接続しスルーするだけ */ class TestChiselModule extends Module { val io = IO(new Bundle { val in = Input(Bool()) val out = Output(Bool()) }) // 信号の接続 io.out := io.in }
しれっとIO宣言部分にnew Bundle{}
という記述が出てきてますが、今はこういうものだとお考えください。
IOポートの宣言自体はこのBundle
を使って以下のような形で書けばOKです。
val io = IO(new Bundle { val <ポート名1> = [Input or Output](<Chiselの型>) val <ポート名2> = [Input or Output](<Chiselの型>) ... val <ポート名N> = [Input or Output](<Chiselの型>) })
エラボレート
Chiselのモジュールを書いたのでとりあえずエラボレートしてみたい!!となると思うので、先にエラボレートの方法を。
これも以前にまとめているので、こちらも合わせてどうぞ。
エラボレートするには以下のようにChiselで定義されているオブジェクトDriver
に定義されているexecute
メソッドに作ったモジュールのインスタンスを渡せばOKです。
Driver.execute(Array[String](), () => new <モジュール名>)
第一引数はエラボレート時に指定できるオプションをArray[String]
で渡す感じになります。
例えば以下のようにすることで出力先のディレクトリを指定すること出来ます。
Driver.execute(Array("--target_dir=<出力先ディレクトリ>"))
TestChiselModule
のエラボレート用オブジェクト
では先ほどのTestChiselModule
をエラボレートしてみます。
まずは上記のDriver.execute
を実行コードに含むScalaのメインオブジェクトを作成します。
以下の例では2つのエラボレート用のmain関数を作成しています。どちらを使ってもOKです。
まあApp
トレイト使ったほうが楽ですよね。
import chisel3._ /** * TestChiselModuleのエラボレート */ object ElaborateTestChiselModule { /** * Scalaのメイン関数 * @param args プログラム引数 */ def main(args: Array[String]) { Driver.execute(Array( "--target-dir=rtl/chisel-basic" ), () => new TestChiselModule ) } } /** * TestChiselModuleのエラボレートのAppトレイト使用版 * Appトレイトを継承することでmainメソッドを作らなくて済む */ object ElaborateTestChiselModuleApp extends App{ Driver.execute(Array( "--target-dir=rtl/chisel-basic" ), () => new TestChiselModule ) }
エラボレート時のsbtコマンド
先ほどの作成したElaborateTestChiselModule
を実行するには以下のコマンドを実行します。
sbt "runMain ElaborateTestChiselModule"
sbtのコマンドrunMain
は"src/main/scala"の下にある特定にmain
メソッドを実行するコマンドになります。
sbt "runMain <mainメソッド名>"
が一般形です。この際にパッケージで階層化している場合にはパッケージ名を含んだ完全なパスが必要になります。
実行すると以下のような感じでログが表示されてRTLが生成されるはずです。
[IJ]sbt:chiselBasic> runMain ElaborateTestChiselModule [info] Updating ... [info] Done updating. [warn] There may be incompatibilities among your library dependencies; run 'evicted' to see detailed eviction warnings. [info] Compiling 2 Scala sources to /home/diningyo/prj/study/2000_chisel/500_learning-chisel3/subprj/chisel-basic/target/scala-2.11/classes ... [warn] /home/diningyo/prj/study/2000_chisel/500_learning-chisel3/subprj/chisel-basic/src/main/scala/TestChiselModule.scala:16:6: reflective access of structural type member value out should be enabled [warn] by making the implicit value scala.language.reflectiveCalls visible. [warn] This can be achieved by adding the import clause 'import scala.language.reflectiveCalls' [warn] or by setting the compiler option -language:reflectiveCalls. [warn] See the Scaladoc for value scala.language.reflectiveCalls for a discussion [warn] why the feature should be explicitly enabled. [warn] io.out := io.in [warn] ^ [warn] /home/diningyo/prj/study/2000_chisel/500_learning-chisel3/subprj/chisel-basic/src/main/scala/TestChiselModule.scala:16:16: reflective access of structural type member value in should be enabled [warn] by making the implicit value scala.language.reflectiveCalls visible. [warn] io.out := io.in [warn] ^ [warn] two warnings found [info] Done compiling. [warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list [info] Packaging /home/diningyo/prj/study/2000_chisel/500_learning-chisel3/subprj/chisel-basic/target/scala-2.11/chiselbasic_2.11-2.0.jar ... [info] Done packaging. [info] Running ElaborateTestChiselModule [info] [0.001] Elaborating design... [info] [0.812] Done elaborating. Total FIRRTL Compile Time: 276.1 ms [success] Total time: 10 s, completed 2019/05/26 9:38:16
- 生成されるRTL
エラボレートが終わると--target_dir
で指定したディレクトリに以下の3のファイルが生成されます。
$ ls -l rtl/chisel-basic/ -rw-rw-r-- 1 diningyo diningyo 6 5月 26 09:42 TestChiselModule.anno.json -rw-rw-r-- 1 diningyo diningyo 375 5月 26 09:42 TestChiselModule.fir -rw-rw-r-- 1 diningyo diningyo 231 5月 26 09:42 TestChiselModule.v
この段階では"TestChiselModule.anno.json"は空のJSONファイルなので気にしなくて大丈夫です。
"TestChiselModule.fir"はFIRRTLといってChisel3で導入されたChiselとVerilog HDLの中間表現が記述されたファイルになります
ChiselでVerilog HDLのRTLを生成する際には
- Chiselソース → FIRRTL
- FIRRTL → Verilog HDL
という2段階の処理を経てVerilog HDLのRTLが生成されるようになっています。 このFIRRTLファイルは以下のようにほぼRTLと似たような形式のものになっているので、なんとなく読めるかと思います。
- TestChiselModule.fir
;buildInfoPackage: chisel3, version: 3.1.7, scalaVersion: 2.11.12, sbtVersion: 1.1.1, builtAtString: 2019-03-20 22:15:13.399, builtAtMillis: 1553120113399 circuit TestChiselModule : module TestChiselModule : input clock : Clock input reset : UInt<1> output io : {flip in : UInt<1>, out : UInt<1>} io.out <= io.in @[TestChiselModule.scala 17:10]
- TestChiselModule.v
module TestChiselModule( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input io_in, // @[:@6.4] output io_out // @[:@6.4] ); assign io_out = io_in; // @[TestChiselModule.scala 17:10:@8.4] endmodule
FIRRTLについての細かな文法が知りたい場合は以下のFIRRTLの仕様書を読むと良いかと思います。
その2へ続く。