ある意味前々回の続きのネタ。前々回の記事ではシミュレーション実行時にログにサイクル数を出す方法を探したが、今回は波形上で確認するための細工を行ってみる。
”前々回の記事”は以下。
Chiselの波形上に経過サイクルを表示
となんか大層な感じに書いたが、やること自体は大したこと無くてテスト対象のモジュールの上位にもうひとつ階層を追加して、そこにタイマーを仕込むだけ。しかもこれはRocket Chipに入っているモジュールのテスト環境を追っていて、同じような(でもRocket ChipのはLazyModule使ったもっと高度な)方法でタイムアウトさせているのを見たから。
イメージとしては以下のようなもの。
WDT(Watchdog Timer)
仕込むタイマーは以下のようなものにした。
/** * Watchdog Timer * @param limit シミュレーションのMAXサイクル数 * @param abortEn timeout時にassertでシミュレーションを終了するかどうか */ class WDT(limit: Int, abortEn: Boolean) extends Module { val io = IO(new Bundle { val timeout = Output(Bool()) }) val timer = RegInit(0.U(log2Ceil(limit).W)) when (timer =/= limit.U) { timer := timer + 1.U } val timeout = timer === limit.U if (abortEn) { assert(!timeout, "WDT has expired!!") } else { printf("WDT has expired!!") } io.timeout := timeout }
クラスパラメータabortEn
に応じてassert
で強制終了するか、メッセージを出すだけにするかを選択出来るようにしてある。
ChiselFlatSpec
を使ってテストを作っていく場合はテスト1つ毎に環境が出来るのでlimit
でパターンに応じたタイムアウトサイクルを設定する方が良いかな、ということでこの設定値もクラスパラメータとしてもらうようになっている。
WDTが勝手にインスタンスされる環境にする
コレだけだとあんまり面白くも無いのでWDTがインスタンスされるベースのテストベンチトップを作っておき、継承して作ったサブクラス側でテスト対象のモジュール(DUT)を接続するような仕組みにした。
ブロック図としては以下のような形で青い四角がスーパークラス、緑の四角がサブクラスで実際にテスト対象のモジュールをインスタンスするクラスとなっている。
テスト対象のモジュールはBaseSimDTM
を継承して作ったSimDTM
クラスでBaseSimDTM
でインスタンスしたwdt
の端子を接続するためにBaseSimDTMIO
をインスタンス時にMix-inしている。一つ微妙なのがSimDTM
側でconnect
を明示的に呼び出さないとfinish
/timeout
が接続されないところ。。
/** * BaseSimDTM用の信号 */ trait BaseSimDTMIO extends Bundle { val timeout = Output(Bool()) val finish = Output(Bool()) } /** * シミュレーションのトップモジュール * @param limit シミュレーションのMAXサイクル数 * @param abortEn timeout時にassertでシミュレーションを終了するかどうか */ abstract class BaseSimDTM(limit: Int, abortEn: Boolean = true) extends Module { // スーパークラス側では宣言だけ val io: BaseSimDTMIO val wdt = Module(new WDT(limit, abortEn)) def connect(finish: Bool): Unit = { io.finish := finish io.timeout := wdt.io.timeout } } /** * テスト用のシミュレーショントップモジュール * @param limit シミュレーションのMAXサイクル数 * @param abortEn timeout時にassertでシミュレーションを終了するかどうか */ class SimDTM(limit: Int, abortEn: Boolean = true) extends BaseSimDTM(limit, abortEn) { // BaseSimDTMIOをMix-in val io = IO(new Bundle with BaseSimDTMIO) // これ呼ばないとio.finish/io.timeoutが接続されない connect(false.B) } /** * SimDTM用テストクラス */ class SimDTMTester extends ChiselFlatSpec { "SimDTM" should "limitの設定したサイクルが経過するとassertで終了する" in { iotesters.Driver.execute( Array("-tgvo=on"), () => new SimDTM(20, true)) { c => new PeekPokeTester(c) { for (_ <- 0 until 25) { println(f"step = $t") step(1) } } } } }
上記のSimDTMTester
を実行してみるといかように途中でassert
に引っかかって落ちることが確認できた。
file loaded in 0.171862814 seconds, 21 symbols, 14 statements [info] [0.002] SEED 1564668944705 [info] [0.002] step = 0 [info] [0.003] step = 1 [info] [0.003] step = 2 [info] [0.004] step = 3 [info] [0.004] step = 4 [info] [0.004] step = 5 [info] [0.005] step = 6 [info] [0.005] step = 7 [info] [0.005] step = 8 [info] [0.005] step = 9 [info] [0.006] step = 10 [info] [0.006] step = 11 [info] [0.006] step = 12 [info] [0.007] step = 13 [info] [0.007] step = 14 [info] [0.008] step = 15 [info] [0.009] step = 16 [info] [0.009] step = 17 [info] [0.010] step = 18 [info] [0.010] step = 19 [info] [0.011] step = 20 Assertion failed: WDT has expired!! at BaseSimDTM.scala:25 assert(!timeout, "WDT has expired!!") treadle.executable.StopException: Failure Stop: result 1 at treadle.executable.StopException$.apply(TreadleException.scala:15)
この時の実行波形は以下のようにSimDTM
クラスのインスタンス時に指定した20サイクル目でタイムアウトしている。前々回くらいに紹介したシミュレーション時にサイクル数を取得する方法とペアで使えば波形使ったデバッグ効率が幾分か改善すると思う。
とりあえず自分で各モジュールのテスト環境を構築するときは今回作ったようなBaseSimDTM
を継承する形にして作業を進めているがPeekPokeTester
の仕組みでは作るのが難しいタイムアウト機構を作れるのは結構便利。
ということでChiselのテスト環境にタイムアウトを仕込む話でした。