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

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

Chisel Bootcamp - Module3.1(4) - match文のChiselのモジュールへの適用

スポンサーリンク

前回の記事ではChisel BootcampのModule3.1でパラメタライズの際に指定できるオプションとデフォルト引数を見ていった。

www.tech-diningyo.info

今日も引き続きModule3.1を見ていく。今日はScalaの文法であるmatch文とそれをChiselに適用するとどうなるかということについて勉強していく。

Module 3.1: ジェネレータ:パラメータ

match/case

この項の最初の一文が、この構文がChiselにとって以下に大切かを示しているので、そのまま引用する。

Scalamatchの概念はChisel全体を通して使用されており、Chiselの基礎を理解したプログラマの一員となるためには必要なことである。

このmatch文だが、以前にもドワンゴの資料を使って学習しているのでこちらも必要に応じて参照してみてほしい。

www.tech-diningyo.info

Scalamatch文は以下の機能を提供する

  • Cのswitch-caseのようなシンプルなマッチ
  • 値のアドホックな組み合わせのより複雑なマッチ
  • 不明な方や仕様外の型に基づく処理を行う機能、例えば:
    • val mixedList = List(1, "string", false)のようなヘテロジニアスなリストを値として指定できる
    • とあるスーパークラスの変数だが、サブクラスでは明示されていない変数
  • 正規表現に基づく文字列の展開
例題:値のマッチ

さっそく例を使って文法を確認しておこう。

// 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つの変数の組み合わせでマッチ処理が行われている。これはなんとなくVerilogcase文を連接{}使って書いた感じに似ている。

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の型マッチにはいくつかの制約が存在する。これはScalaJVM上で動作しており、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でScalamatchを使用した例を見ていく。

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から来る制約は意識しておく必要があるが、文法的には非常に強力な文法なので、自分で実装を行う際には意識して使っていきたいと思う。