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

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

MultiIOModuleを使ったデバッグ用ポートについての作成

スポンサーリンク

今回はデバッグ用にポートを作る場合の方法について、今の時点の知識で考えなおしてみた。

案1:traitにしといてMix-in(結果→失敗でした)

以下の様にMultiIOModuleを使うことでMix-in出来るかと思ったけど、無理だった。

// See LICENSE for license details.

import chisel3._
import chisel3.util._
import chisel3.experimental.MultiIOModule
import chisel3.iotesters.PeekPokeTester

class SomeData extends Bundle {
  val a = UInt(8.W)
  val b = UInt(16.W)
  val c = UInt(24.W)
}

class SomeDataIO extends DecoupledIO(new SomeData) {
  override def cloneType: this.type = new SomeDataIO().asInstanceOf[this.type]
}

trait DebugIO extends Bundle {
  val bits: Int
  val dbg = IO(new Bundle {
    val count = Output(UInt(bits.W))
  })
}

// 以下のようにMix-inでポートを足したかった。
class DebugWithMultiIOModule(debug: Boolean) extends MultiIOModule with DebugIO {

  val bits = log2Ceil(4)

  val io = IO(new Bundle {
    val in = Flipped(new SomeDataIO)
    val out = new SomeDataIO
  })
  val q = Queue(io.in, 4)

  io.out <> q
}

object Test extends App {

  val module = "DebugWithMultiIOModule"

  iotesters.Driver.execute(
    Array(
      s"-tn=$module",
      s"-td=test_run_dir/$module"
    ),
    () => new DebugWithMultiIOModule(true)) {
    c => new PeekPokeTester(c) {
      step(100)
    }
  }
}

コメントに書いたとおりでBundleを継承したクラス内で、デバッグ用のポートを宣言してその中でIOに包んだポートを作成してしまおうとした。 これが出来ればモジュール生成時にデバッグ用のパラメータを見てデバッグ論理をインスタンスできそうなんだけど。。。

既に記載した通りでこれはChiselの仕組み上、うまく行かなくて以下の様に2つのエラーが発生する。

[IJ]sbt:dirv> runMain Test
[info] Compiling 27 Scala sources to /home/diningyo/prj/risc-v/dirv/target/scala-2.11/classes ...
[error] /home/diningyo/prj/risc-v/dirv/src/main/scala/Test.scala:18:13: not found: value IO
[error]   val dbg = IO(new Bundle {
[error]             ^
[error] /home/diningyo/prj/risc-v/dirv/src/main/scala/Test.scala:23:73: illegal inheritance; superclass ImplicitModule
[error]  is not a subclass of the superclass Bundle
[error]  of the mixin trait DebugIO
[error] class DebugWithMultiIOModule(debug: Boolean) extends MultiIOModule with DebugIO {
[error]                                                                         ^
[error] two errors found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 2 s, completed 2019/09/15 21:06:17

ひとつは「Bundle内ではIOを使うことが出来ないということ」で、もうひとつは「Moduleの派生クラスにBundleを継承したクラスはミックスイン出来ない」ということだ。

もう少し粘ってみる

ふと思ったけどBundleを継承する必要無い気がした。ということで素のtraitにして再トライ。

trait DebugIO {
  val bits: Int
  val dbg = new Bundle {
    val count = Output(UInt(bits.W))
  }
}

以下がエラボレートの際のログ。

[IJ]sbt:debugWithMultiIOModule> runMain Test
[info] Compiling 1 Scala source to /home/diningyo/prj/100_Chisel/000_learning-chisel3/subprj/debug-with-MultiIOModule/target/scala-2.11/classes ...
[info] Done compiling.
[info] Packaging /home/diningyo/prj/100_Chisel/000_learning-chisel3/subprj/debug-with-MultiIOModule/target/scala-2.11/debugwithmultiiomodule_2.11-2.0.jar ...
[info] Done packaging.
[info] Running Test
[info] [0.000] Elaborating design...
[info] [1.421] Done elaborating.
Total FIRRTL Compile Time: 624.3 ms
Total FIRRTL Compile Time: 372.7 ms
file loaded in 0.550296164 seconds, 99 symbols, 70 statements
[info] [0.000] SEED 1568165285822
test DebugWithMultiIOModule Success: 0 tests passed in 105 cycles in 0.026754 seconds 3924.58 Hz
[info] [0.000] RAN 100 CYCLES PASSED
[success] Total time: 7 s, completed 2019/09/11 10:28:10

おっ、通った!!でもこれならMix-inする必要ないわ。 ということで、debugで囲ってデバッグ用のポートはベタ書きにする。

class DebugWithMultiIOModule(debug: Boolean) extends MultiIOModule {

  val bits = log2Ceil(4)

  val io = IO(new Bundle {
    val in = Flipped(new SomeDataIO)
    val out = new SomeDataIO
  })
  val q = Module(new Queue(chiselTypeOf(io.in.bits), 4))

  q.io.enq <> io.in
  io.out <> q.io.deq

  if (debug) {
    val dbg = IO(new Bundle {
        val count = Output(UInt(bits.W))
    })
    dbg.count := q.io.count
  }
}

ところが、上記にするとポートの名前が拾えなくてエラーになる。

[IJ]sbt:debugWithMultiIOModule> runMain Test
[info] Compiling 1 Scala source to /home/diningyo/prj/100_Chisel/000_learning-chisel3/subprj/debug-with-MultiIOModule/target/scala-2.11/classes ...
[info] Done compiling.
[info] Packaging /home/diningyo/prj/100_Chisel/000_learning-chisel3/subprj/debug-with-MultiIOModule/target/scala-2.11/debugwithmultiiomodule_2.11-2.0.jar ...
[info] Done packaging.
[info] Running Test
[info] [0.000] Elaborating design...
[info] [1.343] Done elaborating.
Total FIRRTL Compile Time: 591.5 ms
Total FIRRTL Compile Time: 237.9 ms
file loaded in 0.431778518 seconds, 107 symbols, 78 statements
[info] [0.000] SEED 1568166255516
test DebugWithMultiIOModule Success: 0 tests passed in 105 cycles in 0.039715 seconds 2643.82 Hz
[info] [0.015] RAN 100 CYCLES PASSED
[success] Total time: 6 s, completed 2019/09/11 10:44:19
[IJ]sbt:debugWithMultiIOModule> runMain Test
[info] Compiling 1 Scala source to /home/diningyo/prj/100_Chisel/000_learning-chisel3/subprj/debug-with-MultiIOModule/target/scala-2.11/classes ...
[info] Done compiling.
[info] Packaging /home/diningyo/prj/100_Chisel/000_learning-chisel3/subprj/debug-with-MultiIOModule/target/scala-2.11/debugwithmultiiomodule_2.11-2.0.jar ...
[info] Done packaging.
[info] Running Test
[info] [0.000] Elaborating design...
[error] (run-main-9) java.lang.IllegalArgumentException: requirement failed: Unable to name port DebugWithMultiIOModule$$anon$2@101 in DebugWithMultiIOModule@0
[error] java.lang.IllegalArgumentException: requirement failed: Unable to name port DebugWithMultiIOModule$$anon$2@101 in DebugWithMultiIOModule@0
[error]         at scala.Predef$.require(Predef.scala:224)
[error]         at chisel3.core.UserModule$$anonfun$generateComponent$2.apply(UserModule.scala:49)
[error]         at chisel3.core.UserModule$$anonfun$generateComponent$2.apply(UserModule.scala:48)
[error]         at scala.collection.mutable.ResizableArray$class.foreach(ResizableArray.scala:59)
[error]         at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:48)
[error]         at chisel3.core.UserModule.generateComponent(UserModule.scala:48)
[error]         at chisel3.core.Module$.do_apply(Module.scala:63)
[error]         at chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:93)
[error]         at chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:93)
[error]         at chisel3.internal.Builder$$anonfun$build$1.apply(Builder.scala:297)
[error]         at chisel3.internal.Builder$$anonfun$build$1.apply(Builder.scala:295)
[error]         at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
[error]         at chisel3.internal.Builder$.build(Builder.scala:295)
[error]         at chisel3.Driver$.elaborate(Driver.scala:93)
[error]         at chisel3.Driver$.execute(Driver.scala:140)
[error]         at chisel3.iotesters.setupTreadleBackend$.apply(TreadleBackend.scala:139)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply$mcZ$sp(Driver.scala:54)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply(Driver.scala:39)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1$$anonfun$apply$mcZ$sp$1.apply(Driver.scala:39)
[error]         at logger.Logger$$anonfun$makeScope$1.apply(Logger.scala:138)
[error]         at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
[error]         at logger.Logger$.makeScope(Logger.scala:136)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1.apply$mcZ$sp(Driver.scala:39)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1.apply(Driver.scala:39)
[error]         at chisel3.iotesters.Driver$$anonfun$execute$1.apply(Driver.scala:39)
[error]         at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
[error]         at chisel3.iotesters.Driver$.execute(Driver.scala:38)
[error]         at chisel3.iotesters.Driver$.execute(Driver.scala:100)
[error]         at Test$.delayedEndpoint$Test$1(DebugWithMultiIOModule.scala:50)
[error]         at Test$delayedInit$body.apply(DebugWithMultiIOModule.scala:41)
[error]         at scala.Function0$class.apply$mcV$sp(Function0.scala:34)
[error]         at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
[error]         at scala.App$$anonfun$main$1.apply(App.scala:76)
[error]         at scala.App$$anonfun$main$1.apply(App.scala:76)
[error]         at scala.collection.immutable.List.foreach(List.scala:392)
[error]         at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
[error]         at scala.App$class.main(App.scala:76)
[error]         at Test$.main(DebugWithMultiIOModule.scala:41)
[error]         at Test.main(DebugWithMultiIOModule.scala)
[error]         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error]         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error]         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error]         at java.lang.reflect.Method.invoke(Method.java:498)
[error] Nonzero exit code: 1
[error] (Compile / runMain) Nonzero exit code: 1
[error] Total time: 4 s, completed 2019/09/11 10:46:26

どうもif文でくくったため、名前が維持できずにエラーになっている様子。 @chiselName入れても結果は一緒。

[error] (run-main-a) java.lang.IllegalArgumentException: requirement failed: Unable to name port DebugWithMultiIOModule$$anon$2@101 in DebugWithMultiIOModule@0

案2:ポートのビット幅0にして最適化で消してもらう(こちらは成功)

デバッグポートは以下のようにビット幅0にしとくと接続の記述だけにすることが出来る。

class DebugIO(bits: Int) extends Bundle {
  val count = Output(UInt(bits.W))
}

class DebugWithMultiIOModule(debug: Boolean) extends MultiIOModule {

  val bits = log2Ceil(4)

  val io = IO(new Bundle {
    val in = Flipped(new SomeDataIO)
    val out = new SomeDataIO
  })
  val q = Module(new Queue(chiselTypeOf(io.in.bits), 4))

  q.io.enq <> io.in
  io.out <> q.io.deq

  // debug
  val dbgBits = if (debug) 32 else 0
  val dbg = IO(new DebugIO(dbgBits))

  dbg.count := q.io.count
}

上記のようにするとFIRRTLの最適化のタイミングで、ポートのビット幅が0のポートは削除されるのでif式を使った記述にしなくても良くなる。
以下は実際にdebugの信号を切り替えて試してみたFIRRTLの結果。なおiotesters.Drivertreadleを使ってシミュレーションを行うと"<モジュール名>.fir"と"<モジュール名>.lo.fir"の2つのファイルが作成されていて、"*.lo.fir"が最適化&展開済みのFIRRTL記述となっている。この"*.lo.fir"はほぼVerilog-HDLのRTLと一対一対応するレベルで、こちらからはデバッグ用のポートが消えているのが確認できた。

  • debug == true の場合のFIRRTL(最適化前)
  module DebugWithMultiIOModule :
    input clock : Clock
    input reset : UInt<1>
    output io : {flip in : {flip ready : UInt<1>, valid : UInt<1>, bits : {a : UInt<8>, b : UInt<16>, c : UInt<24>}}, out : {flip ready : UInt<1>, valid : UInt<1>, bits : {a : UInt<8>, b : UInt<16>, c : UInt<24>}}}
    output dbg : {count : UInt<0>}
  • debug == false の場合のFIRRTL(最適化前)
  module DebugWithMultiIOModule :
    input clock : Clock
    input reset : UInt<1>
    output io : {flip in : {flip ready : UInt<1>, valid : UInt<1>, bits : {a : UInt<8>, b : UInt<16>, c : UInt<24>}}, out : {flip ready : UInt<1>, valid : UInt<1>, bits : {a : UInt<8>, b : UInt<16>, c : UInt<24>}}}
    output dbg : {count : UInt<32>}
  • debug == true の場合のFIRRTL(最適化後)
  module DebugWithMultiIOModule : @[:@58.2]
    input clock : Clock @[:@59.4]
    input reset : UInt<1> @[:@60.4]
    output io_in_ready : UInt<1> @[:@61.4]
    input io_in_valid : UInt<1> @[:@61.4]
    input io_in_bits_a : UInt<8> @[:@61.4]
    input io_in_bits_b : UInt<16> @[:@61.4]
    input io_in_bits_c : UInt<24> @[:@61.4]
    input io_out_ready : UInt<1> @[:@61.4]
    output io_out_valid : UInt<1> @[:@61.4]
    output io_out_bits_a : UInt<8> @[:@61.4]
    output io_out_bits_b : UInt<16> @[:@61.4]
    output io_out_bits_c : UInt<24> @[:@61.4]
    output dbg_count : UInt<32> @[:@62.4]

    inst q of Queue @[DebugWithMultiIOModule.scala 32:17:@64.4]
  • debug == false の場合のFIRRTL(最適化後)
  module DebugWithMultiIOModule : @[:@58.2]
    input clock : Clock @[:@59.4]
    input reset : UInt<1> @[:@60.4]
    output io_in_ready : UInt<1> @[:@61.4]
    input io_in_valid : UInt<1> @[:@61.4]
    input io_in_bits_a : UInt<8> @[:@61.4]
    input io_in_bits_b : UInt<16> @[:@61.4]
    input io_in_bits_c : UInt<24> @[:@61.4]
    input io_out_ready : UInt<1> @[:@61.4]
    output io_out_valid : UInt<1> @[:@61.4]
    output io_out_bits_a : UInt<8> @[:@61.4]
    output io_out_bits_b : UInt<16> @[:@61.4]
    output io_out_bits_c : UInt<24> @[:@61.4]

    inst q of Queue @[DebugWithMultiIOModule.scala 32:17:@64.4]

ということで、すこし半端だけどデバッグ用のポートについてはOption型(ここではSomeのこと)を使うよりはビット幅0にしたほうが、余計なif式のブロックやgetを使ったアクセス等が消えるし、RTL的にも接頭辞がdbg_になるしでコード的にもすっきりしていいと思ったという話でした。