diff --git a/core/src/main/scala/chisel3/Const.scala b/core/src/main/scala/chisel3/Const.scala index 26e76ddcf4f..aea9089f750 100644 --- a/core/src/main/scala/chisel3/Const.scala +++ b/core/src/main/scala/chisel3/Const.scala @@ -3,16 +3,18 @@ package chisel3 import chisel3._ -import chisel3.internal.{requireIsChiselType, Builder} +import chisel3.internal.{requireIsChiselType, requireNoProbeTypeModifier, Builder} +import chisel3.experimental.SourceInfo /** Create a constant type in FIRRTL, which is guaranteed to take a single * constant value. */ object Const { - def apply[T <: Data](source: => T): T = { + def apply[T <: Data](source: => T)(implicit sourceInfo: SourceInfo): T = { val prevId = Builder.idGen.value val data = source // should only evaluate source once requireIsChiselType(data) + requireNoProbeTypeModifier(data, "Cannot create Const of a Probe.") val ret = if (!data.mustClone(prevId)) data else data.cloneType.asInstanceOf[T] ret.isConst = true ret diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index aedd6bf1b77..0c91a91538b 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -341,6 +341,8 @@ object Flipped { * @define coll data */ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { + import Data.ProbeInfo + // This is a bad API that punches through object boundaries. private[chisel3] def flatten: IndexedSeq[Element] = { this match { @@ -372,6 +374,11 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { } } + // probeInfo only exists if this is a probe type + private var _probeInfoVar: ProbeInfo = null + private[chisel3] def probeInfo: Option[ProbeInfo] = Option(_probeInfoVar) + private[chisel3] def probeInfo_=(probeInfo: Option[ProbeInfo]) = _probeInfoVar = probeInfo.getOrElse(null) + // If this Data is constant, it must hold a constant value private var _isConst: Boolean = false private[chisel3] def isConst: Boolean = _isConst @@ -659,12 +666,13 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { /** Internal API; Chisel users should look at chisel3.chiselTypeOf(...). * * Returns a copy of this data type, with hardware bindings (if any) removed. - * Directionality data is still preserved. + * Directionality data and probe information is still preserved. */ private[chisel3] def cloneTypeFull: this.type = { val clone = this.cloneType // get a fresh object, without bindings // Only the top-level direction needs to be fixed up, cloneType should do the rest clone.specifiedDirection = specifiedDirection + probe.setProbeModifier(clone, probeInfo) clone.isConst = isConst clone } @@ -774,6 +782,8 @@ object Data { // Needed for the `implicit def toConnectableDefault` import scala.language.implicitConversions + private[chisel3] case class ProbeInfo(val writable: Boolean) + /** Provides :<=, :>=, :<>=, and :#= between consumer and producer of the same T <: Data */ implicit class ConnectableDefault[T <: Data](consumer: T) extends connectable.ConnectableOperators[T](consumer) @@ -927,6 +937,8 @@ trait WireFactory { val prevId = Builder.idGen.value val t = source // evaluate once (passed by name) requireIsChiselType(t, "wire type") + requireNoProbeTypeModifier(t, "Cannot make a wire of a Chisel type with a probe modifier.") + val x = if (!t.mustClone(prevId)) t else t.cloneTypeFull // Bind each element of x to being a Wire diff --git a/core/src/main/scala/chisel3/Mem.scala b/core/src/main/scala/chisel3/Mem.scala index 9ff81383d44..01e11a1a709 100644 --- a/core/src/main/scala/chisel3/Mem.scala +++ b/core/src/main/scala/chisel3/Mem.scala @@ -55,6 +55,8 @@ sealed abstract class MemBase[T <: Data](val t: T, val length: BigInt, sourceInf if (t.isConst) Builder.error("Mem type cannot be const.")(sourceInfo) + requireNoProbeTypeModifier(t, "Cannot make a Mem of a Chisel type with a probe modifier.")(sourceInfo) + _parent.foreach(_.addId(this)) // if the memory is created in a scope with an implicit clock (-> clockInst is defined), we will perform checks that diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index a7a73b1f457..7899d36f554 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -84,6 +84,7 @@ abstract class RawModule extends BaseModule { id.forceName(default = "REG", _namespace) case WireBinding(_, _) => id.forceName(default = "_WIRE", _namespace) + // probes have their refs set eagerly case _ => // don't name literals } } // else, don't name unbound types diff --git a/core/src/main/scala/chisel3/Reg.scala b/core/src/main/scala/chisel3/Reg.scala index 34997778e63..5c490bc601f 100644 --- a/core/src/main/scala/chisel3/Reg.scala +++ b/core/src/main/scala/chisel3/Reg.scala @@ -40,6 +40,7 @@ object Reg { val t = source // evaluate once (passed by name) requireIsChiselType(t, "reg type") if (t.isConst) Builder.error("Cannot create register with constant value.")(sourceInfo) + requireNoProbeTypeModifier(t, "Cannot make a register of a Chisel type with a probe modifier.") val reg = if (!t.mustClone(prevId)) t else t.cloneTypeFull val clock = Node(Builder.forcedClock) @@ -177,6 +178,7 @@ object RegInit { reg.bind(RegBinding(Builder.forcedUserModule, Builder.currentWhen)) requireIsHardware(init, "reg initializer") + requireNoProbeTypeModifier(t, "Cannot make a register of a Chisel type with a probe modifier.") pushCommand(DefRegInit(sourceInfo, reg, clock.ref, reset.ref, init.ref)) reg } diff --git a/core/src/main/scala/chisel3/connectable/Connectable.scala b/core/src/main/scala/chisel3/connectable/Connectable.scala index 6cb4ba65fd9..59c1376991b 100644 --- a/core/src/main/scala/chisel3/connectable/Connectable.scala +++ b/core/src/main/scala/chisel3/connectable/Connectable.scala @@ -113,6 +113,11 @@ final class Connectable[+T <: Data] private ( this.copy(excluded = excluded ++ excludedMembers.toSet).asInstanceOf[Connectable[S]] } + /** Exclude probes */ + def excludeProbes: Connectable[T] = excludeEach { + case f if (DataMirror.hasProbeTypeModifier(f)) => Seq(f) + } + /** Add any elements of members that are OpaqueType */ private def addOpaque(members: Seq[Data]): Seq[Data] = { members.flatMap { diff --git a/core/src/main/scala/chisel3/connectable/Connection.scala b/core/src/main/scala/chisel3/connectable/Connection.scala index eb0f0256cff..54babaf2392 100644 --- a/core/src/main/scala/chisel3/connectable/Connection.scala +++ b/core/src/main/scala/chisel3/connectable/Connection.scala @@ -8,6 +8,7 @@ import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl.DefInvalid import chisel3.experimental.{prefix, SourceInfo, UnlocatableSourceInfo} import chisel3.experimental.{attach, Analog} +import chisel3.reflect.DataMirror.hasProbeTypeModifier import Alignment.matchingZipOfChildren import scala.collection.mutable @@ -234,6 +235,10 @@ private[chisel3] object Connection { deriveChildAlignment(f, proAlign) ) } + // Check that neither consumer nor producer contains probes + case (consumer: Data, producer: Data) + if (hasProbeTypeModifier(consumer) || hasProbeTypeModifier(producer)) => + errors = "Cannot use connectables with probe types. Exclude them prior to connection." +: errors case (consumer, producer) => val alignment = ( conAlign.alignsWith(proAlign), diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index 6b5f3acebaf..5d3b500b7af 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -57,6 +57,10 @@ private[chisel3] object BiConnect { ) def DontCareCantBeSink = BiConnectException(": DontCare cannot be a connection sink (LHS)") + def LeftProbeBiConnectionException(left: Data) = + BiConnectException(s"Left of Probed type cannot participate in a bi connection (<>)") + def RightProbeBiConnectionException(right: Data) = + BiConnectException(s"Right of Probed type cannot participate in a bi connection (<>)") /** This function is what recursively tries to connect a left and right together * @@ -81,6 +85,12 @@ private[chisel3] object BiConnect { context_mod: RawModule ): Unit = { (left, right) match { + // Disallow monoconnecting Probe types + case (left_e: Data, _) if containsProbe(left_e) => + throw LeftProbeBiConnectionException(left_e) + case (_, right_e: Data) if containsProbe(right_e) => + throw RightProbeBiConnectionException(right_e) + // Handle element case (root case) case (left_a: Analog, right_a: Analog) => try { diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala index 165bd021d5c..89f8b82b89f 100644 --- a/core/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -4,6 +4,7 @@ package chisel3.internal import chisel3._ import chisel3.experimental.{Analog, BaseModule, SourceInfo} +import chisel3.internal.containsProbe import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl.{Connect, Converter, DefInvalid} import chisel3.experimental.dataview.{isView, reify, reifyToAggregate} @@ -73,6 +74,10 @@ private[chisel3] object MonoConnect { MonoConnectException( s"Source ${formatName(source)} and sink ${formatName(sink)} of type Analog cannot participate in a mono connection (:=)" ) + def SourceProbeMonoConnectionException(source: Data) = + MonoConnectException(s"Source ${formatName(source)} of Probed type cannot participate in a mono connection (:=)") + def SinkProbeMonoConnectionException(sink: Data) = + MonoConnectException(s"Sink ${formatName(sink)} of Probed type cannot participate in a mono connection (:=)") def checkWhenVisibility(x: Data): Boolean = { x.topBinding match { @@ -97,6 +102,12 @@ private[chisel3] object MonoConnect { ): Unit = { (sink, source) match { + // Disallow monoconnecting Probe types + case (_, source_e: Data) if containsProbe(source_e) => + throw SourceProbeMonoConnectionException(source_e) + case (sink_e: Data, _) if containsProbe(sink_e) => + throw SinkProbeMonoConnectionException(sink_e) + // Handle legal element cases, note (Bool, Bool) is caught by the first two, as Bool is a UInt case (sink_e: Bool, source_e: UInt) => elemConnect(sourceInfo, sink_e, source_e, context_mod) diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index aaf45a44f67..93872a6a34a 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -93,6 +93,12 @@ private[chisel3] object Converter { // TODO Simplify case lit: ILit => throw new InternalErrorException(s"Unexpected ILit: $lit") + case e @ ProbeExpr(probe) => + fir.ProbeExpr(convert(probe, ctx, info)) + case e @ RWProbeExpr(probe) => + fir.RWProbeExpr(convert(probe, ctx, info)) + case e @ ProbeRead(probe) => + fir.ProbeRead(convert(probe, ctx, info)) case other => throw new InternalErrorException(s"Unexpected type in convert $other") } @@ -175,6 +181,31 @@ private[chisel3] object Converter { e.name ) ) + case e @ ProbeDefine(sourceInfo, sink, probeExpr) => + Some(fir.ProbeDefine(convert(sourceInfo), convert(sink, ctx, sourceInfo), convert(probeExpr, ctx, sourceInfo))) + case e @ ProbeForceInitial(sourceInfo, probe, value) => + Some(fir.ProbeForceInitial(convert(sourceInfo), convert(probe, ctx, sourceInfo), convert(value, ctx, sourceInfo))) + case e @ ProbeReleaseInitial(sourceInfo, probe) => + Some(fir.ProbeReleaseInitial(convert(sourceInfo), convert(probe, ctx, sourceInfo))) + case e @ ProbeForce(sourceInfo, clock, cond, probe, value) => + Some( + fir.ProbeForce( + convert(sourceInfo), + convert(clock, ctx, sourceInfo), + convert(cond, ctx, sourceInfo), + convert(probe, ctx, sourceInfo), + convert(value, ctx, sourceInfo) + ) + ) + case e @ ProbeRelease(sourceInfo, clock, cond, probe) => + Some( + fir.ProbeRelease( + convert(sourceInfo), + convert(clock, ctx, sourceInfo), + convert(cond, ctx, sourceInfo), + convert(probe, ctx, sourceInfo) + ) + ) case e @ Verification(_, op, info, clk, pred, msg) => val firOp = op match { case Formal.Assert => fir.Formal.Assert @@ -298,10 +329,24 @@ private[chisel3] object Converter { case d => d.specifiedDirection } - def extractType(data: Data, info: SourceInfo): fir.Type = extractType(data, false, info) + def extractType(data: Data, info: SourceInfo): fir.Type = extractType(data, false, info, true, true) - def extractType(data: Data, clearDir: Boolean, info: SourceInfo, checkConst: Boolean = true): fir.Type = data match { - case _ if (checkConst && data.isConst) => fir.ConstType(extractType(data, clearDir, info, false)) + def extractType( + data: Data, + clearDir: Boolean, + info: SourceInfo, + checkProbe: Boolean, + checkConst: Boolean + ): fir.Type = data match { + // extract underlying type for probe + case d if (checkProbe && d.probeInfo.nonEmpty) => + if (d.probeInfo.get.writable) { + fir.RWProbeType(extractType(d, clearDir, info, false, checkConst)) + } else { + fir.ProbeType(extractType(d, clearDir, info, false, checkConst)) + } + // extract underlying type for const + case d if (checkConst && d.isConst) => fir.ConstType(extractType(d, clearDir, info, checkProbe, false)) case _: Clock => fir.ClockType case _: AsyncReset => fir.AsyncResetType case _: ResetType => fir.ResetType @@ -312,21 +357,24 @@ private[chisel3] object Converter { case d: Vec[_] => val childClearDir = clearDir || d.specifiedDirection == SpecifiedDirection.Input || d.specifiedDirection == SpecifiedDirection.Output - fir.VectorType(extractType(d.sample_element, childClearDir, info), d.length) + // if Vector is a probe, don't emit Probe<...> on its elements + fir.VectorType(extractType(d.sample_element, childClearDir, info, checkProbe, true), d.length) case d: Record => { val childClearDir = clearDir || d.specifiedDirection == SpecifiedDirection.Input || d.specifiedDirection == SpecifiedDirection.Output + // if Record is a probe, don't emit Probe<...> on its elements def eltField(elt: Data): fir.Field = (childClearDir, firrtlUserDirOf(elt)) match { - case (true, _) => fir.Field(getRef(elt, info).name, fir.Default, extractType(elt, true, info)) + case (true, _) => + fir.Field(getRef(elt, info).name, fir.Default, extractType(elt, true, info, checkProbe, true)) case (false, SpecifiedDirection.Unspecified | SpecifiedDirection.Output) => - fir.Field(getRef(elt, info).name, fir.Default, extractType(elt, false, info)) + fir.Field(getRef(elt, info).name, fir.Default, extractType(elt, false, info, checkProbe, true)) case (false, SpecifiedDirection.Flip | SpecifiedDirection.Input) => - fir.Field(getRef(elt, info).name, fir.Flip, extractType(elt, false, info)) + fir.Field(getRef(elt, info).name, fir.Flip, extractType(elt, false, info, checkProbe, true)) } if (!d._isOpaqueType) fir.BundleType(d._elements.toIndexedSeq.reverse.map { case (_, e) => eltField(e) }) else - extractType(d._elements.head._2, childClearDir, info) + extractType(d._elements.head._2, childClearDir, info, checkProbe, true) } } @@ -347,7 +395,7 @@ private[chisel3] object Converter { case SpecifiedDirection.Input | SpecifiedDirection.Output => true case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => false } - val tpe = extractType(port.id, clearDir, port.sourceInfo) + val tpe = extractType(port.id, clearDir, port.sourceInfo, true, true) fir.Port(convert(port.sourceInfo), getRef(port.id, port.sourceInfo).name, dir, tpe) } diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 5c634e8388d..aaccde07e12 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -212,6 +212,14 @@ case class Index(imm: Arg, value: Arg) extends Arg { override def localName: String = s"${imm.localName}[${value.localName}]" } +sealed trait ProbeDetails { this: Arg => + val probe: Arg + override def name: String = s"$probe" +} +private[chisel3] case class ProbeExpr(probe: Arg) extends Arg with ProbeDetails +private[chisel3] case class RWProbeExpr(probe: Arg) extends Arg with ProbeDetails +private[chisel3] case class ProbeRead(probe: Arg) extends Arg with ProbeDetails + @deprecated(deprecatedPublicAPIMsg, "Chisel 3.6") object Width { def apply(x: Int): Width = KnownWidth(x) @@ -308,6 +316,7 @@ case class DefMemPort[T <: Data]( index: Arg, clock: Arg) extends Definition + @nowarn("msg=class Port") // delete when Port becomes private @deprecated(deprecatedPublicAPIMsg, "Chisel 3.6") case class DefInstance(sourceInfo: SourceInfo, id: BaseModule, ports: Seq[Port]) extends Definition @@ -337,6 +346,13 @@ case class Port(id: Data, dir: SpecifiedDirection, sourceInfo: SourceInfo) @deprecated(deprecatedPublicAPIMsg, "Chisel 3.6") case class Printf(id: printf.Printf, sourceInfo: SourceInfo, clock: Arg, pable: Printable) extends Definition +private[chisel3] case class ProbeDefine(sourceInfo: SourceInfo, sink: Arg, probe: Arg) extends Command +private[chisel3] case class ProbeForceInitial(sourceInfo: SourceInfo, probe: Arg, value: Arg) extends Command +private[chisel3] case class ProbeReleaseInitial(sourceInfo: SourceInfo, probe: Arg) extends Command +private[chisel3] case class ProbeForce(sourceInfo: SourceInfo, clock: Arg, cond: Arg, probe: Arg, value: Arg) + extends Command +private[chisel3] case class ProbeRelease(sourceInfo: SourceInfo, clock: Arg, cond: Arg, probe: Arg) extends Command + @deprecated(deprecatedPublicAPIMsg, "Chisel 3.6") object Formal extends Enumeration { val Assert = Value("assert") diff --git a/core/src/main/scala/chisel3/internal/package.scala b/core/src/main/scala/chisel3/internal/package.scala index 6d63955a003..7b70cb9275c 100644 --- a/core/src/main/scala/chisel3/internal/package.scala +++ b/core/src/main/scala/chisel3/internal/package.scala @@ -3,7 +3,8 @@ package chisel3 import firrtl.annotations.{IsModule, ModuleTarget} -import chisel3.experimental.{BaseModule, UnlocatableSourceInfo} +import chisel3.experimental.{BaseModule, SourceInfo, UnlocatableSourceInfo} +import chisel3.reflect.DataMirror.hasProbeTypeModifier import chisel3.internal.firrtl.{Component, DefModule} import chisel3.internal.Builder.Prefix @@ -101,4 +102,41 @@ package object internal { */ private[chisel3] val ViewParent = Module.do_apply(new ViewParentAPI)(UnlocatableSourceInfo) + + private[chisel3] def requireHasProbeTypeModifier( + probe: Data, + errorMessage: String = "" + )( + implicit sourceInfo: SourceInfo + ): Unit = { + val msg = if (errorMessage.isEmpty) s"Expected a probe." else errorMessage + if (!hasProbeTypeModifier(probe)) Builder.error(msg) + } + + private[chisel3] def requireNoProbeTypeModifier( + probe: Data, + errorMessage: String = "" + )( + implicit sourceInfo: SourceInfo + ): Unit = { + val msg = if (errorMessage.isEmpty) s"Did not expect a probe." else errorMessage + if (hasProbeTypeModifier(probe)) Builder.error(msg) + } + + private[chisel3] def requireHasWritableProbeTypeModifier( + probe: Data, + errorMessage: String = "" + )( + implicit sourceInfo: SourceInfo + ): Unit = { + val msg = if (errorMessage.isEmpty) s"Expected a writable probe." else errorMessage + requireHasProbeTypeModifier(probe, msg) + if (!probe.probeInfo.get.writable) Builder.error(msg) + } + + private[chisel3] def containsProbe(data: Data): Boolean = data match { + case a: Aggregate => + a.elementsIterator.foldLeft(false)((res: Boolean, d: Data) => res || containsProbe(d)) + case leaf => leaf.probeInfo.nonEmpty + } } diff --git a/core/src/main/scala/chisel3/probe/Probe.scala b/core/src/main/scala/chisel3/probe/Probe.scala new file mode 100644 index 00000000000..bd686e1d87d --- /dev/null +++ b/core/src/main/scala/chisel3/probe/Probe.scala @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.probe + +import chisel3._ +import chisel3.Data.ProbeInfo +import chisel3.experimental.SourceInfo +import chisel3.internal.{containsProbe, requireIsChiselType, requireNoProbeTypeModifier, Builder} + +import scala.language.experimental.macros + +/** Utilities for creating and working with Chisel types that have a probe or + * writable probe modifier. + */ +private[chisel3] sealed trait ProbeBase { + + protected def apply[T <: Data](source: => T, writable: Boolean)(implicit sourceInfo: SourceInfo): T = { + val prevId = Builder.idGen.value + // call Output() to coerce passivity + val data = Output(source) // should only evaluate source once + requireNoProbeTypeModifier(data, "Cannot probe a probe.") + if (containsProbe(data)) { + Builder.error("Cannot create a probe of an aggregate containing a probe.") + } + if (writable && data.isConst) { + Builder.error("Cannot create a writable probe of a const type.") + } + + val ret: T = if (!data.mustClone(prevId)) data else data.cloneType.asInstanceOf[T] + setProbeModifier(ret, Some(ProbeInfo(writable))) + ret + } +} + +object Probe extends ProbeBase with SourceInfoDoc { + + /** Mark a Chisel type as with a probe modifier. + */ + def apply[T <: Data](source: => T): T = macro chisel3.internal.sourceinfo.ProbeTransform.sourceApply[T] + + /** @group SourceInfoTransformMacro */ + def do_apply[T <: Data](source: => T)(implicit sourceInfo: SourceInfo): T = super.apply(source, false) +} + +object RWProbe extends ProbeBase with SourceInfoDoc { + + /** Mark a Chisel type with a writable probe modifier. + */ + def apply[T <: Data](source: => T): T = macro chisel3.internal.sourceinfo.ProbeTransform.sourceApply[T] + + /** @group SourceInfoTransformMacro */ + def do_apply[T <: Data](source: => T)(implicit sourceInfo: SourceInfo): T = super.apply(source, true) +} diff --git a/core/src/main/scala/chisel3/probe/ProbeValue.scala b/core/src/main/scala/chisel3/probe/ProbeValue.scala new file mode 100644 index 00000000000..0ccdea9a386 --- /dev/null +++ b/core/src/main/scala/chisel3/probe/ProbeValue.scala @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.probe + +import chisel3.{Data, SourceInfoDoc} +import chisel3.internal.{Builder, OpBinding} +import chisel3.internal.firrtl.{ProbeExpr, RWProbeExpr} +import chisel3.experimental.{requireIsHardware, SourceInfo} + +import scala.language.experimental.macros + +private[chisel3] sealed trait ProbeValueBase { + protected def apply[T <: Data](source: T, writable: Boolean)(implicit sourceInfo: SourceInfo): T = { + requireIsHardware(source) + // construct probe to return with cloned info + val clone = if (writable) RWProbe(source.cloneType) else Probe(source.cloneType) + clone.bind(OpBinding(Builder.forcedUserModule, Builder.currentWhen)) + if (writable) { + clone.setRef(RWProbeExpr(source.ref)) + } else { + clone.setRef(ProbeExpr(source.ref)) + } + clone + } +} + +object ProbeValue extends ProbeValueBase with SourceInfoDoc { + + /** Create a read-only probe expression. */ + def apply[T <: Data](source: T): T = macro chisel3.internal.sourceinfo.ProbeTransform.sourceApply[T] + + /** @group SourceInfoTransformMacro */ + def do_apply[T <: Data](source: T)(implicit sourceInfo: SourceInfo): T = super.apply(source, writable = false) +} + +object RWProbeValue extends ProbeValueBase with SourceInfoDoc { + + /** Create a read/write probe expression. */ + def apply[T <: Data](source: T): T = macro chisel3.internal.sourceinfo.ProbeTransform.sourceApply[T] + + /** @group SourceInfoTransformMacro */ + def do_apply[T <: Data](source: T)(implicit sourceInfo: SourceInfo): T = super.apply(source, writable = true) +} diff --git a/core/src/main/scala/chisel3/probe/package.scala b/core/src/main/scala/chisel3/probe/package.scala new file mode 100644 index 00000000000..4bca840f8e2 --- /dev/null +++ b/core/src/main/scala/chisel3/probe/package.scala @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3 + +import chisel3._ +import chisel3.internal._ +import chisel3.internal.Builder.pushCommand +import chisel3.internal.firrtl._ +import chisel3.Data.ProbeInfo +import chisel3.experimental.SourceInfo +import chisel3.reflect.DataMirror.{checkTypeEquivalence, hasProbeTypeModifier} + +import scala.language.experimental.macros + +package object probe extends SourceInfoDoc { + + private[chisel3] def setProbeModifier[T <: Data](data: T, probeInfo: Option[ProbeInfo]): Unit = { + probeInfo.foreach { _ => + data.probeInfo = probeInfo + data match { + case a: Aggregate => + a.elementsIterator.foreach { e => setProbeModifier(e, probeInfo) } + case _ => // do nothing + } + } + } + + /** Initialize a probe with a provided probe value. */ + def define[T <: Data](sink: T, probeExpr: T)(implicit sourceInfo: SourceInfo): Unit = { + if (!checkTypeEquivalence(sink, probeExpr)) { + Builder.error("Cannot define a probe on a non-equivalent type.") + } + requireHasProbeTypeModifier(sink, "Expected sink to be a probe.") + requireHasProbeTypeModifier(probeExpr, "Expected source to be a probe expression.") + if (sink.probeInfo.get.writable) { + requireHasWritableProbeTypeModifier( + probeExpr, + "Cannot use a non-writable probe expression to define a writable probe." + ) + } + pushCommand(ProbeDefine(sourceInfo, sink.ref, probeExpr.ref)) + } + + /** Access the value of a probe. */ + def read[T <: Data](source: T): T = macro chisel3.internal.sourceinfo.ProbeTransform.sourceRead[T] + + /** @group SourceInfoTransformMacro */ + def do_read[T <: Data](source: T)(implicit sourceInfo: SourceInfo): T = { + requireIsHardware(source) + requireHasProbeTypeModifier(source) + // construct clone to bind to ProbeRead + val clone = source.cloneTypeFull + clone.bind(OpBinding(Builder.forcedUserModule, Builder.currentWhen)) + clone.setRef(ProbeRead(source.ref)) + // return a non-probe type Data that can be used in Data connects + clone.probeInfo = None + clone + } + + /** Override existing driver of a writable probe on initialization. */ + def forceInitial(probe: Data, value: Data)(implicit sourceInfo: SourceInfo): Unit = { + requireHasWritableProbeTypeModifier(probe, "Cannot forceInitial a non-writable Probe.") + pushCommand(ProbeForceInitial(sourceInfo, probe.ref, value.ref)) + } + + /** Release initial driver on a probe. */ + def releaseInitial(probe: Data)(implicit sourceInfo: SourceInfo): Unit = { + requireHasWritableProbeTypeModifier(probe, "Cannot releaseInitial a non-writable Probe.") + pushCommand(ProbeReleaseInitial(sourceInfo, probe.ref)) + } + + /** Override existing driver of a writable probe. */ + def force(clock: Clock, cond: Bool, probe: Data, value: Data)(implicit sourceInfo: SourceInfo): Unit = { + requireHasWritableProbeTypeModifier(probe, "Cannot force a non-writable Probe.") + pushCommand(ProbeForce(sourceInfo, clock.ref, cond.ref, probe.ref, value.ref)) + } + + /** Release driver on a probe. */ + def release(clock: Clock, cond: Bool, probe: Data)(implicit sourceInfo: SourceInfo): Unit = { + requireHasWritableProbeTypeModifier(probe, "Cannot release a non-writable Probe.") + pushCommand(ProbeRelease(sourceInfo, clock.ref, cond.ref, probe.ref)) + } + +} diff --git a/core/src/main/scala/chisel3/reflect/DataMirror.scala b/core/src/main/scala/chisel3/reflect/DataMirror.scala index a0709907503..c58c892f40d 100644 --- a/core/src/main/scala/chisel3/reflect/DataMirror.scala +++ b/core/src/main/scala/chisel3/reflect/DataMirror.scala @@ -42,6 +42,12 @@ object DataMirror { */ def isReg(x: Data): Boolean = hasBinding[RegBinding](x) + /** Check if a given `Data` is a Probe + * @param x the `Data` to check + * @return `true` if x is a Probe, `false` otherwise + */ + def hasProbeTypeModifier(x: Data): Boolean = x.probeInfo.nonEmpty + /** Get an early guess for the name of this [[Data]] * * '''Warning: it is not guaranteed that this name will end up in the output FIRRTL or Verilog.''' diff --git a/firrtl/src/main/scala/firrtl/ir/IR.scala b/firrtl/src/main/scala/firrtl/ir/IR.scala index 86e85fe16fe..04161dc7d88 100644 --- a/firrtl/src/main/scala/firrtl/ir/IR.scala +++ b/firrtl/src/main/scala/firrtl/ir/IR.scala @@ -327,6 +327,21 @@ object Print { } } +case class ProbeDefine(info: Info, sink: Expression, probeExpr: Expression) extends Statement with UseSerializer +case class ProbeExpr(expr: Expression, tpe: Type = UnknownType) extends Expression with UseSerializer + +case class RWProbeExpr(expr: Expression, tpe: Type = UnknownType) extends Expression with UseSerializer +case class ProbeRead(expr: Expression, tpe: Type = UnknownType) extends Expression with UseSerializer + +case class ProbeForceInitial(info: Info, probe: Expression, value: Expression) extends Statement with UseSerializer +case class ProbeReleaseInitial(info: Info, probe: Expression) extends Statement with UseSerializer +case class ProbeForce(info: Info, clock: Expression, cond: Expression, probe: Expression, value: Expression) + extends Statement + with UseSerializer +case class ProbeRelease(info: Info, clock: Expression, cond: Expression, probe: Expression) + extends Statement + with UseSerializer + // formal object Formal extends Enumeration { val Assert = Value("assert") @@ -444,6 +459,9 @@ object GroundType { } abstract class AggregateType extends Type +case class ProbeType(underlying: Type) extends Type with UseSerializer +case class RWProbeType(underlying: Type) extends Type with UseSerializer + case class ConstType(underlying: Type) extends Type with UseSerializer case class UIntType(width: Width) extends GroundType with UseSerializer diff --git a/firrtl/src/main/scala/firrtl/ir/Serializer.scala b/firrtl/src/main/scala/firrtl/ir/Serializer.scala index 72b46958838..3445c6cd843 100644 --- a/firrtl/src/main/scala/firrtl/ir/Serializer.scala +++ b/firrtl/src/main/scala/firrtl/ir/Serializer.scala @@ -98,7 +98,10 @@ object Serializer { case ValidIf(cond, value, _) => b ++= "validif("; s(cond); b ++= ", "; s(value); b += ')' case SIntLiteral(value, width) => b ++= "SInt"; s(width); b ++= "(\"h"; b ++= value.toString(16); b ++= "\")" - case other => b ++= other.serialize // Handle user-defined nodes + case ProbeExpr(expr, _) => b ++= "probe("; s(expr); b += ')' + case RWProbeExpr(expr, _) => b ++= "rwprobe("; s(expr); b += ')' + case ProbeRead(expr, _) => b ++= "read("; s(expr); b += ')' + case other => b ++= other.serialize // Handle user-defined nodes } // Helper for some not-real Statements that only exist for Serialization @@ -281,6 +284,16 @@ object Serializer { case firrtl.CDefMPort(info, name, _, mem, exps, direction) => b ++= direction.serialize; b ++= " mport "; b ++= name; b ++= " = "; b ++= mem b += '['; s(exps.head); b ++= "], "; s(exps(1)); s(info) + case ProbeDefine(info, sink, probeExpr) => + b ++= "define "; s(sink); b ++= " = "; s(probeExpr); s(info) + case ProbeForceInitial(info, probe, value) => + b ++= "force_initial("; s(probe); b ++= ", "; s(value); b += ')'; s(info) + case ProbeReleaseInitial(info, probe) => + b ++= "release_initial("; s(probe); b += ')'; s(info) + case ProbeForce(info, clock, cond, probe, value) => + b ++= "force("; s(clock); b ++= ", "; s(cond); b ++= ", "; s(probe); b ++= ", "; s(value); b += ')'; s(info) + case ProbeRelease(info, clock, cond, probe) => + b ++= "release("; s(clock); b ++= ", "; s(cond); b ++= ", "; s(probe); b += ')'; s(info) case other => b ++= other.serialize // Handle user-defined nodes } @@ -312,6 +325,8 @@ object Serializer { private def s(node: Type)(implicit b: StringBuilder, indent: Int): Unit = node match { // Types + case ProbeType(underlying: Type) => b ++= "Probe<"; s(underlying); b += '>' + case RWProbeType(underlying: Type) => b ++= "RWProbe<"; s(underlying); b += '>' case ConstType(underlying: Type) => b ++= "const "; s(underlying) case UIntType(width: Width) => b ++= "UInt"; s(width) case SIntType(width: Width) => b ++= "SInt"; s(width) diff --git a/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala b/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala index b57496f379a..f53859ba0a4 100644 --- a/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala +++ b/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala @@ -207,6 +207,48 @@ class SerializerSpec extends AnyFlatSpec with Matchers { SMemTestCircuit.circuit(ReadUnderWrite.Old).serialize should include("old") } + it should "support emitting Probe/RWProbe types and related expressions/statements" in { + val probeInt = DefWire(NoInfo, "foo", ProbeType(UIntType(IntWidth(3)))) + Serializer.serialize(probeInt) should be("wire foo : Probe>") + + val rwProbeBundle = Port( + NoInfo, + "foo", + Output, + RWProbeType(BundleType(Seq(Field("bar", Default, UIntType(IntWidth(32)))))) + ) + Serializer.serialize(rwProbeBundle) should be("output foo : RWProbe<{ bar : UInt<32>}>") + + val probeVec = Port( + NoInfo, + "foo", + Output, + RWProbeType(VectorType(UIntType(IntWidth(32)), 4)) + ) + Serializer.serialize(probeVec) should be("output foo : RWProbe[4]>") + + val probeDefine = ProbeDefine(NoInfo, SubField(Reference("c"), "in"), ProbeExpr(Reference("in"))) + Serializer.serialize(probeDefine) should be("define c.in = probe(in)") + + val rwProbeDefine = ProbeDefine(NoInfo, SubField(Reference("c"), "in"), RWProbeExpr(Reference("in"))) + Serializer.serialize(rwProbeDefine) should be("define c.in = rwprobe(in)") + + val probeRead = Connect(NoInfo, Reference("out"), ProbeRead(Reference("c.out"))) + Serializer.serialize(probeRead) should be("out <= read(c.out)") + + val probeForceInitial = ProbeForceInitial(NoInfo, Reference("outProbe"), UIntLiteral(100, IntWidth(8))) + Serializer.serialize(probeForceInitial) should be("force_initial(outProbe, UInt<8>(\"h64\"))") + + val probeReleaseInitial = ProbeReleaseInitial(NoInfo, Reference("outProbe")) + Serializer.serialize(probeReleaseInitial) should be("release_initial(outProbe)") + + val probeForce = ProbeForce(NoInfo, Reference("clock"), Reference("cond"), Reference("outProbe"), Reference("in")) + Serializer.serialize(probeForce) should be("force(clock, cond, outProbe, in)") + + val probeRelease = ProbeRelease(NoInfo, Reference("clock"), Reference("cond"), Reference("outProbe")) + Serializer.serialize(probeRelease) should be("release(clock, cond, outProbe)") + } + it should "support lazy serialization" in { var stmtSerialized = false case class HackStmt(stmt: Statement) extends Statement { diff --git a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala index b693723ce13..36d70d13b5e 100644 --- a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala +++ b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala @@ -34,6 +34,18 @@ class UIntTransform(val c: Context) extends SourceInfoTransformMacro { } } +class ProbeTransform(val c: Context) extends SourceInfoTransformMacro { + import c.universe._ + def sourceApply[T: c.WeakTypeTag](source: c.Tree): c.Tree = { + val tpe = weakTypeOf[T] + q"$thisObj.do_apply[$tpe]($source)($implicitSourceInfo)" + } + def sourceRead[T: c.WeakTypeTag](source: c.Tree): c.Tree = { + val tpe = weakTypeOf[T] + q"$thisObj.do_read[$tpe]($source)($implicitSourceInfo)" + } +} + // Workaround for https://github.com/sbt/sbt/issues/3966 object InstTransform // Module instantiation transform diff --git a/src/test/scala/chiselTests/ConstSpec.scala b/src/test/scala/chiselTests/ConstSpec.scala index ccc75016043..2991c8a972e 100644 --- a/src/test/scala/chiselTests/ConstSpec.scala +++ b/src/test/scala/chiselTests/ConstSpec.scala @@ -1,9 +1,10 @@ package chiselTests import chisel3._ -import chiselTests.{ChiselFlatSpec, Utils} +import chisel3.probe.Probe import chisel3.experimental.BundleLiterals.AddBundleLiteralConstructor import chisel3.experimental.VecLiterals.AddVecLiteralConstructor +import chiselTests.{ChiselFlatSpec, Utils} import circt.stage.ChiselStage class ConstSpec extends ChiselFlatSpec with Utils { @@ -59,4 +60,16 @@ class ConstSpec extends ChiselFlatSpec with Utils { exc.getMessage should be("Mem type cannot be const.") } + "Const of Probe" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new Module { + val p = Const(Probe(Bool())) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot create Const of a Probe.") + } + } diff --git a/src/test/scala/chiselTests/ProbeSpec.scala b/src/test/scala/chiselTests/ProbeSpec.scala new file mode 100644 index 00000000000..825092fd149 --- /dev/null +++ b/src/test/scala/chiselTests/ProbeSpec.scala @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.probe._ +import circt.stage.ChiselStage + +class ProbeSpec extends ChiselFlatSpec with Utils { + // Strip SourceInfos and split into lines + private def processChirrtl(chirrtl: String): Array[String] = + chirrtl.split('\n').map(line => line.takeWhile(_ != '@').trim()) + + "Simple probe usage" should "work" in { + val chirrtl = ChiselStage.emitCHIRRTL( + new RawModule { + val a = IO(Output(RWProbe(Bool()))) + + val w = WireInit(Bool(), false.B) + val w_probe = RWProbeValue(w) + define(a, w_probe) + }, + Array("--full-stacktrace") + ) + processChirrtl(chirrtl) should contain("define a = rwprobe(w)") + } + + "U-Turn example" should "emit FIRRTL probe statements and expressions" in { + val chirrtl = ChiselStage.emitCHIRRTL( + new Module { + + val io = IO(new Bundle { + val x = Input(Bool()) + val y = Output(Bool()) + }) + + class UTurn() extends RawModule { + val io = IO(new Bundle { + val in = Input(RWProbe(Bool())) + val out = Output(RWProbe(Bool())) + }) + define(io.out, io.in) + } + + val u1 = Module(new UTurn()) + val u2 = Module(new UTurn()) + + val n = RWProbeValue(io.x) + + define(u1.io.in, n) + define(u2.io.in, u1.io.out) + + io.y := read(u2.io.out) + + forceInitial(u1.io.out, false.B) + + releaseInitial(u1.io.out) + + force(clock, io.x, u2.io.out, u1.io.out) + release(clock, io.y, u2.io.out) + }, + Array("--full-stacktrace") + ) + + (processChirrtl(chirrtl) should contain).allOf( + "output io : { flip in : RWProbe>, out : RWProbe>}", + "define u1.io.in = rwprobe(io.x)", + "define u2.io.in = u1.io.out", + "io.y <= read(u2.io.out)", + "force_initial(u1.io.out, UInt<1>(\"h0\"))", + "release_initial(u1.io.out)", + "force(clock, io.x, u2.io.out, u1.io.out)", + "release(clock, io.y, u2.io.out)" + ) + } + + "Probe methods in when contexts" should "work" in { + val chirrtl = ChiselStage.emitCHIRRTL( + new RawModule { + val in = IO(Input(Bool())) + val out = IO(Output(RWProbe(Bool()))) + + val w = WireInit(Bool(), false.B) + + when(in) { + define(out, RWProbeValue(in)) + w := read(out) + } + }, + Array("--full-stacktrace") + ) + (processChirrtl(chirrtl) should contain).allOf( + "when in :", + "define out = rwprobe(in)", + "w <= read(out)" + ) + } + + "Subfields of probed bundles" should "be accessible" in { + val chirrtl = ChiselStage.emitCHIRRTL( + new RawModule { + + class FooBundle() extends Bundle { + val a = Bool() + val b = Bool() + } + + class Foo() extends RawModule { + val p = IO(Output(Probe(new FooBundle()))) + val x = Wire(new FooBundle()) + define(p, ProbeValue(x)) + } + + val x = IO(Output(Bool())) + val y = IO(Output(Probe(Bool()))) + val f = Module(new Foo()) + x := read(f.p.b) + define(y, f.p.b) + }, + Array("--full-stacktrace") + ) + + (processChirrtl(chirrtl) should contain).allOf( + "output p : Probe<{ a : UInt<1>, b : UInt<1>}>", + "wire x : { a : UInt<1>, b : UInt<1>}", + "define p = probe(x)", + "x <= read(f.p.b)", + "define y = f.p.b" + ) + } + + "Subindices of probed vectors" should "be accessible" in { + val chirrtl = ChiselStage.emitCHIRRTL( + new RawModule { + class VecChild() extends RawModule { + val p = IO(Output(Vec(2, Probe(Vec(2, UInt(16.W)))))) + } + + val outProbe = IO(Output(Probe(UInt(16.W)))) + val child = Module(new VecChild()) + define(outProbe, child.p(0)(1)) + }, + Array("--full-stacktrace") + ) + + (processChirrtl(chirrtl) should contain).allOf( + "output p : Probe[2]>[2]", + "output outProbe : Probe>", + "define outProbe = child.p[0][1]" + ) + } + + "Properly excluded bulk connectors" should "work with Bundles containing probes" in { + val chirrtl = ChiselStage.emitCHIRRTL( + new RawModule { + class FooBundle() extends Bundle { + val bar = Probe(Bool()) + val baz = UInt(4.W) + val qux = Flipped(Probe(UInt(4.W))) + val fizz = Flipped(Bool()) + } + val io = IO(new Bundle { + val in = Flipped(new FooBundle()) + val a = new FooBundle() + val b = new FooBundle() + val c = new FooBundle() + val d = Output(new FooBundle()) + }) + + io.a.exclude(_.bar, _.qux) :<>= io.in.exclude(_.bar, _.qux) + io.b.exclude(_.bar, _.qux) :<= io.in.exclude(_.bar, _.qux) + io.c.excludeProbes :>= io.in.excludeProbes + io.d.excludeProbes :#= io.in.excludeProbes + }, + Array("--full-stacktrace") + ) + + (processChirrtl(chirrtl) should contain).allOf( + "io.in.fizz <= io.a.fizz", + "io.a.baz <= io.in.baz", + "io.b.baz <= io.in.baz", + "io.in.fizz <= io.c.fizz", + "io.d.fizz <= io.in.fizz", + "io.d.baz <= io.in.baz" + ) + } + + "Improperly excluded :<>= connector" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + class FooBundle extends Bundle { + val bar = Probe(Bool()) + val baz = UInt(4.W) + } + val io = IO(new Bundle { + val in = Input(new FooBundle) + val out = Output(new FooBundle) + }) + io.out :<>= io.in + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot use connectables with probe types. Exclude them prior to connection.") + } + + ":= connector with probe" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val io = IO(new Bundle { + val in = Input(Bool()) + val out = Output(Probe(Bool())) + }) + io.out := io.in + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be( + "Connection between sink (ProbeSpec_Anon.io.out: IO[Bool]) and source (ProbeSpec_Anon.io.in: IO[Bool]) failed @: Sink io.out in ProbeSpec_Anon of Probed type cannot participate in a mono connection (:=)" + ) + } + + ":= connector with aggregate of probe" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val io = IO(new Bundle { + val in = Input(Vec(2, Bool())) + val out = Output(Vec(2, Probe(Bool()))) + }) + io.out := io.in + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be( + "Connection between sink (ProbeSpec_Anon.io.out: IO[Bool[2]]) and source (ProbeSpec_Anon.io.in: IO[Bool[2]]) failed @: Sink io.out in ProbeSpec_Anon of Probed type cannot participate in a mono connection (:=)" + ) + } + + "<> connector" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val io = IO(new Bundle { + val in = Input(Vec(2, Probe(Bool()))) + val out = Output(Vec(2, Probe(Bool()))) + }) + io.out <> io.in + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be( + "Connection between left (ProbeSpec_Anon.io.out: IO[Bool[2]]) and source (ProbeSpec_Anon.io.in: IO[Bool[2]]) failed @Left of Probed type cannot participate in a bi connection (<>)" + ) + } + + "Probe define between non-connectable data types" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val p = IO(Output(Probe(UInt(4.W)))) + + val w = WireInit(Bool(), false.B) + val v = ProbeValue(w) + + define(p, v) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot define a probe on a non-equivalent type.") + } + + "Probe of a probe type" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val a = Output(RWProbe(Probe(Bool()))) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot probe a probe.") + } + + "Probes of aggregates containing probes" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val out = IO(Output(Probe(new Bundle { + val a = Probe(Bool()) + }))) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot create a probe of an aggregate containing a probe.") + } + + "Wire() of a probe" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val w = Wire(Probe(Bool())) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot make a wire of a Chisel type with a probe modifier.") + } + + "WireInit of a probe" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val w = WireInit(RWProbe(Bool()), false.B) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot make a wire of a Chisel type with a probe modifier.") + } + + "Reg() of a probe" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val w = Reg(RWProbe(Bool())) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot make a register of a Chisel type with a probe modifier.") + } + + "RegInit of a probe" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new Module { + val w = RegInit(Probe(Bool()), false.B) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot make a register of a Chisel type with a probe modifier.") + } + + "Memories of probes" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val mem = SyncReadMem(1024, RWProbe(Vec(4, UInt(32.W)))) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot make a Mem of a Chisel type with a probe modifier.") + } + + "Defining a Probe with a rwprobe()" should "work" in { + val chirrtl = ChiselStage.emitCHIRRTL( + new RawModule { + val in = IO(Input(Bool())) + val out = IO(Output(Probe(Bool()))) + define(out, RWProbeValue(in)) + }, + Array("--full-stacktrace") + ) + processChirrtl(chirrtl) should contain("define out = rwprobe(in)") + } + + "Defining a RWProbe with a probe()" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val in = IO(Input(Bool())) + val out = IO(Output(RWProbe(Bool()))) + define(out, ProbeValue(in)) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot use a non-writable probe expression to define a writable probe.") + } + + "Force of a non-writable Probe" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new Module { + val in = IO(Input(Bool())) + val out = IO(Output(Probe(Bool()))) + force(clock, in, out, in) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot force a non-writable Probe.") + } + + "Probes of Const type" should "work" in { + val chirrtl = ChiselStage.emitCHIRRTL( + new RawModule { + val out = IO(Probe(Const(Bool()))) + }, + Array("--full-stacktrace") + ) + processChirrtl(chirrtl) should contain("output out : Probe>") + } + + "RWProbes of Const type" should "fail" in { + val exc = intercept[chisel3.ChiselException] { + ChiselStage.emitCHIRRTL( + new RawModule { + val out = IO(RWProbe(Const(Bool()))) + }, + Array("--throw-on-first-error") + ) + } + exc.getMessage should be("Cannot create a writable probe of a const type.") + } + +}