diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index bf41ae926f0..b3776c46d2f 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -520,63 +520,81 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { /** Whether this Data has the same model ("data type") as that Data. * Data subtypes should overload this with checks against their own type. + * @param that the Data to check for type equivalence against. + * @param strictProbeInfo whether probe info (including its RW-ness and Color) must match */ private[chisel3] final def typeEquivalent( - that: Data - ): Boolean = findFirstTypeMismatch(that, strictTypes = true, strictWidths = true).isEmpty + that: Data, + strictProbeInfo: Boolean = true + ): Boolean = + findFirstTypeMismatch(that, strictTypes = true, strictWidths = true, strictProbeInfo = strictProbeInfo).isEmpty /** Find and report any type mismatches * * @param that Data being compared to this * @param strictTypes Does class of Bundles or Records need to match? Inverse of "structural". * @param strictWidths do widths need to match? + * @param strictProbeInfo does probe info need to match (includes RW and Color) * @return None if types are equivalent, Some String reporting the first mismatch if not */ private[chisel3] final def findFirstTypeMismatch( - that: Data, - strictTypes: Boolean, - strictWidths: Boolean + that: Data, + strictTypes: Boolean, + strictWidths: Boolean, + strictProbeInfo: Boolean ): Option[String] = { + + def checkProbeInfo(left: Data, right: Data): Option[String] = + Option.when(strictProbeInfo && (left.probeInfo != right.probeInfo)) { + def probeInfoStr(info: Option[ProbeInfo]) = info.map { info => + s"Some(writeable=${info.writable})" + }.getOrElse("None") + s": Left ($left with probeInfo: ${probeInfoStr(left.probeInfo)}) and Right ($right with probeInfo: ${probeInfoStr(right.probeInfo)}) have different probeInfo." + } + def rec(left: Data, right: Data): Option[String] = - (left, right) match { - // Careful, EnumTypes are Element and if we don't implement this, then they are all always equal - case (e1: EnumType, e2: EnumType) => - // TODO, should we implement a form of structural equality for enums? - if (e1.factory == e2.factory) None - else Some(s": Left ($e1) and Right ($e2) have different types.") - // Properties should be considered equal when getPropertyType is equal, not when getClass is equal. - case (p1: Property[_], p2: Property[_]) => - if (p1.getPropertyType != p2.getPropertyType) { - Some(s": Left ($p1) and Right ($p2) have different types") - } else { - None - } - case (e1: Element, e2: Element) if e1.getClass == e2.getClass => - if (strictWidths && e1.width != e2.width) { - Some(s": Left ($e1) and Right ($e2) have different widths.") - } else { - None - } - case (r1: Record, r2: Record) if !strictTypes || r1.getClass == r2.getClass => - val (larger, smaller, msg) = - if (r1._elements.size >= r2._elements.size) (r1, r2, "Left") else (r2, r1, "Right") - larger._elements.flatMap { - case (name, data) => - val recurse = smaller._elements.get(name) match { - case None => Some(s": Dangling field on $msg") - case Some(data2) => rec(data, data2) - } - recurse.map("." + name + _) - }.headOption - case (v1: Vec[_], v2: Vec[_]) => - if (v1.size != v2.size) { - Some(s": Left (size ${v1.size}) and Right (size ${v2.size}) have different lengths.") - } else { - val recurse = rec(v1.sample_element, v2.sample_element) - recurse.map("[_]" + _) - } - case _ => Some(s": Left ($left) and Right ($right) have different types.") + checkProbeInfo(left, right).orElse { + (left, right) match { + // Careful, EnumTypes are Element and if we don't implement this, then they are all always equal + case (e1: EnumType, e2: EnumType) => + // TODO, should we implement a form of structural equality for enums? + if (e1.factory == e2.factory) None + else Some(s": Left ($e1) and Right ($e2) have different types.") + // Properties should be considered equal when getPropertyType is equal, not when getClass is equal. + case (p1: Property[_], p2: Property[_]) => + if (p1.getPropertyType != p2.getPropertyType) { + Some(s": Left ($p1) and Right ($p2) have different types") + } else { + None + } + case (e1: Element, e2: Element) if e1.getClass == e2.getClass => + if (strictWidths && e1.width != e2.width) { + Some(s": Left ($e1) and Right ($e2) have different widths.") + } else { + None + } + case (r1: Record, r2: Record) if !strictTypes || r1.getClass == r2.getClass => + val (larger, smaller, msg) = + if (r1._elements.size >= r2._elements.size) (r1, r2, "Left") else (r2, r1, "Right") + larger._elements.flatMap { + case (name, data) => + val recurse = smaller._elements.get(name) match { + case None => Some(s": Dangling field on $msg") + case Some(data2) => rec(data, data2) + } + recurse.map("." + name + _) + }.headOption + case (v1: Vec[_], v2: Vec[_]) => + if (v1.size != v2.size) { + Some(s": Left (size ${v1.size}) and Right (size ${v2.size}) have different lengths.") + } else { + val recurse = rec(v1.sample_element, v2.sample_element) + recurse.map("[_]" + _) + } + case _ => Some(s": Left ($left) and Right ($right) have different types.") + } } + val leftType = if (this.hasBinding) this.cloneType else this val rightType = if (that.hasBinding) that.cloneType else that rec(leftType, rightType) diff --git a/core/src/main/scala/chisel3/probe/package.scala b/core/src/main/scala/chisel3/probe/package.scala index 14a81dd3dc6..7091f22434a 100644 --- a/core/src/main/scala/chisel3/probe/package.scala +++ b/core/src/main/scala/chisel3/probe/package.scala @@ -33,7 +33,7 @@ package object probe extends SourceInfoDoc { * @param probeExpr value to initialize the sink to */ def define[T <: Data](sink: T, probeExpr: T)(implicit sourceInfo: SourceInfo): Unit = { - if (!checkTypeEquivalence(sink, probeExpr)) { + if (!sink.typeEquivalent(probeExpr, false /* we will check more more detailed probe info below */ )) { Builder.error("Cannot define a probe on a non-equivalent type.") } requireHasProbeTypeModifier(sink, "Expected sink to be a probe.") diff --git a/core/src/main/scala/chisel3/reflect/DataMirror.scala b/core/src/main/scala/chisel3/reflect/DataMirror.scala index af8f154e201..f2e8375222c 100644 --- a/core/src/main/scala/chisel3/reflect/DataMirror.scala +++ b/core/src/main/scala/chisel3/reflect/DataMirror.scala @@ -94,7 +94,10 @@ object DataMirror { * Likewise, Records check that both Records have the same * elements with the same types. * - * Equivalent to being structural, alignment, and width type equivalent + * elements must be the same 'probe-ness' (RWProbe and Probe vs no Probe are not) + * and the same color. + * + * Equivalent to being structural, alignment, width, probe, color type equivalent * * @param x First Chisel type * @param y Second Chisel type diff --git a/src/test/scala/chisel3/TypeEquivalenceSpec.scala b/src/test/scala/chisel3/TypeEquivalenceSpec.scala index 74952ada43e..a972f9d3161 100644 --- a/src/test/scala/chisel3/TypeEquivalenceSpec.scala +++ b/src/test/scala/chisel3/TypeEquivalenceSpec.scala @@ -4,6 +4,8 @@ package chisel3 import circt.stage.ChiselStage.emitCHIRRTL import chisel3.experimental.Analog +import chisel3.probe.{Probe, RWProbe} +import chisel3.layer.Layer import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers._ @@ -29,6 +31,18 @@ object TypeEquivalenceSpec { val e0, e1 = Value val e4 = Value(4.U) } + + class BundleWithProbe(useProbe: Boolean) extends Bundle { + val logic = Bool() + val maybeProbe = if (useProbe) Probe(Bool()) else Bool() + } + + object Red extends Layer(layer.Convention.Bind) { + override def toString = "Red" + } + object Green extends Layer(layer.Convention.Bind) { + override def toString = "Green" + } } import TypeEquivalenceSpec._ @@ -143,13 +157,15 @@ class TypeEquivalenceSpec extends AnyFlatSpec { val y = UInt(8.W) val z = UInt(4.W) val zz = UInt() - x.findFirstTypeMismatch(y, true, true) should be(None) - x.findFirstTypeMismatch(z, true, true) should be( + x.findFirstTypeMismatch(y, true, true, true) should be(None) + x.findFirstTypeMismatch(z, true, true, true) should be( Some(": Left (UInt<8>) and Right (UInt<4>) have different widths.") ) - x.findFirstTypeMismatch(z, true, false) should be(None) - x.findFirstTypeMismatch(zz, true, true) should be(Some(": Left (UInt<8>) and Right (UInt) have different widths.")) - x.findFirstTypeMismatch(zz, true, false) should be(None) + x.findFirstTypeMismatch(z, true, false, true) should be(None) + x.findFirstTypeMismatch(zz, true, true, true) should be( + Some(": Left (UInt<8>) and Right (UInt) have different widths.") + ) + x.findFirstTypeMismatch(zz, true, false, true) should be(None) } it should "support comparing SInts" in { @@ -157,54 +173,60 @@ class TypeEquivalenceSpec extends AnyFlatSpec { val y = SInt(8.W) val z = SInt(4.W) val zz = SInt() - x.findFirstTypeMismatch(y, true, true) should be(None) - x.findFirstTypeMismatch(z, true, true) should be( + x.findFirstTypeMismatch(y, true, true, true) should be(None) + x.findFirstTypeMismatch(z, true, true, true) should be( Some(": Left (SInt<8>) and Right (SInt<4>) have different widths.") ) - x.findFirstTypeMismatch(z, true, false) should be(None) - x.findFirstTypeMismatch(zz, true, true) should be(Some(": Left (SInt<8>) and Right (SInt) have different widths.")) - x.findFirstTypeMismatch(zz, true, false) should be(None) + x.findFirstTypeMismatch(z, true, false, true) should be(None) + x.findFirstTypeMismatch(zz, true, true, true) should be( + Some(": Left (SInt<8>) and Right (SInt) have different widths.") + ) + x.findFirstTypeMismatch(zz, true, false, true) should be(None) } it should "catch comparing SInts and UInts" in { val x = UInt(8.W) val y = SInt(8.W) - x.findFirstTypeMismatch(y, true, true) should be(Some(": Left (UInt<8>) and Right (SInt<8>) have different types.")) + x.findFirstTypeMismatch(y, true, true, true) should be( + Some(": Left (UInt<8>) and Right (SInt<8>) have different types.") + ) } it should "support equivalent Bundles" in { val x = new Foo(true) val y = new Foo(true) - x.findFirstTypeMismatch(y, true, true) should be(None) + x.findFirstTypeMismatch(y, true, true, true) should be(None) } it should "support structurally equivalent Bundles" in { val x = new Foo(false) val y = new Bar - x.findFirstTypeMismatch(y, true, true) should be(Some(": Left (Foo) and Right (Bar) have different types.")) - x.findFirstTypeMismatch(y, false, true) should be(None) + x.findFirstTypeMismatch(y, true, true, true) should be(Some(": Left (Foo) and Right (Bar) have different types.")) + x.findFirstTypeMismatch(y, false, true, true) should be(None) } it should "support Vecs" in { val x = Vec(2, UInt(8.W)) val y = Vec(2, UInt(8.W)) val z = Vec(3, UInt(8.W)) - x.findFirstTypeMismatch(y, true, true) should be(None) - x.findFirstTypeMismatch(z, true, true) should be(Some(": Left (size 2) and Right (size 3) have different lengths.")) + x.findFirstTypeMismatch(y, true, true, true) should be(None) + x.findFirstTypeMismatch(z, true, true, true) should be( + Some(": Left (size 2) and Right (size 3) have different lengths.") + ) } it should "support nested width mismatches" in { val x = new Bar(8) val y = new Bar(4) - x.findFirstTypeMismatch(y, true, false) should be(None) - x.findFirstTypeMismatch(y, true, true) should be( + x.findFirstTypeMismatch(y, true, false, true) should be(None) + x.findFirstTypeMismatch(y, true, true, true) should be( Some(".b: Left (UInt<8>) and Right (UInt<4>) have different widths.") ) val a = Vec(4, new Bar(8)) val b = Vec(4, new Bar(4)) - a.findFirstTypeMismatch(b, true, false) should be(None) - a.findFirstTypeMismatch(b, true, true) should be( + a.findFirstTypeMismatch(b, true, false, true) should be(None) + a.findFirstTypeMismatch(b, true, true, true) should be( Some("[_].b: Left (UInt<8>) and Right (UInt<4>) have different widths.") ) @@ -214,8 +236,8 @@ class TypeEquivalenceSpec extends AnyFlatSpec { val d = new Bundle { val foo = new Bar(4) } - c.findFirstTypeMismatch(d, false, false) should be(None) - c.findFirstTypeMismatch(d, false, true) should be( + c.findFirstTypeMismatch(d, false, false, true) should be(None) + c.findFirstTypeMismatch(d, false, true, true) should be( Some(".foo.b: Left (UInt<8>) and Right (UInt<4>) have different widths.") ) } @@ -224,45 +246,98 @@ class TypeEquivalenceSpec extends AnyFlatSpec { val x = Fizz() val y = Fizz() val z = Buzz() - x.findFirstTypeMismatch(y, true, true) should be(None) - x.findFirstTypeMismatch(z, true, true) should be(Some(": Left (Fizz) and Right (Buzz) have different types.")) + x.findFirstTypeMismatch(y, true, true, true) should be(None) + x.findFirstTypeMismatch(z, true, true, true) should be(Some(": Left (Fizz) and Right (Buzz) have different types.")) // TODO should there be some form of structural typing for ChiselEnums? - x.findFirstTypeMismatch(z, false, true) should be(Some(": Left (Fizz) and Right (Buzz) have different types.")) + x.findFirstTypeMismatch(z, false, true, true) should be( + Some(": Left (Fizz) and Right (Buzz) have different types.") + ) } it should "support comparing Analogs" in { val x = Analog(8.W) val y = Analog(8.W) val z = Analog(4.W) - x.findFirstTypeMismatch(y, true, true) should be(None) - x.findFirstTypeMismatch(z, true, true) should be( + x.findFirstTypeMismatch(y, true, true, true) should be(None) + x.findFirstTypeMismatch(z, true, true, true) should be( Some(": Left (Analog<8>) and Right (Analog<4>) have different widths.") ) - x.findFirstTypeMismatch(z, true, false) should be(None) + x.findFirstTypeMismatch(z, true, false, true) should be(None) } it should "support DontCare" in { - DontCare.findFirstTypeMismatch(DontCare, true, true) should be(None) + DontCare.findFirstTypeMismatch(DontCare, true, true, true) should be(None) } it should "support AsyncReset" in { - AsyncReset().findFirstTypeMismatch(AsyncReset(), true, true) should be(None) - AsyncReset().findFirstTypeMismatch(Bool(), true, true) should be( + AsyncReset().findFirstTypeMismatch(AsyncReset(), true, true, true) should be(None) + AsyncReset().findFirstTypeMismatch(Bool(), true, true, true) should be( Some(": Left (AsyncReset) and Right (Bool) have different types.") ) - AsyncReset().findFirstTypeMismatch(Clock(), true, true) should be( + AsyncReset().findFirstTypeMismatch(Clock(), true, true, true) should be( Some(": Left (AsyncReset) and Right (Clock) have different types.") ) - AsyncReset().findFirstTypeMismatch(Reset(), true, true) should be( + AsyncReset().findFirstTypeMismatch(Reset(), true, true, true) should be( Some(": Left (AsyncReset) and Right (Reset) have different types.") ) } it should "support Clock" in { - Clock().findFirstTypeMismatch(Clock(), true, true) should be(None) + Clock().findFirstTypeMismatch(Clock(), true, true, true) should be(None) } it should "support abstract Reset" in { - Reset().findFirstTypeMismatch(Reset(), true, true) should be(None) + Reset().findFirstTypeMismatch(Reset(), true, true, true) should be(None) + } + + it should "support Probe" in { + Probe(Bool()).findFirstTypeMismatch(Probe(Bool()), true, true, true) should be(None) + } + + it should "support RWProbe" in { + RWProbe(Bool()).findFirstTypeMismatch(RWProbe(Bool()), true, true, true) should be(None) + } + + it should "detect differences between Probe and Not-Probe" in { + Probe(Bool()).findFirstTypeMismatch(Bool(), true, true, true) should be( + Some( + ": Left (Bool with probeInfo: Some(writeable=false)) and Right (Bool with probeInfo: None) have different probeInfo." + ) + ) + } + + it should "work for Bundles with Probes" in { + new BundleWithProbe(true).findFirstTypeMismatch(new BundleWithProbe(true), true, true, true) should be(None) + } + + it should "detect differences between Probe and Not-Probe within a Bundle" in { + new BundleWithProbe(true).findFirstTypeMismatch(new BundleWithProbe(false), true, true, true) should be( + Some( + ".maybeProbe: Left (Bool with probeInfo: Some(writeable=false)) and Right (Bool with probeInfo: None) have different probeInfo." + ) + ) + } + + it should "detect differences between probe types" in { + RWProbe(Bool()).findFirstTypeMismatch(Probe(Bool()), true, true, true) should be( + Some( + ": Left (Bool with probeInfo: Some(writeable=true)) and Right (Bool with probeInfo: Some(writeable=false)) have different probeInfo." + ) + ) + } + + it should "detect differences through probes" in { + Probe(Bool()).findFirstTypeMismatch(Probe(Clock()), true, true, true) should be( + Some(": Left (Bool) and Right (Clock) have different types.") + ) } + + it should "detect differences in probe within a Vector" in { + Vec(3, Probe(Bool())).findFirstTypeMismatch(Vec(3, Bool()), true, true, true) should be( + Some( + "[_]: Left (Bool with probeInfo: Some(writeable=false)) and Right (Bool with probeInfo: None) have different probeInfo." + ) + ) + } + }