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

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

Chiselのシミュレーション時のexpectメソッドの細かい話

スポンサーリンク

今日はChiselで書いたモジュールをテストする際に使用する期待値比較メソッドexpectについての細かい仕様の話。

expectで期待値比較

まずはおさらい。普通に使うと以下のような使い方になる。

iotesters.Driver.execute(Array(""), () => new Module {
  val io = IO(new Bundle {
    val in = Input(UInt(8.W))
    val out = Output(UInt(8.W))
  })

  io.out := io.in
}) {
  c => new PeekPokeTester(c) {
    poke(c.io.in, 0x0)
    expect(c.io.out, 0x1, "c.io.out must be equal to io.in")
  }
}

上記のコードを動かすとio.inの値と違う値を期待値にセットしているので、当然期待値不一致でエラーになる、

[info] [0.000] Elaborating design...
[info] [0.010] Done elaborating.
Total FIRRTL Compile Time: 9.0 ms
Total FIRRTL Compile Time: 16.3 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1564408571523
[info] [0.001] EXPECT AT 0 c.io.out must be equal to io.in  io_out got 212 expected 1 FAIL
test cmd6Helperanonfun1anon2 Success: 0 tests passed in 5 cycles taking 0.002181 seconds
[info] [0.002] RAN 0 CYCLES FAILED FIRST AT CYCLE 0

気づいてなかった細かい仕様

今回の話はexpectメソッドを使っていて期待値比較がFAILした際に、少し違和感を覚えたのがきっかけだった。

再現コードと動作ログ

その時のコードを再現したのが以下のコード。

iotesters.Driver.execute(Array(""), () => new Module {
  val io = IO(new Bundle {
    val in = Input(UInt(8.W))
    val out = Output(UInt(8.W))
  })

  io.out := io.in
}) {
  c => new PeekPokeTester(c) {
    poke(c.io.in, 0x0)
    val out = peek(c.io.out)
    // あえてFAILさせる
    expect(out.U, 0x1, "c.io.out must be equal to io.in")
  }
}

先ほどとの違いはexpectメソッドの第1引数に設定する値をpeekメソッドで取得したio.outの値にしたことにある。

なんでこんな変なことを??と思われると思うのだが、その時は期待値比較付きのreadメソッドを作っていてメソッドの戻り値としてBigIntのリードデータを返したかった(以下のようなイメージ)という事情があった。

  def read(addr: BigInt, exp: BigInt): BigInt = {
    val rddata = peek(io.rddata) // 戻り値はBigInt

    // BigIntなのでUIntに変換して第1引数にセット
    expect(rddata.U, exp, s"rddata must be equal to $exp")
    rddata
  }

話を戻して、上記の再現コードを動かすと以下のようになる。

[info] [0.000] Elaborating design...
[info] [0.006] Done elaborating.
Total FIRRTL Compile Time: 8.1 ms
Total FIRRTL Compile Time: 4.9 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1564408848079
[info] [0.006] EXPECT AT 0 89 == 1 FAIL
test cmd8Helperanonfun1anon2 Success: 0 tests passed in
5 cycles taking 0.008568 seconds
[info] [0.007] RAN 0 CYCLES FAILED FIRST AT CYCLE 0

お気づきだろうか?

そう、FAILしたにも関わらず、メッセージが表示されていないのだ。

[info] [0.006] EXPECT AT 0 89 == 1 FAIL

これに出くわした際にはエラーメッセージを元に探していたので、見つからず焦った。。

因みに"--is-verbose"のオプションを設定してもメッセージが出力されない件は変わらない。

[info] [0.000] Elaborating design...
[info] [0.009] Done elaborating.
Total FIRRTL Compile Time: 4.9 ms
Total FIRRTL Compile Time: 4.0 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1564409172499
[info] [0.001]   POKE io_in <- 0
[info] [0.001]   PEEK io_out -> 0
[info] [0.002] EXPECT AT 0 0 == 1 FAIL <-- メッセージが出ない
test cmd10Helperanonfun1anon2 Success: 0 tests passed in 5 cycles taking 0.002302 seconds
[info] [0.002] RAN 0 CYCLES FAILED FIRST AT CYCLE 0

expectの実装を確認してみる

ということでexpectの実装を確認してみた。上記のexpectの呼び出しを行った際に呼ばれるのが以下の実装部分になる。

  • iotesters/PeekPokeTester.scala
  def expect(signal: Bits, expected: BigInt, msg: => String = ""): Boolean = {
    if (!signal.isLit) {
      val good = backend.expect(signal, expected, msg)
      if (!good) fail
      good
    } else expect(signal.litValue() == expected,
     s"${signal.litValue()} == $expected")
  }

一旦Scalavalで受けたものを.Uで変換すると上記のexpectメソッドのelse項に入るようで、その場合にはmsgが引数に指定した物にならずs"${signal.litValue()} == $expected"になってしまう。

なのでこのような場合は、以下のexpectを使用するように書き換えてやるのが早そうだった。

  def expect (good: Boolean, msg: => String): Boolean = {
    if (_verbose || ! good) println(s"""EXPECT AT $simTime $msg ${if (good) "PASS" else "FAIL"}""")
    if (!good) fail
    good
  }

先ほどのコードを修正すると以下のようになる。

iotesters.Driver.execute(Array("--is-verbose"), () => new Module {
  val io = IO(new Bundle {
    val in = Input(UInt(8.W))
    val out = Output(UInt(8.W))
  })

  io.out := io.in
}) {
  c => new PeekPokeTester(c) {
    poke(c.io.in, 0x0)
    val out = peek(c.io.out)
    expect(out == 0x1, "c.io.out must be equal to io.in")
  }
}

ということで、細かいけどexpectの使い分けについて、でした。