今日は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") }
一旦Scalaのval
で受けたものを.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
の使い分けについて、でした。