Home > Enterprise >  Where does dut in Chisel Test get defined? (About Scala syntax)
Where does dut in Chisel Test get defined? (About Scala syntax)

Time:10-25

I am trying to come up with a better title.
I am new in Chisel and Scala. Below there is a Chisel code defining and testing an module.

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class DeviceUnderTest extends Module {
  val io = IO(new Bundle {
    val a = Input(UInt(2.W))
    val b = Input(UInt(2.W))
    val out = Output(UInt(2.W))
  })
  io.out := io.a & io.b
}

class WaveformTestWithIteration extends AnyFlatSpec with ChiselScalatestTester {
  "WaveformIteration" should "pass" in {
    test(new DeviceUnderTest)
      .withAnnotations(Seq(WriteVcdAnnotation)) ( dut => // ???
        {
          for (a <- 0 until 4; b <- 0 until 4) {
            dut.io.a.poke(a.U)
            dut.io.b.poke(b.U)
            dut.clock.step()
          }
        }
      )
  }
}

The code line with comments ??? is where I am quite puzzled. Where does the variable dut is defined? It seems an reference to instance gained by new DeviceUnderTest.

test(new DeviceUnderTest).withAnnotations(Seq(WriteVcdAnnotation)) return an TestBuilder[T] with apply method:

  class TestBuilder[T <: Module](...) {
    ...
    def apply(testFn: T => Unit): TestResult = {
      runTest(defaults.createDefaultTester(dutGen, finalAnnos))(testFn)
    }
    ...
  }

So, dut => {...} is a function (T) => Unit? But it does not look like a standard lambda ((x:T) => {...})? Or it is something else?
What is this syntax in scala exactly?

CodePudding user response:

Consider the following boiled-down version with a similar structure:

def test[A](testedThing: A)(testBody: A => Unit): Unit = testBody(testedThing)

What it's essentially doing is taking a value x: A and a function f: A => Unit, and applying f to x to obtain f(x).

Here is how you could use it:

test("foo"){ x => 
  println(if x == "foo" then "Success" else "Failure")
} // Success

test("bar"){ x => 
  println(if x == "baz" then "Success" else "Failure")
} // Failure

In both cases, the "string under test" is simply passed to the body of the "test".

Now, you could introduce a few more steps between the creation of the value under test and the specification of the body. For example, you could create a TestBuilder[A], which is essentially just a value a with some bells and whistles (in this case, list of "annotations" - plain strings in the following example):

type Annotation = String

case class TestOutcome(annotations: List[Annotation], successful: Boolean)

trait Test:
  def run: TestOutcome

// This simply captures the value under test of type `A`
case class TestBuilder[A](
  theThingUnderTest: A,
  annotations: List[Annotation]
):
  // Bells and whistles: adding some metadata
  def withAnnotations(moreAnnotations: List[Annotation]): TestBuilder[A] =
    TestBuilder(theThingUnderTest, annotations    moreAnnotations)

  // Combining the value under test with the body of the test produces the
  // actual test
  def apply(testBody: A => Unit): Test = new Test:
    def run =
      try {
        testBody(theThingUnderTest)
        TestOutcome(annotations, true)
      } catch {
        case t: Throwable => TestOutcome(annotations, false)
      }

// This constructs the thing that's being tested, and creates a TestBuilder around it
def test[A](thingUnderTest: A) = TestBuilder(thingUnderTest, Nil)

println(
  test("hello")
    .withAnnotations(List("size of hello should be 5")){ h =>
      assert(h.size == 5)
    }
    .run
)

println(
  test("hello")
    .withAnnotations(List("size of hello should be 42")){ h =>
      assert(h.size == 42)
    }
    .run
)

The principle remains the same: test(a) saves the tested value a, then TestBuilder adds some configuration, and once you add a body { thingUnderTest => /* assertStuff */ } to it, you get a full Test, which you can then run to obtain some results (TestOutcomes, in this case). Thus, the above snippet produces

TestOutcome(List(size of hello should be 5),true)
TestOutcome(List(size of hello should be 42),false)

CodePudding user response:

I think I did not notice that sometimes we can omit type declaration in lambda.

class tester{
  def  apply( fn: (Int) => Int):Int = fn(5)
}

We can write (new tester)(x => {x 1}) instead of (new tester)((x:Int) => {x 1}).

  • Related