From 51864db8176662d134e1a260f92eafc83f9933d8 Mon Sep 17 00:00:00 2001 From: Aditya Naik Date: Tue, 6 Aug 2024 05:35:30 -0700 Subject: Update ChiselEnum.scala And more changes --- core/src/main/scala/chisel3/ChiselEnum.scala | 367 ++++++++++++++++++ core/src/main/scala/chisel3/Width.scala | 71 ++++ .../scala/chisel3/experimental/ChiselEnum.scala | 416 --------------------- .../chisel3/experimental/EnumAnnotations.scala | 63 ++++ .../main/scala/chisel3/experimental/package.scala | 13 + core/src/main/scala/chisel3/internal/Binding.scala | 1 + core/src/main/scala/chisel3/internal/Error.scala | 4 - core/src/main/scala/chisel3/internal/Warning.scala | 36 ++ .../main/scala/chisel3/internal/firrtl/IR.scala | 38 -- core/src/main/scala/chisel3/package.scala | 4 - 10 files changed, 551 insertions(+), 462 deletions(-) create mode 100644 core/src/main/scala/chisel3/ChiselEnum.scala create mode 100644 core/src/main/scala/chisel3/Width.scala delete mode 100644 core/src/main/scala/chisel3/experimental/ChiselEnum.scala create mode 100644 core/src/main/scala/chisel3/experimental/EnumAnnotations.scala create mode 100644 core/src/main/scala/chisel3/internal/Warning.scala (limited to 'core/src') diff --git a/core/src/main/scala/chisel3/ChiselEnum.scala b/core/src/main/scala/chisel3/ChiselEnum.scala new file mode 100644 index 00000000..881bbc62 --- /dev/null +++ b/core/src/main/scala/chisel3/ChiselEnum.scala @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3 + +import scala.collection.mutable +import chisel3._ +import chisel3.internal.Builder +import chisel3.internal.Builder._ +import chisel3.internal.firrtl.PrimOp._ +import chisel3.internal.firrtl._ +import chisel3.internal.{throwException, Binding, Builder, ChildBinding, ConstrainedBinding, Warning, WarningID} +import chisel3.experimental.{annotate, requireIsHardware, ChiselAnnotation} +import chisel3.experimental.EnumAnnotations._ + + +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(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(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 + ): Unit = { + this := factory.apply(that.asUInt) + } + + def ===(that: EnumType): Bool = + compop(EqualOp, that) + def =/=(that: EnumType): Bool = + compop(NotEqualOp, that) + def <(that: EnumType): Bool = + compop(LessOp, that) + def >(that: EnumType): Bool = + compop(GreaterOp, that) + def <=(that: EnumType): Bool = + compop(LessEqOp, that) + def >=(that: EnumType): Bool = + compop(GreaterEqOp, that) + + override def asUInt: UInt = + pushOp(DefPrim(UInt(width), AsUIntOp, ref)) + + protected[chisel3] override def width: Width = factory.width + + def isValid: 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]): Bool = { + // Vec(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* + // ): Bool = isOneOf(u1 +: u2.toSeq) + + def next: 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: Record => + 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 = { + // add after builder.enumAnnos + // 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 = { + 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)) { + // toChar doesn't work with + // An extension method was tried, but could not be fully constructed: + // chisel3.fromLongToLiteral(c.toChar) + r := c.toInt.U + } + } + } + result.map(Character(_)).foldLeft(p"")(_ + _) + } +} + +private[chisel3] object ChiselEnum { + // add after buildercontextcache +// private[chisel3] case object CacheKey extends BuilderContextCache.Key[mutable.HashSet[ChiselAnnotation]] +} + +abstract class ChiselEnum { + class Type extends EnumType(this) + object Type { + def apply(): Type = ChiselEnum.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.zip(enumValues).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(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 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 + Value(name) + } + + def apply(): Type = new Type + + private def castImpl( + n: UInt, + warn: Boolean + ): 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 { + // TODO fold this into warning filters + if (!Builder.suppressEnumCastWarning && warn && !this.isTotal) { + val msg = + s"Casting non-literal UInt to $enumTypeName. You can use $enumTypeName.safe to cast without this warning." + Builder.warning(Warning(WarningID.UnsafeUIntCastToEnum, msg)) + } + 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): 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): (Type, Bool) = { + val t = castImpl(n, warn = false) + (t, t.isValid) + } +} + +// 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) + * } + * }}} + */ +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/Width.scala b/core/src/main/scala/chisel3/Width.scala new file mode 100644 index 00000000..3c1abf5e --- /dev/null +++ b/core/src/main/scala/chisel3/Width.scala @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3 + +object Width { + def apply(x: Int): Width = KnownWidth(x) + def apply(): Width = UnknownWidth +} + +sealed abstract class Width { + type W = Int + def min(that: Width): Width = this.op(that, _ min _) + def max(that: Width): Width = this.op(that, _ max _) + def +(that: Width): Width = this.op(that, _ + _) + def +(that: Int): Width = this.op(this, (a, b) => a + that) + @deprecated( + "The width of shift-right now differs by type, use unsignedShiftRight and signedShiftRight", + "Chisel 7.0.0" + ) + def shiftRight(that: Int): Width = this.op(this, (a, b) => 0.max(a - that)) + def unsignedShiftRight(that: Int): Width = this.op(this, (a, b) => 0.max(a - that)) + def signedShiftRight(that: Int): Width = this.op(this, (a, b) => 1.max(a - that)) + def dynamicShiftLeft(that: Width): Width = + this.op(that, (a, b) => a + (1 << b) - 1) + + def known: Boolean + def get: W + protected def op(that: Width, f: (W, W) => W): Width +} + +case object UnknownWidth extends Width { + def known: Boolean = false + def get: Int = None.get + def op(that: Width, f: (W, W) => W): Width = this + override def toString: String = "" + + @deprecated("UnknownWidth is now a case object, remove the parentheses", "Chisel 7.0") + def apply(): UnknownWidth.type = this + + @deprecated("UnknownWidth is now a case object, remove the parentheses", "Chisel 7.0") + def unapply(x: UnknownWidth.type): Boolean = true +} + +sealed case class KnownWidth private (value: Int) extends Width { + + @deprecated("Use the companion object appy method (remove the \"new\")", "Chisel 7.0") + def this(value: Int, dummy: Int = 0) = this(value.toInt) + + require(value >= 0, s"Widths must be non-negative, got $value") + def known: Boolean = true + def get: Int = value + def op(that: Width, f: (W, W) => W): Width = that match { + case KnownWidth(x) => KnownWidth(f(value, x)) + case _ => that + } + override def toString: String = s"<${value.toString}>" +} +object KnownWidth { + private val maxCached = 1024 + private val cache = new Array[KnownWidth](maxCached + 1) + def apply(value: Int): KnownWidth = { + if (0 <= value && value <= maxCached) { + var w = cache(value) + if (w eq null) { + w = new KnownWidth(value) + cache(value) = w + } + w + } else new KnownWidth(value) + } +} diff --git a/core/src/main/scala/chisel3/experimental/ChiselEnum.scala b/core/src/main/scala/chisel3/experimental/ChiselEnum.scala deleted file mode 100644 index d0ad6949..00000000 --- a/core/src/main/scala/chisel3/experimental/ChiselEnum.scala +++ /dev/null @@ -1,416 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.experimental - -import scala.collection.mutable -import chisel3._ -import chisel3.internal.Builder.pushOp -import chisel3.internal.firrtl.PrimOp._ -import chisel3.internal.firrtl._ -import chisel3.internal.{throwException, Binding, Builder, ChildBinding, ConstrainedBinding} -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(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(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 - ): Unit = { - this := factory.apply(that.asUInt) - } - - def ===(that: EnumType): Bool = - compop(EqualOp, that) - def =/=(that: EnumType): Bool = - compop(NotEqualOp, that) - def <(that: EnumType): Bool = - compop(LessOp, that) - def >(that: EnumType): Bool = - compop(GreaterOp, that) - def <=(that: EnumType): Bool = - compop(LessEqOp, that) - def >=(that: EnumType): Bool = - compop(GreaterEqOp, that) - - override def asUInt: UInt = - pushOp(DefPrim(UInt(width), AsUIntOp, ref)) - - protected[chisel3] override def width: Width = factory.width - - def isValid: 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]): Bool = { - // Vec(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* - // ): Bool = isOneOf(u1 +: u2.toSeq) - - def next: 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 = { - 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.toInt.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(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 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 - Value(name) - } - - def apply(): Type = new Type - - private def castImpl( - n: UInt, - warn: Boolean - ): 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): 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): (Type, Bool) = { - val t = castImpl(n, warn = false) - (t, t.isValid) - } -} - -// 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/EnumAnnotations.scala b/core/src/main/scala/chisel3/experimental/EnumAnnotations.scala new file mode 100644 index 00000000..4719928e --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/EnumAnnotations.scala @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental + +import chisel3._ +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) + } +} diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index 6dc14afb..d07bcf4a 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -23,6 +23,19 @@ package object experimental { type Direction = ActualDirection val Direction = ActualDirection + /** Requires that a node is hardware ("bound") + */ + object requireIsHardware { + def apply(node: Data, msg: String = ""): Unit = { + if (!node.isSynthesizable) { + val prefix = if (msg.nonEmpty) s"$msg " else "" + throw ExpectedHardwareException( + s"$prefix'$node' must be hardware, " + + "not a bare Chisel type. Perhaps you forgot to wrap it in Wire(_) or IO(_)?" + ) + } + } + } // class dump extends chisel3.internal.naming.dump // class treedump extends chisel3.internal.naming.treedump diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 3a12afde..db85b355 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -10,6 +10,7 @@ import scala.collection.immutable.VectorMap /** Requires that a node is hardware ("bound") */ +// remove/move this to chisel3.experimental once references are fixed object requireIsHardware { def apply(node: Data, msg: String = ""): Unit = { node._parent match { // Compatibility layer hack diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala index b7c4be35..730c9510 100644 --- a/core/src/main/scala/chisel3/internal/Error.scala +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -324,10 +324,6 @@ private class Error(msg: => String, line: Option[StackTraceElement]) extends Log def format: String = tag("error", Console.RED) } -class Warning(msg: => String, line: Option[StackTraceElement]) extends LogEntry(msg, line) { - def format: String = tag("warn", Console.YELLOW) -} - private class Info(msg: => String, line: Option[StackTraceElement]) extends LogEntry(msg, line) { def format: String = tag("info", Console.MAGENTA) } diff --git a/core/src/main/scala/chisel3/internal/Warning.scala b/core/src/main/scala/chisel3/internal/Warning.scala new file mode 100644 index 00000000..5ac506c9 --- /dev/null +++ b/core/src/main/scala/chisel3/internal/Warning.scala @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal + +/////////////////////////////////////////////////// +// Never remove IDs and only ever add to the end // +/////////////////////////////////////////////////// + +// TODO should deprecations be included here? +private[chisel3] object WarningID extends Enumeration { + type WarningID = Value + + val NoID = Value(0) // Reserved + val UnsafeUIntCastToEnum = Value(1) + val DynamicBitSelectTooWide = Value(2) + val DynamicBitSelectTooNarrow = Value(3) + val DynamicIndexTooWide = Value(4) + val DynamicIndexTooNarrow = Value(5) + val ExtractFromVecSizeZero = Value(6) + val BundleLiteralValueTooWide = Value(7) + val AsTypeOfReadOnly = Value(8) +} +import WarningID.WarningID + +// Argument order differs from apply below to avoid type signature collision with apply method below +private[chisel3] case class Warning(id: WarningID, msg: String) +private[chisel3] object Warning { + def apply(id: WarningID, msg: String): Warning = { + val num = f"[W${id.id}%03d] " + new Warning(info, id, num + msg) + } + def noInfo(id: WarningID, msg: String): Warning = { + implicit val info = SourceInfo.materializeFromStacktrace + Warning(id, msg) + } +} diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index de3d8716..9fee727c 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -216,44 +216,6 @@ case class LitIndex(imm: Arg, value: Int) extends Arg { override def localName: String = s"${imm.localName}[$value]" } -object Width { - def apply(x: Int): Width = KnownWidth(x) - def apply(): Width = UnknownWidth() -} - -sealed abstract class Width { - type W = Int - def min(that: Width): Width = this.op(that, _ min _) - def max(that: Width): Width = this.op(that, _ max _) - def +(that: Width): Width = this.op(that, _ + _) - def +(that: Int): Width = this.op(this, (a, b) => a + that) - def shiftRight(that: Int): Width = this.op(this, (a, b) => 0.max(a - that)) - def dynamicShiftLeft(that: Width): Width = - this.op(that, (a, b) => a + (1 << b) - 1) - - def known: Boolean - def get: W - protected def op(that: Width, f: (W, W) => W): Width -} - -sealed case class UnknownWidth() extends Width { - def known: Boolean = false - def get: Int = None.get - def op(that: Width, f: (W, W) => W): Width = this - override def toString: String = "" -} - -sealed case class KnownWidth(value: Int) extends Width { - require(value >= 0) - def known: Boolean = true - def get: Int = value - def op(that: Width, f: (W, W) => W): Width = that match { - case KnownWidth(x) => KnownWidth(f(value, x)) - case _ => that - } - override def toString: String = s"<${value.toString}>" -} - object BinaryPoint { def apply(x: Int): BinaryPoint = KnownBinaryPoint(x) def apply(): BinaryPoint = UnknownBinaryPoint diff --git a/core/src/main/scala/chisel3/package.scala b/core/src/main/scala/chisel3/package.scala index 6633e7ab..be5fb7b5 100644 --- a/core/src/main/scala/chisel3/package.scala +++ b/core/src/main/scala/chisel3/package.scala @@ -18,10 +18,6 @@ package object chisel3 { import scala.language.implicitConversions - type ChiselEnum = experimental.ChiselEnum - type EnumType = experimental.EnumType - val suppressEnumCastWarning = experimental.suppressEnumCastWarning - /** * These implicit classes allow one to convert [[scala.Int]] or [[scala.BigInt]] to * Chisel.UInt|Chisel.SInt by calling .asUInt|.asSInt on them, respectively. -- cgit v1.2.3