今日はChiselのテストの時にTips的なやつを。
ChiselFlatSpecを使って作ったテストクラス内の各テストは通常、実装した順に逐次実行される。
この単一クラス内のテストを並列に実行する方法はないのかしら??と探してみたところ、めちゃくちゃお手軽にテストを並列実行できるのがわかったのでそれをご紹介。
ChiselFlatSpecを使ったテストを並列実行する方法
やり方はものすごく簡単なので先に書いてしまう。
ChiselFlatSpec
を使って作ったテスト用のクラスにParallelTestExecution
をミックスインする
ただ、これだけ。
これだけでScalaTestがいい感じに並列実行してくれます。
なおこれをつかって並列化すると以前に紹介したBeforeAndAfterAllConfigMap
を使ったプログラム引数の処理が無視される。
だが、これはリグレッションテストのみを実行するメイン関数でも作っておけばそれで良い気もしている。
気が向けばもう少し中身を見てみようかなーくらい。
例を使って確認してみる
これだけじゃあんまりなので、一応適当なモジュールを作って確認していく。
サンプルはこちら
import chisel3._ /** * 入力信号を所定のサイクル遅らせて出力 * @param delay 何サイクル出力を遅らせるか(delay > 0) */ class DelayBuf(delay: Int) extends Module { val io = IO(new Bundle{ val in = Input(UInt(32.W)) val out = Output(UInt(32.W)) }) val delayBuf = Seq(WireInit(io.in)) ++ Seq.fill(delay)(RegInit(0.U(32.W))) delayBuf.zip(delayBuf.tail).foreach { case (curr, next) => next := curr } io.out := delayBuf(delay) }
動き的にはコメントに書いたものが全てで、入力信号in
をdelay
に指定したサイクル数遅延させるものだ。
delay == 0
の場合 ただのパススルー。
delay == 1
の場合 1サイクル遅延する
delay == 9
の場合 9サイクル遅延する
テスト用クラス
この遅延モジュールに対して、以下のようなテストクラスを作成する。
it should ...
をfor
式でループすることによってループ部分の指定個数分のテストが作成されることになる。
この一回のテストの中で適当な回数io.in
の値を変化させて、出力を比較する作りにした。
import chisel3.iotesters.{Driver, PeekPokeTester, ChiselFlatSpec} import org.scalatest.ParallelTestExecution // これで並列化出来る /** * DelayBufのテストのスーパークラス */ abstract class DelayedBufTester extends ChiselFlatSpec { behavior of "DelayBuf" for (n <- 0 until 600) { it should s"${n}サイクル信号が遅れて出力される" in { Driver.execute(Array[String]( "--generate-vcd-output=on", s"--target-dir=test_run_dir/delay-${n}" // 出力先を遅延サイクル数でユニークに。 ), () => new DelayBuf(n)) { c => new PeekPokeTester(c) { reset() step(1) poke(c.io.in, n) step(n) var prev = n for (setVal <- 0 until 500) { poke(c.io.in, setVal + n) for (_ <- 0 until n) { expect(c.io.out, prev) step(1) } expect(c.io.out, setVal + n) prev = setVal + n step(1) } } } should be (true) } } }
Driver.execute
の第一引数を見てもらえればわかる通り、--target-dir
オプションを指定して、出力先をテスト毎に変更するようにした。なおこれを行わなくても、テスト自体は並列に実行され結果も問題なかった。(どうやってるんだろ??)
今回は並列に実行した時の比較を行うために、上記のDelayedBufTester
を継承した以下の2つのクラスを用意して、個々にテストを実行する。
/** * シーケンシャルにテストするモジュール */ class SequentialTester extends DelayedBufTester /** * パラレルにテストするモジュール */ class ParallelTester extends DelayedBufTester with ParallelTestExecution
テストは以下で実行可能。
- SequentialTesterのみの実行
sbt "testOnly SequentialTester"
- ParallelTesterのみの実行
sbt "testOnly ParallelTester"
実行結果は以下のような感じにPASSすることが確認できた。以下の結果は遅延サイクル数を0-1サイクルに限定して実施した結果。
[info] Done compiling. [info] [0.002] Elaborating design... [info] [0.125] Done elaborating. Total FIRRTL Compile Time: 285.5 ms Total FIRRTL Compile Time: 81.4 ms file loaded in 0.133196499 seconds, 4 symbols, 1 statements [info] [0.001] SEED 1555506337253 test DelayBuf Success: 500 tests passed in 507 cycles in 0.074487 seconds 6806.58 Hz [info] [0.056] RAN 501 CYCLES PASSED [info] [0.000] Elaborating design... [info] [0.051] Done elaborating. Total FIRRTL Compile Time: 15.6 ms Total FIRRTL Compile Time: 17.8 ms file loaded in 0.030885614 seconds, 6 symbols, 2 statements [info] [0.000] SEED 1555506338158 test DelayBuf Success: 1000 tests passed in 1008 cycles in 0.032762 seconds 30767.51 Hz [info] [0.033] RAN 1002 CYCLES PASSED [info] SequentialTester: [info] DelayBuf [info] - should 0サイクル信号が遅れて出力される [info] - should 1サイクル信号が遅れて出力される [info] ScalaTest [info] Run completed in 1 second, 244 milliseconds. [info] Total number of tests run: 2 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [info] Passed: Total 2, Failed 0, Errors 0, Passed 2 [success] Total time: 3 s, completed 2019/04/17 22:05:38
並列化の結果
CPUの使用率
では並列に実行した時に、どうなるかを確認してみよう。
まずはCPUの使用率から。
#因みに実施したPCのCPUは"AMD Ryzen 7 1700"。
- 並列化なしの場合
- 並列化有りの場合
実行時間
こちらは結果をわかりやすくするために、遅延サイクル数を0-599にした場合の実行時間となっている。
- 並列化なし
[info] ScalaTest [info] Run completed in 19 minutes, 5 seconds. [info] Total number of tests run: 600 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 600, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [info] Passed: Total 600, Failed 0, Errors 0, Passed 600 [success] Total time: 1151 s, completed 2019/04/16 23:26:50
- 並列化あり
[info] ScalaTest [info] Run completed in 1 minute, 48 seconds. [info] Total number of tests run: 600 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 600, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [info] Passed: Total 600, Failed 0, Errors 0, Passed 600 [success] Total time: 109 s, completed 2019/04/16 23:29:28
まとめると以下のようになった。
バックエンドをverilatorにした場合、各テストごとにビルドが実行されるためそこまで差が出なかった。
バックエンド | 並列化なし | 並列化あり | 実行速度比 |
---|---|---|---|
treadle | 1151秒 | 109秒 | 10.6倍 |
verilator | 1707秒 | 812秒 | 2.1倍 |
何がいいって冒頭に書いたとおりParallelTestExecution
をミックスインするだけでいいってとこですよね。これだけで勝手にクラス内のテストまで並列実行してくれるだなんて。。。
因みに複数個のテストクラスがある場合は、何もしなくてもそのテストクラス毎に並列化してくれるみたい。
こういうしっかりした仕組みがあるテストハーネスを使ってハードウェアのテスト実行環境を構築できるのはChselのいいところ。
ということで今日はChiselのテストを並列化して高速化する方法についてでした。