FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

fork/joinが可能なchiselテスト環境chisel-testers2を試す

Chisel-Testers2のアナウンスが流れていた。時間が経っていたが試してみようと思う。

ChiselにはすでにIOTestersというテスト環境があるが、ぶっちゃけこれはあまり使いやすくない。 chisel-testers2は以下の改良(というか特徴)が加わっており、使いやすくなるように改善が行われている。

www.chisel-lang.org

紹介分から抜粋する。

Overview

Testers2 is a test harness for Chisel-based RTL designs, currently supporting directed testing (all test stimulus manually specified - no constrained random and > coverage-driven flows). Testers2 emphasizes tests that are lightweight (minimizes boilerplate code), easy to read and write (understandability), and compose (for better test code reuse).

The core primitives are similar to nonsynthesizable Verilog: input pin assignment (poke), pin value assertion (expect), and time advance (step). Threading concurrency is also supported with the use of fork and join, and concurrent accesses to wires are checked to prevent race conditions.

Migrating from chisel-testers

The core abstractions (poke, expect, step) are similar to chisel-testers, but the syntax is inverted: instead of doing tester.poke(wire, value) with a Scala number value, in testers2 you would write write.poke(value) with a Chisel literal value. Furthermore, as no reference to the tester context is needed, test helper functions can be defined outside a test class and written as libraries.

Currently, this should support all the functionality that was in chisel-testers, and provides additional features. This project is meant to supersede chisel-testers, and eventually may become a default part of chisel3.

Test cases written in chisel-testers cannot be directly used in testers2, as the syntax is significantly different.

Testers2の特徴としては、基本的なテストの方法はIOTestersと変わらずpoke(ピンの値割り当て)、expect(ピンの値の比較)、step(時間を進める)であるが、スレッドを導入することにより並列なテスト記述ができるようになる。つまり、forkjoinが使えるようになり、テストがより記述しやすくなるのである。

さらにテストの記述方法としては、これまでのIOTestersではtester.poke(wire, value)と書いていたものを、wire.poke(value)と記述することができるようになり、信号線に対して直接pokeを送ることができるようになる。これは便利かも。

という訳で試してみる。まずはtesters2のリポジトリ全体をダウンロードしてリグレッションテストから。

git clone https://github.com/ucb-bar/chisel-testers2.git
cd chisel-testers2.git
sbt test

なんとなくPassしたぞ。下記のキャプチャ画像は最後のVerilatorテストのみ。

f:id:msyksphinz:20191207115007p:plain
chisel-testers2のsbt test実行結果。

次に、自分でデザインを書いてみる。シフトレジスタを設計した。

f:id:msyksphinz:20191207142114p:plain
4レベルシフトレジスタの設計とforkを使ったテスト
  • src/main/scala/iotesters2-simple/iotesters2-simple.scala
package iotesters2_simple

import chisel3._
import chisel3.util._

class ShiftRegister(length: Int) extends Module {
  val io = IO(new Bundle {
    val in  = Input(UInt(32.W))
    val out = Output(UInt(32.W))
  })

  require(length >= 1)

  val shift_reg = Reg(Vec(length, UInt(32.W)))
  shift_reg(0) := io.in
  for(i <- 1 until length) {
    shift_reg(i) := shift_reg(i-1)
  }
  io.out := shift_reg(length-1)
}

次に、テストを書いた。と言っても、サンプルプログラムをほぼコピーしたのだが...

https://github.com/ucb-bar/chisel-testers2/blob/master/src/test/scala/chisel3/tests/ShiftRegisterTest.scalagithub.com

  • src/test/scala/iotesters2-simple/iotesters2-simple.scala
package iotesters2_simple

import org.scalatest._

import chisel3._
import chisel3.tester._

class ShiftRegisterTest extends FlatSpec with ChiselScalatestTester with Matchers {
  behavior of "Testers2"

  it should "test shift registers with abstractions" in {
    // TODO: this actually relies on total thread ordering

    def shiftTest(in: UInt, out: UInt, clk: Clock, value: UInt) {
      timescope {
        in.poke(value)
        clk.step(1)
      }
      clk.step(3)
      out.expect(value)
    }

    test(new ShiftRegister(4)) { c =>
      fork { shiftTest(c.io.in, c.io.out, c.clock, 42.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 43.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 44.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 45.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 46.U) }
      c.clock.step(1)
      fork { shiftTest(c.io.in, c.io.out, c.clock, 47.U) }.join
    }
  }
}

この時に注意すべきことなのだが、build.sbtに記述するchisel3のバージョンとchisel3-testers2のバージョンを調整する必要がある。

  • build.sbt
...
val defaultVersions = Map(
  // "chisel3" -> "3.2.+",
  "chisel3" -> "3.3-SNAPSHOT",
  "chisel-iotesters" -> "1.3.+",
  "chisel-testers2" -> "0.2-SNAPSHOT",
  "dsptools" -> "1.1.+"
)

libraryDependencies ++= (Seq("chisel3", "chisel-iotesters", "dsptools", "chisel-testers2").map {
  dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) })
...

テストを実行する。

sbt 'testOnly iotesters2_simple.ShiftRegisterTest'
...
Total FIRRTL Compile Time: 516.1 ms
file loaded in 0.0921011 seconds, 13 symbols, 10 statements
test ShiftRegister Success: 0 tests passed in 11 cycles in 0.078559 seconds 140.02 Hz
[info] ShiftRegisterTest:
[info] Testers2
[info] - should test shift registers with abstractions
[info] ScalaTest
[info] Run completed in 2 seconds, 224 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success] Total time: 6 s, completed 2019/12/07 13:11:58
f:id:msyksphinz:20191207141258p:plain
chisel-testers2を使用してテストを記述した結果

テスト結果は問題ない様だ。forkによるテストも問題なく実行できた。