今日はとあるモジュールの実装中にVecの取り扱いで遭遇したエラーとその解決方法について簡単にまとめておく。
Vecの各要素との接続で遭遇したエラー
ざっくりと以下のようなコードを作成してエラボレートしたところエラーが発生した。
詳しい処理の説明は省くが、回路の実装上io.sel
の値に応じて入力のVecのN番目を選択する信号(下記実装ではio.idx_0
とio.idx_1
がそれに相当)を切り替えて選択されたio(N).a
をio.out.a
に接続しようとした。
import chisel3._ import chisel3.util._ class AIO extends Bundle { val b = Bool() val c = UInt(8.W) } class VecErrorIO extends Bundle { val a = Decoupled(new AIO) } class VecError extends Module { val io = IO(new Bundle { val sel = Input(Bool()) val idx_0 = Input(UInt(2.W)) val idx_1 = Input(UInt(2.W)) val in = Vec(4, Flipped(new VecErrorIO)) val out = new VecErrorIO }) // エラー再現用なので、必要な端子以外はDontCareにしてある io := DontCare // io.selの値に応じてio.in(N).aをio.out.aに接続しようしてエラー io.out.a := Mux(io.sel, io.in(io.idx_0).a, io.in(io.idx_1).a) } object Elaborate extends App { Driver.execute(Array( "-tn=VecError", "-td=rtl/VecError" ), () => new VecError ) }
上記のコードをエラボレートすると下記のようなエラーが発生する。
[error] (run-main-2) chisel3.internal.ChiselException: Connection between sink (chisel3.util.DecoupledIO@cf) and source (chisel3.util.DecoupledIO@22a) failed @.ready: Sink is unwriteable by current module. [error] chisel3.internal.ChiselException: Connection between sink (chisel3.util.DecoupledIO@cf) and source (chisel3.util.DecoupledIO@22a) failed @.ready: Sink is unwriteable by current module. [error] at chisel3.internal.throwException$.apply(Error.scala:13) [error] at chisel3.core.Data.connect(Data.scala:303) [error] at chisel3.core.Data.$colon$eq(Data.scala:365) [error] at VecError.<init>(QueueError.scala:25)
エラーの内容は接続しようとした.a
の中のready
がライト出来ないものだということのようだ。(@.readyの"@"が"io.out.a"を示しているみたい)
確かにDecoupled
でラップしたIOになっているので、その中のready
信号は入力になるためライト出来ないのは間違いない。
その状況下で:=
でアサインをしようとしたため、ready
のみ入力に接続を行うという操作になり、エラーになったようだ。
修正案1:<>
ならどうだろう
演算子:=
は”右辺の信号を左辺に接続する”であり、その際にIOについては接続方向のチェックも行われている。(Data.scalaのconnectメソッド)
なら演算子<>
ならどうだろう??これなら各Aggregate
型の派生クラス単位で要素のチェックを行い、極性が全て一致すれば接続できるはず(と思ってる)。
先ほどのコードの:=
を<>
に変更して再度エラボレート。
[error] (run-main-3) firrtl.passes.PassExceptions: [error] firrtl.passes.CheckTypes$NodePassiveType: @[QueueError.scala 27:18:@51.4]: [module VecError] Node must be a passive type. [error] firrtl.passes.CheckTypes$MuxPassiveTypes: @[QueueError.scala 27:18:@51.4]: [module VecError] Must mux between passive types. [error] firrtl.passes.PassException: 2 errors detected! [error] firrtl.passes.PassExceptions: [error] firrtl.passes.CheckTypes$NodePassiveType: @[QueueError.scala 27:18:@51.4]: [module VecError] Node must be a passive type. [error] firrtl.passes.CheckTypes$MuxPassiveTypes: @[QueueError.scala 27:18:@51.4]: [module VecError] Must mux between passive types. [error] firrtl.passes.PassException: 2 errors detected!
今度はfirrtlでエラーが発生する。
どうもFIRRTLのノードにはPassive
という属性があるようで、この使い方だと怒られた。
もう少し調査が必要だが、たぶん以下のような区分けな気がする。
- Active(で合ってるのか??) : Vec
- Passive : UIntとかBool
多分Vec(Wire(2.U))
のような形でエラボレート後に動的に変更されるタイプの信号をMux
に入れると怒られるのだと思う。
ということでこの推測のもとで更に修正してみる
修正案2:インデックスをMuxで選んで<>
で接続
先ほどの推測が合っているとするとMux
にVec
型の信号を入れなければ良いはずなので以下のように修正してみた。
// io.selの値に応じてio.in(N).aをio.out.aに接続しようしてエラー val idx = Mux(io.sel, io.idx_0, io.idx_1) io.out.a <> io.in(idx).a
単純にMux
でidx
を選択して、その信号を使って接続を行う形。
これでエラボレートすると以下のように正常にエラボレートが終了し所望の形で接続が出来た。
なお最初のエラー例ではio.in(N)
の選択に入力信号io.idx_0/1
を使用しているが、これはエラーには関係なくMux
の第2/3引数にVec
型を渡すのがエラーになる原因だった(なので、定数で指定してもエラー)。
[info] Running Elaborate [info] [0.001] Elaborating design... [info] [0.875] Done elaborating. Total FIRRTL Compile Time: 486.0 ms [success] Total time: 3 s, completed 2019/07/10 23:56:01
よくよく考えると最初の実装のようにMux
にVec型のio.in
を入れると、仮に出来たとしてもMux
がvalid
/ready
/bits
に対してそれぞれ作成されることになるのでめちゃくちゃ筋が悪いことに後で気づいた。
エラーにしてくれてよかった気がする。