diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index 76a8bc956a1..96e8d50af3c 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -606,15 +606,18 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { /** Require that two things are type equivalent, and if they are not, print a helpful error message as * to why not. + * + * @param that the Data to compare to for type equivalence + * @param message if they are not type equivalent, contextual message to add to the exception thrown */ - private[chisel3] def requireTypeEquivalent(that: Data): Unit = { + private[chisel3] def requireTypeEquivalent(that: Data, message: String = ""): Unit = { require( this.typeEquivalent(that), { val reason = this .findFirstTypeMismatch(that, strictTypes = true, strictWidths = true, strictProbeInfo = true) .map(s => s"\nbecause $s") .getOrElse("") - s"$this is not typeEquivalent to $that$reason" + s"$message$this is not typeEquivalent to $that$reason" } ) } diff --git a/core/src/main/scala/chisel3/reflect/DataMirror.scala b/core/src/main/scala/chisel3/reflect/DataMirror.scala index f2e8375222c..65c553bd00c 100644 --- a/core/src/main/scala/chisel3/reflect/DataMirror.scala +++ b/core/src/main/scala/chisel3/reflect/DataMirror.scala @@ -105,6 +105,20 @@ object DataMirror { */ def checkTypeEquivalence(x: Data, y: Data): Boolean = x.typeEquivalent(y) + /** Require that two things are type equivalent + * + * If they are not, print a helpful error message as + * to why not. + * + * Requires structural, alignment, width, probe, color type equivalent + * @param x First Chisel type + * @param y Second chisel type + * @param message if they are not type equivalent, contextual message to add to the exception thrown + */ + def requireTypeEquivalent(x: Data, y: Data, message: String = ""): Unit = { + x.requireTypeEquivalent(y, message) + } + /** Check if two Chisel types have the same alignments for all matching members * * This means that for matching members in Aggregates, they must have matching member alignments relative to the parent type diff --git a/src/test/scala/chisel3/TypeEquivalenceSpec.scala b/src/test/scala/chisel3/TypeEquivalenceSpec.scala index a972f9d3161..4d22e2ca1ee 100644 --- a/src/test/scala/chisel3/TypeEquivalenceSpec.scala +++ b/src/test/scala/chisel3/TypeEquivalenceSpec.scala @@ -17,6 +17,14 @@ object TypeEquivalenceSpec { val c = if (hasC) Some(UInt(8.W)) else None } + class FooParent(hasC: Boolean) extends Bundle { + val foo = new Foo(hasC) + } + + class FooGrandparent(hasC: Boolean) extends Bundle { + val bar = new FooParent(hasC) + } + class Bar(bWidth: Int = 8) extends Bundle { val a = UInt(8.W) val b = UInt(bWidth.W) @@ -340,4 +348,22 @@ class TypeEquivalenceSpec extends AnyFlatSpec { ) } + behavior.of("Data.requireTypeEquivalent") + + it should "have a good user message if it fails for wrong scala type" in { + val result = the[IllegalArgumentException] thrownBy { + Bool().requireTypeEquivalent(UInt(1.W), "This test should fail, because: ") + } + result.getMessage should include("This test should fail, because: Bool is not typeEquivalent to UInt<1>") + } + + it should "have a good user message if it fails for mismatched bundles" in { + val result = the[IllegalArgumentException] thrownBy { + (new FooGrandparent(true)).requireTypeEquivalent(new FooGrandparent(false), "This test should fail, because: ") + } + result.getMessage should include("This test should fail, because:") + // Also says 'FooGrandparent$1 is not typeEquivalent to FooGrandparent$1' which isn't terribly interesting to match on + result.getMessage should include(".bar.foo.c: Dangling field on Left") + } + }