前回の記事ではChisel BootcampのModule3.1でパラメタライズの際に指定できるオプションとデフォルト引数を見ていった。
今日も引き続きModule3.1を見ていく。今日はScalaの文法であるmatch
文とそれをChiselに適用するとどうなるかということについて勉強していく。
Module 3.1: ジェネレータ:パラメータ
match
/case
文
この項の最初の一文が、この構文がChiselにとって以下に大切かを示しているので、そのまま引用する。
Scalaの
match
の概念はChisel全体を通して使用されており、Chiselの基礎を理解したプログラマの一員となるためには必要なことである。
このmatch
文だが、以前にもドワンゴの資料を使って学習しているのでこちらも必要に応じて参照してみてほしい。
Scalaのmatch
文は以下の機能を提供する
例題:値のマッチ
さっそく例を使って文法を確認しておこう。
// yはどこかのコードで定義されたInt型の変数 val y = 7 /// ... val x = y match { case 0 => "zero" // 書き方その1。1行に収まる場合に向く case 1 => // 書き方その2。1行に収まらない場合に向く "one" // コードブロックは次の行に続く case 2 => { // 書き方その2の別のシンタックス。ただし"{}"は必須ではない "two" } case _ => "many" // "_"はワイルドカードで全ての値にマッチ } println("y is " + x)
実行結果
y: Int = 7 x: String = "many"
これは上に記載したC言語のような値のマッチに相当する処理なので、特にわかりにくい点も無いと思う。
コード例に書いたが、文法的には以下の点を抑えておく必要がある
=>
の後にコードブロックが続く。その際にコードブロックの終わりは、以下のいずれかになる。- 終端の
}
({}
を使ってコードブロックを明示した場合) - 次の
case
文
- 終端の
match
式のcase
へのマッチ処理は、コードの上から実装した順に検索されて処理が行われる。- いずれかの
case
文にマッチした場合、それ以降のcase
は実行されない(C言語とは異なる)
- いずれかの
_
はワイルドカードで他のcase
にマッチしなかった場合の制御を行う(defalut項)
例題:複数の値のマッチ
より複雑なマッチ処理も行うことが出来る。
def animalType(biggerThanBreadBox: Boolean, meanAsCanBe: Boolean): String = { (biggerThanBreadBox, meanAsCanBe) match { case (true, true) => "wolverine" case (true, false) => "elephant" case (false, true) => "shrew" case (false, false) => "puppy" } } println(animalType(true, true))
実行結果
wolverine defined function animalType
この例では、2つの変数の組み合わせでマッチ処理が行われている。これはなんとなくVerilogでcase
文を連接{}
使って書いた感じに似ている。
function void func(input a, input b) case ({a, b}) 2'b00 : func = 2'd0; 2'b01 : func = 2'd1; 2'b10 : func = 2'd2; 2'b11 : func = 2'd3; default : func = 2'dx; // ほんとはこれはいらない。 endcase endfunction
関数の引数をそのまま受けてmatch
文に繋げることができてるのはなんとなく新鮮に感じる。
例題:型によるマッチ
先に書いたように型を使ったマッチ処理も可能
val sequence = Seq("a", 1, 0.0) sequence.foreach { x => x match { case s: String => println(s"$x is a String") case s: Int => println(s"$x is an Int") case s: Double => println(s"$x is a Double") case _ => println(s"$x is an unknown type!") } }
実行結果
a is a String 1 is an Int 0.0 is a Double sequence: Seq[Any] = List("a", 1, 0.0)
上記の例ではSeq
に入れたString
/Int
/Double
型の各要素をSeq
のメソッドforeach
でループし、その処理の中でmatch
文によるマッチ処理を行い、型を表示している。
例題:複数の型によるマッチ
型によるマッチ処理は複数の型を同時にチェックすることも可能。
val sequence = Seq("a", 1, 0.0) sequence.foreach { x => x match { case _: Int | _: Double => println(s"$x is a number!") case _ => println(s"$x is an unknown type!") } }
実行結果
a is an unknown type! 1 is a number! 0.0 is a number!
コードサンプルのように|
を使うことで、or条件を作り、複数の条件のいずれかにマッチした場合に対応する処理を呼ぶことも可能。
ただこの場合には、マッチ時に_
を使わなければならない。
val sequence = Seq("a", 1, 0.0) sequence.foreach { x => x match { case s: Int | _: Double => println(s"$x is a number!") case s => println(s"$x is an unknown type!") } }
上記コードを実行すると、以下の様にエラーとなる。
cmd8.sc:4: illegal variable in pattern alternative case s: Int | _: Double => println(s"$x is a number!") ^Compilation Failed
例題:型マッチと抹消
Scalaの型マッチにはいくつかの制約が存在する。これはScalaがJVM上で動作しており、JVMが多様な形を持つマッチに対応が出来ないためである。例えば以下のコードは一見うまく動きそうに見えるが、実際には動作しない。
val sequence = Seq(Seq("a"), Seq(1), Seq(0.0)) sequence.foreach { x => x match { case s: Seq[String] => println(s"$x is a String") case s: Seq[Int] => println(s"$x is an Int") case s: Seq[Double] => println(s"$x is a Double") } }
実行結果
このコードを実行すると、以下の結果が得られる。
List(a) is a String List(1) is a String List(0.0) is a String
ご覧のとおり、List
内のいずれの要素もString
として扱われてしまう。これは上に書いたとおりJVMの制約から来るもので、各case
文の以下の部分が削除されるためである。
- [String]
- [Int]
- [Double]
このため、実際にマッチ処理が行われる際にはただのList
として処理が行われ、その結果match
文内の最初のcase
にマッチしたものとして処理が行われる。
通常、Scalaのコンパイラは上記のようなコードを検出すると警告を与えてくれるので、注意しておこう。
例題:オプションのリセットへのマッチ
最後にChiselでScalaのmatch
を使用した例を見ていく。
class DelayBy1(resetValue: Option[UInt] = None) extends Module { val io = IO(new Bundle { val in = Input( UInt(16.W)) val out = Output(UInt(16.W)) }) val reg = resetValue match { case Some(r) => RegInit(r) case None => Reg(UInt()) } reg := io.in io.out := reg } println(getVerilog(new DelayBy1)) println(getVerilog(new DelayBy1(Some(3.U))))
このコードは、前回出てきた”入力データを1サイクル遅延する”モジュールをmatch
文を用いて書き換えたものだ。
- 前回のコード
class DelayBy1(resetValue: Option[UInt] = None) extends Module { val io = IO(new Bundle { val in = Input( UInt(16.W)) val out = Output(UInt(16.W)) }) val reg = if (resetValue.isDefined) { // resetValue = Some(number) RegInit(resetValue.get) } else { //resetValue = None Reg(UInt()) } reg := io.in io.out := reg }
得られる結果自体は前回と一緒になるが、リセットを実装するかどうかのパラメータの処理をmatch
文で書き直したことにより、コードの見通しが良くなったのがわかるかと思う。match
文を使いこなすことで、見通しよく、かつ効率的に実装を行えそうな感触。
これでmatch
文についての例題の確認は終了。JVMから来る制約は意識しておく必要があるが、文法的には非常に強力な文法なので、自分で実装を行う際には意識して使っていきたいと思う。