Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Commit

Permalink
partialPoke/Expect: add more tests, improve implementation (#589) (#590)
Browse files Browse the repository at this point in the history
* partialPoke/Expect: add more tests, improve implementation

* fix error reporting for partialExpect

(cherry picked from commit 8f692f2)

Co-authored-by: Kevin Laeufer <[email protected]>
  • Loading branch information
mergify[bot] and ekiwi authored Dec 20, 2022
1 parent 02f7094 commit 0e81af7
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 95 deletions.
11 changes: 11 additions & 0 deletions src/main/scala/chiseltest/exceptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,14 @@ class StopException(message: String) extends Exception(message)

/** Indicates that a Chisel `assert(...)` or `assume(...)` statement has failed. */
class ChiselAssertionError(message: String) extends Exception(message)

/** Indicates that a value used in a poke/expect is not a literal.
* It could be hardware or a DontCare which is only allowed when using pokePartial/expectPartial.
*/
class NonLiteralValueError(val value: chisel3.Data, val signal: chisel3.Data, op: String)
extends Exception(
s"""Value $value for entry $signal is not a literal value!
|You need to fully specify all fields/entries when using $op.
|Maybe try using `${op}Partial` if you only want to use incomplete Vec/Bundle literals.
|""".stripMargin
)
10 changes: 6 additions & 4 deletions src/main/scala/chiseltest/internal/TestEnvInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,19 @@ trait TestEnvInterface {
def signalExpectFailure(message: String): Unit = {
val trace = new Throwable
val expectStackDepth = trace.getStackTrace.indexWhere(ste =>
ste.getClassName.startsWith("chiseltest.package$") && ste.getMethodName == "expect"
ste.getClassName.startsWith(
"chiseltest.package$"
) && (ste.getMethodName == "expect" || ste.getMethodName == "expectPartial")
)
require(
expectStackDepth != -1,
s"Failed to find expect in stack trace:\r\n${trace.getStackTrace.mkString("\r\n")}"
)

val trimmedTrace = trace.getStackTrace.drop(expectStackDepth + 2)
val detailedTrace = topFileName.map(getExpectDetailedTrace(trimmedTrace.toSeq, _)).getOrElse("")
val trimmedTrace = trace.getStackTrace.drop(expectStackDepth)
val failureLocation: String = topFileName.map(getExpectDetailedTrace(trimmedTrace.toSeq, _)).getOrElse("")
val stackIndex = expectStackDepth + 1
batchedFailures += new FailedExpectException(message + detailedTrace, stackIndex)
batchedFailures += new FailedExpectException(message + failureLocation, stackIndex)
}

/** If there are any failures, reports them and end the test now.
Expand Down
169 changes: 78 additions & 91 deletions src/main/scala/chiseltest/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -196,37 +196,15 @@ package object chiseltest {
*/
def pokePartial(value: T): Unit = {
require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch")
x.elements.filter { case (k, v) =>
DataMirror.directionOf(v) != ActualDirection.Output && {
value.elements(k) match {
case _: Record => true
case data: Data => data.isLit
}
}
}.foreach { case (k, v) =>
v match {
case record: Record => record.pokePartial(value.elements(k).asInstanceOf[Record])
case data: Data => data.poke(value.elements(k))
}
}
x.pokeInternal(value, allowPartial = true)
}

/** Check the given signal with a [[Record.litValue()]];
* elements of `x` which contain no literal will be ignored.
*/
def expectPartial(value: T): Unit = {
require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch")
x.elements.filter { case (k, _) =>
value.elements(k) match {
case _: Record => true
case d: Data => d.isLit
}
}.foreach { case (k, v) =>
v match {
case record: Record => record.expectPartial(value.elements(k).asInstanceOf[Record])
case data: Data => data.expect(value.elements(k))
}
}
x.expectInternal(value, None, allowPartial = true)
}
}

Expand All @@ -242,64 +220,63 @@ package object chiseltest {
*/
def pokePartial(value: T): Unit = {
require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch")
x.getElements.zipWithIndex.filter { case (v, index) =>
DataMirror.directionOf(v) != ActualDirection.Output && {
value.getElements(index) match {
case _: T => true
case data: Data => data.isLit
}
}
}.foreach { case (v, index) =>
v match {
case vec: T => vec.pokePartial(value.getElements(index).asInstanceOf[T])
case data: Data => data.poke(value.getElements(index))
}
}
x.pokeInternal(value, allowPartial = true)
}

/** Check the given signal with a [[Vec.litValue()]];
* elements of `x` which contain no literal will be ignored.
*/
def expectPartial(value: T): Unit = {
require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch")
x.getElements.zipWithIndex.filter { case (_, index) =>
value.getElements(index) match {
case _: T => true
case d: Data => d.isLit
}
}.foreach { case (v, index) =>
v match {
case vec: T => vec.expectPartial(value.getElements(index).asInstanceOf[T])
case data: Data => data.expect(value.getElements(index))
}
}
x.expectInternal(value, None, allowPartial = true)
}
}

implicit class testableData[T <: Data](x: T) {
import Utils._

def poke(value: T): Unit = (x, value) match {
case (x: Bool, value: Bool) => x.poke(value)
case (x: UInt, value: UInt) => x.poke(value)
case (x: SInt, value: SInt) => x.poke(value)
case (x: FixedPoint, value: FixedPoint) => x.poke(value)
case (x: Interval, value: Interval) => x.poke(value)
case (x: Record, value: Record) =>
require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch")
x.elements.zip(value.elements).foreach { case ((_, x), (_, value)) =>
x.poke(value)
}
case (x: Vec[_], value: Vec[_]) =>
require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch")
x.getElements.zip(value.getElements).foreach { case (x, value) =>
x.poke(value)
def poke(value: T): Unit = pokeInternal(value, allowPartial = false)

private def isAllowedNonLitGround(value: T, allowPartial: Boolean, op: String): Boolean = {
val isGroundType = value match {
case _: Vec[_] | _: Record => false
case _ => true
}
// if we are dealing with a ground type non-literal, this is only allowed if we are doing a partial poke/expect
if (isGroundType && !value.isLit) {
if (allowPartial) { true }
else {
throw new NonLiteralValueError(value, x, op)
}
case (x: EnumType, value: EnumType) =>
require(DataMirror.checkTypeEquivalence(x, value), s"EnumType mismatch")
pokeBits(x, value.litValue)
case x => throw new LiteralTypeException(s"don't know how to poke $x")
// TODO: aggregate types
} else {
false
}
}

private[chiseltest] def pokeInternal(value: T, allowPartial: Boolean): Unit = {
if (isAllowedNonLitGround(value, allowPartial, "poke")) return
(x, value) match {
case (x: Bool, value: Bool) => x.poke(value)
case (x: UInt, value: UInt) => x.poke(value)
case (x: SInt, value: SInt) => x.poke(value)
case (x: FixedPoint, value: FixedPoint) => x.poke(value)
case (x: Interval, value: Interval) => x.poke(value)
case (x: Record, value: Record) =>
require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch")
x.elements.zip(value.elements).foreach { case ((_, x), (_, value)) =>
x.pokeInternal(value, allowPartial)
}
case (x: Vec[_], value: Vec[_]) =>
require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch")
x.getElements.zip(value.getElements).foreach { case (x, value) =>
x.pokeInternal(value, allowPartial)
}
case (x: EnumType, value: EnumType) =>
require(DataMirror.checkTypeEquivalence(x, value), s"EnumType mismatch")
pokeBits(x, value.litValue)
case x => throw new LiteralTypeException(s"don't know how to poke $x")
// TODO: aggregate types
}
}

def peek(): T = x match {
Expand All @@ -322,31 +299,41 @@ package object chiseltest {
case x => throw new LiteralTypeException(s"don't know how to peek $x")
}

protected def expectInternal(value: T, message: Option[() => String]): Unit = (x, value) match {
case (x: Bool, value: Bool) => x.expectInternal(value.litValue, message)
case (x: UInt, value: UInt) => x.expectInternal(value.litValue, message)
case (x: SInt, value: SInt) => x.expectInternal(value.litValue, message)
case (x: FixedPoint, value: FixedPoint) => x.expectInternal(value, message)
case (x: Interval, value: Interval) => x.expectInternal(value, message)
case (x: Record, value: Record) =>
require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch")
x.elements.zip(value.elements).foreach { case ((_, x), (_, value)) =>
x.expectInternal(value, message)
}
case (x: Vec[_], value: Vec[_]) =>
require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch")
x.getElements.zip(value.getElements).foreach { case (x, value) =>
x.expectInternal(value, message)
}
case (x: EnumType, value: EnumType) =>
require(DataMirror.checkTypeEquivalence(x, value), s"EnumType mismatch")
Utils.expectBits(x, value.litValue, message, Some(enumToString(x)))
case x => throw new LiteralTypeException(s"don't know how to expect $x")
// TODO: aggregate types
private[chiseltest] def expectInternal(value: T, message: Option[() => String], allowPartial: Boolean): Unit = {
if (isAllowedNonLitGround(value, allowPartial, "expect")) return
(x, value) match {
case (x: Bool, value: Bool) => x.expectInternal(value.litValue, message)
case (x: UInt, value: UInt) => x.expectInternal(value.litValue, message)
case (x: SInt, value: SInt) => x.expectInternal(value.litValue, message)
case (x: FixedPoint, value: FixedPoint) => x.expectInternal(value, message)
case (x: Interval, value: Interval) => x.expectInternal(value, message)
case (x: Record, value: Record) =>
require(DataMirror.checkTypeEquivalence(x, value), s"Record type mismatch")
x.elements.zip(value.elements).foreach { case ((_, x), (_, value)) =>
x.expectInternal(value, message, allowPartial)
}
case (x: Vec[_], value: Vec[_]) =>
require(DataMirror.checkTypeEquivalence(x, value), s"Vec type mismatch")
x.getElements.zip(value.getElements).zipWithIndex.foreach { case ((subX, value), index) =>
value match {
case DontCare =>
throw new RuntimeException(
s"Vec $x needs to be fully specified when using expect. Index $index is missing." +
"Maybe try using `expectPartial` if you only want to check for some elements."
)
case other => subX.expectInternal(other, message, allowPartial)
}
}
case (x: EnumType, value: EnumType) =>
require(DataMirror.checkTypeEquivalence(x, value), s"EnumType mismatch")
Utils.expectBits(x, value.litValue, message, Some(enumToString(x)))
case x => throw new LiteralTypeException(s"don't know how to expect $x")
// TODO: aggregate types
}
}

def expect(value: T): Unit = expectInternal(value, None)
def expect(value: T, message: => String): Unit = expectInternal(value, Some(() => message))
def expect(value: T): Unit = expectInternal(value, None, allowPartial = false)
def expect(value: T, message: => String): Unit = expectInternal(value, Some(() => message), allowPartial = false)

/** @return the single clock that drives the source of this signal.
* @throws ClockResolutionException if sources of this signal have more than one, or zero clocks
Expand Down
Loading

0 comments on commit 0e81af7

Please sign in to comment.