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(-) 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 +++++++++--- src/test/scala/chiselTests/VecToTargetSpec.scala | 86 ++++++++++++++++++++++ 2 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 src/test/scala/chiselTests/VecToTargetSpec.scala 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 diff --git a/src/test/scala/chiselTests/VecToTargetSpec.scala b/src/test/scala/chiselTests/VecToTargetSpec.scala new file mode 100644 index 00000000..20c6f306 --- /dev/null +++ b/src/test/scala/chiselTests/VecToTargetSpec.scala @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.stage.ChiselStage + +trait VecToTargetSpecUtils extends Utils { + this: ChiselFunSpec => + + class Foo extends RawModule { + val vec = IO(Input(Vec(4, Bool()))) + + // Index a Vec with a Scala literal. + val scalaLit = 0 + val vecSubaccessScalaLit = vec(scalaLit) + + // Index a Vec with a Chisel literal. + val chiselLit = 0.U + val vecSubaccessChiselLit = vec(chiselLit) + + // Index a Vec with a node. + val node = IO(Input(UInt(2.W))) + val vecSubaccessNode = vec(node) + + // Put an otherwise un-targetable Vec subaccess into a temp. + val vecSubaccessTmp = WireInit(vecSubaccessNode) + } + + val expectedError = "You cannot target Vec subaccess:" + + def conversionSucceeds(data: InstanceId) = { + describe(".toTarget") { + it("should convert successfully") { + data.toTarget + } + } + + describe(".toNamed") { + it("should convert successfully") { + data.toNamed + } + } + } + + def conversionFails(data: InstanceId) = { + describe(".toTarget") { + it("should fail to convert with a useful error message") { + (the[ChiselException] thrownBy extractCause[ChiselException] { + data.toTarget + }).getMessage should include(expectedError) + } + } + + describe(".toNamed") { + it("should fail to convert with a useful error message") { + (the[ChiselException] thrownBy extractCause[ChiselException] { + data.toNamed + }).getMessage should include(expectedError) + } + } + } +} + +class VecToTargetSpec extends ChiselFunSpec with VecToTargetSpecUtils { + describe("Vec subaccess") { + var foo: Foo = null + ChiselStage.elaborate { foo = new Foo; foo } + + describe("with a Scala literal") { + (it should behave).like(conversionSucceeds(foo.vecSubaccessScalaLit)) + } + + describe("with a Chisel literal") { + (it should behave).like(conversionFails(foo.vecSubaccessChiselLit)) + } + + describe("with a Node") { + (it should behave).like(conversionFails(foo.vecSubaccessNode)) + } + + describe("with an un-targetable construct that is assigned to a temporary") { + (it should behave).like(conversionSucceeds(foo.vecSubaccessTmp)) + } + } +} -- 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 + docs/src/cookbooks/cookbook.md | 5 +- docs/src/cookbooks/verilog-vs-chisel.md | 1 - docs/src/explanations/chisel-enum.md | 2 - src/test/scala/chiselTests/AsTypeOfTester.scala | 2 +- .../scala/chiselTests/BundleElementsSpec.scala | 2 +- src/test/scala/chiselTests/BundleLiteralSpec.scala | 2 +- src/test/scala/chiselTests/ChiselEnum.scala | 824 ++++++++++++++++++++ src/test/scala/chiselTests/DataEqualitySpec.scala | 2 +- src/test/scala/chiselTests/DataPrint.scala | 2 +- src/test/scala/chiselTests/StrongEnum.scala | 826 --------------------- src/test/scala/chiselTests/VecLiteralSpec.scala | 2 +- src/test/scala/chiselTests/WarningSpec.scala | 2 - .../scala/chiselTests/experimental/TraceSpec.scala | 1 - src/test/scala/cookbook/FSM.scala | 3 +- 22 files changed, 1312 insertions(+), 1313 deletions(-) delete mode 100644 core/src/main/scala/chisel3/StrongEnum.scala create mode 100644 core/src/main/scala/chisel3/experimental/ChiselEnum.scala create mode 100644 src/test/scala/chiselTests/ChiselEnum.scala delete mode 100644 src/test/scala/chiselTests/StrongEnum.scala 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. diff --git a/docs/src/cookbooks/cookbook.md b/docs/src/cookbooks/cookbook.md index e7485e66..5b8239a7 100644 --- a/docs/src/cookbooks/cookbook.md +++ b/docs/src/cookbooks/cookbook.md @@ -445,13 +445,12 @@ getVerilogString(new Top(new UsingCloneTypeBundle(UInt(8.W)))) ### How do I create a finite state machine (FSM)? -The advised way is to use [`ChiselEnum`](https://www.chisel-lang.org/api/latest/chisel3/experimental/index.html#ChiselEnum=chisel3.experimental.EnumFactory) to construct enumerated types representing the state of the FSM. -State transitions are then handled with [`switch`](https://www.chisel-lang.org/api/latest/chisel3/util/switch$.html)/[`is`](https://www.chisel-lang.org/api/latest/chisel3/util/is$.html) and [`when`](https://www.chisel-lang.org/api/latest/chisel3/when$.html)/[`.elsewhen`](https://www.chisel-lang.org/api/latest/chisel3/WhenContext.html#elsewhen(elseCond:=%3Echisel3.Bool)(block:=%3EUnit)(implicitsourceInfo:chisel3.internal.sourceinfo.SourceInfo,implicitcompileOptions:chisel3.CompileOptions):chisel3.WhenContext)/[`.otherwise`](https://www.chisel-lang.org/api/latest/chisel3/WhenContext.html#otherwise(block:=%3EUnit)(implicitsourceInfo:chisel3.internal.sourceinfo.SourceInfo,implicitcompileOptions:chisel3.CompileOptions):Unit). +The advised way is to use `ChiselEnum` to construct enumerated types representing the state of the FSM. +State transitions are then handled with `switch`/`is` and `when`/`.elsewhen`/`.otherwise`. ```scala mdoc:silent:reset import chisel3._ import chisel3.util.{switch, is} -import chisel3.experimental.ChiselEnum object DetectTwoOnes { object State extends ChiselEnum { diff --git a/docs/src/cookbooks/verilog-vs-chisel.md b/docs/src/cookbooks/verilog-vs-chisel.md index 1adf609e..75cc0ec8 100644 --- a/docs/src/cookbooks/verilog-vs-chisel.md +++ b/docs/src/cookbooks/verilog-vs-chisel.md @@ -15,7 +15,6 @@ This page serves as a quick introduction to Chisel for those familiar with Veril import chisel3._ import chisel3.util.{switch, is} import chisel3.stage.ChiselStage -import chisel3.experimental.ChiselEnum import chisel3.util.{Cat, Fill, DecoupledIO} ``` diff --git a/docs/src/explanations/chisel-enum.md b/docs/src/explanations/chisel-enum.md index 16b5570d..b76fd746 100644 --- a/docs/src/explanations/chisel-enum.md +++ b/docs/src/explanations/chisel-enum.md @@ -16,8 +16,6 @@ In contrast with `Chisel.util.Enum`, `ChiselEnum` are subclasses of `Data`, whic import chisel3._ import chisel3.util._ import chisel3.stage.ChiselStage -import chisel3.experimental.ChiselEnum -import chisel3.experimental.suppressEnumCastWarning ``` ```scala mdoc:invisible diff --git a/src/test/scala/chiselTests/AsTypeOfTester.scala b/src/test/scala/chiselTests/AsTypeOfTester.scala index 2141cac2..a1668914 100644 --- a/src/test/scala/chiselTests/AsTypeOfTester.scala +++ b/src/test/scala/chiselTests/AsTypeOfTester.scala @@ -3,7 +3,7 @@ package chiselTests import chisel3._ -import chisel3.experimental.{ChiselEnum, DataMirror, FixedPoint} +import chisel3.experimental.{DataMirror, FixedPoint} import chisel3.testers.BasicTester class AsTypeOfBundleTester extends BasicTester { diff --git a/src/test/scala/chiselTests/BundleElementsSpec.scala b/src/test/scala/chiselTests/BundleElementsSpec.scala index fab2e733..afca3d81 100644 --- a/src/test/scala/chiselTests/BundleElementsSpec.scala +++ b/src/test/scala/chiselTests/BundleElementsSpec.scala @@ -3,7 +3,7 @@ package chiselTests import chisel3._ -import chisel3.experimental.{ChiselEnum, FixedPoint} +import chisel3.experimental.FixedPoint import chisel3.stage.ChiselStage import chisel3.util.Decoupled import org.scalatest.exceptions.TestFailedException diff --git a/src/test/scala/chiselTests/BundleLiteralSpec.scala b/src/test/scala/chiselTests/BundleLiteralSpec.scala index bc6522bb..f90e230d 100644 --- a/src/test/scala/chiselTests/BundleLiteralSpec.scala +++ b/src/test/scala/chiselTests/BundleLiteralSpec.scala @@ -7,7 +7,7 @@ import chisel3.stage.ChiselStage import chisel3.testers.BasicTester import chisel3.experimental.BundleLiterals._ import chisel3.experimental.VecLiterals.AddVecLiteralConstructor -import chisel3.experimental.{BundleLiteralException, ChiselEnum, ChiselRange, FixedPoint, Interval} +import chisel3.experimental.{BundleLiteralException, ChiselRange, FixedPoint, Interval} class BundleLiteralSpec extends ChiselFlatSpec with Utils { object MyEnum extends ChiselEnum { diff --git a/src/test/scala/chiselTests/ChiselEnum.scala b/src/test/scala/chiselTests/ChiselEnum.scala new file mode 100644 index 00000000..dbad273b --- /dev/null +++ b/src/test/scala/chiselTests/ChiselEnum.scala @@ -0,0 +1,824 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.experimental.AffectsChiselPrefix +import chisel3.internal.firrtl.UnknownWidth +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} +import chisel3.util._ +import chisel3.testers.BasicTester +import org.scalatest.Assertion +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +object EnumExample extends ChiselEnum { + val e0, e1, e2 = Value + + val e100 = Value(100.U) + val e101 = Value(101.U) + + val litValues = List(0.U, 1.U, 2.U, 100.U, 101.U) +} + +object OtherEnum extends ChiselEnum { + val otherEnum = Value +} + +object NonLiteralEnumType extends ChiselEnum { + val nonLit = Value(UInt()) +} + +object NonIncreasingEnum extends ChiselEnum { + val x = Value(2.U) + val y = Value(2.U) +} + +class SimpleConnector(inType: Data, outType: Data) extends Module { + val io = IO(new Bundle { + val in = Input(inType) + val out = Output(outType) + }) + + io.out := io.in +} + +class CastToUInt extends Module { + val io = IO(new Bundle { + val in = Input(EnumExample()) + val out = Output(UInt()) + }) + + io.out := io.in.asUInt() +} + +class CastFromLit(in: UInt) extends Module { + val io = IO(new Bundle { + val out = Output(EnumExample()) + val valid = Output(Bool()) + }) + + io.out := EnumExample(in) + io.valid := io.out.isValid +} + +class CastFromNonLit extends Module { + val io = IO(new Bundle { + val in = Input(UInt(EnumExample.getWidth.W)) + val out = Output(EnumExample()) + val valid = Output(Bool()) + }) + + io.out := EnumExample(io.in) + io.valid := io.out.isValid +} + +class SafeCastFromNonLit extends Module { + val io = IO(new Bundle { + val in = Input(UInt(EnumExample.getWidth.W)) + val out = Output(EnumExample()) + val valid = Output(Bool()) + }) + + val (enum, valid) = EnumExample.safe(io.in) + io.out := enum + io.valid := valid +} + +class CastFromNonLitWidth(w: Option[Int] = None) extends Module { + val width = if (w.isDefined) w.get.W else UnknownWidth() + + val io = IO(new Bundle { + val in = Input(UInt(width)) + val out = Output(EnumExample()) + }) + + io.out := EnumExample(io.in) +} + +class EnumOps(val xType: ChiselEnum, val yType: ChiselEnum) extends Module { + val io = IO(new Bundle { + val x = Input(xType()) + val y = Input(yType()) + + val lt = Output(Bool()) + val le = Output(Bool()) + val gt = Output(Bool()) + val ge = Output(Bool()) + val eq = Output(Bool()) + val ne = Output(Bool()) + }) + + io.lt := io.x < io.y + io.le := io.x <= io.y + io.gt := io.x > io.y + io.ge := io.x >= io.y + io.eq := io.x === io.y + io.ne := io.x =/= io.y +} + +object ChiselEnumFSM { + object State extends ChiselEnum { + val sNone, sOne1, sTwo1s = Value + + val correct_annotation_map = Map[String, BigInt]("sNone" -> 0, "sOne1" -> 1, "sTwo1s" -> 2) + } +} + +class ChiselEnumFSM extends Module { + import ChiselEnumFSM.State + import ChiselEnumFSM.State._ + + // This FSM detects two 1's one after the other + val io = IO(new Bundle { + val in = Input(Bool()) + val out = Output(Bool()) + val state = Output(State()) + }) + + val state = RegInit(sNone) + + io.out := (state === sTwo1s) + io.state := state + + switch(state) { + is(sNone) { + when(io.in) { + state := sOne1 + } + } + is(sOne1) { + when(io.in) { + state := sTwo1s + }.otherwise { + state := sNone + } + } + is(sTwo1s) { + when(!io.in) { + state := sNone + } + } + } +} + +object Opcode extends ChiselEnum { + val load = Value(0x03.U) + val imm = Value(0x13.U) + val auipc = Value(0x17.U) + val store = Value(0x23.U) + val reg = Value(0x33.U) + val lui = Value(0x37.U) + val br = Value(0x63.U) + val jalr = Value(0x67.U) + val jal = Value(0x6f.U) +} + +class LoadStoreExample extends Module { + val io = IO(new Bundle { + val opcode = Input(Opcode()) + val load_or_store = Output(Bool()) + }) + io.load_or_store := io.opcode.isOneOf(Opcode.load, Opcode.store) + printf(p"${io.opcode}") +} + +class CastToUIntTester extends BasicTester { + for ((enum, lit) <- EnumExample.all.zip(EnumExample.litValues)) { + val mod = Module(new CastToUInt) + mod.io.in := enum + assert(mod.io.out === lit) + } + stop() +} + +class CastFromLitTester extends BasicTester { + for ((enum, lit) <- EnumExample.all.zip(EnumExample.litValues)) { + val mod = Module(new CastFromLit(lit)) + assert(mod.io.out === enum) + assert(mod.io.valid === true.B) + } + stop() +} + +class CastFromNonLitTester extends BasicTester { + for ((enum, lit) <- EnumExample.all.zip(EnumExample.litValues)) { + val mod = Module(new CastFromNonLit) + mod.io.in := lit + assert(mod.io.out === enum) + assert(mod.io.valid === true.B) + } + + val invalid_values = + (1 until (1 << EnumExample.getWidth)).filter(!EnumExample.litValues.map(_.litValue).contains(_)).map(_.U) + + for (invalid_val <- invalid_values) { + val mod = Module(new CastFromNonLit) + mod.io.in := invalid_val + + assert(mod.io.valid === false.B) + } + + stop() +} + +class SafeCastFromNonLitTester extends BasicTester { + for ((enum, lit) <- EnumExample.all.zip(EnumExample.litValues)) { + val mod = Module(new SafeCastFromNonLit) + mod.io.in := lit + assert(mod.io.out === enum) + assert(mod.io.valid === true.B) + } + + val invalid_values = + (1 until (1 << EnumExample.getWidth)).filter(!EnumExample.litValues.map(_.litValue).contains(_)).map(_.U) + + for (invalid_val <- invalid_values) { + val mod = Module(new SafeCastFromNonLit) + mod.io.in := invalid_val + + assert(mod.io.valid === false.B) + } + + stop() +} + +class CastToInvalidEnumTester extends BasicTester { + val invalid_value: UInt = EnumExample.litValues.last + 1.U + Module(new CastFromLit(invalid_value)) +} + +class EnumOpsTester extends BasicTester { + for { + x <- EnumExample.all + y <- EnumExample.all + } { + val mod = Module(new EnumOps(EnumExample, EnumExample)) + mod.io.x := x + mod.io.y := y + + assert(mod.io.lt === (x.asUInt() < y.asUInt())) + assert(mod.io.le === (x.asUInt() <= y.asUInt())) + assert(mod.io.gt === (x.asUInt() > y.asUInt())) + assert(mod.io.ge === (x.asUInt() >= y.asUInt())) + assert(mod.io.eq === (x.asUInt() === y.asUInt())) + assert(mod.io.ne === (x.asUInt() =/= y.asUInt())) + } + stop() +} + +class InvalidEnumOpsTester extends BasicTester { + val mod = Module(new EnumOps(EnumExample, OtherEnum)) + mod.io.x := EnumExample.e0 + mod.io.y := OtherEnum.otherEnum +} + +class IsLitTester extends BasicTester { + for (e <- EnumExample.all) { + val wire = WireDefault(e) + + assert(e.isLit) + assert(!wire.isLit) + } + stop() +} + +class NextTester extends BasicTester { + for ((e, n) <- EnumExample.all.zip(EnumExample.litValues.tail :+ EnumExample.litValues.head)) { + assert(e.next.litValue == n.litValue) + val w = WireDefault(e) + assert(w.next === EnumExample(n)) + } + stop() +} + +class WidthTester extends BasicTester { + assert(EnumExample.getWidth == EnumExample.litValues.last.getWidth) + assert(EnumExample.all.forall(_.getWidth == EnumExample.litValues.last.getWidth)) + assert(EnumExample.all.forall { e => + val w = WireDefault(e) + w.getWidth == EnumExample.litValues.last.getWidth + }) + stop() +} + +class ChiselEnumFSMTester extends BasicTester { + import ChiselEnumFSM.State._ + + val dut = Module(new ChiselEnumFSM) + + // Inputs and expected results + val inputs: Vec[Bool] = VecInit(false.B, true.B, false.B, true.B, true.B, true.B, false.B, true.B, true.B, false.B) + val expected: Vec[Bool] = + VecInit(false.B, false.B, false.B, false.B, false.B, true.B, true.B, false.B, false.B, true.B) + val expected_state = VecInit(sNone, sNone, sOne1, sNone, sOne1, sTwo1s, sTwo1s, sNone, sOne1, sTwo1s) + + val cntr = Counter(inputs.length) + val cycle = cntr.value + + dut.io.in := inputs(cycle) + assert(dut.io.out === expected(cycle)) + assert(dut.io.state === expected_state(cycle)) + + when(cntr.inc()) { + stop() + } +} + +class IsOneOfTester extends BasicTester { + import EnumExample._ + + // is one of itself + assert(e0.isOneOf(e0)) + + // is one of Seq of itself + assert(e0.isOneOf(Seq(e0))) + assert(e0.isOneOf(Seq(e0, e0, e0, e0))) + assert(e0.isOneOf(e0, e0, e0, e0)) + + // is one of Seq of multiple elements + val subset = Seq(e0, e1, e2) + assert(e0.isOneOf(subset)) + assert(e1.isOneOf(subset)) + assert(e2.isOneOf(subset)) + + // is not element not in subset + assert(!e100.isOneOf(subset)) + assert(!e101.isOneOf(subset)) + + // test multiple elements with variable number of arguments + assert(e0.isOneOf(e0, e1, e2)) + assert(e1.isOneOf(e0, e1, e2)) + assert(e2.isOneOf(e0, e1, e2)) + assert(!e100.isOneOf(e0, e1, e2)) + assert(!e101.isOneOf(e0, e1, e2)) + + // is not another value + assert(!e0.isOneOf(e1)) + assert(!e2.isOneOf(e101)) + + stop() +} + +class ChiselEnumSpec extends ChiselFlatSpec with Utils { + import chisel3.internal.ChiselException + + behavior.of("ChiselEnum") + + it should "fail to instantiate non-literal enums with the Value function" in { + an[ExceptionInInitializerError] should be thrownBy extractCause[ExceptionInInitializerError] { + ChiselStage.elaborate(new SimpleConnector(NonLiteralEnumType(), NonLiteralEnumType())) + } + } + + it should "fail to instantiate non-increasing enums with the Value function" in { + an[ExceptionInInitializerError] should be thrownBy extractCause[ExceptionInInitializerError] { + ChiselStage.elaborate(new SimpleConnector(NonIncreasingEnum(), NonIncreasingEnum())) + } + } + + it should "connect enums of the same type" in { + ChiselStage.elaborate(new SimpleConnector(EnumExample(), EnumExample())) + ChiselStage.elaborate(new SimpleConnector(EnumExample(), EnumExample.Type())) + } + + it should "fail to connect a strong enum to a UInt" in { + a[ChiselException] should be thrownBy extractCause[ChiselException] { + ChiselStage.elaborate(new SimpleConnector(EnumExample(), UInt())) + } + } + + it should "fail to connect enums of different types" in { + a[ChiselException] should be thrownBy extractCause[ChiselException] { + ChiselStage.elaborate(new SimpleConnector(EnumExample(), OtherEnum())) + } + + a[ChiselException] should be thrownBy extractCause[ChiselException] { + ChiselStage.elaborate(new SimpleConnector(EnumExample.Type(), OtherEnum.Type())) + } + } + + it should "cast enums to UInts correctly" in { + assertTesterPasses(new CastToUIntTester) + } + + it should "cast literal UInts to enums correctly" in { + assertTesterPasses(new CastFromLitTester) + } + + it should "cast non-literal UInts to enums correctly and detect illegal casts" in { + assertTesterPasses(new CastFromNonLitTester) + } + + it should "safely cast non-literal UInts to enums correctly and detect illegal casts" in { + assertTesterPasses(new SafeCastFromNonLitTester) + } + + it should "prevent illegal literal casts to enums" in { + a[ChiselException] should be thrownBy extractCause[ChiselException] { + ChiselStage.elaborate(new CastToInvalidEnumTester) + } + } + + it should "only allow non-literal casts to enums if the width is smaller than or equal to the enum width" in { + for (w <- 0 to EnumExample.getWidth) + ChiselStage.elaborate(new CastFromNonLitWidth(Some(w))) + + a[ChiselException] should be thrownBy extractCause[ChiselException] { + ChiselStage.elaborate(new CastFromNonLitWidth) + } + + for (w <- (EnumExample.getWidth + 1) to (EnumExample.getWidth + 100)) { + a[ChiselException] should be thrownBy extractCause[ChiselException] { + ChiselStage.elaborate(new CastFromNonLitWidth(Some(w))) + } + } + } + + it should "execute enum comparison operations correctly" in { + assertTesterPasses(new EnumOpsTester) + } + + it should "fail to compare enums of different types" in { + a[ChiselException] should be thrownBy extractCause[ChiselException] { + ChiselStage.elaborate(new InvalidEnumOpsTester) + } + } + + it should "correctly check whether or not enums are literal" in { + assertTesterPasses(new IsLitTester) + } + + it should "return the correct next values for enums" in { + assertTesterPasses(new NextTester) + } + + it should "return the correct widths for enums" in { + assertTesterPasses(new WidthTester) + } + + it should "maintain Scala-level type-safety" in { + def foo(e: EnumExample.Type): Unit = {} + + "foo(EnumExample.e1); foo(EnumExample.e1.next)" should compile + "foo(OtherEnum.otherEnum)" shouldNot compile + } + + it should "prevent enums from being declared without names" in { + "object UnnamedEnum extends ChiselEnum { Value }" shouldNot compile + } + + "ChiselEnum FSM" should "work" in { + assertTesterPasses(new ChiselEnumFSMTester) + } + + "Casting a UInt to an Enum" should "warn if the UInt can express illegal states" in { + object MyEnum extends ChiselEnum { + val e0, e1, e2 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(MyEnum())) + out := MyEnum(in) + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should include("warn") + log should include("Casting non-literal UInt") + } + + it should "NOT warn if the Enum is total" in { + object TotalEnum extends ChiselEnum { + val e0, e1, e2, e3 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(TotalEnum())) + out := TotalEnum(in) + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + (log should not).include("warn") + } + + it should "suppress warning using suppressEnumCastWarning" in { + object TestEnum extends ChiselEnum { + val e0, e1, e2 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(TestEnum())) + suppressEnumCastWarning { + val res = TestEnum(in) + out := res + } + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + (log should not).include("warn") + } + + it should "suppress exactly one warning using suppressEnumCastWarning" in { + object TestEnum1 extends ChiselEnum { + val e0, e1, e2 = Value + } + object TestEnum2 extends ChiselEnum { + val e0, e1, e2 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out1 = IO(Output(TestEnum1())) + val out2 = IO(Output(TestEnum2())) + suppressEnumCastWarning { + out1 := TestEnum1(in) + } + out2 := TestEnum2(in) + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should include("warn") + log should include("TestEnum2") // not suppressed + (log should not).include("TestEnum1") // suppressed + } + + "Casting a UInt to an Enum with .safe" should "NOT warn" in { + object MyEnum extends ChiselEnum { + val e0, e1, e2 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(MyEnum())) + out := MyEnum.safe(in)._1 + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + (log should not).include("warn") + } + + it should "NOT generate any validity logic if the Enum is total" in { + object TotalEnum extends ChiselEnum { + val e0, e1, e2, e3 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(TotalEnum())) + val (res, valid) = TotalEnum.safe(in) + assert(valid.litToBoolean, "It should be true.B") + out := res + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + (log should not).include("warn") + } + + it should "correctly check if the enumeration is one of the values in a given sequence" in { + assertTesterPasses(new IsOneOfTester) + } + + it should "work with Printables" in { + ChiselStage.emitChirrtl(new LoadStoreExample) should include( + """printf(clock, UInt<1>("h1"), "%c%c%c%c%c", _chiselTestsOpcodePrintable[0], _chiselTestsOpcodePrintable[1], _chiselTestsOpcodePrintable[2], _chiselTestsOpcodePrintable[3], _chiselTestsOpcodePrintable[4])""" + ) + } +} + +class ChiselEnumAnnotator extends Module { + import EnumExample._ + + object LocalEnum extends ChiselEnum { + val le0, le1 = Value + val le2 = Value + val le100 = Value(100.U) + } + + val io = IO(new Bundle { + val in = Input(EnumExample()) + val out = Output(EnumExample()) + val other = Output(OtherEnum()) + val local = Output(LocalEnum()) + }) + + class Bund extends Bundle { + val field = EnumExample() + val other = OtherEnum() + val local = LocalEnum() + val vec = Vec(5, EnumExample()) + val inner_bundle1 = new Bundle { + val x = UInt(4.W) + val y = Vec(3, UInt(4.W)) + val e = EnumExample() + val v = Vec(3, EnumExample()) + } + val inner_bundle2 = new Bundle {} + val inner_bundle3 = new Bundle { + val x = Bool() + } + val inner_bundle4 = new Bundle { + val inner_inner_bundle = new Bundle {} + } + } + + val simple = Wire(EnumExample()) + val vec = VecInit(e0, e1, e2) + val vec_of_vecs = VecInit(VecInit(e0, e1), VecInit(e100, e101)) + + val bund = Wire(new Bund()) + val vec_of_bundles = Wire(Vec(5, new Bund())) + + io.out := e101 + io.other := OtherEnum.otherEnum + io.local := LocalEnum.le0 + simple := e100 + bund := DontCare + vec_of_bundles := DontCare + + // Make sure that dynamically indexing into a Vec of enums will not cause an elaboration error. + // The components created here will not be annotated. + val cycle = RegInit(0.U) + cycle := cycle + 1.U + + val indexed1 = vec_of_vecs(cycle)(cycle) + val indexed2 = vec_of_bundles(cycle) +} + +class ChiselEnumAnnotatorWithChiselName extends Module { + import EnumExample._ + + object LocalEnum extends ChiselEnum with AffectsChiselPrefix { + val le0, le1 = Value + val le2 = Value + val le100 = Value(100.U) + } + + val io = IO(new Bundle { + val in = Input(EnumExample()) + val out = Output(EnumExample()) + val other = Output(OtherEnum()) + val local = Output(LocalEnum()) + }) + + class Bund extends Bundle { + val field = EnumExample() + val other = OtherEnum() + val local = LocalEnum() + val vec = Vec(5, EnumExample()) + val inner_bundle1 = new Bundle { + val x = UInt(4.W) + val y = Vec(3, UInt(4.W)) + val e = EnumExample() + val v = Vec(3, EnumExample()) + } + val inner_bundle2 = new Bundle {} + val inner_bundle3 = new Bundle { + val x = Bool() + } + val inner_bundle4 = new Bundle { + val inner_inner_bundle = new Bundle {} + } + } + + val simple = Wire(EnumExample()) + val vec = VecInit(e0, e1, e2) + val vec_of_vecs = VecInit(VecInit(e0, e1), VecInit(e100, e101)) + + val bund = Wire(new Bund()) + val vec_of_bundles = Wire(Vec(5, new Bund())) + + io.out := e101 + io.other := OtherEnum.otherEnum + io.local := LocalEnum.le0 + simple := e100 + bund := DontCare + vec_of_bundles := DontCare + + // Make sure that dynamically indexing into a Vec of enums will not cause an elaboration error. + // The components created here will not be annotated. + val cycle = RegInit(0.U) + cycle := cycle + 1.U + + val indexed1 = vec_of_vecs(cycle)(cycle) + val indexed2 = vec_of_bundles(cycle) +} + +class ChiselEnumAnnotationSpec extends AnyFreeSpec with Matchers { + import chisel3.experimental.EnumAnnotations._ + import firrtl.annotations.{Annotation, ComponentName} + + val enumExampleName = "EnumExample" + val otherEnumName = "OtherEnum" + val localEnumName = "LocalEnum" + + case class CorrectDefAnno(typeName: String, definition: Map[String, BigInt]) + case class CorrectCompAnno(targetName: String, typeName: String) + case class CorrectVecAnno(targetName: String, typeName: String, fields: Set[Seq[String]]) + + val correctDefAnnos = Seq( + CorrectDefAnno(otherEnumName, Map("otherEnum" -> 0)), + CorrectDefAnno(enumExampleName, Map("e0" -> 0, "e1" -> 1, "e2" -> 2, "e100" -> 100, "e101" -> 101)), + CorrectDefAnno(localEnumName, Map("le0" -> 0, "le1" -> 1, "le2" -> 2, "le100" -> 100)) + ) + + val correctCompAnnos = Seq( + CorrectCompAnno("io.other", otherEnumName), + CorrectCompAnno("io.local", localEnumName), + CorrectCompAnno("io.out", enumExampleName), + CorrectCompAnno("io.in", enumExampleName), + CorrectCompAnno("simple", enumExampleName), + CorrectCompAnno("bund.field", enumExampleName), + CorrectCompAnno("bund.other", otherEnumName), + CorrectCompAnno("bund.local", localEnumName), + CorrectCompAnno("bund.inner_bundle1.e", enumExampleName) + ) + + val correctVecAnnos = Seq( + CorrectVecAnno("vec", enumExampleName, Set()), + CorrectVecAnno("vec_of_vecs", enumExampleName, Set()), + CorrectVecAnno( + "vec_of_bundles", + enumExampleName, + Set(Seq("field"), Seq("vec"), Seq("inner_bundle1", "e"), Seq("inner_bundle1", "v")) + ), + CorrectVecAnno("vec_of_bundles", otherEnumName, Set(Seq("other"))), + CorrectVecAnno("vec_of_bundles", localEnumName, Set(Seq("local"))), + CorrectVecAnno("bund.vec", enumExampleName, Set()), + CorrectVecAnno("bund.inner_bundle1.v", enumExampleName, Set()) + ) + + def printAnnos(annos: Seq[Annotation]) { + println("Enum definitions:") + annos.foreach { + case EnumDefAnnotation(enumTypeName, definition) => println(s"\t$enumTypeName: $definition") + case _ => + } + println("Enum components:") + annos.foreach { + case EnumComponentAnnotation(target, enumTypeName) => println(s"\t$target => $enumTypeName") + case _ => + } + println("Enum vecs:") + annos.foreach { + case EnumVecAnnotation(target, enumTypeName, fields) => println(s"\t$target[$fields] => $enumTypeName") + case _ => + } + } + + def isCorrect(anno: EnumDefAnnotation, correct: CorrectDefAnno): Boolean = { + (anno.typeName == correct.typeName || + anno.typeName.endsWith("." + correct.typeName) || + anno.typeName.endsWith("$" + correct.typeName)) && + anno.definition == correct.definition + } + + def isCorrect(anno: EnumComponentAnnotation, correct: CorrectCompAnno): Boolean = { + (anno.target match { + case ComponentName(name, _) => name == correct.targetName + case _ => throw new Exception("Unknown target type in EnumComponentAnnotation") + }) && + (anno.enumTypeName == correct.typeName || anno.enumTypeName.endsWith("." + correct.typeName) || + anno.enumTypeName.endsWith("$" + correct.typeName)) + } + + def isCorrect(anno: EnumVecAnnotation, correct: CorrectVecAnno): Boolean = { + (anno.target match { + case ComponentName(name, _) => name == correct.targetName + case _ => throw new Exception("Unknown target type in EnumVecAnnotation") + }) && + (anno.typeName == correct.typeName || anno.typeName.endsWith("." + correct.typeName) || + anno.typeName.endsWith("$" + correct.typeName)) && + anno.fields.map(_.toSeq).toSet == correct.fields + } + + def allCorrectDefs(annos: Seq[EnumDefAnnotation], corrects: Seq[CorrectDefAnno]): Boolean = { + corrects.forall(c => annos.exists(isCorrect(_, c))) && + correctDefAnnos.length == annos.length + } + + // Because temporary variables might be formed and annotated, we do not check that every component or vector + // annotation is accounted for in the correct results listed above + def allCorrectComps(annos: Seq[EnumComponentAnnotation], corrects: Seq[CorrectCompAnno]): Boolean = + corrects.forall(c => annos.exists(isCorrect(_, c))) + + def allCorrectVecs(annos: Seq[EnumVecAnnotation], corrects: Seq[CorrectVecAnno]): Boolean = + corrects.forall(c => annos.exists(isCorrect(_, c))) + + def test(strongEnumAnnotatorGen: () => Module) { + val annos = (new ChiselStage).execute( + Array("--target-dir", "test_run_dir", "--no-run-firrtl"), + Seq(ChiselGeneratorAnnotation(strongEnumAnnotatorGen)) + ) + + val enumDefAnnos = annos.collect { case a: EnumDefAnnotation => a } + val enumCompAnnos = annos.collect { case a: EnumComponentAnnotation => a } + val enumVecAnnos = annos.collect { case a: EnumVecAnnotation => a } + + allCorrectDefs(enumDefAnnos, correctDefAnnos) should be(true) + allCorrectComps(enumCompAnnos, correctCompAnnos) should be(true) + allCorrectVecs(enumVecAnnos, correctVecAnnos) should be(true) + + } + + "Test that strong enums annotate themselves appropriately" in { + test(() => new ChiselEnumAnnotator) + test(() => new ChiselEnumAnnotatorWithChiselName) + } +} diff --git a/src/test/scala/chiselTests/DataEqualitySpec.scala b/src/test/scala/chiselTests/DataEqualitySpec.scala index 4ac3292d..8fbbaf94 100644 --- a/src/test/scala/chiselTests/DataEqualitySpec.scala +++ b/src/test/scala/chiselTests/DataEqualitySpec.scala @@ -3,7 +3,7 @@ package chiselTests import chisel3._ import chisel3.experimental.VecLiterals._ import chisel3.experimental.BundleLiterals._ -import chisel3.experimental.{Analog, ChiselEnum, ChiselRange, FixedPoint, Interval} +import chisel3.experimental.{Analog, ChiselRange, FixedPoint, Interval} import chisel3.stage.ChiselStage import chisel3.testers.BasicTester import chisel3.util.Valid diff --git a/src/test/scala/chiselTests/DataPrint.scala b/src/test/scala/chiselTests/DataPrint.scala index 091722b8..82fa1519 100644 --- a/src/test/scala/chiselTests/DataPrint.scala +++ b/src/test/scala/chiselTests/DataPrint.scala @@ -5,7 +5,7 @@ package chiselTests import org.scalatest._ import chisel3._ -import chisel3.experimental.{ChiselEnum, FixedPoint} +import chisel3.experimental.FixedPoint import chisel3.experimental.BundleLiterals._ import chisel3.stage.ChiselStage import org.scalatest.matchers.should.Matchers diff --git a/src/test/scala/chiselTests/StrongEnum.scala b/src/test/scala/chiselTests/StrongEnum.scala deleted file mode 100644 index e9f412fe..00000000 --- a/src/test/scala/chiselTests/StrongEnum.scala +++ /dev/null @@ -1,826 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chiselTests - -import chisel3._ -import chisel3.experimental.ChiselEnum -import chisel3.experimental.AffectsChiselPrefix -import chisel3.experimental.suppressEnumCastWarning -import chisel3.internal.firrtl.UnknownWidth -import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} -import chisel3.util._ -import chisel3.testers.BasicTester -import org.scalatest.Assertion -import org.scalatest.freespec.AnyFreeSpec -import org.scalatest.matchers.should.Matchers - -object EnumExample extends ChiselEnum { - val e0, e1, e2 = Value - - val e100 = Value(100.U) - val e101 = Value(101.U) - - val litValues = List(0.U, 1.U, 2.U, 100.U, 101.U) -} - -object OtherEnum extends ChiselEnum { - val otherEnum = Value -} - -object NonLiteralEnumType extends ChiselEnum { - val nonLit = Value(UInt()) -} - -object NonIncreasingEnum extends ChiselEnum { - val x = Value(2.U) - val y = Value(2.U) -} - -class SimpleConnector(inType: Data, outType: Data) extends Module { - val io = IO(new Bundle { - val in = Input(inType) - val out = Output(outType) - }) - - io.out := io.in -} - -class CastToUInt extends Module { - val io = IO(new Bundle { - val in = Input(EnumExample()) - val out = Output(UInt()) - }) - - io.out := io.in.asUInt() -} - -class CastFromLit(in: UInt) extends Module { - val io = IO(new Bundle { - val out = Output(EnumExample()) - val valid = Output(Bool()) - }) - - io.out := EnumExample(in) - io.valid := io.out.isValid -} - -class CastFromNonLit extends Module { - val io = IO(new Bundle { - val in = Input(UInt(EnumExample.getWidth.W)) - val out = Output(EnumExample()) - val valid = Output(Bool()) - }) - - io.out := EnumExample(io.in) - io.valid := io.out.isValid -} - -class SafeCastFromNonLit extends Module { - val io = IO(new Bundle { - val in = Input(UInt(EnumExample.getWidth.W)) - val out = Output(EnumExample()) - val valid = Output(Bool()) - }) - - val (enum, valid) = EnumExample.safe(io.in) - io.out := enum - io.valid := valid -} - -class CastFromNonLitWidth(w: Option[Int] = None) extends Module { - val width = if (w.isDefined) w.get.W else UnknownWidth() - - val io = IO(new Bundle { - val in = Input(UInt(width)) - val out = Output(EnumExample()) - }) - - io.out := EnumExample(io.in) -} - -class EnumOps(val xType: ChiselEnum, val yType: ChiselEnum) extends Module { - val io = IO(new Bundle { - val x = Input(xType()) - val y = Input(yType()) - - val lt = Output(Bool()) - val le = Output(Bool()) - val gt = Output(Bool()) - val ge = Output(Bool()) - val eq = Output(Bool()) - val ne = Output(Bool()) - }) - - io.lt := io.x < io.y - io.le := io.x <= io.y - io.gt := io.x > io.y - io.ge := io.x >= io.y - io.eq := io.x === io.y - io.ne := io.x =/= io.y -} - -object StrongEnumFSM { - object State extends ChiselEnum { - val sNone, sOne1, sTwo1s = Value - - val correct_annotation_map = Map[String, BigInt]("sNone" -> 0, "sOne1" -> 1, "sTwo1s" -> 2) - } -} - -class StrongEnumFSM extends Module { - import StrongEnumFSM.State - import StrongEnumFSM.State._ - - // This FSM detects two 1's one after the other - val io = IO(new Bundle { - val in = Input(Bool()) - val out = Output(Bool()) - val state = Output(State()) - }) - - val state = RegInit(sNone) - - io.out := (state === sTwo1s) - io.state := state - - switch(state) { - is(sNone) { - when(io.in) { - state := sOne1 - } - } - is(sOne1) { - when(io.in) { - state := sTwo1s - }.otherwise { - state := sNone - } - } - is(sTwo1s) { - when(!io.in) { - state := sNone - } - } - } -} - -object Opcode extends ChiselEnum { - val load = Value(0x03.U) - val imm = Value(0x13.U) - val auipc = Value(0x17.U) - val store = Value(0x23.U) - val reg = Value(0x33.U) - val lui = Value(0x37.U) - val br = Value(0x63.U) - val jalr = Value(0x67.U) - val jal = Value(0x6f.U) -} - -class LoadStoreExample extends Module { - val io = IO(new Bundle { - val opcode = Input(Opcode()) - val load_or_store = Output(Bool()) - }) - io.load_or_store := io.opcode.isOneOf(Opcode.load, Opcode.store) - printf(p"${io.opcode}") -} - -class CastToUIntTester extends BasicTester { - for ((enum, lit) <- EnumExample.all.zip(EnumExample.litValues)) { - val mod = Module(new CastToUInt) - mod.io.in := enum - assert(mod.io.out === lit) - } - stop() -} - -class CastFromLitTester extends BasicTester { - for ((enum, lit) <- EnumExample.all.zip(EnumExample.litValues)) { - val mod = Module(new CastFromLit(lit)) - assert(mod.io.out === enum) - assert(mod.io.valid === true.B) - } - stop() -} - -class CastFromNonLitTester extends BasicTester { - for ((enum, lit) <- EnumExample.all.zip(EnumExample.litValues)) { - val mod = Module(new CastFromNonLit) - mod.io.in := lit - assert(mod.io.out === enum) - assert(mod.io.valid === true.B) - } - - val invalid_values = - (1 until (1 << EnumExample.getWidth)).filter(!EnumExample.litValues.map(_.litValue).contains(_)).map(_.U) - - for (invalid_val <- invalid_values) { - val mod = Module(new CastFromNonLit) - mod.io.in := invalid_val - - assert(mod.io.valid === false.B) - } - - stop() -} - -class SafeCastFromNonLitTester extends BasicTester { - for ((enum, lit) <- EnumExample.all.zip(EnumExample.litValues)) { - val mod = Module(new SafeCastFromNonLit) - mod.io.in := lit - assert(mod.io.out === enum) - assert(mod.io.valid === true.B) - } - - val invalid_values = - (1 until (1 << EnumExample.getWidth)).filter(!EnumExample.litValues.map(_.litValue).contains(_)).map(_.U) - - for (invalid_val <- invalid_values) { - val mod = Module(new SafeCastFromNonLit) - mod.io.in := invalid_val - - assert(mod.io.valid === false.B) - } - - stop() -} - -class CastToInvalidEnumTester extends BasicTester { - val invalid_value: UInt = EnumExample.litValues.last + 1.U - Module(new CastFromLit(invalid_value)) -} - -class EnumOpsTester extends BasicTester { - for { - x <- EnumExample.all - y <- EnumExample.all - } { - val mod = Module(new EnumOps(EnumExample, EnumExample)) - mod.io.x := x - mod.io.y := y - - assert(mod.io.lt === (x.asUInt() < y.asUInt())) - assert(mod.io.le === (x.asUInt() <= y.asUInt())) - assert(mod.io.gt === (x.asUInt() > y.asUInt())) - assert(mod.io.ge === (x.asUInt() >= y.asUInt())) - assert(mod.io.eq === (x.asUInt() === y.asUInt())) - assert(mod.io.ne === (x.asUInt() =/= y.asUInt())) - } - stop() -} - -class InvalidEnumOpsTester extends BasicTester { - val mod = Module(new EnumOps(EnumExample, OtherEnum)) - mod.io.x := EnumExample.e0 - mod.io.y := OtherEnum.otherEnum -} - -class IsLitTester extends BasicTester { - for (e <- EnumExample.all) { - val wire = WireDefault(e) - - assert(e.isLit) - assert(!wire.isLit) - } - stop() -} - -class NextTester extends BasicTester { - for ((e, n) <- EnumExample.all.zip(EnumExample.litValues.tail :+ EnumExample.litValues.head)) { - assert(e.next.litValue == n.litValue) - val w = WireDefault(e) - assert(w.next === EnumExample(n)) - } - stop() -} - -class WidthTester extends BasicTester { - assert(EnumExample.getWidth == EnumExample.litValues.last.getWidth) - assert(EnumExample.all.forall(_.getWidth == EnumExample.litValues.last.getWidth)) - assert(EnumExample.all.forall { e => - val w = WireDefault(e) - w.getWidth == EnumExample.litValues.last.getWidth - }) - stop() -} - -class StrongEnumFSMTester extends BasicTester { - import StrongEnumFSM.State._ - - val dut = Module(new StrongEnumFSM) - - // Inputs and expected results - val inputs: Vec[Bool] = VecInit(false.B, true.B, false.B, true.B, true.B, true.B, false.B, true.B, true.B, false.B) - val expected: Vec[Bool] = - VecInit(false.B, false.B, false.B, false.B, false.B, true.B, true.B, false.B, false.B, true.B) - val expected_state = VecInit(sNone, sNone, sOne1, sNone, sOne1, sTwo1s, sTwo1s, sNone, sOne1, sTwo1s) - - val cntr = Counter(inputs.length) - val cycle = cntr.value - - dut.io.in := inputs(cycle) - assert(dut.io.out === expected(cycle)) - assert(dut.io.state === expected_state(cycle)) - - when(cntr.inc()) { - stop() - } -} - -class IsOneOfTester extends BasicTester { - import EnumExample._ - - // is one of itself - assert(e0.isOneOf(e0)) - - // is one of Seq of itself - assert(e0.isOneOf(Seq(e0))) - assert(e0.isOneOf(Seq(e0, e0, e0, e0))) - assert(e0.isOneOf(e0, e0, e0, e0)) - - // is one of Seq of multiple elements - val subset = Seq(e0, e1, e2) - assert(e0.isOneOf(subset)) - assert(e1.isOneOf(subset)) - assert(e2.isOneOf(subset)) - - // is not element not in subset - assert(!e100.isOneOf(subset)) - assert(!e101.isOneOf(subset)) - - // test multiple elements with variable number of arguments - assert(e0.isOneOf(e0, e1, e2)) - assert(e1.isOneOf(e0, e1, e2)) - assert(e2.isOneOf(e0, e1, e2)) - assert(!e100.isOneOf(e0, e1, e2)) - assert(!e101.isOneOf(e0, e1, e2)) - - // is not another value - assert(!e0.isOneOf(e1)) - assert(!e2.isOneOf(e101)) - - stop() -} - -class StrongEnumSpec extends ChiselFlatSpec with Utils { - import chisel3.internal.ChiselException - - behavior.of("Strong enum tester") - - it should "fail to instantiate non-literal enums with the Value function" in { - an[ExceptionInInitializerError] should be thrownBy extractCause[ExceptionInInitializerError] { - ChiselStage.elaborate(new SimpleConnector(NonLiteralEnumType(), NonLiteralEnumType())) - } - } - - it should "fail to instantiate non-increasing enums with the Value function" in { - an[ExceptionInInitializerError] should be thrownBy extractCause[ExceptionInInitializerError] { - ChiselStage.elaborate(new SimpleConnector(NonIncreasingEnum(), NonIncreasingEnum())) - } - } - - it should "connect enums of the same type" in { - ChiselStage.elaborate(new SimpleConnector(EnumExample(), EnumExample())) - ChiselStage.elaborate(new SimpleConnector(EnumExample(), EnumExample.Type())) - } - - it should "fail to connect a strong enum to a UInt" in { - a[ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate(new SimpleConnector(EnumExample(), UInt())) - } - } - - it should "fail to connect enums of different types" in { - a[ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate(new SimpleConnector(EnumExample(), OtherEnum())) - } - - a[ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate(new SimpleConnector(EnumExample.Type(), OtherEnum.Type())) - } - } - - it should "cast enums to UInts correctly" in { - assertTesterPasses(new CastToUIntTester) - } - - it should "cast literal UInts to enums correctly" in { - assertTesterPasses(new CastFromLitTester) - } - - it should "cast non-literal UInts to enums correctly and detect illegal casts" in { - assertTesterPasses(new CastFromNonLitTester) - } - - it should "safely cast non-literal UInts to enums correctly and detect illegal casts" in { - assertTesterPasses(new SafeCastFromNonLitTester) - } - - it should "prevent illegal literal casts to enums" in { - a[ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate(new CastToInvalidEnumTester) - } - } - - it should "only allow non-literal casts to enums if the width is smaller than or equal to the enum width" in { - for (w <- 0 to EnumExample.getWidth) - ChiselStage.elaborate(new CastFromNonLitWidth(Some(w))) - - a[ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate(new CastFromNonLitWidth) - } - - for (w <- (EnumExample.getWidth + 1) to (EnumExample.getWidth + 100)) { - a[ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate(new CastFromNonLitWidth(Some(w))) - } - } - } - - it should "execute enum comparison operations correctly" in { - assertTesterPasses(new EnumOpsTester) - } - - it should "fail to compare enums of different types" in { - a[ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate(new InvalidEnumOpsTester) - } - } - - it should "correctly check whether or not enums are literal" in { - assertTesterPasses(new IsLitTester) - } - - it should "return the correct next values for enums" in { - assertTesterPasses(new NextTester) - } - - it should "return the correct widths for enums" in { - assertTesterPasses(new WidthTester) - } - - it should "maintain Scala-level type-safety" in { - def foo(e: EnumExample.Type): Unit = {} - - "foo(EnumExample.e1); foo(EnumExample.e1.next)" should compile - "foo(OtherEnum.otherEnum)" shouldNot compile - } - - it should "prevent enums from being declared without names" in { - "object UnnamedEnum extends ChiselEnum { Value }" shouldNot compile - } - - "StrongEnum FSM" should "work" in { - assertTesterPasses(new StrongEnumFSMTester) - } - - "Casting a UInt to an Enum" should "warn if the UInt can express illegal states" in { - object MyEnum extends ChiselEnum { - val e0, e1, e2 = Value - } - - class MyModule extends Module { - val in = IO(Input(UInt(2.W))) - val out = IO(Output(MyEnum())) - out := MyEnum(in) - } - val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) - log should include("warn") - log should include("Casting non-literal UInt") - } - - it should "NOT warn if the Enum is total" in { - object TotalEnum extends ChiselEnum { - val e0, e1, e2, e3 = Value - } - - class MyModule extends Module { - val in = IO(Input(UInt(2.W))) - val out = IO(Output(TotalEnum())) - out := TotalEnum(in) - } - val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) - (log should not).include("warn") - } - - it should "suppress warning using suppressEnumCastWarning" in { - object TestEnum extends ChiselEnum { - val e0, e1, e2 = Value - } - - class MyModule extends Module { - val in = IO(Input(UInt(2.W))) - val out = IO(Output(TestEnum())) - suppressEnumCastWarning { - val res = TestEnum(in) - out := res - } - } - val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) - (log should not).include("warn") - } - - it should "suppress exactly one warning using suppressEnumCastWarning" in { - object TestEnum1 extends ChiselEnum { - val e0, e1, e2 = Value - } - object TestEnum2 extends ChiselEnum { - val e0, e1, e2 = Value - } - - class MyModule extends Module { - val in = IO(Input(UInt(2.W))) - val out1 = IO(Output(TestEnum1())) - val out2 = IO(Output(TestEnum2())) - suppressEnumCastWarning { - out1 := TestEnum1(in) - } - out2 := TestEnum2(in) - } - val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) - log should include("warn") - log should include("TestEnum2") // not suppressed - (log should not).include("TestEnum1") // suppressed - } - - "Casting a UInt to an Enum with .safe" should "NOT warn" in { - object MyEnum extends ChiselEnum { - val e0, e1, e2 = Value - } - - class MyModule extends Module { - val in = IO(Input(UInt(2.W))) - val out = IO(Output(MyEnum())) - out := MyEnum.safe(in)._1 - } - val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) - (log should not).include("warn") - } - - it should "NOT generate any validity logic if the Enum is total" in { - object TotalEnum extends ChiselEnum { - val e0, e1, e2, e3 = Value - } - - class MyModule extends Module { - val in = IO(Input(UInt(2.W))) - val out = IO(Output(TotalEnum())) - val (res, valid) = TotalEnum.safe(in) - assert(valid.litToBoolean, "It should be true.B") - out := res - } - val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) - (log should not).include("warn") - } - - it should "correctly check if the enumeration is one of the values in a given sequence" in { - assertTesterPasses(new IsOneOfTester) - } - - it should "work with Printables" in { - ChiselStage.emitChirrtl(new LoadStoreExample) should include( - """printf(clock, UInt<1>("h1"), "%c%c%c%c%c", _chiselTestsOpcodePrintable[0], _chiselTestsOpcodePrintable[1], _chiselTestsOpcodePrintable[2], _chiselTestsOpcodePrintable[3], _chiselTestsOpcodePrintable[4])""" - ) - } -} - -class StrongEnumAnnotator extends Module { - import EnumExample._ - - object LocalEnum extends ChiselEnum { - val le0, le1 = Value - val le2 = Value - val le100 = Value(100.U) - } - - val io = IO(new Bundle { - val in = Input(EnumExample()) - val out = Output(EnumExample()) - val other = Output(OtherEnum()) - val local = Output(LocalEnum()) - }) - - class Bund extends Bundle { - val field = EnumExample() - val other = OtherEnum() - val local = LocalEnum() - val vec = Vec(5, EnumExample()) - val inner_bundle1 = new Bundle { - val x = UInt(4.W) - val y = Vec(3, UInt(4.W)) - val e = EnumExample() - val v = Vec(3, EnumExample()) - } - val inner_bundle2 = new Bundle {} - val inner_bundle3 = new Bundle { - val x = Bool() - } - val inner_bundle4 = new Bundle { - val inner_inner_bundle = new Bundle {} - } - } - - val simple = Wire(EnumExample()) - val vec = VecInit(e0, e1, e2) - val vec_of_vecs = VecInit(VecInit(e0, e1), VecInit(e100, e101)) - - val bund = Wire(new Bund()) - val vec_of_bundles = Wire(Vec(5, new Bund())) - - io.out := e101 - io.other := OtherEnum.otherEnum - io.local := LocalEnum.le0 - simple := e100 - bund := DontCare - vec_of_bundles := DontCare - - // Make sure that dynamically indexing into a Vec of enums will not cause an elaboration error. - // The components created here will not be annotated. - val cycle = RegInit(0.U) - cycle := cycle + 1.U - - val indexed1 = vec_of_vecs(cycle)(cycle) - val indexed2 = vec_of_bundles(cycle) -} - -class StrongEnumAnnotatorWithChiselName extends Module { - import EnumExample._ - - object LocalEnum extends ChiselEnum with AffectsChiselPrefix { - val le0, le1 = Value - val le2 = Value - val le100 = Value(100.U) - } - - val io = IO(new Bundle { - val in = Input(EnumExample()) - val out = Output(EnumExample()) - val other = Output(OtherEnum()) - val local = Output(LocalEnum()) - }) - - class Bund extends Bundle { - val field = EnumExample() - val other = OtherEnum() - val local = LocalEnum() - val vec = Vec(5, EnumExample()) - val inner_bundle1 = new Bundle { - val x = UInt(4.W) - val y = Vec(3, UInt(4.W)) - val e = EnumExample() - val v = Vec(3, EnumExample()) - } - val inner_bundle2 = new Bundle {} - val inner_bundle3 = new Bundle { - val x = Bool() - } - val inner_bundle4 = new Bundle { - val inner_inner_bundle = new Bundle {} - } - } - - val simple = Wire(EnumExample()) - val vec = VecInit(e0, e1, e2) - val vec_of_vecs = VecInit(VecInit(e0, e1), VecInit(e100, e101)) - - val bund = Wire(new Bund()) - val vec_of_bundles = Wire(Vec(5, new Bund())) - - io.out := e101 - io.other := OtherEnum.otherEnum - io.local := LocalEnum.le0 - simple := e100 - bund := DontCare - vec_of_bundles := DontCare - - // Make sure that dynamically indexing into a Vec of enums will not cause an elaboration error. - // The components created here will not be annotated. - val cycle = RegInit(0.U) - cycle := cycle + 1.U - - val indexed1 = vec_of_vecs(cycle)(cycle) - val indexed2 = vec_of_bundles(cycle) -} - -class StrongEnumAnnotationSpec extends AnyFreeSpec with Matchers { - import chisel3.experimental.EnumAnnotations._ - import firrtl.annotations.{Annotation, ComponentName} - - val enumExampleName = "EnumExample" - val otherEnumName = "OtherEnum" - val localEnumName = "LocalEnum" - - case class CorrectDefAnno(typeName: String, definition: Map[String, BigInt]) - case class CorrectCompAnno(targetName: String, typeName: String) - case class CorrectVecAnno(targetName: String, typeName: String, fields: Set[Seq[String]]) - - val correctDefAnnos = Seq( - CorrectDefAnno(otherEnumName, Map("otherEnum" -> 0)), - CorrectDefAnno(enumExampleName, Map("e0" -> 0, "e1" -> 1, "e2" -> 2, "e100" -> 100, "e101" -> 101)), - CorrectDefAnno(localEnumName, Map("le0" -> 0, "le1" -> 1, "le2" -> 2, "le100" -> 100)) - ) - - val correctCompAnnos = Seq( - CorrectCompAnno("io.other", otherEnumName), - CorrectCompAnno("io.local", localEnumName), - CorrectCompAnno("io.out", enumExampleName), - CorrectCompAnno("io.in", enumExampleName), - CorrectCompAnno("simple", enumExampleName), - CorrectCompAnno("bund.field", enumExampleName), - CorrectCompAnno("bund.other", otherEnumName), - CorrectCompAnno("bund.local", localEnumName), - CorrectCompAnno("bund.inner_bundle1.e", enumExampleName) - ) - - val correctVecAnnos = Seq( - CorrectVecAnno("vec", enumExampleName, Set()), - CorrectVecAnno("vec_of_vecs", enumExampleName, Set()), - CorrectVecAnno( - "vec_of_bundles", - enumExampleName, - Set(Seq("field"), Seq("vec"), Seq("inner_bundle1", "e"), Seq("inner_bundle1", "v")) - ), - CorrectVecAnno("vec_of_bundles", otherEnumName, Set(Seq("other"))), - CorrectVecAnno("vec_of_bundles", localEnumName, Set(Seq("local"))), - CorrectVecAnno("bund.vec", enumExampleName, Set()), - CorrectVecAnno("bund.inner_bundle1.v", enumExampleName, Set()) - ) - - def printAnnos(annos: Seq[Annotation]) { - println("Enum definitions:") - annos.foreach { - case EnumDefAnnotation(enumTypeName, definition) => println(s"\t$enumTypeName: $definition") - case _ => - } - println("Enum components:") - annos.foreach { - case EnumComponentAnnotation(target, enumTypeName) => println(s"\t$target => $enumTypeName") - case _ => - } - println("Enum vecs:") - annos.foreach { - case EnumVecAnnotation(target, enumTypeName, fields) => println(s"\t$target[$fields] => $enumTypeName") - case _ => - } - } - - def isCorrect(anno: EnumDefAnnotation, correct: CorrectDefAnno): Boolean = { - (anno.typeName == correct.typeName || - anno.typeName.endsWith("." + correct.typeName) || - anno.typeName.endsWith("$" + correct.typeName)) && - anno.definition == correct.definition - } - - def isCorrect(anno: EnumComponentAnnotation, correct: CorrectCompAnno): Boolean = { - (anno.target match { - case ComponentName(name, _) => name == correct.targetName - case _ => throw new Exception("Unknown target type in EnumComponentAnnotation") - }) && - (anno.enumTypeName == correct.typeName || anno.enumTypeName.endsWith("." + correct.typeName) || - anno.enumTypeName.endsWith("$" + correct.typeName)) - } - - def isCorrect(anno: EnumVecAnnotation, correct: CorrectVecAnno): Boolean = { - (anno.target match { - case ComponentName(name, _) => name == correct.targetName - case _ => throw new Exception("Unknown target type in EnumVecAnnotation") - }) && - (anno.typeName == correct.typeName || anno.typeName.endsWith("." + correct.typeName) || - anno.typeName.endsWith("$" + correct.typeName)) && - anno.fields.map(_.toSeq).toSet == correct.fields - } - - def allCorrectDefs(annos: Seq[EnumDefAnnotation], corrects: Seq[CorrectDefAnno]): Boolean = { - corrects.forall(c => annos.exists(isCorrect(_, c))) && - correctDefAnnos.length == annos.length - } - - // Because temporary variables might be formed and annotated, we do not check that every component or vector - // annotation is accounted for in the correct results listed above - def allCorrectComps(annos: Seq[EnumComponentAnnotation], corrects: Seq[CorrectCompAnno]): Boolean = - corrects.forall(c => annos.exists(isCorrect(_, c))) - - def allCorrectVecs(annos: Seq[EnumVecAnnotation], corrects: Seq[CorrectVecAnno]): Boolean = - corrects.forall(c => annos.exists(isCorrect(_, c))) - - def test(strongEnumAnnotatorGen: () => Module) { - val annos = (new ChiselStage).execute( - Array("--target-dir", "test_run_dir", "--no-run-firrtl"), - Seq(ChiselGeneratorAnnotation(strongEnumAnnotatorGen)) - ) - - val enumDefAnnos = annos.collect { case a: EnumDefAnnotation => a } - val enumCompAnnos = annos.collect { case a: EnumComponentAnnotation => a } - val enumVecAnnos = annos.collect { case a: EnumVecAnnotation => a } - - allCorrectDefs(enumDefAnnos, correctDefAnnos) should be(true) - allCorrectComps(enumCompAnnos, correctCompAnnos) should be(true) - allCorrectVecs(enumVecAnnos, correctVecAnnos) should be(true) - - } - - "Test that strong enums annotate themselves appropriately" in { - test(() => new StrongEnumAnnotator) - test(() => new StrongEnumAnnotatorWithChiselName) - } -} diff --git a/src/test/scala/chiselTests/VecLiteralSpec.scala b/src/test/scala/chiselTests/VecLiteralSpec.scala index e2eb791d..dcc96b17 100644 --- a/src/test/scala/chiselTests/VecLiteralSpec.scala +++ b/src/test/scala/chiselTests/VecLiteralSpec.scala @@ -5,7 +5,7 @@ package chiselTests import chisel3._ import chisel3.experimental.BundleLiterals.AddBundleLiteralConstructor import chisel3.experimental.VecLiterals._ -import chisel3.experimental.{ChiselEnum, FixedPoint, VecLiteralException} +import chisel3.experimental.{FixedPoint, VecLiteralException} import chisel3.stage.ChiselStage import chisel3.testers.BasicTester import chisel3.util.Counter diff --git a/src/test/scala/chiselTests/WarningSpec.scala b/src/test/scala/chiselTests/WarningSpec.scala index 1cef1ffc..1bb9e6dc 100644 --- a/src/test/scala/chiselTests/WarningSpec.scala +++ b/src/test/scala/chiselTests/WarningSpec.scala @@ -5,8 +5,6 @@ package chiselTests import chisel3._ import chisel3.util._ import chisel3.stage.ChiselStage -import chisel3.experimental.ChiselEnum -import chisel3.experimental.EnumType class WarningSpec extends ChiselFlatSpec with Utils { behavior.of("Warnings") diff --git a/src/test/scala/chiselTests/experimental/TraceSpec.scala b/src/test/scala/chiselTests/experimental/TraceSpec.scala index 31ccdf9b..1d67ba0b 100644 --- a/src/test/scala/chiselTests/experimental/TraceSpec.scala +++ b/src/test/scala/chiselTests/experimental/TraceSpec.scala @@ -3,7 +3,6 @@ package chiselTests import chisel3._ -import chisel3.experimental.ChiselEnum import chisel3.experimental.Trace._ import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, DesignAnnotation} import chisel3.util.experimental.InlineInstance diff --git a/src/test/scala/cookbook/FSM.scala b/src/test/scala/cookbook/FSM.scala index 66f3063f..40f8abc7 100644 --- a/src/test/scala/cookbook/FSM.scala +++ b/src/test/scala/cookbook/FSM.scala @@ -4,11 +4,10 @@ package cookbook import chisel3._ import chisel3.util._ -import chisel3.experimental.ChiselEnum /* ### How do I create a finite state machine? * - * Use Chisel StrongEnum to construct the states and switch & is to construct the FSM + * Use ChiselEnum to construct the states and switch & is to construct the FSM * control logic */ -- cgit v1.2.3 From 6a63353f2a6c3311e61b9a7b5b899d8ad904a86d Mon Sep 17 00:00:00 2001 From: mergify[bot] Date: Thu, 12 Jan 2023 00:04:06 +0000 Subject: TruthTable improvements: structural equality and delete sort (backport #2935) (#2936) * TruthTable improvements: structural equality and delete sort (#2935) We had implemented equals, but not hashCode. This commit also changes the implemental of equals to just use the underlying values instead of wasting the compute calling .toString. Delete TruthTable.sort, it is unused. (cherry picked from commit b5d9c08b2d0994b94df2380425282206fe1f25bc) * Restore and deprecate TruthTable.sort Co-authored-by: Jack Koenig Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>--- .../chisel3/util/experimental/decode/TruthTable.scala | 14 ++++++++++---- .../chiselTests/util/experimental/TruthTableSpec.scala | 4 +++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala index 2720e690..8259564f 100644 --- a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -3,13 +3,17 @@ package chisel3.util.experimental.decode import chisel3.util.BitPat +import scala.util.hashing.MurmurHash3 import scala.collection.mutable -sealed class TruthTable private (val table: Seq[(BitPat, BitPat)], val default: BitPat, val sort: Boolean) { +sealed class TruthTable private (val table: Seq[(BitPat, BitPat)], val default: BitPat, _sort: Boolean) { def inputWidth = table.head._1.getWidth def outputWidth = table.head._2.getWidth + @deprecated("This field is unused and will be removed.", "Chisel 3.5") + def sort: Boolean = _sort + override def toString: String = { def writeRow(map: (BitPat, BitPat)): String = s"${map._1.rawString}->${map._2.rawString}" @@ -17,15 +21,17 @@ sealed class TruthTable private (val table: Seq[(BitPat, BitPat)], val default: (table.map(writeRow) ++ Seq(s"${" " * (inputWidth + 2)}${default.rawString}")).mkString("\n") } - def copy(table: Seq[(BitPat, BitPat)] = this.table, default: BitPat = this.default, sort: Boolean = this.sort) = - TruthTable(table, default, sort) + def copy(table: Seq[(BitPat, BitPat)] = this.table, default: BitPat = this.default, sort: Boolean = _sort) = + TruthTable(table, default) override def equals(y: Any): Boolean = { y match { - case y: TruthTable => toString == y.toString + case that: TruthTable => this.table == that.table && this.default == that.default case _ => false } } + + override lazy val hashCode: Int = MurmurHash3.productHash((table, default)) } object TruthTable { diff --git a/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala index 9b2dd600..b5dcee6b 100644 --- a/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala +++ b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala @@ -35,7 +35,9 @@ class TruthTableSpec extends AnyFlatSpec { assert(table.toString contains " 0") } "TruthTable" should "deserialize" in { - assert(TruthTable.fromString(str) == table) + val table2 = TruthTable.fromString(str) + assert(table2 === table) + assert(table2.hashCode === table.hashCode) } "TruthTable" should "merge same key" in { assert( -- cgit v1.2.3