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

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

Chiselのシミュレーションを所定のサイクルで終了する方法

スポンサーリンク

ある意味前々回の続きのネタ。前々回の記事ではシミュレーション実行時にログにサイクル数を出す方法を探したが、今回は波形上で確認するための細工を行ってみる。

”前々回の記事”は以下。

Chiselの波形上に経過サイクルを表示

となんか大層な感じに書いたが、やること自体は大したこと無くてテスト対象のモジュールの上位にもうひとつ階層を追加して、そこにタイマーを仕込むだけ。しかもこれはRocket Chipに入っているモジュールのテスト環境を追っていて、同じような(でもRocket ChipのはLazyModule使ったもっと高度な)方法でタイムアウトさせているのを見たから。

イメージとしては以下のようなもの。

f:id:diningyo-kpuku-jougeki:20190801232549p:plain
WDTを仕込んだシミュレーション環境

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)を接続するような仕組みにした。

ブロック図としては以下のような形で青い四角がスーパークラス、緑の四角がサブクラスで実際にテスト対象のモジュールをインスタンスするクラスとなっている。

f:id:diningyo-kpuku-jougeki:20190801233154p:plain
ちょっとだけ修正したテスト環境

テスト対象のモジュールは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のテスト環境にタイムアウトを仕込む話でした。