ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Chisel Bootcamp - Module3.6(1) - ScalaとChiselの型について

スポンサーリンク

前回は久々のChisel-Bootcampの学習を勧めModule3.5の残りを見ていった。

www.tech-diningyo.info

今回は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の型

IntUInt

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

BooleanBool

これはBooleanBoolでも同様。

  • 正しい接続
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]で変数xTにキャスト出来る。この時、キャスト不可の場合には例外が投げられる。

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)
  }}
  • コメントアウト時の実行結果 以下の様に"sinksourceの型があってないのでエラー"と怒られる。
  [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()の様な関数を持つものもある。 そのため上記のコードのasTypeOfasSIntに変更しても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から再開する。