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

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

Chiselのswitchも普通にパラメタライズ出来た

スポンサーリンク

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だけパラメタライズできないということはなく、ただ単にやり方が間違っていただけ、というお話でした。