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

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

Chisel Bootcamp - Module3.1(3) - デフォルト引数とOptionクラス

スポンサーリンク

前回の記事ではChisel BootcampのModule3.1のパラメタライズの2つ目の例について見ていった。

www.tech-diningyo.info

今日も引き続きModule3.1を見ていく。今日はパラメタライズの際に指定できるオプションとデフォルト引数についてだ。

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

オプションとデフォルト引数

まずはScalaの文法についての説明から。

関数を実装した際に、戻り値がある場合、ない場合が混在するケースがある。通常をこれはエラーになるのだが、これを回避する方法がScalaには備わっている

例題:Mapのインデックス間違い

ということで、まずは例題から。

val map = Map("a" -> 1)
val a = map("a")
println(a)
val b = map("b")
println(b)

上記はScalaMapを定義した後に、valMapの値を設定しているコードになる。

これを実行してみると以下の様にエラーが発生する

1

java.util.NoSuchElementException: key not found: b
  scala.collection.MapLike$class.default(MapLike.scala:228)
  scala.collection.AbstractMap.default(Map.scala:59)
  scala.collection.MapLike$class.apply(MapLike.scala:141)
  scala.collection.AbstractMap.apply(Map.scala:59)
  ammonite.$sess.cmd2$Helper.<init>(cmd2.sc:4)
  ammonite.$sess.cmd2$.<init>(cmd2.sc:7)
  ammonite.$sess.cmd2$.<clinit>(cmd2.sc:-1)

最初の1println(a)の表示で、その後のbを定義する際に"キーが見つからない"という例外が発生する。

例題:不確実なインデックス参照

上記の例外を回避するためにはMapクラスが提供しているgetメソッドを使えばいい。getメソッドを使うと抽象クラスOptionの値が返ってくる。このOptionは以下の2つのサブクラスを持っている。

  • Some
  • None

先ほどの例題のコードをgetメソッドを用いて書き換えてみる。

val map = Map("a" -> 1)
val a = map.get("a")
println(a)
val b = map.get("b")
println(b)

これを実行してみる。

Some(1)
None

map: Map[String, Int] = Map("a" -> 1)
a: Option[Int] = Some(1)
b: Option[Int] = None

先ほど発生していたjava.util.NoSuchElementExceptionは発生せず、代わりにNoneが返却される結果となった。

結果だけ見るとなんてこと無いのだが、Bootcampの説明によると:

後のセクションでわかると思うが、Optionは極めて重要だ。なぜならこれはmatch文においてScalaの型と値をチェックするすることに使われるからだ。

とのこと。

例題:GetとElse

先のMapと同様にOptiongetメソッドを持っている。このgetメソッドをNoneインスタンスで呼ぶとエラーとなるが、このようなケースのためにデフォルト処理getOrElseが提供されている。

Nonegetを呼ぶとエラー
val some = Some(1)
val none = None
println(some.get)          // Returns 1
 println(none.get)       // Errors!
  • 実行結果
1

java.util.NoSuchElementException: None.get
  scala.None$.get(Option.scala:347)
  ammonite.$sess.cmd6$Helper.<init>(cmd6.sc:4)
  ammonite.$sess.cmd6$.<init>(cmd6.sc:7)
  ammonite.$sess.cmd6$.<clinit>(cmd6.sc:-1)
getOrElseで処理
val some = Some(1)
val none = None
println(some.get)          // Returns 1
// println(none.get)       // Errors!
println(some.getOrElse(2)) // Returns 1
println(none.getOrElse(2)) // Returns 2
  • 実行結果
1
1
2

some: Some[Int] = Some(1)
none: None.type = None

上記で見るとわかる通りsome/noneの両方でgetOrElseを呼んでエラーを回避することが出来ている。

getOrElseメソッド

と、ここでgetOrElseの引数が気になったのでおなじみになりつつあるScala Standard Libraryをチェック。

final def getOrElse[B >: Nothing](default: ⇒ B): B

Returns the option's value if the option is nonempty, otherwise return the result of evaluating default.

default    the default expression.

”デフォルト値を設定してる”ということのようだ。

これを踏まえて先の実行結果を見なおしてみると、

  • someの場合はインスタンス時にSome(1)として定義しているのでgetOrElse1が返却される
  • noneの場合は値がないのでgetOrElseの引数として与えた2が返却される

ということになる。

デフォルト値付きのパラメータ

オブジェクトや関数が多数のパラメータを持っている場合、それらを全て指定するのは面倒だしエラーの原因にもなる。

Module1では名前付き引数とデフォルトパラメータを使って例が登場している。

www.tech-diningyo.info

デフォルト値が都合の良い値である場合は良いが、時々そうはなっていないことがある。Optionを使うことで、必要な場合にのみデフォルト値を変更するような仕組みを作ることが出来る。

例題:リセット・オプション

さてChiselに話を戻そう。次に紹介するコードは、ただ単に入力を1サイクル遅延して出力するだけのモジュールだ。ここで注目すべきなのはモジュールの処理ではなくModuleのパラメータとその使用方法にある。

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
}

println(getVerilog(new DelayBy1))
println(getVerilog(new DelayBy1(Some(3.U))))

この例ではOption[UInt] = Noneとあるようにデフォルト値として先ほど紹介したOptionのサブクラスNoneを設定している。このオプションの値により生成されるRTLの違いについて確認してみよう。

実行結果:resetValueを明示しない場合(new DelayBy1)

生成されるRTLは以下の様にリセット無しのレジスタ生成される。(これまでと同様に関係ある部分のみ抜粋)

Total FIRRTL Compile Time: 283.1 ms
module cmd7HelperDelayBy1( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [15:0] io_in, // @[:@6.4]
  output [15:0] io_out // @[:@6.4]
);
  reg [15:0] reg$; // @[cmd7.sc 9:12:@8.4]
  reg [31:0] _RAND_0;
  assign io_out = reg$;
  always @(posedge clock) begin
    reg$ <= io_in;
  end
endmodule
実行結果:resetValueを設定した場合(new DelayBy1(Some(3.U))

こちらの結果は以下の様にリセット付きのレジスタとなる。

module cmd7HelperDelayBy1( // @[:@3.2]
  input         clock, // @[:@4.4]
  input         reset, // @[:@5.4]
  input  [15:0] io_in, // @[:@6.4]
  output [15:0] io_out // @[:@6.4]
);
  reg [15:0] reg$; // @[cmd7.sc 7:16:@8.4]
  reg [31:0] _RAND_0;
  assign io_out = reg$;
  always @(posedge clock) begin
    if (reset) begin
      reg$ <= 16'h3;
    end else begin
      reg$ <= io_in;
    end
  end
endmodule

この例で見たようにOptionを使うことで、通常の値の範囲にある-1のような値を"none"を示す値として使用すると言ったようなありがちだが、不格好な実装を避けてリセット無しのレジスタを生成することが可能になる。

Optionという一つの仕組みを一通り見てChiselでどう使うかを確認しただけなのだが、結構な分量になったので今日はここまでにする。。

次回はScalaMatch文の使い方についてで、ここで今回勉強したOptionが再度登場することになる。

にしてもModule3.1、ボリューム多いな。。。