Chiselのswitchについて少し勘違いしていたことがあったのでそのまとめ。
思い込み:Chiselのswitch直下ではパラメタライズ出来ない
Chiselのswicth
には制限が存在していて、次のようなコードをエラボレートするとエラーが発生する。
import chisel3._ import chisel3.experimental.ChiselEnum import chisel3.util._ object State extends ChiselEnum { val A = Value val B = Value val C = Value } // ビルドエラーが発生する例 class NGPrameteriseSwitch(p: Boolean = true) extends Module { val io = IO(new Bundle { val out_stm = Output(UInt(State.getWidth.W)) }) val r_stm = RegInit(State.A) switch (r_stm) { is (State.A) { if (p) { r_stm := State.C } else { r_stm := State.B } } if (p) { is (State.B) { r_stm := State.A } } else { // switch の直下はis()以外はNGになる is (State.C) { r_stm := State.A } } } io.out_stm := r_stm.asUInt() }
意図としてはステートマシンの一部をクラスパラメーターで切り替えたいというものになる。 一見した所、問題無さそうに見えるかもしれない。実際、Scalaの文法上では何もエラーは存在しておらず、上記のコードはChiselとしての評価時に例外が発生するようになっている。 実際に評価すると発生するエラーは次のようなものだ。
[error] java.lang.Exception: Cannot include blocks that do not begin with is() in switch. [error] at chisel3.util.switch$$anonfun$2.apply(Conditional.scala:102) [error] at chisel3.util.switch$$anonfun$2.apply(Conditional.scala:97) [error] at scala.collection.LinearSeqOptimized$class.foldLeft(LinearSeqOptimized.scala:124) [error] at scala.collection.immutable.List.foldLeft(List.scala:84) [error] at chisel3.util.switch$.impl(Conditional.scala:97) [error] switch (r_stm) { [error] ^ [error] one error found
見ての通り"Conditional.scala"に定義されているswitch
オブジェクトのmatch
式で発生していて、次のようにis
以外は例外が投げられている。
object switch { // scalastyle:ignore object.name def apply[T <: Element](cond: T)(x: => Any): Unit = macro impl def impl(c: Context)(cond: c.Tree)(x: c.Tree): c.Tree = { import c.universe._ val q"..$body" = x val res = body.foldLeft(q"""new SwitchContext($cond, None, Set.empty)""") { case (acc, tree) => tree match { // TODO: remove when Chisel compatibility package is removed case q"Chisel.`package`.is.apply( ..$params )( ..$body )" => q"$acc.is( ..$params )( ..$body )" case q"chisel3.util.is.apply( ..$params )( ..$body )" => q"$acc.is( ..$params )( ..$body )" // コレに引っかかって例外が発生 case b => throw new Exception(s"Cannot include blocks that do not begin with is() in switch.") } } q"""{ $res }""" } }
実際はパラメタライズできる
で、自分はここまでの状況で「あぁ、switch
はパラメタライズ出来んのか、、、。」と思考停止していたのだが、普通にできたというのが本記事にメインの話。
方法だが次のようにswitch
自体を分割して。パラメタライズしたい方のswitch
を、if
式なりmatch
式なりでくるんでしまえばよかった。
class PrameteriseSwitch(p: Boolean = true) extends Module { val io = IO(new Bundle { val out_stm = Output(UInt(State.getWidth.W)) }) val r_stm = RegInit(State.A) switch (r_stm) { is (State.A) { if (p) { r_stm := State.C } else { r_stm := State.B } } is (State.B) { r_stm := State.A } } // p == trueの時のみ存在する if (p) { switch (r_stm) { is (State.C) { r_stm := State.A } } } io.out_stm := r_stm.asUInt() }
クラスパラメータを切り替えて生成したRTLは次のようになる。
p = false
の場合
module PrameteriseSwitch( input clock, input reset, output [1:0] io_out_stm ); reg [1:0] r_stm; // @[ParameterizeSwitch.scala 60:22] reg [31:0] _RAND_0; wire _T_2; // @[Conditional.scala 37:30] wire _T_5; // @[Conditional.scala 37:30] // State.A assign _T_2 = 2'h0 == r_stm; // @[Conditional.scala 37:30] // Stete.B assign _T_5 = 2'h1 == r_stm; // @[Conditional.scala 37:30] State.B assign io_out_stm = r_stm; // @[ParameterizeSwitch.scala 83:14] always @(posedge clock) begin if (reset) begin r_stm <= 2'h0; end else if (_T_2) begin // State.A r_stm <= 2'h1; end else if (_T_5) begin // State.B r_stm <= 2'h0; end end endmodule
p = true
の場合
module PrameteriseSwitch( input clock, input reset, output [1:0] io_out_stm ); reg [1:0] r_stm; // @[ParameterizeSwitch.scala 60:22] reg [31:0] _RAND_0; wire _T_2; // @[Conditional.scala 37:30] wire _T_5; // @[Conditional.scala 37:30] wire _T_8; // @[Conditional.scala 37:30] // State.A assign _T_2 = 2'h0 == r_stm; // @[Conditional.scala 37:30] // State.B assign _T_5 = 2'h1 == r_stm; // @[Conditional.scala 37:30] // State.Cの変数が追加された assign _T_8 = 2'h2 == r_stm; // @[Conditional.scala 37:30] assign io_out_stm = r_stm; // @[ParameterizeSwitch.scala 83:14] always @(posedge clock) begin if (reset) begin r_stm <= 2'h0; end else if (_T_8) begin // State.A r_stm <= 2'h0; end else if (_T_2) begin // State.B r_stm <= 2'h2; end else if (_T_5) begin // State.Cのケースが増えた r_stm <= 2'h0; end end endmodule
ということでswitch
だけパラメタライズできないということはなく、ただ単にやり方が間違っていただけ、というお話でした。