Skip to content

New Timed Assertions API #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 88 additions & 11 deletions src/main/scala/chiselverify/assertions/AssertTimed.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,21 @@ object AssertTimed {
* @author Victor Alexander Hansen, s194027@student.dtu.dk
* @author Niels Frederik Flemming Holm Frandsen, s194053@student.dtu.dk
*/
def apply[T <: Module](dut: T, cond: () => Boolean, message: String)
def apply[T <: Module](dut: T, cond: => Boolean, message: String)
(delayType: DelayType): TesterThreadList = delayType match {
//Basic assertion
case NoDelay => fork {
assert(cond(), message)
assert(cond, message)
}

//Checks for the argument condition to be true in the number of cycles passed
case Always(delay) =>
// Assertion for single thread clock cycle 0
assert(cond(), message)
assert(cond, message)
fork {
dut.clock.step(1)
(1 until delay) foreach (_ => {
assert(cond(), message)
assert(cond, message)
dut.clock.step(1)
})
}
Expand All @@ -112,29 +112,29 @@ object AssertTimed {
*/
case Eventually(delay) =>
//Sample condition at cycle 0
val initRes = cond()
val initRes = cond
fork {
assert((1 until delay).exists(_ => {
dut.clock.step()
cond()
cond
}) || initRes, message)
}

//Asserts the passed condition after stepping x clock cycles after the fork
case Exactly(delay) =>
fork {
dut.clock.step(delay)
assert(cond(), message)
assert(cond, message)
}

//Checks for the argument condition to not be true in the number of cycles passed
case Never(delay) =>
// Assertion for single thread clock cycle 0
assert(!cond(), message)
assert(!cond, message)
fork {
dut.clock.step(1)
(1 until delay) foreach (_ => {
assert(!cond(), message)
assert(!cond, message)
dut.clock.step(1)
})
}
Expand All @@ -147,7 +147,7 @@ object AssertTimed {
case EventuallyAlways(delay) =>
fork {
var i = 0
while (!cond()) {
while (!cond) {
if (i == delay) {
assert(cond = false, message)
}
Expand All @@ -156,11 +156,88 @@ object AssertTimed {
}

(0 until delay - i) foreach (_ => {
assert(cond(), message)
assert(cond, message)
dut.clock.step(1)
})
}

case _ => throw new IllegalArgumentException("Delay Type not implemented for assertions")
}

/* FANCY SYNTACTIC SUGAR BELOW */

abstract class DT
object Evt extends DT
object Alw extends DT
object Nvr extends DT
object Exct extends DT

/**
* Thread Wrapper
*/
case class TW(t: TesterThreadList) {
def cycles: Boolean = {
t.join()
true
}

}

implicit def TWtoThreadList(tw: TW): TesterThreadList = tw.t

def dtToDelayType(dt: DT, delay: Int): DelayType = dt match {
case Evt => Eventually(delay)
case Alw => Always(delay)
case Nvr => Never(delay)
case Exct => Exactly(delay)
}

implicit def opToOption(op:TimedOperator): Option[TimedOperator] = Some(op)

/**
* Intermediate wrapper for expect types
*/
case class IE(data: Data, expected: UInt)

//Implicit data wrapping
implicit def dataToDW(data: Data): DW = DW(data)
case class DW(data: Data) {
def expected(value: UInt): IE = IE(data, value)
}

case class eventually(d: Int = 100, msg: String = s"EVENTUALLY ASSERTION FAILED") {
def apply[T <: Module](op: TimedOperator)(implicit dut: T): Unit =
AssertTimed(dut, op, msg)(Eventually(d)).join()
def apply[T <: Module](cond: => Boolean)(implicit dut: T): Unit =
AssertTimed(dut, cond, msg)(Eventually(d)).join()
def apply[T <: Module](ie: IE)(implicit dut: T): Unit =
ExpectTimed(dut, ie.data, ie.expected, msg)(Eventually(d)).join()
}

case class always(d: Int = 100, msg: String = s"ALWAYS ASSERTION FAILED") {
def apply[T <: Module](op: TimedOperator)(implicit dut: T): Unit =
AssertTimed(dut, op, msg)(Always(d)).join()
def apply[T <: Module](cond: => Boolean)(implicit dut: T): Unit =
AssertTimed(dut, cond, msg)(Always(d)).join()
def apply[T <: Module](ie: IE)(implicit dut: T): Unit =
ExpectTimed(dut, ie.data, ie.expected, msg)(Always(d)).join()
}

case class never(d: Int = 100, msg: String = s"NEVER ASSERTION FAILED") {
def apply[T <: Module](op: TimedOperator)(implicit dut: T): Unit =
AssertTimed(dut, op, msg)(Never(d)).join()
def apply[T <: Module](cond: => Boolean)(implicit dut: T): Unit =
AssertTimed(dut, cond, msg)(Never(d)).join()
def apply[T <: Module](ie: IE)(implicit dut: T): Unit =
ExpectTimed(dut, ie.data, ie.expected, msg)(Never(d)).join()
}

case class exact(d: Int = 100, msg: String = s"EXACTLY ASSERTION FAILED") {
def apply[T <: Module](op: TimedOperator)(implicit dut: T): Unit =
AssertTimed(dut, op, msg)(Exactly(d)).join()
def apply[T <: Module](cond: => Boolean)(implicit dut: T): Unit =
AssertTimed(dut, cond, msg)(Exactly(d)).join()
def apply[T <: Module](ie: IE)(implicit dut: T): Unit =
ExpectTimed(dut, ie.data, ie.expected, msg)(Exactly(d)).join()
}
}
15 changes: 15 additions & 0 deletions src/main/scala/chiselverify/timing/TimedOp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,19 @@ object TimedOp {
case class GtEq(op1: Data, op2: Data) extends TimedOperator(op1, op2) {
override def apply(value1: BigInt, value2: BigInt): Boolean = value1 >= value2
}

/* MORE FANCY SYNTACTIC SUGAR BELOW */

/**
* Internal data wrapper
*/
case class ID(data: Data) {
def ?==(that: Data): Equals = Equals(data, that)
def ?<(that: Data): Lt = Lt(data, that)
def ?<=(that: Data): LtEq = LtEq(data, that)
def ?>(that: Data): Gt = Gt(data, that)
def ?>=(that: Data): GtEq = GtEq(data, that)
}

implicit def dataToID(data: Data): ID = ID(data)
}
69 changes: 65 additions & 4 deletions src/test/scala/verifyTests/assertions/TimedAssertionTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package verifyTests.assertions
import chisel3._
import chisel3.tester._
import chiseltest.ChiselScalatestTester
import chiselverify.assertions.AssertTimed._
import chiselverify.assertions.{AssertTimed, ExpectTimed}
import chiselverify.timing.TimedOp.{Equals, Gt, GtEq, Lt, LtEq}
import chiselverify.timing.TimedOp.{Equals, Gt, GtEq, Lt, LtEq, dataToID}
import chiselverify.timing._
import org.scalatest.{FlatSpec, Matchers}
import org.scalatest.{FlatSpec, Matchers, concurrent}
import verifyTests.ToyDUT.AssertionsToyDUT

import scala.language.postfixOps

class TimedAssertionTests extends FlatSpec with ChiselScalatestTester with Matchers {
def toUInt(i: Int): UInt = (BigInt(i) & 0x00ffffffffL).asUInt(32.W)

Expand All @@ -25,13 +28,13 @@ class TimedAssertionTests extends FlatSpec with ChiselScalatestTester with Match
dut.io.b.poke(10.U)
dut.clock.step(1)
println(s"aEqb is ${dut.io.aEqb.peek().litValue()}")
AssertTimed(dut, () => dut.io.aEqb.peek().litValue() == 1, "aEqb timing is wrong")(Always(9)).join()
AssertTimed(dut, dut.io.aEqb.peek().litValue() == 1, "aEqb timing is wrong")(Always(9)).join()
}

def testEventually(): Unit = {
dut.io.a.poke(10.U)
dut.io.b.poke(10.U)
AssertTimed(dut, () => dut.io.aEvEqC.peek().litValue() == 1, "a eventually isn't c")(Eventually(11)).join()
AssertTimed(dut, dut.io.aEvEqC.peek().litValue() == 1, "a eventually isn't c")(Eventually(11)).join()
}

def testExactly(): Unit = {
Expand Down Expand Up @@ -270,6 +273,50 @@ class TimedAssertionTests extends FlatSpec with ChiselScalatestTester with Match
}
}

def testGenericSugarOp[T <: AssertionsToyDUT](dut: T, et : EventType): Unit = {
implicit val _dut: T = dut
/**
* Basic test to see if we get the right amount of hits
*/
def testAlways(): Unit = {
dut.io.a.poke(10.U)
dut.io.b.poke(10.U)
dut.clock.step(1)
println(s"aEqb is ${dut.io.aEqb.peek().litValue()}")
always(9) { dut.io.aEqb ?== dut.io.isOne }

}

def testEventually(): Unit = {
dut.io.a.poke(4.U)
dut.io.b.poke(2.U)
dut.clock.step()
eventually(4) { dut.io.outB ?> dut.io.outCNotSupB }
}

def testExactly(): Unit = {
dut.io.a.poke(6.U)
dut.io.b.poke(5.U)
dut.clock.step(2)
println(s"C = ${dut.io.outC.peek().litValue()}")
exact(7) { dut.io.outB ?< dut.io.outCSupB }
}

def testNever(): Unit = {
dut.io.a.poke(10.U)
dut.io.b.poke(0.U)
dut.clock.step(1)
never(10) { dut.io.outB ?>= dut.io.outC }
}

et match {
case Always => testAlways()
case Eventually => testEventually()
case Exactly => testExactly()
case Never => testNever()
}
}

"Timed Assertions Always" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGeneric(dut, Always) }
}
Expand Down Expand Up @@ -347,4 +394,18 @@ class TimedAssertionTests extends FlatSpec with ChiselScalatestTester with Match
"Timed Assertions Never with GreaterThan or Equal to Op" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGenericGtEqOp(dut, Never) }
}

"Timed Assertions Always with sugar" should "pass" in {
test(new AssertionsToyDUT(32)){dut => testGenericSugarOp(dut, Always)}
}
"Timed Assertions Eventually with sugar" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGenericSugarOp(dut, Eventually) }
}
"Timed Assertions Exactly with sugar" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGenericSugarOp(dut, Exactly) }
}
"Timed Assertions Never with sugar" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGenericSugarOp(dut, Never) }
}

}
55 changes: 55 additions & 0 deletions src/test/scala/verifyTests/assertions/TimedExpectTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package verifyTests.assertions
import chisel3._
import chisel3.tester.{testableClock, testableData}
import chiseltest.ChiselScalatestTester
import chiselverify.assertions.AssertTimed.{always, dataToDW, eventually, exact, never}
import chiselverify.assertions._
import chiselverify.timing._
import org.scalatest.{FlatSpec, Matchers}
Expand Down Expand Up @@ -54,6 +55,47 @@ class TimedExpectTests extends FlatSpec with ChiselScalatestTester with Matcher
}
}

def testGenericSugar[T <: AssertionsToyDUT](dut: T, et : EventType): Unit = {
implicit val _dut: T = dut

/**
* Basic test to see if we get the right amount of hits
*/
def testAlways(): Unit = {
dut.io.a.poke(10.U)
dut.io.b.poke(10.U)
dut.clock.step(1)
always(9, "aEqb expected timing is wrong") { dut.io.aEqb expected 1.U }
}

def testEventually(): Unit = {
dut.io.a.poke(10.U)
dut.io.b.poke(10.U)
dut.clock.step(1)
eventually(11, "a never equals b within the first 11 cycles") { dut.io.aEvEqC expected 1.U }
}

def testExactly(): Unit = {
dut.io.a.poke(7.U)
dut.clock.step(1)
exact(6, "aEqb expected timing is wrong") { dut.io.aEvEqC expected 1.U }
}

def testNever(): Unit = {
dut.io.a.poke(10.U)
dut.io.b.poke(20.U)
dut.clock.step(1)
never(10, "a is equal to b at some point") { dut.io.aNevEqb expected 0.U }
}

et match {
case Always => testAlways()
case Eventually => testEventually()
case Exactly => testExactly()
case Never => testNever()
}
}

"Timed Expect Always" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGeneric(dut, Always) }
}
Expand All @@ -66,4 +108,17 @@ class TimedExpectTests extends FlatSpec with ChiselScalatestTester with Matcher
"Timed Expect Never" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGeneric(dut, Never) }
}

"Timed Expect Always with Sugar" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGenericSugar(dut, Always) }
}
"Timed Expect Eventually with Sugar" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGenericSugar(dut, Eventually) }
}
"Timed Expect Exactly with Sugar" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGenericSugar(dut, Exactly) }
}
"Timed Expect Never with Sugar" should "pass" in {
test(new AssertionsToyDUT(32)){ dut => testGenericSugar(dut, Never) }
}
}