From 12785c6b2b8e378a5aa9db1833df7486d8f2a486 Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Mon, 9 Jan 2023 22:57:22 +0000 Subject: Improve performance of ChiselEnum annotations (#2923) (#2927) ChiselEnums check if they should create annotations every time an instance of them is bound. Because so many annotations would be created, they check to see if an equivalent annotation has already been added to the annotations. Previously, this used a linear search of the annotations, now it uses a HashSet. (cherry picked from commit 96bde092e449281dc70ebdb05f21695468c3e5fa) Co-authored-by: Jack Koenig --- core/src/main/scala/chisel3/StrongEnum.scala | 6 ++++-- core/src/main/scala/chisel3/internal/Builder.scala | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'core/src/main') diff --git a/core/src/main/scala/chisel3/StrongEnum.scala b/core/src/main/scala/chisel3/StrongEnum.scala index 3c9f4105..c1967949 100644 --- a/core/src/main/scala/chisel3/StrongEnum.scala +++ b/core/src/main/scala/chisel3/StrongEnum.scala @@ -240,11 +240,13 @@ abstract class EnumType(private[chisel3] val factory: EnumFactory, selfAnnotatin case None => EnumComponentChiselAnnotation(this, enumTypeName) } - if (!Builder.annotations.contains(anno)) { + if (!Builder.enumAnnos.contains(anno)) { + Builder.enumAnnos += anno annotate(anno) } - if (!Builder.annotations.contains(factory.globalAnnotation)) { + if (!Builder.enumAnnos.contains(factory.globalAnnotation)) { + Builder.enumAnnos += factory.globalAnnotation annotate(factory.globalAnnotation) } } diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index d06b7992..fe7d7bea 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -485,6 +485,10 @@ private[chisel3] class DynamicContext( val newAnnotations = ArrayBuffer[ChiselMultiAnnotation]() var currentModule: Option[BaseModule] = None + // Enum annotations are added every time a StrongEnum is bound + // To keep the number down, we keep them unique in the annotations + val enumAnnos = mutable.HashSet[ChiselAnnotation]() + /** Contains a mapping from a elaborated module to their aspect * Set by [[ModuleAspect]] */ @@ -553,6 +557,8 @@ private[chisel3] object Builder extends LazyLogging { def components: ArrayBuffer[Component] = dynamicContext.components def annotations: ArrayBuffer[ChiselAnnotation] = dynamicContext.annotations + def enumAnnos: mutable.HashSet[ChiselAnnotation] = dynamicContext.enumAnnos + // TODO : Unify this with annotations in the future - done this way for backward compatability def newAnnotations: ArrayBuffer[ChiselMultiAnnotation] = dynamicContext.newAnnotations -- cgit v1.2.3 From 9a7945fd86fcad02da0556d8f4a30daa4b005f9d Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Tue, 10 Jan 2023 07:19:45 +0000 Subject: Check for Vec subaccess in NamedComponent and throw a nicer error. (backport #2907) (#2928) * Check for Vec subaccess in NamedComponent and throw a nicer error. (#2907) This would previously end up throwing an exception later, when trying to create a component name and realizing that it was invalid. Instead, this detects Vec subaccesses early, and gives a more precise error and suggestion. (cherry picked from commit d8c30961c7b293ee19024a487698630367ee71c6) # Conflicts: # core/src/main/scala/chisel3/internal/Builder.scala * Resolve backport conflicts Co-authored-by: Mike Urbach --- core/src/main/scala/chisel3/internal/Builder.scala | 46 ++++++++++++++++------ 1 file changed, 35 insertions(+), 11 deletions(-) (limited to 'core/src/main') diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index fe7d7bea..be2c4cfb 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -299,25 +299,31 @@ private[chisel3] trait HasId extends InstanceId { // Builder.deprecated mechanism, we have to create our own one off ErrorLog and print the // warning right away. // It's especially bad because --warnings-as-errors does not work with these warnings - val nameGuess = _computeName(None) match { - case Some(name) => s": '$name'" - case None => "" - } - val parentGuess = _parent match { - case Some(ViewParent) => s", in module '${reifyParent.pathName}'" - case Some(p) => s", in module '${p.pathName}'" - case None => "" - } val errors = new ErrorLog(false) val logger = new _root_.logger.Logger(this.getClass.getName) val msg = - "Accessing the .instanceName or .toTarget of non-hardware Data is deprecated" + nameGuess + parentGuess + ". " + + "Accessing the .instanceName or .toTarget of non-hardware Data is deprecated" + _errorContext + ". " + "This will become an error in Chisel 3.6." errors.deprecated(msg, None) errors.checkpoint(logger) _computeName(None).get } + private[chisel3] def _errorContext: String = { + val nameGuess: String = _computeName(None) match { + case Some(name) => s": '$name'" + case None => "" + } + + val parentGuess: String = _parent match { + case Some(ViewParent) => s", in module '${reifyParent.pathName}'" + case Some(p) => s", in module '${p.pathName}'" + case None => "" + } + + nameGuess + parentGuess + } + // Helper for reifying views if they map to a single Target private[chisel3] def reifyTarget: Option[Data] = this match { case d: Data => reifySingleData(d) // Only Data can be views @@ -393,13 +399,16 @@ private[chisel3] trait NamedComponent extends HasId { /** Returns a FIRRTL ComponentName that references this object * @note Should not be called until circuit elaboration is complete */ - final def toNamed: ComponentName = + final def toNamed: ComponentName = { + assertValidTarget() ComponentName(this.instanceName, ModuleName(this.parentModName, CircuitName(this.circuitName))) + } /** Returns a FIRRTL ReferenceTarget that references this object * @note Should not be called until circuit elaboration is complete */ final def toTarget: ReferenceTarget = { + assertValidTarget() val name = this.instanceName if (!validComponentName(name)) throwException(s"Illegal component name: $name (note: literals are illegal)") import _root_.firrtl.annotations.{Target, TargetToken} @@ -423,6 +432,21 @@ private[chisel3] trait NamedComponent extends HasId { case None => localTarget } } + + private def assertValidTarget(): Unit = { + val isVecSubaccess = getOptionRef.map { + case Index(_, _: ULit) => true // Vec literal indexing + case Index(_, _: Node) => true // Vec dynamic indexing + case _ => false + }.getOrElse(false) + + if (isVecSubaccess) { + throwException( + s"You cannot target Vec subaccess" + _errorContext + + ". Instead, assign it to a temporary (for example, with WireInit) and target the temporary." + ) + } + } } // Mutable global state for chisel that can appear outside a Builder context -- cgit v1.2.3 From d4570fb9d29371c35641ba79b76662f99677f192 Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Wed, 11 Jan 2023 22:40:32 +0000 Subject: Promote ChiselEnum user APIs from experimental (backport #2929) (#2931) * Promote ChiselEnum user APIs from experimental (#2929) They are commonly used and if we were ever to change them, we will need to go through a deprecation process anyway. Note that the EnumAnnotations remain in chisel3.experimental because, like all Chisel annotation support, they are slated to be deprecated and removed. (cherry picked from commit 424e9446f1675fe0168e22bdfbbe85db997376e6) # Conflicts: # docs/src/cookbooks/verilog-vs-chisel.md # docs/src/explanations/chisel-enum.md * Maintain binary compatbility, use aliases in chisel3 package * Deprecate the actual classes in chisel3.experimental * Also fix backport conflicts Co-authored-by: Jack Koenig --- core/src/main/scala/chisel3/Aggregate.scala | 2 +- core/src/main/scala/chisel3/Data.scala | 2 +- core/src/main/scala/chisel3/StrongEnum.scala | 466 -------------------- .../scala/chisel3/experimental/ChiselEnum.scala | 470 +++++++++++++++++++++ .../main/scala/chisel3/experimental/package.scala | 1 + core/src/main/scala/chisel3/internal/Builder.scala | 2 +- .../main/scala/chisel3/internal/MonoConnect.scala | 2 +- core/src/main/scala/chisel3/package.scala | 4 + 8 files changed, 479 insertions(+), 470 deletions(-) delete mode 100644 core/src/main/scala/chisel3/StrongEnum.scala create mode 100644 core/src/main/scala/chisel3/experimental/ChiselEnum.scala (limited to 'core/src/main') diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index b6836ea7..dbf6969f 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -8,7 +8,7 @@ import chisel3.experimental.dataview.{isView, reifySingleData, InvalidViewExcept import scala.collection.immutable.{SeqMap, VectorMap} import scala.collection.mutable.{HashSet, LinkedHashMap} import scala.language.experimental.macros -import chisel3.experimental.{BaseModule, BundleLiteralException, ChiselEnum, EnumType, OpaqueType, VecLiteralException} +import chisel3.experimental.{BaseModule, BundleLiteralException, OpaqueType, VecLiteralException} import chisel3.internal._ import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl._ diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index dddc0d5d..259e6545 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -5,7 +5,7 @@ package chisel3 import chisel3.experimental.dataview.reify import scala.language.experimental.macros -import chisel3.experimental.{Analog, BaseModule, DataMirror, EnumType, FixedPoint, Interval} +import chisel3.experimental.{Analog, BaseModule, DataMirror, FixedPoint, Interval} import chisel3.internal.Builder.pushCommand import chisel3.internal._ import chisel3.internal.firrtl._ diff --git a/core/src/main/scala/chisel3/StrongEnum.scala b/core/src/main/scala/chisel3/StrongEnum.scala deleted file mode 100644 index c1967949..00000000 --- a/core/src/main/scala/chisel3/StrongEnum.scala +++ /dev/null @@ -1,466 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.experimental - -import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context -import scala.collection.mutable -import chisel3._ -import chisel3.internal.Builder.pushOp -import chisel3.internal.firrtl.PrimOp._ -import chisel3.internal.firrtl._ -import chisel3.internal.sourceinfo._ -import chisel3.internal.{throwException, Binding, Builder, ChildBinding, ConstrainedBinding, InstanceId} -import firrtl.annotations._ - -object EnumAnnotations { - - /** An annotation for strong enum instances that are ''not'' inside of Vecs - * - * @param target the enum instance being annotated - * @param enumTypeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') - */ - case class EnumComponentAnnotation(target: Named, enumTypeName: String) extends SingleTargetAnnotation[Named] { - def duplicate(n: Named): EnumComponentAnnotation = this.copy(target = n) - } - - case class EnumComponentChiselAnnotation(target: InstanceId, enumTypeName: String) extends ChiselAnnotation { - def toFirrtl: EnumComponentAnnotation = EnumComponentAnnotation(target.toNamed, enumTypeName) - } - - /** An annotation for Vecs of strong enums. - * - * The ''fields'' parameter deserves special attention, since it may be difficult to understand. Suppose you create a the following Vec: - * - * {{{ - * VecInit(new Bundle { - * val e = MyEnum() - * val b = new Bundle { - * val inner_e = MyEnum() - * } - * val v = Vec(3, MyEnum()) - * } - * }}} - * - * Then, the ''fields'' parameter will be: ''Seq(Seq("e"), Seq("b", "inner_e"), Seq("v"))''. Note that for any Vec that doesn't contain Bundles, this field will simply be an empty Seq. - * - * @param target the Vec being annotated - * @param typeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') - * @param fields a list of all chains of elements leading from the Vec instance to its inner enum fields. - */ - case class EnumVecAnnotation(target: Named, typeName: String, fields: Seq[Seq[String]]) - extends SingleTargetAnnotation[Named] { - def duplicate(n: Named): EnumVecAnnotation = this.copy(target = n) - } - - case class EnumVecChiselAnnotation(target: InstanceId, typeName: String, fields: Seq[Seq[String]]) - extends ChiselAnnotation { - override def toFirrtl: EnumVecAnnotation = EnumVecAnnotation(target.toNamed, typeName, fields) - } - - /** An annotation for enum types (rather than enum ''instances''). - * - * @param typeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') - * @param definition a map describing which integer values correspond to which enum names - */ - case class EnumDefAnnotation(typeName: String, definition: Map[String, BigInt]) extends NoTargetAnnotation - - case class EnumDefChiselAnnotation(typeName: String, definition: Map[String, BigInt]) extends ChiselAnnotation { - override def toFirrtl: Annotation = EnumDefAnnotation(typeName, definition) - } -} -import EnumAnnotations._ - -abstract class EnumType(private[chisel3] val factory: EnumFactory, selfAnnotating: Boolean = true) extends Element { - - // Use getSimpleName instead of enumTypeName because for debugging purposes - // the fully qualified name isn't necessary (compared to for the - // Enum annotation), and it's more consistent with Bundle printing. - override def toString: String = { - litOption match { - case Some(value) => - factory.nameOfValue(value) match { - case Some(name) => s"${factory.getClass.getSimpleName.init}($value=$name)" - case None => stringAccessor(s"${factory.getClass.getSimpleName.init}($value=(invalid))") - } - case _ => stringAccessor(s"${factory.getClass.getSimpleName.init}") - } - } - - override def cloneType: this.type = factory().asInstanceOf[this.type] - - private[chisel3] def compop(sourceInfo: SourceInfo, op: PrimOp, other: EnumType): Bool = { - requireIsHardware(this, "bits operated on") - requireIsHardware(other, "bits operated on") - - if (!this.typeEquivalent(other)) { - throwException(s"Enum types are not equivalent: ${this.enumTypeName}, ${other.enumTypeName}") - } - - pushOp(DefPrim(sourceInfo, Bool(), op, this.ref, other.ref)) - } - - private[chisel3] override def typeEquivalent(that: Data): Boolean = { - this.getClass == that.getClass && - this.factory == that.asInstanceOf[EnumType].factory - } - - private[chisel3] override def connectFromBits( - that: Bits - )( - implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions - ): Unit = { - this := factory.apply(that.asUInt) - } - - final def ===(that: EnumType): Bool = macro SourceInfoTransform.thatArg - final def =/=(that: EnumType): Bool = macro SourceInfoTransform.thatArg - final def <(that: EnumType): Bool = macro SourceInfoTransform.thatArg - final def <=(that: EnumType): Bool = macro SourceInfoTransform.thatArg - final def >(that: EnumType): Bool = macro SourceInfoTransform.thatArg - final def >=(that: EnumType): Bool = macro SourceInfoTransform.thatArg - - def do_===(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = - compop(sourceInfo, EqualOp, that) - def do_=/=(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = - compop(sourceInfo, NotEqualOp, that) - def do_<(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = - compop(sourceInfo, LessOp, that) - def do_>(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = - compop(sourceInfo, GreaterOp, that) - def do_<=(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = - compop(sourceInfo, LessEqOp, that) - def do_>=(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = - compop(sourceInfo, GreaterEqOp, that) - - override def do_asUInt(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = - pushOp(DefPrim(sourceInfo, UInt(width), AsUIntOp, ref)) - - protected[chisel3] override def width: Width = factory.width - - def isValid(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { - if (litOption.isDefined) { - true.B - } else { - if (factory.isTotal) true.B else factory.all.map(this === _).reduce(_ || _) - } - } - - /** Test if this enumeration is equal to any of the values in a given sequence - * - * @param s a [[scala.collection.Seq$ Seq]] of enumeration values to look for - * @return a hardware [[Bool]] that indicates if this value matches any of the given values - */ - final def isOneOf(s: Seq[EnumType])(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { - VecInit(s.map(this === _)).asUInt().orR() - } - - /** Test if this enumeration is equal to any of the values given as arguments - * - * @param u1 the first value to look for - * @param u2 zero or more additional values to look for - * @return a hardware [[Bool]] that indicates if this value matches any of the given values - */ - final def isOneOf( - u1: EnumType, - u2: EnumType* - )( - implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions - ): Bool = isOneOf(u1 +: u2.toSeq) - - def next(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): this.type = { - if (litOption.isDefined) { - val index = factory.all.indexOf(this) - - if (index < factory.all.length - 1) { - factory.all(index + 1).asInstanceOf[this.type] - } else { - factory.all.head.asInstanceOf[this.type] - } - } else { - val enums_with_nexts = factory.all.zip(factory.all.tail :+ factory.all.head) - val next_enum = SeqUtils.priorityMux(enums_with_nexts.map { case (e, n) => (this === e, n) }) - next_enum.asInstanceOf[this.type] - } - } - - private[chisel3] def bindToLiteral(num: BigInt, w: Width): Unit = { - val lit = ULit(num, w) - lit.bindLitArg(this) - } - - override private[chisel3] def bind( - target: Binding, - parentDirection: SpecifiedDirection = SpecifiedDirection.Unspecified - ): Unit = { - super.bind(target, parentDirection) - - // Make sure we only annotate hardware and not literals - if (selfAnnotating && isSynthesizable && topBindingOpt.get.isInstanceOf[ConstrainedBinding]) { - annotateEnum() - } - } - - // This function conducts a depth-wise search to find all enum-type fields within a vector or bundle (or vector of bundles) - private def enumFields(d: Aggregate): Seq[Seq[String]] = d match { - case v: Vec[_] => - v.sample_element match { - case b: Bundle => enumFields(b) - case _ => Seq() - } - case b: Bundle => - b.elements.collect { - case (name, e: EnumType) if this.typeEquivalent(e) => Seq(Seq(name)) - case (name, v: Vec[_]) if this.typeEquivalent(v.sample_element) => Seq(Seq(name)) - case (name, b2: Bundle) => enumFields(b2).map(name +: _) - }.flatten.toSeq - } - - private def outerMostVec(d: Data = this): Option[Vec[_]] = { - val currentVecOpt = d match { - case v: Vec[_] => Some(v) - case _ => None - } - - d.binding match { - case Some(ChildBinding(parent)) => - outerMostVec(parent) match { - case outer @ Some(_) => outer - case None => currentVecOpt - } - case _ => currentVecOpt - } - } - - private def annotateEnum(): Unit = { - val anno = outerMostVec() match { - case Some(v) => EnumVecChiselAnnotation(v, enumTypeName, enumFields(v)) - case None => EnumComponentChiselAnnotation(this, enumTypeName) - } - - if (!Builder.enumAnnos.contains(anno)) { - Builder.enumAnnos += anno - annotate(anno) - } - - if (!Builder.enumAnnos.contains(factory.globalAnnotation)) { - Builder.enumAnnos += factory.globalAnnotation - annotate(factory.globalAnnotation) - } - } - - protected def enumTypeName: String = factory.enumTypeName - - def toPrintable: Printable = { - implicit val sourceInfo = UnlocatableSourceInfo - implicit val compileOptions = ExplicitCompileOptions.Strict - val allNames = factory.allNames.zip(factory.all) - val nameSize = allNames.map(_._1.length).max - def leftPad(str: String): String = { - str.reverse.padTo(nameSize, ' ').reverse - } - val allNamesPadded = allNames.map { case (name, value) => leftPad(name) -> value } - - val result = Wire(Vec(nameSize, UInt(8.W))).suggestName(s"_${enumTypeName}Printable") - result.foreach(_ := '?'.U) - - for ((name, value) <- allNamesPadded) { - when(this === value) { - for ((r, c) <- result.zip(name)) { - r := c.toChar.U - } - } - } - result.map(Character(_)).foldLeft(p"")(_ + _) - } -} - -abstract class EnumFactory { - class Type extends EnumType(this) - object Type { - def apply(): Type = EnumFactory.this.apply() - } - - private var id: BigInt = 0 - private[chisel3] var width: Width = 0.W - - private case class EnumRecord(inst: Type, name: String) - private val enumRecords = mutable.ArrayBuffer.empty[EnumRecord] - - private def enumNames = enumRecords.map(_.name).toSeq - private def enumValues = enumRecords.map(_.inst.litValue).toSeq - private def enumInstances = enumRecords.map(_.inst).toSeq - - private[chisel3] val enumTypeName = getClass.getName.init - - // Do all bitvectors of this Enum's width represent legal states? - private[chisel3] def isTotal: Boolean = { - (this.getWidth < 31) && // guard against Integer overflow - (enumRecords.size == (1 << this.getWidth)) - } - - private[chisel3] def globalAnnotation: EnumDefChiselAnnotation = - EnumDefChiselAnnotation(enumTypeName, (enumNames, enumValues).zipped.toMap) - - def getWidth: Int = width.get - - def all: Seq[Type] = enumInstances - /* Accessor for Seq of names in enumRecords */ - def allNames: Seq[String] = enumNames - - private[chisel3] def nameOfValue(id: BigInt): Option[String] = { - enumRecords.find(_.inst.litValue == id).map(_.name) - } - - protected def Value: Type = macro EnumMacros.ValImpl - protected def Value(id: UInt): Type = macro EnumMacros.ValCustomImpl - - protected def do_Value(name: String): Type = { - val result = new Type - - // We have to use UnknownWidth here, because we don't actually know what the final width will be - result.bindToLiteral(id, UnknownWidth()) - - enumRecords.append(EnumRecord(result, name)) - - width = (1.max(id.bitLength)).W - id += 1 - - result - } - - protected def do_Value(name: String, id: UInt): Type = { - // TODO: These throw ExceptionInInitializerError which can be confusing to the user. Get rid of the error, and just - // throw an exception - if (id.litOption.isEmpty) { - throwException(s"$enumTypeName defined with a non-literal type") - } - if (id.litValue < this.id) { - throwException(s"Enums must be strictly increasing: $enumTypeName") - } - - this.id = id.litValue - do_Value(name) - } - - def apply(): Type = new Type - - private def castImpl( - n: UInt, - warn: Boolean - )( - implicit sourceInfo: SourceInfo, - connectionCompileOptions: CompileOptions - ): Type = { - if (n.litOption.isDefined) { - enumInstances.find(_.litValue == n.litValue) match { - case Some(result) => result - case None => throwException(s"${n.litValue} is not a valid value for $enumTypeName") - } - } else if (!n.isWidthKnown) { - throwException(s"Non-literal UInts being cast to $enumTypeName must have a defined width") - } else if (n.getWidth > this.getWidth) { - throwException(s"The UInt being cast to $enumTypeName is wider than $enumTypeName's width ($getWidth)") - } else { - if (!Builder.suppressEnumCastWarning && warn && !this.isTotal) { - Builder.warning( - s"Casting non-literal UInt to $enumTypeName. You can use $enumTypeName.safe to cast without this warning." - ) - } - val glue = Wire(new UnsafeEnum(width)) - glue := n - val result = Wire(new Type) - result := glue - result - } - } - - /** Cast an [[UInt]] to the type of this Enum - * - * @note will give a Chisel elaboration time warning if the argument could hit invalid states - * @param n the UInt to cast - * @return the equivalent Enum to the value of the cast UInt - */ - def apply(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Type = - castImpl(n, warn = true) - - /** Safely cast an [[UInt]] to the type of this Enum - * - * @param n the UInt to cast - * @return the equivalent Enum to the value of the cast UInt and a Bool indicating if the - * Enum is valid - */ - def safe(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): (Type, Bool) = { - val t = castImpl(n, warn = false) - (t, t.isValid) - } -} - -private[chisel3] object EnumMacros { - def ValImpl(c: Context): c.Tree = { - import c.universe._ - - // Much thanks to michael_s for this solution: - // stackoverflow.com/questions/18450203/retrieve-the-name-of-the-value-a-scala-macro-invocation-will-be-assigned-to - val term = c.internal.enclosingOwner - val name = term.name.decodedName.toString.trim - - if (name.contains(" ")) { - c.abort(c.enclosingPosition, "Value cannot be called without assigning to an enum") - } - - q"""this.do_Value($name)""" - } - - def ValCustomImpl(c: Context)(id: c.Expr[UInt]): c.universe.Tree = { - import c.universe._ - - val term = c.internal.enclosingOwner - val name = term.name.decodedName.toString.trim - - if (name.contains(" ")) { - c.abort(c.enclosingPosition, "Value cannot be called without assigning to an enum") - } - - q"""this.do_Value($name, $id)""" - } -} - -// This is an enum type that can be connected directly to UInts. It is used as a "glue" to cast non-literal UInts -// to enums. -private[chisel3] class UnsafeEnum(override val width: Width) extends EnumType(UnsafeEnum, selfAnnotating = false) { - override def cloneType: this.type = new UnsafeEnum(width).asInstanceOf[this.type] -} -private object UnsafeEnum extends EnumFactory - -/** Suppress enum cast warnings - * - * Users should use [[EnumFactory.safe .safe]] when possible. - * - * This is primarily used for casting from [[UInt]] to a Bundle type that contains an Enum. - * {{{ - * class MyBundle extends Bundle { - * val addr = UInt(8.W) - * val op = OpEnum() - * } - * - * // Since this is a cast to a Bundle, cannot use OpCode.safe - * val bundle = suppressEnumCastWarning { - * someUInt.asTypeOf(new MyBundle) - * } - * }}} - */ -object suppressEnumCastWarning { - def apply[T](block: => T): T = { - val parentWarn = Builder.suppressEnumCastWarning - - Builder.suppressEnumCastWarning = true - - val res = block // execute block - - Builder.suppressEnumCastWarning = parentWarn - res - } -} diff --git a/core/src/main/scala/chisel3/experimental/ChiselEnum.scala b/core/src/main/scala/chisel3/experimental/ChiselEnum.scala new file mode 100644 index 00000000..37c6fb58 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/ChiselEnum.scala @@ -0,0 +1,470 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental + +import scala.language.experimental.macros +import scala.reflect.macros.blackbox.Context +import scala.collection.mutable +import chisel3._ +import chisel3.internal.Builder.pushOp +import chisel3.internal.firrtl.PrimOp._ +import chisel3.internal.firrtl._ +import chisel3.internal.sourceinfo._ +import chisel3.internal.{throwException, Binding, Builder, ChildBinding, ConstrainedBinding, InstanceId} +import firrtl.annotations._ + +object EnumAnnotations { + + /** An annotation for strong enum instances that are ''not'' inside of Vecs + * + * @param target the enum instance being annotated + * @param enumTypeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') + */ + case class EnumComponentAnnotation(target: Named, enumTypeName: String) extends SingleTargetAnnotation[Named] { + def duplicate(n: Named): EnumComponentAnnotation = this.copy(target = n) + } + + case class EnumComponentChiselAnnotation(target: InstanceId, enumTypeName: String) extends ChiselAnnotation { + def toFirrtl: EnumComponentAnnotation = EnumComponentAnnotation(target.toNamed, enumTypeName) + } + + /** An annotation for Vecs of strong enums. + * + * The ''fields'' parameter deserves special attention, since it may be difficult to understand. Suppose you create a the following Vec: + * + * {{{ + * VecInit(new Bundle { + * val e = MyEnum() + * val b = new Bundle { + * val inner_e = MyEnum() + * } + * val v = Vec(3, MyEnum()) + * } + * }}} + * + * Then, the ''fields'' parameter will be: ''Seq(Seq("e"), Seq("b", "inner_e"), Seq("v"))''. Note that for any Vec that doesn't contain Bundles, this field will simply be an empty Seq. + * + * @param target the Vec being annotated + * @param typeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') + * @param fields a list of all chains of elements leading from the Vec instance to its inner enum fields. + */ + case class EnumVecAnnotation(target: Named, typeName: String, fields: Seq[Seq[String]]) + extends SingleTargetAnnotation[Named] { + def duplicate(n: Named): EnumVecAnnotation = this.copy(target = n) + } + + case class EnumVecChiselAnnotation(target: InstanceId, typeName: String, fields: Seq[Seq[String]]) + extends ChiselAnnotation { + override def toFirrtl: EnumVecAnnotation = EnumVecAnnotation(target.toNamed, typeName, fields) + } + + /** An annotation for enum types (rather than enum ''instances''). + * + * @param typeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') + * @param definition a map describing which integer values correspond to which enum names + */ + case class EnumDefAnnotation(typeName: String, definition: Map[String, BigInt]) extends NoTargetAnnotation + + case class EnumDefChiselAnnotation(typeName: String, definition: Map[String, BigInt]) extends ChiselAnnotation { + override def toFirrtl: Annotation = EnumDefAnnotation(typeName, definition) + } +} + +import EnumAnnotations._ + +@deprecated("This type has moved to chisel3", "Chisel 3.5") +abstract class EnumType(private[chisel3] val factory: ChiselEnum, selfAnnotating: Boolean = true) extends Element { + + // Use getSimpleName instead of enumTypeName because for debugging purposes + // the fully qualified name isn't necessary (compared to for the + // Enum annotation), and it's more consistent with Bundle printing. + override def toString: String = { + litOption match { + case Some(value) => + factory.nameOfValue(value) match { + case Some(name) => s"${factory.getClass.getSimpleName.init}($value=$name)" + case None => stringAccessor(s"${factory.getClass.getSimpleName.init}($value=(invalid))") + } + case _ => stringAccessor(s"${factory.getClass.getSimpleName.init}") + } + } + + override def cloneType: this.type = factory().asInstanceOf[this.type] + + private[chisel3] def compop(sourceInfo: SourceInfo, op: PrimOp, other: EnumType): Bool = { + requireIsHardware(this, "bits operated on") + requireIsHardware(other, "bits operated on") + + if (!this.typeEquivalent(other)) { + throwException(s"Enum types are not equivalent: ${this.enumTypeName}, ${other.enumTypeName}") + } + + pushOp(DefPrim(sourceInfo, Bool(), op, this.ref, other.ref)) + } + + private[chisel3] override def typeEquivalent(that: Data): Boolean = { + this.getClass == that.getClass && + this.factory == that.asInstanceOf[EnumType].factory + } + + private[chisel3] override def connectFromBits( + that: Bits + )( + implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions + ): Unit = { + this := factory.apply(that.asUInt) + } + + final def ===(that: EnumType): Bool = macro SourceInfoTransform.thatArg + final def =/=(that: EnumType): Bool = macro SourceInfoTransform.thatArg + final def <(that: EnumType): Bool = macro SourceInfoTransform.thatArg + final def <=(that: EnumType): Bool = macro SourceInfoTransform.thatArg + final def >(that: EnumType): Bool = macro SourceInfoTransform.thatArg + final def >=(that: EnumType): Bool = macro SourceInfoTransform.thatArg + + def do_===(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + compop(sourceInfo, EqualOp, that) + def do_=/=(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + compop(sourceInfo, NotEqualOp, that) + def do_<(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + compop(sourceInfo, LessOp, that) + def do_>(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + compop(sourceInfo, GreaterOp, that) + def do_<=(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + compop(sourceInfo, LessEqOp, that) + def do_>=(that: EnumType)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + compop(sourceInfo, GreaterEqOp, that) + + override def do_asUInt(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = + pushOp(DefPrim(sourceInfo, UInt(width), AsUIntOp, ref)) + + protected[chisel3] override def width: Width = factory.width + + def isValid(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { + if (litOption.isDefined) { + true.B + } else { + if (factory.isTotal) true.B else factory.all.map(this === _).reduce(_ || _) + } + } + + /** Test if this enumeration is equal to any of the values in a given sequence + * + * @param s a [[scala.collection.Seq$ Seq]] of enumeration values to look for + * @return a hardware [[Bool]] that indicates if this value matches any of the given values + */ + final def isOneOf(s: Seq[EnumType])(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { + VecInit(s.map(this === _)).asUInt().orR() + } + + /** Test if this enumeration is equal to any of the values given as arguments + * + * @param u1 the first value to look for + * @param u2 zero or more additional values to look for + * @return a hardware [[Bool]] that indicates if this value matches any of the given values + */ + final def isOneOf( + u1: EnumType, + u2: EnumType* + )( + implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions + ): Bool = isOneOf(u1 +: u2.toSeq) + + def next(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): this.type = { + if (litOption.isDefined) { + val index = factory.all.indexOf(this) + + if (index < factory.all.length - 1) { + factory.all(index + 1).asInstanceOf[this.type] + } else { + factory.all.head.asInstanceOf[this.type] + } + } else { + val enums_with_nexts = factory.all.zip(factory.all.tail :+ factory.all.head) + val next_enum = SeqUtils.priorityMux(enums_with_nexts.map { case (e, n) => (this === e, n) }) + next_enum.asInstanceOf[this.type] + } + } + + private[chisel3] def bindToLiteral(num: BigInt, w: Width): Unit = { + val lit = ULit(num, w) + lit.bindLitArg(this) + } + + override private[chisel3] def bind( + target: Binding, + parentDirection: SpecifiedDirection = SpecifiedDirection.Unspecified + ): Unit = { + super.bind(target, parentDirection) + + // Make sure we only annotate hardware and not literals + if (selfAnnotating && isSynthesizable && topBindingOpt.get.isInstanceOf[ConstrainedBinding]) { + annotateEnum() + } + } + + // This function conducts a depth-wise search to find all enum-type fields within a vector or bundle (or vector of bundles) + private def enumFields(d: Aggregate): Seq[Seq[String]] = d match { + case v: Vec[_] => + v.sample_element match { + case b: Bundle => enumFields(b) + case _ => Seq() + } + case b: Bundle => + b.elements.collect { + case (name, e: EnumType) if this.typeEquivalent(e) => Seq(Seq(name)) + case (name, v: Vec[_]) if this.typeEquivalent(v.sample_element) => Seq(Seq(name)) + case (name, b2: Bundle) => enumFields(b2).map(name +: _) + }.flatten.toSeq + } + + private def outerMostVec(d: Data = this): Option[Vec[_]] = { + val currentVecOpt = d match { + case v: Vec[_] => Some(v) + case _ => None + } + + d.binding match { + case Some(ChildBinding(parent)) => + outerMostVec(parent) match { + case outer @ Some(_) => outer + case None => currentVecOpt + } + case _ => currentVecOpt + } + } + + private def annotateEnum(): Unit = { + val anno = outerMostVec() match { + case Some(v) => EnumVecChiselAnnotation(v, enumTypeName, enumFields(v)) + case None => EnumComponentChiselAnnotation(this, enumTypeName) + } + + if (!Builder.enumAnnos.contains(anno)) { + Builder.enumAnnos += anno + annotate(anno) + } + + if (!Builder.enumAnnos.contains(factory.globalAnnotation)) { + Builder.enumAnnos += factory.globalAnnotation + annotate(factory.globalAnnotation) + } + } + + protected def enumTypeName: String = factory.enumTypeName + + def toPrintable: Printable = { + implicit val sourceInfo = UnlocatableSourceInfo + implicit val compileOptions = ExplicitCompileOptions.Strict + val allNames = factory.allNames.zip(factory.all) + val nameSize = allNames.map(_._1.length).max + def leftPad(str: String): String = { + str.reverse.padTo(nameSize, ' ').reverse + } + val allNamesPadded = allNames.map { case (name, value) => leftPad(name) -> value } + + val result = Wire(Vec(nameSize, UInt(8.W))).suggestName(s"_${enumTypeName}Printable") + result.foreach(_ := '?'.U) + + for ((name, value) <- allNamesPadded) { + when(this === value) { + for ((r, c) <- result.zip(name)) { + r := c.toChar.U + } + } + } + result.map(Character(_)).foldLeft(p"")(_ + _) + } +} + +@deprecated("This type has been moved and renamed to chisel3.ChiselEnum", "Chisel 3.5") +abstract class EnumFactory { + class Type extends EnumType(this) + object Type { + def apply(): Type = EnumFactory.this.apply() + } + + private var id: BigInt = 0 + private[chisel3] var width: Width = 0.W + + private case class EnumRecord(inst: Type, name: String) + private val enumRecords = mutable.ArrayBuffer.empty[EnumRecord] + + private def enumNames = enumRecords.map(_.name).toSeq + private def enumValues = enumRecords.map(_.inst.litValue).toSeq + private def enumInstances = enumRecords.map(_.inst).toSeq + + private[chisel3] val enumTypeName = getClass.getName.init + + // Do all bitvectors of this Enum's width represent legal states? + private[chisel3] def isTotal: Boolean = { + (this.getWidth < 31) && // guard against Integer overflow + (enumRecords.size == (1 << this.getWidth)) + } + + private[chisel3] def globalAnnotation: EnumDefChiselAnnotation = + EnumDefChiselAnnotation(enumTypeName, (enumNames, enumValues).zipped.toMap) + + def getWidth: Int = width.get + + def all: Seq[Type] = enumInstances + /* Accessor for Seq of names in enumRecords */ + def allNames: Seq[String] = enumNames + + private[chisel3] def nameOfValue(id: BigInt): Option[String] = { + enumRecords.find(_.inst.litValue == id).map(_.name) + } + + protected def Value: Type = macro EnumMacros.ValImpl + protected def Value(id: UInt): Type = macro EnumMacros.ValCustomImpl + + protected def do_Value(name: String): Type = { + val result = new Type + + // We have to use UnknownWidth here, because we don't actually know what the final width will be + result.bindToLiteral(id, UnknownWidth()) + + enumRecords.append(EnumRecord(result, name)) + + width = (1.max(id.bitLength)).W + id += 1 + + result + } + + protected def do_Value(name: String, id: UInt): Type = { + // TODO: These throw ExceptionInInitializerError which can be confusing to the user. Get rid of the error, and just + // throw an exception + if (id.litOption.isEmpty) { + throwException(s"$enumTypeName defined with a non-literal type") + } + if (id.litValue < this.id) { + throwException(s"Enums must be strictly increasing: $enumTypeName") + } + + this.id = id.litValue + do_Value(name) + } + + def apply(): Type = new Type + + private def castImpl( + n: UInt, + warn: Boolean + )( + implicit sourceInfo: SourceInfo, + connectionCompileOptions: CompileOptions + ): Type = { + if (n.litOption.isDefined) { + enumInstances.find(_.litValue == n.litValue) match { + case Some(result) => result + case None => throwException(s"${n.litValue} is not a valid value for $enumTypeName") + } + } else if (!n.isWidthKnown) { + throwException(s"Non-literal UInts being cast to $enumTypeName must have a defined width") + } else if (n.getWidth > this.getWidth) { + throwException(s"The UInt being cast to $enumTypeName is wider than $enumTypeName's width ($getWidth)") + } else { + if (!Builder.suppressEnumCastWarning && warn && !this.isTotal) { + Builder.warning( + s"Casting non-literal UInt to $enumTypeName. You can use $enumTypeName.safe to cast without this warning." + ) + } + val glue = Wire(new UnsafeEnum(width)) + glue := n + val result = Wire(new Type) + result := glue + result + } + } + + /** Cast an [[UInt]] to the type of this Enum + * + * @note will give a Chisel elaboration time warning if the argument could hit invalid states + * @param n the UInt to cast + * @return the equivalent Enum to the value of the cast UInt + */ + def apply(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Type = + castImpl(n, warn = true) + + /** Safely cast an [[UInt]] to the type of this Enum + * + * @param n the UInt to cast + * @return the equivalent Enum to the value of the cast UInt and a Bool indicating if the + * Enum is valid + */ + def safe(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): (Type, Bool) = { + val t = castImpl(n, warn = false) + (t, t.isValid) + } +} + +private[chisel3] object EnumMacros { + def ValImpl(c: Context): c.Tree = { + import c.universe._ + + // Much thanks to michael_s for this solution: + // stackoverflow.com/questions/18450203/retrieve-the-name-of-the-value-a-scala-macro-invocation-will-be-assigned-to + val term = c.internal.enclosingOwner + val name = term.name.decodedName.toString.trim + + if (name.contains(" ")) { + c.abort(c.enclosingPosition, "Value cannot be called without assigning to an enum") + } + + q"""this.do_Value($name)""" + } + + def ValCustomImpl(c: Context)(id: c.Expr[UInt]): c.universe.Tree = { + import c.universe._ + + val term = c.internal.enclosingOwner + val name = term.name.decodedName.toString.trim + + if (name.contains(" ")) { + c.abort(c.enclosingPosition, "Value cannot be called without assigning to an enum") + } + + q"""this.do_Value($name, $id)""" + } +} + +// This is an enum type that can be connected directly to UInts. It is used as a "glue" to cast non-literal UInts +// to enums. +private[chisel3] class UnsafeEnum(override val width: Width) extends EnumType(UnsafeEnum, selfAnnotating = false) { + override def cloneType: this.type = new UnsafeEnum(width).asInstanceOf[this.type] +} +private object UnsafeEnum extends ChiselEnum + +/** Suppress enum cast warnings + * + * Users should use [[ChiselEnum.safe .safe]] when possible. + * + * This is primarily used for casting from [[UInt]] to a Bundle type that contains an Enum. + * {{{ + * class MyBundle extends Bundle { + * val addr = UInt(8.W) + * val op = OpEnum() + * } + * + * // Since this is a cast to a Bundle, cannot use OpCode.safe + * val bundle = suppressEnumCastWarning { + * someUInt.asTypeOf(new MyBundle) + * } + * }}} + */ +@deprecated("This type has moved to chisel3", "Chisel 3.5") +object suppressEnumCastWarning { + def apply[T](block: => T): T = { + val parentWarn = Builder.suppressEnumCastWarning + + Builder.suppressEnumCastWarning = true + + val res = block // execute block + + Builder.suppressEnumCastWarning = parentWarn + res + } +} diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index 42ec9666..2b493aab 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -22,6 +22,7 @@ package object experimental { implicit def fromDoubleToDoubleParam(x: Double): DoubleParam = DoubleParam(x) implicit def fromStringToStringParam(x: String): StringParam = StringParam(x) + @deprecated("This type has moved to chisel3", "Chisel 3.5") type ChiselEnum = EnumFactory // Rocket Chip-style clonemodule diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index be2c4cfb..ab1435c5 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -509,7 +509,7 @@ private[chisel3] class DynamicContext( val newAnnotations = ArrayBuffer[ChiselMultiAnnotation]() var currentModule: Option[BaseModule] = None - // Enum annotations are added every time a StrongEnum is bound + // Enum annotations are added every time a ChiselEnum is bound // To keep the number down, we keep them unique in the annotations val enumAnnos = mutable.HashSet[ChiselAnnotation]() diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala index 4e762a7c..a0cca4a6 100644 --- a/core/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -3,7 +3,7 @@ package chisel3.internal import chisel3._ -import chisel3.experimental.{Analog, BaseModule, EnumType, FixedPoint, Interval, UnsafeEnum} +import chisel3.experimental.{Analog, BaseModule, FixedPoint, Interval, UnsafeEnum} import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl.{Connect, Converter, DefInvalid} import chisel3.experimental.dataview.{isView, reify, reifyToAggregate} diff --git a/core/src/main/scala/chisel3/package.scala b/core/src/main/scala/chisel3/package.scala index 87024683..afffad1c 100644 --- a/core/src/main/scala/chisel3/package.scala +++ b/core/src/main/scala/chisel3/package.scala @@ -14,6 +14,10 @@ package object chisel3 { import scala.language.implicitConversions + type ChiselEnum = experimental.ChiselEnum + type EnumType = experimental.EnumType + val suppressEnumCastWarning = experimental.suppressEnumCastWarning + /** * These implicit classes allow one to convert [[scala.Int]] or [[scala.BigInt]] to * Chisel.UInt|Chisel.SInt by calling .asUInt|.asSInt on them, respectively. -- cgit v1.2.3