diff options
Diffstat (limited to 'core/src/main/scala/chisel3/experimental/ChiselEnum.scala')
| -rw-r--r-- | core/src/main/scala/chisel3/experimental/ChiselEnum.scala | 470 |
1 files changed, 470 insertions, 0 deletions
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 <EnumType>.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 + } +} |
