前回の記事ではChisel BootcampのModule3.1のパラメタライズの2つ目の例について見ていった。
今日も引き続き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)
上記はScalaのMap
を定義した後に、val
にMap
の値を設定しているコードになる。
これを実行してみると以下の様にエラーが発生する
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)
最初の1
はprintln(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
と同様にOption
もget
メソッドを持っている。このget
メソッドをNone
のインスタンスで呼ぶとエラーとなるが、このようなケースのためにデフォルト処理getOrElse
が提供されている。
None
でget
を呼ぶとエラー
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)
として定義しているのでgetOrElse
で1
が返却されるnone
の場合は値がないのでgetOrElse
の引数として与えた2
が返却される
ということになる。
デフォルト値付きのパラメータ
オブジェクトや関数が多数のパラメータを持っている場合、それらを全て指定するのは面倒だしエラーの原因にもなる。
Module1では名前付き引数とデフォルトパラメータを使って例が登場している。
デフォルト値が都合の良い値である場合は良いが、時々そうはなっていないことがある。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でどう使うかを確認しただけなのだが、結構な分量になったので今日はここまでにする。。
次回はScalaのMatch
文の使い方についてで、ここで今回勉強したOption
が再度登場することになる。
にしてもModule3.1、ボリューム多いな。。。