前回は久々のChisel-Bootcampの学習を勧めModule3.5の残りを見ていった。
今回はModule3.6に入りScalaとChiselの”型”についてを学習していく。
ジェネレータ:型について
モチベーション
これまでどおりまずはモチベーションから。
Scalaは強く型付けされたプログラミング言語だ。これは両刃の剣となる。Python(動的型付けの言語)でコンパイルと実行が可能な多くのプログラムはScalaではコンパイルに失敗するだろう。一方でScalaでコンパイルに成功したプログラムは同様の内容のPythonプログラムと比べて実行時のエラーがごく僅かになるだろう。 このセクションのゴールはScalaにおいて型が第一級のクラスであるという概念に親しみを覚えることにある。初めは生産性が上がらないと感じるかもしれないが、すぐにコンパイル時のエラーメッセージを理解することや、多くのエラーを補足する型システムを使ったプログラムの構築の方法を学ばぶだろう。
このModuleは結構なボリュームがあるので、既に知っている部分についてはサッと流す感じにするつもり。
静的な"型"
Scalaにおけるオブジェクトは型を持っていて、大抵の場合それはオブジェクトのクラスになる。
.getClass
でそのオブジェクトの型を取得できるので見てみよう。
println(10.getClass) println(10.0.getClass) println("ten".getClass)
実行すると以下のようになる。
int double class java.lang.String
これは自分で定義したクラスにおいても同様。
class MyClass { def myMethod = ??? } println(new MyClass().getClass)
- 実行結果
class ammonite.$sess.cmd3$Helper$MyClass
関数において何も値を返さない場合はUnit
が戻り値の型になる。
var counter = 0 def increment(): Unit = { counter += 1 } increment()
- 実行結果
counter: Int = 1
defined function increment
ScalaとChiselの型
Int
とUInt
Chiselのハードウェアオブジェクト(Wire
とかReg
)にScalaの型を持つ値を入れると怒られる。
- 正しい接続
val a = Wire(UInt(4.W)) a := 0.U // 0.U はChiselのUInt
- 誤った接続
val a = Wire(UInt(4.W)) a := 0 // 0 はScalaのInt
Boolean
とBool
これはBoolean
とBool
でも同様。
- 正しい接続
val bool = Wire(Bool()) val boolean: Boolean = false // legal when (bool) { ... } // whenはChiselの条件分岐:ChiselのBoolは正しい if (boolean) { ... } // ifはScalaの条件分岐:ScalaのBooleanが正解
- 誤った接続
// illegal when (boolean) { ... } if (booln) { ... }
ここでScalaの強力な型付けが頑張ってくれる。というは、上記の誤った接続をするとどうなるかというと以下のようにコンパイル時にエラーにしてくれる。しかもエラーメッセージがとても分かりやすい。
cmd6.sc:12: type mismatch; found : chisel3.core.Bool required: Boolean if (bool) { ^cmd6.sc:15: type mismatch; found : Boolean required: chisel3.core.Bool when (boolean) { ^Compilation Failed
Scalaの型強制
asInstanceOf
x.asInstanceOf[T]
で変数x
をT
にキャスト出来る。この時、キャスト不可の場合には例外が投げられる。
val x: UInt = 3.U try { println(x.asInstanceOf[Int]) } catch { case e: java.lang.ClassCastException => println("As expected, we can't cast UInt to Int") } // UIntはDataを継承したものなので、UInt→Dataのキャストは可能 println(x.asInstanceOf[Data])
- 実行結果
As expected, we can't cast UInt to Int
chisel3.core.UInt@0
asTypeOf
次に示すコードはasTypeOf
というキャスト用の関数を使う例になる。
コード中のコメントアウト部分ををそのままで実行するとエラーが発生する。
class TypeConvertDemo extends Module { val io = IO(new Bundle { val in = Input(UInt(4.W)) val out = Output(SInt(4.W)) }) io.out := io.in//.asTypeOf(io.out) } Driver(() => new TypeConvertDemo) { c => new PeekPokeTester(c) { poke(c.io.in, 3) expect(c.io.out, 3) poke(c.io.in, 15) expect(c.io.out, -1) }}
- コメントアウト時の実行結果
以下の様に"
sink
とsource
の型があってないのでエラー"と怒られる。
[info] [0.000] Elaborating design... chisel3.internal.ChiselException: Connection between sink (chisel3.core.SInt@10) and source (chisel3.core.UInt@e) failed @: Sink (chisel3.core.SInt@10) and Source (chisel3.core.UInt@e) have different types. chisel3.internal.throwException$.apply(Error.scala:13) chisel3.core.Data.connect(Data.scala:303) chisel3.core.Data.$colon$eq(Data.scala:365) ammonite.$sess.cmd8$Helper$TypeConvertDemo.<init>(cmd8.sc:6) ammonite.$sess.cmd8$Helper$$anonfun$1.apply(cmd8.sc:9) ammonite.$sess.cmd8$Helper$$anonfun$1.apply(cmd8.sc:9) chisel3.core.Module$.do_apply(Module.scala:49) chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:93) chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:93) chisel3.internal.Builder$$anonfun$build$1.apply(Builder.scala:297) chisel3.internal.Builder$$anonfun$build$1.apply(Builder.scala:295) scala.util.DynamicVariable.withValue(DynamicVariable.scala:58) chisel3.internal.Builder$.build(Builder.scala:295) chisel3.Driver$.elaborate(Driver.scala:93) chisel3.Driver$.execute(Driver.scala:140) chisel3.iotesters.setupFirrtlTerpBackend$.apply(FirrtlTerpBackend.scala:130) chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply$mcZ$sp(Driver.scala:52) chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply(Driver.scala:39) chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply(Driver.scala:39) logger.Logger$$anonfun$makeScope$1.apply(Logger.scala:129) scala.util.DynamicVariable.withValue(DynamicVariable.scala:58) logger.Logger$.makeScope(Logger.scala:127) chisel3.iotesters.Driver$$anonfun$execute$1.apply$mcZ$sp(Driver.scala:39) chisel3.iotesters.Driver$$anonfun$execute$1.apply(Driver.scala:39) chisel3.iotesters.Driver$$anonfun$execute$1.apply(Driver.scala:39) scala.util.DynamicVariable.withValue(DynamicVariable.scala:58) chisel3.iotesters.Driver$.execute(Driver.scala:38) chisel3.iotesters.Driver$.apply(Driver.scala:229) ammonite.$sess.cmd8$Helper.<init>(cmd8.sc:9) ammonite.$sess.cmd8$.<init>(cmd8.sc:7) ammonite.$sess.cmd8$.<clinit>(cmd8.sc:-1)
一般的にはasTypeOf()
を使うが、Chiselのオブジェクトの中にはasUInt()
やasSInt()
の様な関数を持つものもある。
そのため上記のコードのasTypeOf
をasSInt
に変更してもOK。
型を使ったマッチ
Module3.1で紹介したScalaの強力な文法match
だが、これは型に応じて何かをするようなコードを書くときに特に便利だ。
以下はクラスのパラメータに指定された型に応じて、処理を分岐させる例となる。
class ConstantSum(in1: Data, in2: Data) extends Module { val io = IO(new Bundle { val out = Output(in1.cloneType) // cloneTypeでin1の型を複製 // in1はData型を継承していればOKで、その型のcloneTypeが呼ばれる。 }) (in1, in2) match { case (x: UInt, y: UInt) => io.out := x + y case (x: SInt, y: SInt) => io.out := x + y case _ => throw new Exception("I give up!") } }
- 実行結果
UInt
同士/SInt
同士の場合には加算が行われるモジュールが生成され、それ以外の場合には例外が発生する。
[info] [0.000] Elaborating design... [info] [0.006] Done elaborating. Total FIRRTL Compile Time: 78.3 ms module cmd11HelperConstantSum( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] output [1:0] io_out // @[:@6.4] ); wire [3:0] _T_7; // @[cmd11.sc 6:48:@8.4] wire [2:0] _T_8; // @[cmd11.sc 6:48:@9.4] assign _T_7 = 3'h3 + 3'h4; // @[cmd11.sc 6:48:@8.4] assign _T_8 = _T_7[2:0]; // @[cmd11.sc 6:48:@9.4] assign io_out = _T_8[1:0]; endmodule [info] [0.000] Elaborating design... [info] [0.003] Done elaborating. Total FIRRTL Compile Time: 11.9 ms module cmd11HelperConstantSum( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] output [2:0] io_out // @[:@6.4] ); wire [4:0] _T_7; // @[cmd11.sc 7:48:@8.4] wire [3:0] _T_8; // @[cmd11.sc 7:48:@9.4] wire [3:0] _T_9; // @[cmd11.sc 7:48:@10.4] wire [2:0] _GEN_0; assign _T_7 = $signed(-4'sh3) + $signed(4'sh4); // @[cmd11.sc 7:48:@8.4] assign _T_8 = _T_7[3:0]; // @[cmd11.sc 7:48:@9.4] assign _T_9 = $signed(_T_8); // @[cmd11.sc 7:48:@10.4] assign _GEN_0 = _T_9[2:0]; assign io_out = $signed(_GEN_0); endmodule [info] [0.000] Elaborating design... java.lang.Exception: I give up! ammonite.$sess.cmd11$Helper$ConstantSum.<init>(cmd11.sc:8) ammonite.$sess.cmd11$Helper$$anonfun$3.apply(cmd11.sc:13) ammonite.$sess.cmd11$Helper$$anonfun$3.apply(cmd11.sc:13) chisel3.core.Module$.do_apply(Module.scala:49) chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:93) chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:93) chisel3.internal.Builder$$anonfun$build$1.apply(Builder.scala:297) chisel3.internal.Builder$$anonfun$build$1.apply(Builder.scala:295) scala.util.DynamicVariable.withValue(DynamicVariable.scala:58) chisel3.internal.Builder$.build(Builder.scala:295) chisel3.Driver$.elaborate(Driver.scala:93) chisel3.Driver$.execute(Driver.scala:140) chisel3.Driver$.execute(Driver.scala:202) ammonite.$file.dummy.source.load$minusivy$Helper.getVerilog(Main.sc:9) ammonite.$sess.cmd11$Helper.<init>(cmd11.sc:13) ammonite.$sess.cmd11$.<init>(cmd11.sc:7) ammonite.$sess.cmd11$.<clinit>(cmd11.sc:-1)
因みにmatch
文はあくまでScalaの文法なので、Chiselのハードウェアオブジェクトを使ったマッチは出来ない。
例えば以下のように、入力io.in
に応じてハードウェアの処理を切り替えるような事は出来ないということだ。
class InputIsZero extends Module { val io = IO(new Bundle { val in = Input(UInt(16.W)) val out = Output(Bool()) }) io.out := (io.in match { // case 0.Uはエラー case (0.U) => true.B case _ => false.B }) }
match
文の途中で若干キリが悪いが、今日はここまで。
次回はmatch
の際に使われるunapply
から再開する。