diff --git a/src/main/scala/chisel3/simulator/PeekPokeAPI.scala b/src/main/scala/chisel3/simulator/PeekPokeAPI.scala index 28ca102a932..174e5adf42a 100644 --- a/src/main/scala/chisel3/simulator/PeekPokeAPI.scala +++ b/src/main/scala/chisel3/simulator/PeekPokeAPI.scala @@ -3,11 +3,30 @@ package chisel3.simulator import svsim._ import chisel3._ +import chisel3.reflect.DataMirror import chisel3.experimental.{SourceInfo, SourceLine} +import chisel3.experimental.BundleLiterals._ +import chisel3.experimental.VecLiterals._ import chisel3.internal.ExceptionHelpers import firrtl.options.StageUtils.dramaticMessage import scala.util.control.NoStackTrace +private[simulator] trait Peekable[T <: Data] { + def peek(): T + + def expect(expected: T, buildMessage: (String, T) => String)(implicit sourceInfo: SourceInfo): Unit + + def expect(expected: T, message: String)(implicit sourceInfo: SourceInfo): Unit = + expect(expected, (_, _) => message) + + def expect(expected: T)(implicit sourceInfo: SourceInfo): Unit = + expect(expected, (observed, expected) => s"Expectation failed: observed value $observed != $expected") +} + +private[simulator] trait Pokable[T <: Data] { + def poke(literal: T): Unit +} + object PeekPokeAPI extends PeekPokeAPI trait PeekPokeAPI { @@ -21,28 +40,45 @@ trait PeekPokeAPI { ) ) with NoStackTrace + object FailedExpectationException { def apply[T]( - observed: T, - expected: T, - message: String, - sourceInfo: SourceInfo, - extraContext: Seq[String] + observed: T, + expected: T, + message: String, + sourceInfo: SourceInfo ): FailedExpectationException[T] = { + val extraContext = + sourceInfo match { + case sl: SourceLine => + ExceptionHelpers.getErrorLineInFile(Seq(), sl) + case _ => + Seq() + } val fullMessage = s"$message ${sourceInfo.makeMessage()}" + (if (extraContext.nonEmpty) s"\n${extraContext.mkString("\n")}" else "") - new FailedExpectationException(observed, expected, fullMessage) + new FailedExpectationException[T](observed, expected, fullMessage) } } - implicit class testableClock(clock: Clock) { + sealed trait AnyTestableData[T <: Data] { + protected def data: T + + protected val simulatedModule = AnySimulatedModule.current + + protected final def simulationPort = simulatedModule.port(data) + } + + private[simulator] trait PeekPokable[T <: Data] extends Peekable[T] with Pokable[T] with AnyTestableData[T] + + implicit class testableClock(clock: Clock) extends AnyTestableData[Clock] { + val data = clock + def step(cycles: Int = 1): Unit = { - val module = AnySimulatedModule.current - module.willEvaluate() + simulatedModule.willEvaluate() if (cycles == 0) { - module.controller.run(0) + simulatedModule.controller.run(0) } else { - val simulationPort = module.port(clock) simulationPort.tick( timestepsPerPhase = 1, maxCycles = cycles, @@ -58,65 +94,117 @@ trait PeekPokeAPI { * Stops early if the `sentinelPort` is equal to the `sentinelValue`. */ def stepUntil(sentinelPort: Data, sentinelValue: BigInt, maxCycles: Int): Unit = { - val module = AnySimulatedModule.current - module.willEvaluate() - val simulationPort = module.port(clock) + simulatedModule.willEvaluate() simulationPort.tick( timestepsPerPhase = 1, maxCycles = maxCycles, inPhaseValue = 0, outOfPhaseValue = 1, - sentinel = Some(module.port(sentinelPort), sentinelValue) + sentinel = Some(simulatedModule.port(sentinelPort), sentinelValue) ) } } - sealed trait SimulationData[T <: Data] { - val data: T + sealed trait TestableElement[T <: Element] extends PeekPokable[T] { + protected def isSigned = false - private def isSigned = data.isInstanceOf[SInt] + private[simulator] protected def encode(width: Int, value: BigInt): T - private[simulator] def encode(width: Int, value: BigInt): T - private final def encode(value: Simulation.Value): T = { + private[simulator] final def encode(value: Simulation.Value): T = { encode(value.bitCount, value.asBigInt) } - final def peek(): T = encode(data.peekValue()) - final def expect(expected: T)(implicit sourceInfo: SourceInfo): Unit = { - data.expect( - expected.litValue, - encode(_).litValue, - (observed: BigInt, expected: BigInt) => s"Expectation failed: observed value $observed != $expected", - sourceInfo + protected final def peekValue(): Simulation.Value = { + simulatedModule.willPeek() + simulationPort.get(isSigned = isSigned) + } + + def peek(): T = encode(peekValue()) + + def poke(literal: T): Unit = poke(literal.litValue) + + def poke(value: BigInt): Unit = { + simulatedModule.willPoke() + simulationPort.set(value) + } + + private[simulator] protected def check[U](checkFn: Simulation.Value => Unit): Unit = { + simulatedModule.willPeek() + simulationPort.check(isSigned = isSigned)(checkFn) + } + + private[simulator] protected final def expect[U]( + expected: U, + sameValue: (Simulation.Value, U) => Boolean, + observedValToString: Simulation.Value => String, + buildMessage: (String, U) => String, + sourceInfo: SourceInfo + ): Unit = { + check(observedValue => + if (!sameValue(observedValue, expected)) { + val observedStr = observedValToString(observedValue) + throw FailedExpectationException( + observedStr, + expected.toString, + buildMessage(observedStr, expected), + sourceInfo + ) + } ) } - final def expect(expected: T, message: String)(implicit sourceInfo: SourceInfo): Unit = { - data.expect(expected.litValue, encode(_).litValue, (_: BigInt, _: BigInt) => message, sourceInfo) + + override def expect(expected: T)(implicit sourceInfo: SourceInfo): Unit = { + expect( + expected, + (observed: String, expected: T) => s"Expectation failed: observed value $observed != $expected" + ) } - final def expect(expected: BigInt)(implicit sourceInfo: SourceInfo): Unit = { - data.expect( + + override def expect(expected: T, buildMessage: (String, T) => String)(implicit sourceInfo: SourceInfo): Unit = { + require(expected.isLit, s"Expected value: $expected must be a literal") + + expect( expected, - _.asBigInt, - (observed: BigInt, expected: BigInt) => s"Expectation failed: observed value $observed != $expected", + (observed: Simulation.Value, expected: T) => observed.asBigInt == expected.litValue, + encode(_).toString, + (obs: String, exp: T) => buildMessage(obs, exp), sourceInfo ) } - final def expect(expected: BigInt, message: String)(implicit sourceInfo: SourceInfo): Unit = { - data.expect(expected, _.asBigInt, (_: BigInt, _: BigInt) => message, sourceInfo) - } + + final def expect(expected: BigInt, buildMessage: (String, BigInt) => String)( + implicit sourceInfo: SourceInfo + ): Unit = expect( + expected, + (obs: Simulation.Value, exp: BigInt) => obs.asBigInt == exp, + _.asBigInt.toString, + buildMessage, + sourceInfo + ) + + final def expect(expected: BigInt)(implicit sourceInfo: SourceInfo): Unit = expect( + expected, + (observed: String, expected: BigInt) => s"Expectation failed: observed value $observed != $expected" + ) + + final def expect(expected: BigInt, message: String)(implicit sourceInfo: SourceInfo): Unit = + expect(expected, (_: String, _: BigInt) => message) } - implicit final class testableSInt(val data: SInt) extends SimulationData[SInt] { + implicit final class testableSInt(val data: SInt) extends TestableElement[SInt] { + override def isSigned = true + override def encode(width: Int, value: BigInt) = value.asSInt(width.W) } - implicit final class testableUInt(val data: UInt) extends SimulationData[UInt] { + implicit final class testableUInt(val data: UInt) extends TestableElement[UInt] { override def encode(width: Int, value: BigInt) = value.asUInt(width.W) } - implicit final class testableBool(val data: Bool) extends SimulationData[Bool] { + implicit final class testableBool(val data: Bool) extends TestableElement[Bool] { override def encode(width: Int, value: BigInt): Bool = { + require(width <= 1, "Bool must have width 1") if (value.isValidByte) { value.byteValue match { case 0 => false.B @@ -127,58 +215,140 @@ trait PeekPokeAPI { throw new Exception(s"peeked Bool with value $value, not 0 or 1") } } + + def poke(value: Boolean): Unit = poke(value.B) + + def peekBoolean(): Boolean = peek().litToBoolean + + override def expect(expected: Bool)(implicit sourceInfo: SourceInfo): Unit = expect(expected.litValue) + + def expect(value: Boolean)(implicit sourceInfo: SourceInfo): Unit = expect(value.B) } - implicit final class testableData[T <: Data](data: T) { - private def isSigned = data.isInstanceOf[SInt] + implicit final class testablReset(val data: Reset) extends TestableElement[Reset] { + def poke(value: Boolean): Unit = poke(value.B) - def poke(boolean: Boolean): Unit = { - poke(if (boolean) 1 else 0) - } - def poke(literal: T): Unit = { - poke(literal.litValue) + def encode(width: Int, value: BigInt): Reset = testableBool(data.asBool).encode(width, value) + } + + implicit class testableEnum[T <: EnumType](val data: T) extends TestableElement[T] { + override def encode(width: Int, value: BigInt): T = { + data.factory.all.find(_.litValue == value).get.asInstanceOf[T] } - def poke(value: BigInt): Unit = { - val module = AnySimulatedModule.current - module.willPoke() - val simulationPort = module.port(data) - simulationPort.set(value) + } + + implicit class testableRecord[T <: Record](val data: T)(implicit sourceInfo: SourceInfo) extends PeekPokable[T] { + + override def peek(): T = { + chiselTypeOf(data).Lit( + data.elements.toSeq.map { case (name: String, elt: Data) => + (rec: Record) => rec.elements(name) -> elt.peek() + }: _* + ) } - def peekValue(): Simulation.Value = { - val module = AnySimulatedModule.current - module.willPeek() - val simulationPort = module.port(data) - simulationPort.get(isSigned = isSigned) + + override def poke(value: T): Unit = data.elements.foreach { case (name, d) => + d.poke(value.elements(name)) } - def expect[T]( - expected: T, - encode: (Simulation.Value) => T, - buildMessage: (T, T) => String, - sourceInfo: SourceInfo + + def expect(expected: T, buildMessage: (String, T) => String, allowPartial: Boolean)( + implicit sourceInfo: SourceInfo ): Unit = { - val module = AnySimulatedModule.current - module.willPeek() - val simulationPort = module.port(data) - - simulationPort.check(isSigned = isSigned) { observedValue => - val observed = encode(observedValue) - if (observed != expected) { - val extraContext = - sourceInfo match { - case sl: SourceLine => - ExceptionHelpers.getErrorLineInFile(Seq(), sl) - case _ => - Seq() + require(DataMirror.checkTypeEquivalence(data, expected), "Type mismatch") + + // FIXME: I can't understand why but _not_ getting the peeked value as a `val` beforehand results in infinite recursion + val peekedValue = peek().toString + + data.elements.foreach { case (name, d) => + expected.elements(name) match { + case DontCare => + if (!allowPartial) { + throw new Exception( + s"Field '$name' is not initiazlized in the expected value $expected" + ) } - throw FailedExpectationException( - observed, - expected, - buildMessage(observed, expected), - sourceInfo, - extraContext - ) + case exp => + d.expect( + exp, + (obs: String, _) => + s"${buildMessage(peekedValue, expected)}:\n Expected value of field '$name' to be $exp, got $obs" + ) } } } + + override def expect(expected: T, buildMessage: (String, T) => String)(implicit sourceInfo: SourceInfo): Unit = + expect(expected, buildMessage, allowPartial = false) + } + + implicit class testableData[T <: Data](val data: T) extends PeekPokable[T] { + + def peek(): T = { + data match { + case x: Bool => new testableBool(x).peek().asInstanceOf[T] + case x: UInt => new testableUInt(x).peek().asInstanceOf[T] + case x: SInt => new testableSInt(x).peek().asInstanceOf[T] + case x: EnumType => new testableEnum(x).peek().asInstanceOf[T] + case x: Record => new testableRecord(x).peek().asInstanceOf[T] + case x: Vec[_] => + val elementValueFns = x.getElements.map(_.peek()) + Vec.Lit(elementValueFns: _*).asInstanceOf[T] + case x => throw new Exception(s"don't know how to peek $x") + } + } + + override def expect( + expected: T, + buildMessage: (String, T) => String + )(implicit sourceInfo: SourceInfo): Unit = { + + def buildMsgFn[S](observed: String, expected: S): String = + buildMessage(observed, expected.asInstanceOf[T]) + + (data, expected) match { + case (dat: Bool, exp: Bool) => + new testableBool(dat).expect(exp, buildMsgFn _) + case (dat: UInt, exp: UInt) => + new testableUInt(dat).expect(exp, buildMsgFn _) + case (dat: SInt, exp: SInt) => + new testableSInt(dat).expect(exp, buildMsgFn _) + case (dat: EnumType, exp: EnumType) => + new testableEnum(dat).expect(exp, buildMsgFn _) + case (dat: Record, exp: Record) => + new testableRecord(dat).expect(exp, buildMsgFn _) + case (dat: Vec[_], exp: Vec[_]) => + require( + exp.length == dat.length, + s"Vec length mismatch: Data port has ${dat.length} elements while the expected value is of length ${exp.length}" + ) + val peekedValue = dat.peek().toString + dat.getElements.zip(exp.getElements).zipWithIndex.foreach { case ((datEl, valEl), index) => + valEl match { + case DontCare => + // TODO: missing elements? + case ve => + datEl.expect( + ve, + (obs: String, exp: Data) => buildMessage(peekedValue, exp.asInstanceOf[T]) + s" at index $index" + ) + } + } + case x => throw new Exception(s"don't know how to expect $x") + } + } + + def poke(literal: T): Unit = (data, literal) match { + case (x: Bool, lit: Bool) => x.poke(lit) + case (x: UInt, lit: UInt) => x.poke(lit) + case (x: SInt, lit: SInt) => x.poke(lit) + case (x: EnumType, lit: EnumType) => x.poke(lit) + case (x: Record, lit: Record) => x.poke(lit) + case (x: Vec[_], lit: Vec[_]) => + require(x.length == lit.length, s"Vec length mismatch: expected ${x.length}, got ${lit.length}") + x.getElements.zip(lit.getElements).foreach { case (portEl, valueEl) => + portEl.poke(valueEl) + } + case x => throw new Exception(s"don't know how to poke $x") + } } } diff --git a/src/test/scala-2/chiselTests/simulator/PeekPokeApiSpec.scala b/src/test/scala-2/chiselTests/simulator/PeekPokeApiSpec.scala new file mode 100644 index 00000000000..6635fcdc824 --- /dev/null +++ b/src/test/scala-2/chiselTests/simulator/PeekPokeApiSpec.scala @@ -0,0 +1,282 @@ +package chiselTests.simulator + +import chisel3._ +import chisel3.experimental.BundleLiterals._ +import chisel3.experimental.VecLiterals._ +import chisel3.util._ +import chisel3.simulator._ +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.must.Matchers + +object TestOp extends ChiselEnum { + val Add, Sub, Mul = Value +} + +class TestPeekPokeEnum(w: Int) extends Module { + object CmpResult extends ChiselEnum { + val LT, EQ, GT = Value + } + + val vecDim = 3 + + val io = IO(new Bundle { + val in = Input(Valid(new Bundle { + val a = UInt(w.W) + val b = UInt(w.W) + val v1 = Vec(vecDim, UInt(w.W)) + val v2 = Vec(vecDim, UInt(w.W)) + })) + val op = Input(TestOp()) + val out = Valid(new Bundle { + val c = UInt(w.W) + val cmp = CmpResult() + val vSum = Vec(vecDim, UInt((w + 1).W)) + val vOutProduct = Vec(vecDim, Vec(vecDim, UInt((2 * w).W))) + val vDot = UInt((2 * w + vecDim - 1).W) + }) + }) + + val a = io.in.bits.a + val b = io.in.bits.b + + val result = Wire(chiselTypeOf(io.out.bits)) + + result.c :#= MuxCase( + 0.U, + Seq( + (io.op === TestOp.Add) -> (a + b), + (io.op === TestOp.Sub) -> (a - b), + (io.op === TestOp.Mul) -> (a * b).take(w) + ) + ) + + // Supress the following warning: + // [W001] Casting non-literal UInt to chiselTests.simulator.TestPeekPokeEnum$CmpResult. + // You can use chiselTests.simulator.TestPeekPokeEnum$CmpResult.safe to cast without this warning. + // The warning seems to be a (unrelated) bug + suppressEnumCastWarning { + result.cmp :#= Mux1H( + Seq( + (a < b) -> CmpResult.LT, + (a === b) -> CmpResult.EQ, + (a > b) -> CmpResult.GT + ) + ) + } + + // addition of vectors + result.vSum :#= io.in.bits.v1.zip(io.in.bits.v2).map { case (x, y) => x +& y } + // inner product + result.vDot :#= io.in.bits.v1.zip(io.in.bits.v2).map { case (x, y) => x * y }.reduce(_ +& _) + // outer product + result.vOutProduct :#= io.in.bits.v1.map { x => VecInit(io.in.bits.v2.map { y => x * y }) } + + io.out :#= Pipe(io.in.valid, result) +} + +class PeekPokeAPISpec extends AnyFunSpec with ChiselSim with Matchers { + val rand = scala.util.Random + + describe("PeekPokeAPI with testableData") { + val w = 32 + it("should peek and poke various data types correctly") { + val numTests = 100 + simulate(new TestPeekPokeEnum(w)) { dut => + assert(w == dut.io.in.bits.a.getWidth) + val vecDim = dut.vecDim + val truncationMask = (BigInt(1) << w) - 1 + for { + _ <- 0 until numTests + a = BigInt(w, rand) + b = BigInt(w, rand) + v1 = Seq.fill(vecDim)(BigInt(w, rand)) + v2 = Seq.fill(vecDim)(BigInt(w, rand)) + op <- TestOp.all + } { + + dut.io.in.bits.poke( + chiselTypeOf(dut.io.in.bits).Lit( + _.a -> a.U, + _.b -> b.U, + _.v1 -> Vec.Lit(v1.map(_.U(w.W)): _*), + _.v2 -> Vec.Lit(v2.map(_.U(w.W)): _*) + ) + ) + dut.io.in.valid.poke(true) + dut.io.op.poke(op) + dut.clock.step() + dut.io.in.valid.poke(false) + + val peekedOp = dut.io.op.peek() + assert(peekedOp.litValue == op.litValue) + assert(peekedOp.toString.contains(TestOp.getClass.getSimpleName.stripSuffix("$"))) + + val expected = op match { + case TestOp.Add => a + b + case TestOp.Sub => a - b + case TestOp.Mul => a * b + case _ => throw new Exception("Invalid operation") + } + val expectedCmp = a.compare(b) match { + case -1 => dut.CmpResult.LT + case 0 => dut.CmpResult.EQ + case 1 => dut.CmpResult.GT + } + + dut.io.out.valid.expect(true.B) + dut.io.out.valid.expect(true) + dut.io.out.bits.c.expect(expected & truncationMask) + + assert(dut.io.out.bits.cmp.peek().litValue == expectedCmp.litValue) + dut.io.out.bits.cmp.expect(expectedCmp) + + val expectedVSum = Vec.Lit(v1.zip(v2).map { case (x, y) => (x + y).U((w + 1).W) }: _*) + + dut.io.out.bits.vSum.expect(expectedVSum) + + val expVOutProduct = Vec.Lit( + v1.map { x => + Vec.Lit(v2.map { y => (x * y).U((2 * w).W) }: _*) + }: _* + ) + + dut.io.out.bits.vOutProduct.expect(expVOutProduct) + + val expectedBits = chiselTypeOf(dut.io.out.bits).Lit( + _.c -> (expected & truncationMask).U, + _.cmp -> expectedCmp, + _.vSum -> expectedVSum, + _.vDot -> v1.zip(v2).map { case (x, y) => x * y }.reduce(_ + _).U((2 * w + vecDim - 1).W), + _.vOutProduct -> expVOutProduct + ) + + dut.io.out.bits.expect(expectedBits) + + val peekedBits = dut.io.out.bits.peek() + assert(peekedBits.c.litValue == expectedBits.c.litValue) + assert(peekedBits.cmp.litValue == expectedBits.cmp.litValue) + + assert(peekedBits.elements.forall { case (name, el) => expectedBits.elements(name).litValue == el.litValue }) + + dut.io.out.expect( + chiselTypeOf(dut.io.out).Lit( + _.valid -> true.B, + _.bits -> expectedBits + ) + ) + } + } + } + + it("reports failed expects correctly") { + val thrown = the[PeekPokeAPI.FailedExpectationException[_]] thrownBy { + simulate(new TestPeekPokeEnum(w)) { dut => + assert(w == dut.io.in.bits.a.getWidth) + val vecDim = dut.vecDim + val truncationMask = (BigInt(1) << w) - 1 + + dut.io.in.bits.poke( + chiselTypeOf(dut.io.in.bits).Lit( + _.a -> 1.U, + _.b -> 2.U, + _.v1 -> Vec.Lit(Seq.fill(vecDim)(3.U(w.W)): _*), + _.v2 -> Vec.Lit(Seq.fill(vecDim)(4.U(w.W)): _*) + ) + ) + dut.io.in.valid.poke(true) + dut.io.op.poke(TestOp.Add) + dut.clock.step() + + dut.io.out.bits.c.expect(5.U) + } + } + thrown.getMessage must include("observed value UInt<32>(3) != UInt<3>(5)") + (thrown.getMessage must include).regex( + """ @\[.*chiselTests/simulator/PeekPokeApiSpec\.scala:\d+:\d+\]""" + ) + thrown.getMessage must include("dut.io.out.bits.c.expect(5.U)") + thrown.getMessage must include(" ^") + } + + it("reports failed Record expects correctly") { + val thrown = the[PeekPokeAPI.FailedExpectationException[_]] thrownBy { + simulate(new TestPeekPokeEnum(w)) { dut => + assert(w == dut.io.in.bits.a.getWidth) + val vecDim = dut.vecDim + val truncationMask = (BigInt(1) << w) - 1 + + dut.io.in.bits.poke( + chiselTypeOf(dut.io.in.bits).Lit( + _.a -> 1.U, + _.b -> 2.U, + _.v1 -> Vec.Lit(Seq.fill(vecDim)(3.U(w.W)): _*), + _.v2 -> Vec.Lit(Seq.fill(vecDim)(4.U(w.W)): _*) + ) + ) + dut.io.in.valid.poke(true) + dut.io.op.poke(TestOp.Add) + dut.clock.step() + + val expectedBits = chiselTypeOf(dut.io.out.bits).Lit( + _.c -> 3.U, + _.cmp -> dut.CmpResult.LT, + _.vSum -> Vec.Lit(Seq.fill(vecDim)(7.U((w + 1).W)): _*), + _.vDot -> 35.U((2 * w + vecDim - 1).W), + _.vOutProduct -> Vec.Lit( + Seq.fill(vecDim)(Vec.Lit(Seq.tabulate(vecDim)(i => (12 + i).U((2 * w).W)): _*)): _* + ) + ) + dut.io.out.bits.expect(expectedBits) + } + } + thrown.getMessage must include("Observed value: 'UInt<66>(36)") + thrown.getMessage must include("Expected value: 'UInt<66>(35)") + thrown.getMessage must include("Expectation failed: observed value AnonymousBundle") + (thrown.getMessage must include).regex( + """ @\[.*chiselTests/simulator/PeekPokeApiSpec\.scala:\d+:\d+\]""" + ) + thrown.getMessage must include("dut.io.out.bits.expect(expectedBits)") + thrown.getMessage must include(" ^") + } + + it("reports failed expect of Records with Vec fields correctly") { + val thrown = the[PeekPokeAPI.FailedExpectationException[_]] thrownBy { + simulate(new TestPeekPokeEnum(w)) { dut => + assert(w == dut.io.in.bits.a.getWidth) + val vecDim = dut.vecDim + val truncationMask = (BigInt(1) << w) - 1 + + dut.io.in.bits.poke( + chiselTypeOf(dut.io.in.bits).Lit( + _.a -> 1.U, + _.b -> 2.U, + _.v1 -> Vec.Lit(Seq.fill(vecDim)(3.U(w.W)): _*), + _.v2 -> Vec.Lit(Seq.fill(vecDim)(4.U(w.W)): _*) + ) + ) + dut.io.in.valid.poke(true) + dut.io.op.poke(TestOp.Add) + dut.clock.step() + + dut.io.out.bits.expect( + chiselTypeOf(dut.io.out.bits).Lit( + _.c -> 3.U, + _.cmp -> dut.CmpResult.LT, + _.vSum -> Vec.Lit(Seq.fill(vecDim)(7.U((w + 1).W)): _*), + _.vDot -> 36.U((2 * w + vecDim - 1).W), + _.vOutProduct -> Vec.Lit( + Seq.fill(vecDim)(Vec.Lit(Seq.tabulate(vecDim)(i => (12 + i).U((2 * w).W)): _*)): _* + ) + ) + ) + } + } + thrown.getMessage must include("Expectation failed: observed value") + (thrown.getMessage must include).regex( + """ @\[.*chiselTests/simulator/PeekPokeApiSpec\.scala:\d+:\d+\]""" + ) + thrown.getMessage must include("dut.io.out.bits.expect(") + thrown.getMessage must include(" ^") + } + } +}