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

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

Mux内でVecの要素を選択しようとして遭遇したエラーについて

スポンサーリンク

今日はとあるモジュールの実装中にVecの取り扱いで遭遇したエラーとその解決方法について簡単にまとめておく。

Vecの各要素との接続で遭遇したエラー

ざっくりと以下のようなコードを作成してエラボレートしたところエラーが発生した。
詳しい処理の説明は省くが、回路の実装上io.selの値に応じて入力のVecのN番目を選択する信号(下記実装ではio.idx_0io.idx_1がそれに相当)を切り替えて選択されたio(N).aio.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で選んで<>で接続

先ほどの推測が合っているとするとMuxVec型の信号を入れなければ良いはずなので以下のように修正してみた。

  // 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

単純にMuxidxを選択して、その信号を使って接続を行う形。

これでエラボレートすると以下のように正常にエラボレートが終了し所望の形で接続が出来た。
なお最初のエラー例では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を入れると、仮に出来たとしてもMuxvalid/ready/bitsに対してそれぞれ作成されることになるのでめちゃくちゃ筋が悪いことに後で気づいた。
エラーにしてくれてよかった気がする。