Skip to content

Commit

Permalink
Add safer Chisel annotation API, deprecate old ones (#4643)
Browse files Browse the repository at this point in the history
The new one enables safety checks and smarter logic for views.
  • Loading branch information
jackkoenig authored Feb 14, 2025
1 parent 4ac534f commit a95cfe4
Show file tree
Hide file tree
Showing 32 changed files with 286 additions and 217 deletions.
1 change: 1 addition & 0 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ object v extends Module {
"cat=deprecation&origin=chisel3\\.util\\.experimental\\.BoringUtils.*:s",
"cat=deprecation&origin=chisel3\\.experimental\\.IntrinsicModule:s",
"cat=deprecation&origin=chisel3\\.ltl.*:s",
"cat=deprecation&origin=chisel3\\.InstanceId:s",
// Deprecated for external users, will eventually be removed.
"cat=deprecation&msg=Looking up Modules is deprecated:s",
// Only for testing of deprecated APIs
Expand Down
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ lazy val warningSuppression = Seq(
"cat=deprecation&origin=chisel3\\.util\\.experimental\\.BoringUtils.*:s",
"cat=deprecation&origin=chisel3\\.experimental\\.IntrinsicModule:s",
"cat=deprecation&origin=chisel3\\.ltl.*:s",
"cat=deprecation&origin=chisel3\\.InstanceId:s",
"cat=deprecation&msg=Looking up Modules is deprecated:s",
).mkString(",")
)
Expand Down
47 changes: 41 additions & 6 deletions core/src/main/scala/chisel3/Annotation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
package chisel3.experimental

import scala.language.existentials
import scala.annotation.nowarn
import chisel3.internal.Builder
import chisel3.{Data, InstanceId, RawModule}
import chisel3.{Data, HasTarget, InstanceId, RawModule}
import chisel3.experimental.AnyTargetable
import firrtl.annotations._
import firrtl.options.Unserializable
import firrtl.transforms.{DedupGroupAnnotation, NoDedupAnnotation}
Expand All @@ -13,6 +15,10 @@ import firrtl.transforms.{DedupGroupAnnotation, NoDedupAnnotation}
*
* Defines a conversion to a corresponding FIRRTL Annotation
*/
@deprecated(
"Avoid custom annotations. If you must use annotations, use annotate.apply method that takes Data",
"Chisel 6.7.0"
)
trait ChiselAnnotation {

/** Conversion to FIRRTL Annotation */
Expand All @@ -23,16 +29,45 @@ trait ChiselAnnotation {
*
* Defines a conversion to corresponding FIRRTL Annotation(s)
*/
@deprecated(
"Avoid custom annotations. If you must use annotations, use annotate.apply method that takes Data",
"Chisel 6.7.0"
)
trait ChiselMultiAnnotation {
def toFirrtl: Seq[Annotation]
}

@nowarn("msg=Avoid custom annotations")
object annotate {
def apply(anno: ChiselAnnotation): Unit = {
@deprecated(
"Avoid custom annotations. If you must use annotations, use annotate.apply method that takes Data",
"Chisel 6.7.0"
)
def apply(anno: ChiselAnnotation): Unit =
Builder.annotations += anno
}
def apply(annos: ChiselMultiAnnotation): Unit = {

@deprecated(
"Avoid custom annotations. If you must use annotations, use annotate.apply method that takes Data",
"Chisel 6.7.0"
)
def apply(annos: ChiselMultiAnnotation): Unit =
Builder.newAnnotations += annos

/** Create annotations.
*
* Avoid this API if possible.
*
* Anything being annotated must be passed as arguments so that Chisel can do safety checks.
* The caller is still responsible for calling .toTarget on those arguments in mkAnnos.
*/
def apply(targets: AnyTargetable*)(mkAnnos: => Seq[Annotation]): Unit = {
targets.map(_.a).foreach {
case d: Data => requireIsAnnotatable(d, "Data marked with annotation")
case _ => ()
}
Builder.newAnnotations += new ChiselMultiAnnotation {
def toFirrtl: Seq[Annotation] = mkAnnos
}
}
}

Expand Down Expand Up @@ -76,7 +111,7 @@ object doNotDedup {
* @return Unmodified signal `module`
*/
def apply[T <: RawModule](module: T): Unit = {
annotate(new ChiselAnnotation { def toFirrtl: NoDedupAnnotation = NoDedupAnnotation(module.toNamed) })
annotate(module)(Seq(NoDedupAnnotation(module.toNamed)))
}
}

Expand All @@ -89,6 +124,6 @@ object dedupGroup {
* @return Unmodified signal `module`
*/
def apply[T <: BaseModule](module: T, group: String): Unit = {
annotate(new ChiselAnnotation { def toFirrtl: DedupGroupAnnotation = DedupGroupAnnotation(module.toTarget, group) })
annotate(module)(Seq(DedupGroupAnnotation(module.toTarget, group)))
}
}
9 changes: 9 additions & 0 deletions core/src/main/scala/chisel3/ChiselEnumImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package chisel3

import scala.language.existentials
import scala.annotation.nowarn
import scala.collection.mutable
import chisel3.experimental.{annotate, requireIsHardware, ChiselAnnotation, SourceInfo, UnlocatableSourceInfo}
import chisel3.internal.Builder.pushOp
Expand All @@ -13,6 +14,9 @@ import chisel3.internal.binding.{Binding, ChildBinding, ConstrainedBinding}

import chisel3.experimental.EnumAnnotations._

// Rather than refactoring the annotation work here, we should just remove ChiselEnum annotations
@nowarn("msg=Avoid custom annotations")
@nowarn("msg=Enum annotations will be removed")
private[chisel3] abstract class EnumTypeImpl(private[chisel3] val factory: ChiselEnum, selfAnnotating: Boolean = true)
extends Element { self: EnumType =>

Expand Down Expand Up @@ -231,10 +235,15 @@ private[chisel3] abstract class EnumTypeImpl(private[chisel3] val factory: Chise
}
}

// Rather than refactoring the annotation work here, we should just remove ChiselEnum annotations
@nowarn("msg=Avoid custom annotations")
private[chisel3] object ChiselEnumImpl {
private[chisel3] case object CacheKey extends BuilderContextCache.Key[mutable.HashSet[ChiselAnnotation]]
}

// Rather than refactoring the annotation work here, we should just remove ChiselEnum annotations
@nowarn("msg=Avoid custom annotations")
@nowarn("msg=Enum annotations will be removed")
private[chisel3] trait ChiselEnumImpl { self: ChiselEnum =>
class Type extends EnumType(this)
object Type {
Expand Down
9 changes: 3 additions & 6 deletions core/src/main/scala/chisel3/ModuleImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import chisel3.internal._
import chisel3.internal.binding._
import chisel3.internal.Builder._
import chisel3.internal.firrtl.ir._
import chisel3.experimental.{requireIsChiselType, BaseModule, SourceInfo, UnlocatableSourceInfo}
import chisel3.experimental.{requireIsChiselType, BaseModule, SourceInfo, Targetable, UnlocatableSourceInfo}
import chisel3.properties.{Class, Property}
import chisel3.reflect.DataMirror
import _root_.firrtl.annotations.{
Expand Down Expand Up @@ -980,13 +980,10 @@ private case class ModulePrefixAnnotation(target: IsMember, prefix: String) exte
}

private object ModulePrefixAnnotation {
def annotate[T <: HasId](target: T): Unit = {
def annotate[T <: HasId: Targetable](target: T): Unit = {
val prefix = Builder.getModulePrefix
if (prefix != "") {
val annotation: ChiselAnnotation = new ChiselAnnotation {
def toFirrtl: Annotation = ModulePrefixAnnotation(target.toTarget, prefix)
}
chisel3.experimental.annotate(annotation)
chisel3.experimental.annotate(target)(Seq(ModulePrefixAnnotation(target.toTarget, prefix)))
}
}
}
3 changes: 1 addition & 2 deletions core/src/main/scala/chisel3/dontTouch.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ object dontTouch {
case d if DataMirror.hasProbeTypeModifier(d) => ()
case _: Property[_] => ()
case agg: Aggregate => agg.getElements.foreach(dontTouch.apply)
case _: Element =>
annotate(new ChiselAnnotation { def toFirrtl: DontTouchAnnotation = DontTouchAnnotation(data.toNamed) })
case _: Element => annotate(data)(Seq(DontTouchAnnotation(data.toNamed)))
case _ => throw new ChiselException("Non-hardware dontTouch")
}
data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@

package chisel3.experimental

import scala.annotation.nowarn
import chisel3._
import firrtl.annotations._

@nowarn("msg=Avoid custom 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"'')
*/
@deprecated("Enum annotations will be removed in Chisel 7.0.", "Chisel 6.7.0")
case class EnumComponentAnnotation(target: Named, enumTypeName: String) extends SingleTargetAnnotation[Named] {
def duplicate(n: Named): EnumComponentAnnotation = this.copy(target = n)
}

@deprecated("Enum annotations will be removed in Chisel 7.0.", "Chisel 6.7.0")
case class EnumComponentChiselAnnotation(target: InstanceId, enumTypeName: String) extends ChiselAnnotation {
def toFirrtl: EnumComponentAnnotation = EnumComponentAnnotation(target.toNamed, enumTypeName)
}
Expand All @@ -40,11 +44,13 @@ object EnumAnnotations {
* @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.
*/
@deprecated("Enum annotations will be removed in Chisel 7.0.", "Chisel 6.7.0")
case class EnumVecAnnotation(target: Named, typeName: String, fields: Seq[Seq[String]])
extends SingleTargetAnnotation[Named] {
def duplicate(n: Named): EnumVecAnnotation = this.copy(target = n)
}

@deprecated("Enum annotations will be removed in Chisel 7.0.", "Chisel 6.7.0")
case class EnumVecChiselAnnotation(target: InstanceId, typeName: String, fields: Seq[Seq[String]])
extends ChiselAnnotation {
override def toFirrtl: EnumVecAnnotation = EnumVecAnnotation(target.toNamed, typeName, fields)
Expand All @@ -55,8 +61,10 @@ object EnumAnnotations {
* @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
*/
@deprecated("Enum annotations will be removed in Chisel 7.0.", "Chisel 6.7.0")
case class EnumDefAnnotation(typeName: String, definition: Map[String, BigInt]) extends NoTargetAnnotation

@deprecated("Enum annotations will be removed in Chisel 7.0.", "Chisel 6.7.0")
case class EnumDefChiselAnnotation(typeName: String, definition: Map[String, BigInt]) extends ChiselAnnotation {
override def toFirrtl: Annotation = EnumDefAnnotation(typeName, definition)
}
Expand Down
130 changes: 130 additions & 0 deletions core/src/main/scala/chisel3/experimental/Targetable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3.experimental

import scala.language.implicitConversions

import chisel3.HasTarget
import chisel3.experimental.hierarchy.Hierarchy
import chisel3.internal.NamedComponent
import firrtl.annotations.IsMember

/** Type class for types that can be converted to a Target
*
* See [[AnyTargetable]] for type-erased containers of Targetables
*
* @note This uses type classes instead of inheritance because Hierarchy does not constrain its type parameter
* and thus not all instances of Hierarchy are Targetable.
*/
sealed trait Targetable[A] {

/** Returns a FIRRTL IsMember that refers to this object in the elaborated hardware graph */
def toTarget(a: A): IsMember

/** Returns a FIRRTL IsMember that refers to the absolute path to this object in the elaborated hardware graph */
def toAbsoluteTarget(a: A): IsMember

/** Returns a FIRRTL IsMember that references this object, relative to an optional root.
*
* If `root` is defined, the target is a hierarchical path starting from `root`.
*
* If `root` is not defined, the target is a hierarchical path equivalent to `toAbsoluteTarget`.
*
* @note If `root` is defined, and has not finished elaboration, this must be called within `atModuleBodyEnd`.
* @note The Targetable must be a descendant of `root`, if it is defined.
* @note This doesn't have special handling for Views.
*/
def toRelativeTarget(a: A, root: Option[BaseModule]): IsMember

/** Returns a FIRRTL IsMember that references this object, relative to an optional root.
*
* If `root` is defined, the target is a hierarchical path starting from `root`.
*
* If `root` is not defined, the target is a hierarchical path equivalent to `toAbsoluteTarget`.
*
* @note If `root` is defined, and has not finished elaboration, this must be called within `atModuleBodyEnd`.
* @note The Targetable must be a descendant of `root`, if it is defined.
* @note This doesn't have special handling for Views.
*/
def toRelativeTargetToHierarchy(a: A, root: Option[Hierarchy[BaseModule]]): IsMember
}

object Targetable {

// Extension methods for using Targetable as the user expects
implicit class TargetableSyntax[A](a: A)(implicit targetable: Targetable[A]) {
def toTarget: IsMember = targetable.toTarget(a)
def toAbsoluteTarget: IsMember = targetable.toAbsoluteTarget(a)
def toRelativeTarget(root: Option[BaseModule]): IsMember = targetable.toRelativeTarget(a, root)
def toRelativeTargetToHierarchy(root: Option[Hierarchy[BaseModule]]): IsMember =
targetable.toRelativeTargetToHierarchy(a, root)
}

/** NamedComponent is an awkward private API for all HasId except BaseModule
*
* This instance works for [[Data]], [[MemBase]], and [[SramTarget]]
*/
implicit def forNamedComponent[A <: NamedComponent]: Targetable[A] = new Targetable[A] {
def toTarget(a: A): IsMember = a.toTarget
def toAbsoluteTarget(a: A): IsMember = a.toAbsoluteTarget
def toRelativeTarget(a: A, root: Option[BaseModule]): IsMember = a.toRelativeTarget(root)
def toRelativeTargetToHierarchy(a: A, root: Option[Hierarchy[BaseModule]]): IsMember =
a.toRelativeTargetToHierarchy(root)
}

implicit def forBaseModule[A <: BaseModule]: Targetable[A] = new Targetable[A] {
def toTarget(a: A): IsMember = a.toTarget
def toAbsoluteTarget(a: A): IsMember = a.toAbsoluteTarget
def toRelativeTarget(a: A, root: Option[BaseModule]): IsMember = a.toRelativeTarget(root)
def toRelativeTargetToHierarchy(a: A, root: Option[Hierarchy[BaseModule]]): IsMember =
a.toRelativeTargetToHierarchy(root)
}

implicit def forHierarchy[A <: BaseModule, H[A] <: Hierarchy[A]]: Targetable[H[A]] = new Targetable[H[A]] {
def toTarget(b: H[A]): IsMember = b.toTarget
def toAbsoluteTarget(b: H[A]): IsMember = b.toAbsoluteTarget
def toRelativeTarget(b: H[A], root: Option[BaseModule]): IsMember = b.toRelativeTarget(root)
def toRelativeTargetToHierarchy(b: H[A], root: Option[Hierarchy[BaseModule]]): IsMember =
b.toRelativeTargetToHierarchy(root)
}

implicit def forHasTarget: Targetable[HasTarget] = new Targetable[HasTarget] {
def toTarget(a: HasTarget): IsMember = a.toTarget
def toAbsoluteTarget(a: HasTarget): IsMember = a.toAbsoluteTarget
def toRelativeTarget(a: HasTarget, root: Option[BaseModule]): IsMember = a.toRelativeTarget(root)
def toRelativeTargetToHierarchy(a: HasTarget, root: Option[Hierarchy[BaseModule]]): IsMember =
a.toRelativeTargetToHierarchy(root)
}
}

/** Existential Type class for types that can be converted to a Target
*
* This is useful for containers of Targetables.
*/
sealed trait AnyTargetable {
type A
def a: A
def targetable: Targetable[A]

// Convenience methods
def toTarget: IsMember = targetable.toTarget(a)
def toAbsoluteTarget: IsMember = targetable.toAbsoluteTarget(a)
def toRelativeTarget(root: Option[BaseModule]): IsMember = targetable.toRelativeTarget(a, root)
def toRelativeTargetToHierarchy(root: Option[Hierarchy[BaseModule]]): IsMember =
targetable.toRelativeTargetToHierarchy(a, root)
}

object AnyTargetable {

/** Implicit conversion making working with Targetables easier */
implicit def toAnyTargetable[A](a: A)(implicit targetable: Targetable[A]): AnyTargetable = {
type _A = A
val _a = a
val _targetable = targetable
new AnyTargetable {
type A = _A
val a: A = _a
val targetable: Targetable[A] = _targetable
}
}
}
8 changes: 2 additions & 6 deletions core/src/main/scala/chisel3/experimental/Trace.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ object Trace {

/** Trace a Instance name. */
def traceName(x: RawModule): Unit = {
annotate(new ChiselAnnotation {
def toFirrtl: Annotation = TraceAnnotation(x.toAbsoluteTarget, x.toAbsoluteTarget)
})
annotate(x)(Seq(TraceAnnotation(x.toAbsoluteTarget, x.toAbsoluteTarget)))
}

/** Trace a Data name. This does NOT add "don't touch" semantics to the traced data. If you want this behavior, use an explicit [[chisel3.dontTouch]]. */
Expand All @@ -34,9 +32,7 @@ object Trace {
case aggregate: Aggregate =>
aggregate.elementsIterator.foreach(traceName)
case element: Element =>
annotate(new ChiselAnnotation {
def toFirrtl: Annotation = TraceAnnotation(element.toAbsoluteTarget, element.toAbsoluteTarget)
})
annotate(element)(Seq(TraceAnnotation(element.toAbsoluteTarget, element.toAbsoluteTarget)))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ object Definition extends SourceInfoDoc {
val (ir, module) = Builder.build(Module(proto), dynamicContext)
Builder.components ++= ir._circuit.components
Builder.annotations ++= ir._circuit.annotations
Builder.newAnnotations ++= ir._circuit.newAnnotations
Builder.layers ++= dynamicContext.layers
Builder.options ++= dynamicContext.options
module._circuit = Builder.currentModule
Expand Down
Loading

0 comments on commit a95cfe4

Please sign in to comment.