diff --git a/build.sbt b/build.sbt index ec10fc4503..8d3f72e5ff 100644 --- a/build.sbt +++ b/build.sbt @@ -68,7 +68,8 @@ lazy val warningSuppression = Seq( "cat=deprecation&origin=firrtl\\.options\\.internal\\.WriteableCircuitAnnotation:s", "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\\.ltl.*:s", + "cat=deprecation&msg=Looking up Modules is deprecated:s", ).mkString(",") ) diff --git a/build.sc b/build.sc index e85cd62326..20480dd383 100644 --- a/build.sc +++ b/build.sc @@ -73,7 +73,11 @@ object v extends Module { "cat=deprecation&origin=firrtl\\.options\\.internal\\.WriteableCircuitAnnotation:s", "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\\.ltl.*:s", + // Deprecated for external users, will eventually be removed. + "cat=deprecation&msg=Looking up Modules is deprecated:s", + // Only for testing of deprecated APIs + "cat=deprecation&msg=Use of @instantiable on user-defined types is deprecated:s" ) // ScalacOptions diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/core/IsInstantiable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/core/IsInstantiable.scala index 298c327933..7efc5a96d5 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/core/IsInstantiable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/core/IsInstantiable.scala @@ -11,6 +11,10 @@ trait IsInstantiable object IsInstantiable { implicit class IsInstantiableExtensions[T <: IsInstantiable](i: T) { + @deprecated( + "Use of @instantiable on user-defined types is deprecated. Implement Lookupable for your type instead.", + "Chisel 7.0.0" + ) def toInstance: Instance[T] = new Instance(Proto(i)) } } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala index 29216c0413..d5a2536778 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala @@ -13,13 +13,31 @@ import chisel3.internal.firrtl.ir.{Arg, ILit, Index, LitIndex, ModuleIO, Slot, U import chisel3.internal.{throwException, Builder, ViewParent} import chisel3.internal.binding.{AggregateViewBinding, ChildBinding, CrossModuleBinding, ViewBinding, ViewWriteability} -/** Represents lookup typeclass to determine how a value accessed from an original IsInstantiable - * should be tweaked to return the Instance's version - * Sealed. +/** Typeclass used to recontextualize values from an original Definition to an Instance + * + * Implementations for Chisel types and other common Scala types are provided in the companion object. + * Users can define Lookupable for their own types. + * + * @example {{{ + * case class Foo[T <: Data](name: String, data: T) + * object Foo { + * implicit def lookupable[T <: Data]: Lookupable.Simple[Foo[T]] = + * Lookupable.product2[Foo[T], String, T]( + * x => (x.name, x.data), + * Foo.apply + * ) + * } + * }}} */ @implicitNotFound( - "@public is only legal within a class or trait marked @instantiable, and only on vals of type" + - " Data, BaseModule, MemBase, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable, Option, Either, or Tuple2" + "@public is only legal within a class or trait marked @instantiable, and only on vals of types" + + " that have a Lookupable implementation. Chisel types like Data, BaseModule, and MemBase are supported," + + " as are common Scala types like String, Int, Boolean, Iterable, Option, Either, and Tuples." + + " Please implement Lookupable for ${B}." +) +@deprecatedInheritance( + "Users should use Lookupable factory methods instead (e.g. Lookupable.isLookupable or Lookupable.product)", + "Chisel 7.0.0" ) trait Lookupable[-B] { type C // Return type of the lookup @@ -40,6 +58,24 @@ trait Lookupable[-B] { def definitionLookup[A](that: A => B, definition: Definition[A]): C protected def getProto[A](h: Hierarchy[A]): A = h.proto protected def getUnderlying[A](h: Hierarchy[A]): Underlying[A] = h.underlying + + // Single method that may eventually replace instanceLookup and definitionLookup. + private[chisel3] def hierarchyLookup[A](that: A => B, hierarchy: Hierarchy[A]): C = { + hierarchy match { + case h: Instance[A] => instanceLookup(that, h) + case h: Definition[A] => definitionLookup(that, h) + case h => throw new InternalErrorException(s"Match error: hierarchy=$hierarchy") + } + } +} +// Simplify the implementation of Lookupable by only having 1 virtual method. +// Only the Chisel core types need separate implementations for Instance and Definition. +private trait LookupableImpl[-B] extends Lookupable[B] { + + protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C + + override def instanceLookup[A](that: A => B, instance: Instance[A]): C = impl(that, instance) + override def definitionLookup[A](that: A => B, definition: Definition[A]): C = impl(that, definition) } object Lookupable { @@ -50,6 +86,101 @@ object Lookupable { /** Type alias for simple Lookupable types */ type Simple[B] = Aux[B, B] + /** Provides a lookupable for a type X that does not need special handling + * + * This can only be used by types that do not contain a [[Data]], [[BaseModule]], or any [[IsInstantiable]]. + */ + def isLookupable[X]: Simple[X] = new LookupableImpl[X] { + type C = X + type B = X + override protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C = that(hierarchy.proto) + } + + /** Factory method for creating Lookupable for user-defined types + */ + def product1[X, T1: Lookupable]( + in: X => T1, + out: T1 => X + )( + implicit sourceInfo: SourceInfo + ): Simple[X] = new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a)), hierarchy).asInstanceOf[T1] + out(t1res) + } + } + + /** Factory method for creating Lookupable for user-defined types + */ + def product2[X, T1: Lookupable, T2: Lookupable]( + in: X => (T1, T2), + out: (T1, T2) => X + )( + implicit sourceInfo: SourceInfo + ): Simple[X] = new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a))._1, hierarchy).asInstanceOf[T1] + val t2res = implicitly[Lookupable[T2]].hierarchyLookup[A](a => in(that(a))._2, hierarchy).asInstanceOf[T2] + out(t1res, t2res) + } + } + + /** Factory method for creating Lookupable for user-defined types + */ + def product3[X, T1: Lookupable, T2: Lookupable, T3: Lookupable]( + in: X => (T1, T2, T3), + out: (T1, T2, T3) => X + )( + implicit sourceInfo: SourceInfo + ): Simple[X] = new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a))._1, hierarchy).asInstanceOf[T1] + val t2res = implicitly[Lookupable[T2]].hierarchyLookup[A](a => in(that(a))._2, hierarchy).asInstanceOf[T2] + val t3res = implicitly[Lookupable[T3]].hierarchyLookup[A](a => in(that(a))._3, hierarchy).asInstanceOf[T3] + out(t1res, t2res, t3res) + } + } + + /** Factory method for creating Lookupable for user-defined types + */ + def product4[X, T1: Lookupable, T2: Lookupable, T3: Lookupable, T4: Lookupable]( + in: X => (T1, T2, T3, T4), + out: (T1, T2, T3, T4) => X + )( + implicit sourceInfo: SourceInfo + ): Simple[X] = new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a))._1, hierarchy).asInstanceOf[T1] + val t2res = implicitly[Lookupable[T2]].hierarchyLookup[A](a => in(that(a))._2, hierarchy).asInstanceOf[T2] + val t3res = implicitly[Lookupable[T3]].hierarchyLookup[A](a => in(that(a))._3, hierarchy).asInstanceOf[T3] + val t4res = implicitly[Lookupable[T4]].hierarchyLookup[A](a => in(that(a))._4, hierarchy).asInstanceOf[T4] + out(t1res, t2res, t3res, t4res) + } + } + + /** Factory method for creating Lookupable for user-defined types + */ + def product5[X, T1: Lookupable, T2: Lookupable, T3: Lookupable, T4: Lookupable, T5: Lookupable]( + in: X => (T1, T2, T3, T4, T5), + out: (T1, T2, T3, T4, T5) => X + )( + implicit sourceInfo: SourceInfo + ): Simple[X] = new LookupableImpl[X] { + type C = X + override protected def impl[A](that: A => X, hierarchy: Hierarchy[A]): C = { + val t1res = implicitly[Lookupable[T1]].hierarchyLookup[A](a => in(that(a))._1, hierarchy).asInstanceOf[T1] + val t2res = implicitly[Lookupable[T2]].hierarchyLookup[A](a => in(that(a))._2, hierarchy).asInstanceOf[T2] + val t3res = implicitly[Lookupable[T3]].hierarchyLookup[A](a => in(that(a))._3, hierarchy).asInstanceOf[T3] + val t4res = implicitly[Lookupable[T4]].hierarchyLookup[A](a => in(that(a))._4, hierarchy).asInstanceOf[T4] + val t5res = implicitly[Lookupable[T5]].hierarchyLookup[A](a => in(that(a))._5, hierarchy).asInstanceOf[T5] + out(t1res, t2res, t3res, t4res, t5res) + } + } + /** Clones a data and sets its internal references to its parent module to be in a new context. * * @param data data to be cloned @@ -286,6 +417,7 @@ object Lookupable { } } + @deprecated("Use Lookupable.isLookupable instead of extending this.", "Chisel 7.0.0") class SimpleLookupable[X] extends Lookupable[X] { type B = X type C = X @@ -310,6 +442,7 @@ object Lookupable { } } + @deprecated("Looking up Modules is deprecated, please cast to Instance instead (.toInstance).", "Chisel 7.0.0") implicit def lookupModule[B <: BaseModule](implicit sourceInfo: SourceInfo): Aux[B, Instance[B]] = new Lookupable[B] { type C = Instance[B] @@ -395,13 +528,10 @@ object Lookupable { } implicit def lookupMem[B <: MemBase[_]](implicit sourceInfo: SourceInfo): Simple[B] = - new Lookupable[B] { + new LookupableImpl[B] { type C = B - def definitionLookup[A](that: A => B, definition: Definition[A]): C = { - cloneMemToContext(that(definition.proto), definition.getInnerDataContext.get) - } - def instanceLookup[A](that: A => B, instance: Instance[A]): C = { - cloneMemToContext(that(instance.proto), instance.getInnerDataContext.get) + override protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C = { + cloneMemToContext(that(hierarchy.proto), hierarchy.getInnerDataContext.get) } } @@ -435,13 +565,10 @@ object Lookupable { } implicit def lookupHasTarget(implicit sourceInfo: SourceInfo): Simple[HasTarget] = - new Lookupable[HasTarget] { + new LookupableImpl[HasTarget] { type C = HasTarget - def definitionLookup[A](that: A => HasTarget, definition: Definition[A]): C = { - cloneHasTargetToContext(that(definition.proto), definition.getInnerDataContext.get) - } - def instanceLookup[A](that: A => HasTarget, instance: Instance[A]): C = { - cloneHasTargetToContext(that(instance.proto), instance.getInnerDataContext.get) + override protected def impl[A](that: A => HasTarget, hierarchy: Hierarchy[A]): C = { + cloneHasTargetToContext(that(hierarchy.proto), hierarchy.getInnerDataContext.get) } } @@ -449,108 +576,136 @@ object Lookupable { implicit def lookupIterable[B, F[_] <: Iterable[_]]( implicit sourceInfo: SourceInfo, lookupable: Lookupable[B] - ): Aux[F[B], F[lookupable.C]] = new Lookupable[F[B]] { + ): Aux[F[B], F[lookupable.C]] = new LookupableImpl[F[B]] { type C = F[lookupable.C] - def definitionLookup[A](that: A => F[B], definition: Definition[A]): C = { - val ret = that(definition.proto).asInstanceOf[Iterable[B]] - ret.map { (x: B) => lookupable.definitionLookup[A](_ => x, definition) }.asInstanceOf[C] - } - def instanceLookup[A](that: A => F[B], instance: Instance[A]): C = { - import instance._ - val ret = that(proto).asInstanceOf[Iterable[B]] - ret.map { (x: B) => lookupable.instanceLookup[A](_ => x, instance) }.asInstanceOf[C] + override protected def impl[A](that: A => F[B], hierarchy: Hierarchy[A]): C = { + val ret = that(hierarchy.proto).asInstanceOf[Iterable[B]] + ret.map { (x: B) => lookupable.hierarchyLookup[A](_ => x, hierarchy) }.asInstanceOf[C] } } implicit def lookupOption[B]( implicit sourceInfo: SourceInfo, lookupable: Lookupable[B] - ): Aux[Option[B], Option[lookupable.C]] = new Lookupable[Option[B]] { + ): Aux[Option[B], Option[lookupable.C]] = new LookupableImpl[Option[B]] { type C = Option[lookupable.C] - def definitionLookup[A](that: A => Option[B], definition: Definition[A]): C = { - val ret = that(definition.proto) - ret.map { (x: B) => lookupable.definitionLookup[A](_ => x, definition) } - } - def instanceLookup[A](that: A => Option[B], instance: Instance[A]): C = { - import instance._ - val ret = that(proto) - ret.map { (x: B) => lookupable.instanceLookup[A](_ => x, instance) } + override protected def impl[A](that: A => Option[B], hierarchy: Hierarchy[A]): C = { + val ret = that(hierarchy.proto) + ret.map { (x: B) => lookupable.hierarchyLookup[A](_ => x, hierarchy) } } } implicit def lookupEither[L, R]( implicit sourceInfo: SourceInfo, lookupableL: Lookupable[L], lookupableR: Lookupable[R] - ): Aux[Either[L, R], Either[lookupableL.C, lookupableR.C]] = new Lookupable[Either[L, R]] { + ): Aux[Either[L, R], Either[lookupableL.C, lookupableR.C]] = new LookupableImpl[Either[L, R]] { type C = Either[lookupableL.C, lookupableR.C] - def definitionLookup[A](that: A => Either[L, R], definition: Definition[A]): C = { - val ret = that(definition.proto) - ret.map { (x: R) => lookupableR.definitionLookup[A](_ => x, definition) }.left.map { (x: L) => - lookupableL.definitionLookup[A](_ => x, definition) - } - } - def instanceLookup[A](that: A => Either[L, R], instance: Instance[A]): C = { - import instance._ - val ret = that(proto) - ret.map { (x: R) => lookupableR.instanceLookup[A](_ => x, instance) }.left.map { (x: L) => - lookupableL.instanceLookup[A](_ => x, instance) + override protected def impl[A](that: A => Either[L, R], hierarchy: Hierarchy[A]): C = { + val ret = that(hierarchy.proto) + ret.map { (x: R) => lookupableR.hierarchyLookup[A](_ => x, hierarchy) }.left.map { (x: L) => + lookupableL.hierarchyLookup[A](_ => x, hierarchy) } } } - implicit def lookupTuple2[X, Y]( + // TODO Once Lookupable return type change is removed, we can just call product factory above. + implicit def lookupTuple2[T1, T2]( implicit sourceInfo: SourceInfo, - lookupableX: Lookupable[X], - lookupableY: Lookupable[Y] - ): Aux[(X, Y), (lookupableX.C, lookupableY.C)] = new Lookupable[(X, Y)] { - type C = (lookupableX.C, lookupableY.C) - def definitionLookup[A](that: A => (X, Y), definition: Definition[A]): C = { - val ret = that(definition.proto) - ( - lookupableX.definitionLookup[A](_ => ret._1, definition), - lookupableY.definitionLookup[A](_ => ret._2, definition) - ) + lookupableT1: Lookupable[T1], + lookupableT2: Lookupable[T2] + ): Aux[(T1, T2), (lookupableT1.C, lookupableT2.C)] = new LookupableImpl[(T1, T2)] { + type C = (lookupableT1.C, lookupableT2.C) + override protected def impl[A](that: A => (T1, T2), hierarchy: Hierarchy[A]): C = { + val t1res = lookupableT1.hierarchyLookup[A](that(_)._1, hierarchy) + val t2res = lookupableT2.hierarchyLookup[A](that(_)._2, hierarchy) + (t1res, t2res) } - def instanceLookup[A](that: A => (X, Y), instance: Instance[A]): C = { - import instance._ - val ret = that(proto) - (lookupableX.instanceLookup[A](_ => ret._1, instance), lookupableY.instanceLookup[A](_ => ret._2, instance)) + } + + // TODO Once Lookupable return type change is removed, we can just call product factory above. + implicit def lookupTuple3[T1, T2, T3]( + implicit sourceInfo: SourceInfo, + lookupableT1: Lookupable[T1], + lookupableT2: Lookupable[T2], + lookupableT3: Lookupable[T3] + ): Aux[(T1, T2, T3), (lookupableT1.C, lookupableT2.C, lookupableT3.C)] = new LookupableImpl[(T1, T2, T3)] { + type C = (lookupableT1.C, lookupableT2.C, lookupableT3.C) + override protected def impl[A](that: A => (T1, T2, T3), hierarchy: Hierarchy[A]): C = { + val t1res = lookupableT1.hierarchyLookup[A](that(_)._1, hierarchy) + val t2res = lookupableT2.hierarchyLookup[A](that(_)._2, hierarchy) + val t3res = lookupableT3.hierarchyLookup[A](that(_)._3, hierarchy) + (t1res, t2res, t3res) } } + // TODO Once Lookupable return type change is removed, we can just call product factory above. + implicit def lookupTuple4[T1, T2, T3, T4]( + implicit sourceInfo: SourceInfo, + lookupableT1: Lookupable[T1], + lookupableT2: Lookupable[T2], + lookupableT3: Lookupable[T3], + lookupableT4: Lookupable[T4] + ): Aux[(T1, T2, T3, T4), (lookupableT1.C, lookupableT2.C, lookupableT3.C, lookupableT4.C)] = + new LookupableImpl[(T1, T2, T3, T4)] { + type C = (lookupableT1.C, lookupableT2.C, lookupableT3.C, lookupableT4.C) + override protected def impl[A](that: A => (T1, T2, T3, T4), hierarchy: Hierarchy[A]): C = { + val t1res = lookupableT1.hierarchyLookup[A](that(_)._1, hierarchy) + val t2res = lookupableT2.hierarchyLookup[A](that(_)._2, hierarchy) + val t3res = lookupableT3.hierarchyLookup[A](that(_)._3, hierarchy) + val t4res = lookupableT4.hierarchyLookup[A](that(_)._4, hierarchy) + (t1res, t2res, t3res, t4res) + } + } + + // TODO Once Lookupable return type change is removed, we can just call product factory above. + implicit def lookupTuple5[T1, T2, T3, T4, T5]( + implicit sourceInfo: SourceInfo, + lookupableT1: Lookupable[T1], + lookupableT2: Lookupable[T2], + lookupableT3: Lookupable[T3], + lookupableT4: Lookupable[T4], + lookupableT5: Lookupable[T5] + ): Aux[(T1, T2, T3, T4, T5), (lookupableT1.C, lookupableT2.C, lookupableT3.C, lookupableT4.C, lookupableT5.C)] = + new LookupableImpl[(T1, T2, T3, T4, T5)] { + type C = (lookupableT1.C, lookupableT2.C, lookupableT3.C, lookupableT4.C, lookupableT5.C) + override protected def impl[A](that: A => (T1, T2, T3, T4, T5), hierarchy: Hierarchy[A]): C = { + val t1res = lookupableT1.hierarchyLookup[A](that(_)._1, hierarchy) + val t2res = lookupableT2.hierarchyLookup[A](that(_)._2, hierarchy) + val t3res = lookupableT3.hierarchyLookup[A](that(_)._3, hierarchy) + val t4res = lookupableT4.hierarchyLookup[A](that(_)._4, hierarchy) + val t5res = lookupableT5.hierarchyLookup[A](that(_)._5, hierarchy) + (t1res, t2res, t3res, t4res, t5res) + } + } + + @deprecated( + "Use of @instantiable on user-defined types is deprecated. Implement Lookupable for your type instead.", + "Chisel 7.0.0" + ) implicit def lookupIsInstantiable[B <: IsInstantiable]( implicit sourceInfo: SourceInfo - ): Aux[B, Instance[B]] = new Lookupable[B] { + ): Aux[B, Instance[B]] = new LookupableImpl[B] { type C = Instance[B] - def definitionLookup[A](that: A => B, definition: Definition[A]): C = { - val ret = that(definition.proto) - val underlying = new InstantiableClone[B] { - val getProto = ret - lazy val _innerContext: Hierarchy[_] = definition - } - new Instance(Clone(underlying)) - } - def instanceLookup[A](that: A => B, instance: Instance[A]): C = { - val ret = that(instance.proto) + override protected def impl[A](that: A => B, hierarchy: Hierarchy[A]): C = { + val ret = that(hierarchy.proto) val underlying = new InstantiableClone[B] { val getProto = ret - lazy val _innerContext: Hierarchy[_] = instance + lazy val _innerContext: Hierarchy[_] = hierarchy } new Instance(Clone(underlying)) } } - implicit def lookupIsLookupable[B <: IsLookupable](implicit sourceInfo: SourceInfo): SimpleLookupable[B] = - new SimpleLookupable[B]() - - implicit val lookupInt: SimpleLookupable[Int] = new SimpleLookupable[Int]() - implicit val lookupByte: SimpleLookupable[Byte] = new SimpleLookupable[Byte]() - implicit val lookupShort: SimpleLookupable[Short] = new SimpleLookupable[Short]() - implicit val lookupLong: SimpleLookupable[Long] = new SimpleLookupable[Long]() - implicit val lookupFloat: SimpleLookupable[Float] = new SimpleLookupable[Float]() - implicit val lookupDouble: SimpleLookupable[Double] = new SimpleLookupable[Double]() - implicit val lookupChar: SimpleLookupable[Char] = new SimpleLookupable[Char]() - implicit val lookupString: SimpleLookupable[String] = new SimpleLookupable[String]() - implicit val lookupBoolean: SimpleLookupable[Boolean] = new SimpleLookupable[Boolean]() - implicit val lookupBigInt: SimpleLookupable[BigInt] = new SimpleLookupable[BigInt]() - implicit val lookupUnit: SimpleLookupable[Unit] = new SimpleLookupable[Unit]() + implicit def lookupIsLookupable[B <: IsLookupable](implicit sourceInfo: SourceInfo): Simple[B] = isLookupable[B] + + implicit val lookupInt: Simple[Int] = isLookupable[Int] + implicit val lookupByte: Simple[Byte] = isLookupable[Byte] + implicit val lookupShort: Simple[Short] = isLookupable[Short] + implicit val lookupLong: Simple[Long] = isLookupable[Long] + implicit val lookupFloat: Simple[Float] = isLookupable[Float] + implicit val lookupDouble: Simple[Double] = isLookupable[Double] + implicit val lookupChar: Simple[Char] = isLookupable[Char] + implicit val lookupString: Simple[String] = isLookupable[String] + implicit val lookupBoolean: Simple[Boolean] = isLookupable[Boolean] + implicit val lookupBigInt: Simple[BigInt] = isLookupable[BigInt] + implicit val lookupUnit: Simple[Unit] = isLookupable[Unit] } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/package.scala b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala index c9f0170654..838c2328f7 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/package.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala @@ -9,4 +9,6 @@ package object hierarchy { val Hierarchy = core.Hierarchy type IsInstantiable = core.IsInstantiable type IsLookupable = core.IsLookupable + type Lookupable[P] = core.Lookupable[P] + val Lookupable = core.Lookupable } diff --git a/docs/src/cookbooks/hierarchy.md b/docs/src/cookbooks/hierarchy.md index 1a3e264b7d..732916333b 100644 --- a/docs/src/cookbooks/hierarchy.md +++ b/docs/src/cookbooks/hierarchy.md @@ -85,16 +85,17 @@ chisel3.docs.emitSystemVerilog(new AddTwoInstantiate(16)) ## How do I access internal fields of an instance? -You can mark internal members of a class or trait marked with `@instantiable` with the `@public` annotation. -The requirements are that the field is publicly accessible, is a `val` or `lazy val`, and is a valid type. -The list of valid types are: +You can mark internal members of a Module class or trait marked with `@instantiable` with the `@public` annotation. +The requirements are that the field is publicly accessible, is a `val` or `lazy val`, and must have an implementation of `Lookupable`. -1. `IsInstantiable` -2. `IsLookupable` -3. `Data` -4. `BaseModule` -5. `Iterable`/`Option `containing a type that meets these requirements -6. Basic type like `String`, `Int`, `BigInt` etc. +Types that are supported by default are: + +1. `Data` +2. `BaseModule` +3. `MemBase` +4. `IsLookupable` +5. `Iterable`/`Option`/`Either` containing a type that meets these requirements +6. Basic type like `String`, `Int`, `BigInt`, `Unit`, etc. To mark a superclass's member as `@public`, use the following pattern (shown with `val clock`). @@ -122,13 +123,15 @@ class MyModule extends Module { } ``` -## How do I make my parameters accessible from an instance? +## How do I make my fields accessible from an instance? -If an instance's parameters are simple (e.g. `Int`, `String` etc.) they can be marked directly with `@public`. +If an instance's fields are simple (e.g. `Int`, `String` etc.) they can be marked directly with `@public`. -Often, parameters are more complicated and are contained in case classes. -In such cases, mark the case class with the `IsLookupable` trait. +Often, fields are more complicated (e.g. a user-defined case class). +If a case class is only made up of simple types (i.e. it does *not* contain any `Data`, `BaseModules`, memories, or `Instances`), +it can extend the `IsLookupable` trait. This indicates to Chisel that instances of the `IsLookupable` class may be accessed from within instances. +(If the class *does* contain things like `Data` or modules, [the section below](#how-do-i-make-case-classes-containing-data-or-modules-accessible-from-an-instance).) However, ensure that these parameters are true for **all** instances of a definition. For example, if our parameters contained an id field which was instance-specific but defaulted to zero, @@ -167,7 +170,140 @@ val chiselCircuit = (new chisel3.stage.phases.Elaborate) println("```") ``` -## How do I look up parameters from a Definition, if I don't want to instantiate it? +## How do I make case classes containing Data or Modules accessible from an instance? + +For case classes containing `Data`, `BaseModule`, `MemBase` or `Instance` types, you can provide an implementation of the `Lookupable` typeclass. + +**Note that Lookupable for Modules is deprecated, please cast to Instance instead (with `.toInstance`).** + +Consider the following case class: + +```scala mdoc:reset +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} + +@instantiable +class MyModule extends Module { + @public val wire = Wire(UInt(8.W)) +} +case class UserDefinedType(name: String, data: UInt, inst: Instance[MyModule]) +``` + +By default, instances of `UserDefinedType` will not be accessible from instances: + +```scala mdoc:fail +@instantiable +class HasUserDefinedType extends Module { + val inst = Module(new MyModule) + val wire = Wire(UInt(8.W)) + @public val x = UserDefinedType("foo", wire, inst.toInstance) +} +``` + +We can implement the `Lookupable` type class for `UserDefinedType` in order to make it accessible. +This involves defining an implicit val in the companion object for `UserDefinedType`. +Because `UserDefinedType` has three fields, we use the `Lookupable.product3` factory. +It takes 4 type parameters: the type of the case class, and the types of each of its fields. + +**If any fields are `BaseModules`, you must change them to be `Instance[_]` in order to define the `Lookupable` typeclass.** + +For more information about typeclasses, see the [DataView section on Type Classes](https://www.chisel-lang.org/chisel3/docs/explanations/dataview#type-classes). + +```scala mdoc +import chisel3.experimental.hierarchy.Lookupable +object UserDefinedType { + // Use Lookupable.Simple type alias as return type. + implicit val lookupable: Lookupable.Simple[UserDefinedType] = + Lookupable.product3[UserDefinedType, String, UInt, Instance[MyModule]]( + // Provide the recipe for converting the UserDefinedType to a Tuple. + x => (x.name, x.data, x.inst), + // Provide the recipe for converting a Tuple to a user defined type. + // For case classes, you can use the built-in factory method. + UserDefinedType.apply + ) +} +``` + +Now, we can access instances of `UserDefinedType` from instances: + +```scala mdoc +@instantiable +class HasUserDefinedType extends Module { + val inst = Module(new MyModule) + val wire = Wire(UInt(8.W)) + @public val x = UserDefinedType("foo", wire, inst.toInstance) +} +class Top extends Module { + val inst = Instance(Definition(new HasUserDefinedType)) + println(s"Name is: ${inst.x.name}") +} +``` + +## How do I make type parameterized case classes accessible from an instance? + +Consider the following type-parameterized case class: + +```scala mdoc:reset +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} + +case class ParameterizedUserDefinedType[A, T <: Data](value: A, data: T) +``` + +Similarly to `HasUserDefinedType` we need to define an implicit to provide the `Lookupable` typeclass. +Unlike the simpler example above, however, we use an `implicit def` to handle the type parameters: + +```scala mdoc +import chisel3.experimental.hierarchy.Lookupable +object ParameterizedUserDefinedType { + // Type class materialization is recursive, so both A and T must have Lookupable instances. + // We required this for A via the context bound `: Lookupable`. + // Data is a Chisel built-in so is known to have a Lookupable instance. + implicit def lookupable[A : Lookupable, T <: Data]: Lookupable.Simple[ParameterizedUserDefinedType[A, T]] = + Lookupable.product2[ParameterizedUserDefinedType[A, T], A, T]( + x => (x.value, x.data), + ParameterizedUserDefinedType.apply + ) +} +``` + +Now, we can access instances of `ParameterizedUserDefinedType` from instances: + +```scala mdoc +class ChildModule extends Module { + @public val wire = Wire(UInt(8.W)) +} +@instantiable +class HasUserDefinedType extends Module { + val wire = Wire(UInt(8.W)) + @public val x = ParameterizedUserDefinedType("foo", wire) + @public val y = ParameterizedUserDefinedType(List(1, 2, 3), wire) +} +class Top extends Module { + val inst = Instance(Definition(new HasUserDefinedType)) + println(s"x.value is: ${inst.x.value}") + println(s"y.value.head is: ${inst.y.value.head}") +} +``` + +## How do I make case classes with lots of fields accessible from an instance? + +Lookupable provides factories for `product1` to `product5`. +If your class has more than 5 fields, you can use nested tuples as "pseduo-fields" in the mapping. + +```scala mdoc +case class LotsOfFields(a: Data, b: Data, c: Data, d: Data, e: Data, f: Data) +object LotsOfFields { + implicit val lookupable: Lookupable.Simple[LotsOfFields] = + Lookupable.product5[LotsOfFields, Data, Data, Data, Data, (Data, Data)]( + x => (x.a, x.b, x.c, x.d, (x.e, x.f)), + // Cannot use factory method directly this time since we have to unpack the tuple. + { case (a, b, c, d, (e, f)) => LotsOfFields(a, b, c, d, e, f) }, + ) +} +``` + +## How do I look up fields from a Definition, if I don't want to instantiate it? Just like `Instance`s, `Definition`'s also contain accessors for `@public` members. As such, you can directly access them: diff --git a/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala index 7e08174e1f..9881edaa82 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala @@ -226,6 +226,22 @@ class DefinitionSpec extends ChiselFunSpec with Utils { val (_, annos) = getFirrtlAndAnnos(new Top) annos should contain(MarkAnnotation("~Top|AddOneWithAnnotation>innerWire".rt, "innerWire")) } + it("(1.n): should work on user-defined types that provide Lookupable") { + class Top extends Module { + val defn = Definition(new HasUserDefinedType) + defn.simple.name should be("foo") + defn.parameterized.value should be(List(1, 2, 3)) + mark(defn.simple.data, "data") + mark(defn.simple.inst, "inst") + mark(defn.parameterized.inst, "inst2") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + (annos.collect { case c: MarkAnnotation => c } should contain).allOf( + MarkAnnotation("~Top|HasUserDefinedType>wire".rt, "data"), + MarkAnnotation("~Top|HasUserDefinedType/inst0:AddOne".it, "inst"), + MarkAnnotation("~Top|HasUserDefinedType/inst1:AddOne".it, "inst2") + ) + } } describe("(2): Annotations on designs not in the same chisel compilation") { it("(2.a): should work on an innerWire, marked in a different compilation") { @@ -390,7 +406,7 @@ class DefinitionSpec extends ChiselFunSpec with Utils { "Cannot create a memory port in a different module (Top) than where the memory is (HasMems)." ) } - it("(3.o): should work on HasTarget") { + it("(3.p): should work on HasTarget") { class Top() extends Module { val i = Definition(new HasHasTarget) mark(i.x, "x") @@ -400,6 +416,20 @@ class DefinitionSpec extends ChiselFunSpec with Utils { MarkAnnotation("~Top|HasHasTarget>sram_sram".rt, "x") ) } + it("(3.q): should work on Tuple5 with a Module in it") { + class Top() extends Module { + val defn = Definition(new HasTuple5()) + val (3, w: UInt, "hi", inst: Instance[AddOne], l) = defn.tup + l should be(List(1, 2, 3)) + mark(w, "wire") + mark(inst, "inst") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos.collect { case c: MarkAnnotation => c } should contain(MarkAnnotation("~Top|HasTuple5>wire".rt, "wire")) + annos.collect { case c: MarkAnnotation => c } should contain( + MarkAnnotation("~Top|HasTuple5/inst:AddOne".it, "inst") + ) + } } describe("(4): toDefinition") { it("(4.a): should work on modules") { diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala index 18db2e9e83..de3d424334 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala @@ -223,6 +223,12 @@ object Examples { @public val xy = (x, y) } @instantiable + class HasTuple5() extends Module { + val wire = Wire(UInt(3.W)) + val inst = Module(new AddOne) + @public val tup = (3, wire, "hi", inst, List(1, 2, 3)) + } + @instantiable class HasHasTarget() extends Module { val sram = SRAM(1024, UInt(8.W), 1, 1, 0) @public val x: HasTarget = sram.underlying.get @@ -376,4 +382,32 @@ object Examples { // Should also work in type-parameterized lookupable things @public val y: (Data, Unit) = (Wire(UInt(3.W)), ()) } + + case class UserDefinedType(name: String, data: UInt, inst: Instance[AddOne]) + object UserDefinedType { + implicit val lookupable: Lookupable.Simple[UserDefinedType] = + Lookupable.product3[UserDefinedType, String, UInt, Instance[AddOne]]( + x => (x.name, x.data, x.inst), + UserDefinedType.apply + ) + } + + case class ParameterizedUserDefinedType[A, M <: BaseModule](value: A, inst: Instance[M]) + object ParameterizedUserDefinedType { + implicit def lookupable[A: Lookupable, M <: BaseModule]: Lookupable.Simple[ParameterizedUserDefinedType[A, M]] = + Lookupable.product2[ParameterizedUserDefinedType[A, M], A, Instance[M]]( + x => (x.value, x.inst), + ParameterizedUserDefinedType.apply + ) + } + + @instantiable + class HasUserDefinedType extends Module { + val defn = Definition(new AddOne) + val inst0: Instance[AddOne] = Instance(defn) + val inst1: Instance[AddOne] = Instance(defn) + val wire = Wire(UInt(8.W)) + @public val simple = UserDefinedType("foo", wire, inst0) + @public val parameterized = ParameterizedUserDefinedType(List(1, 2, 3), inst1) + } } diff --git a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala index 9bc6e0ef36..0f6ebf00b3 100644 --- a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala +++ b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala @@ -248,6 +248,23 @@ class InstanceSpec extends ChiselFunSpec with Utils { ) chirrtl.serialize should include("attach (port, i0.port)") } + it("(1.n): should work on user-defined types that provide Lookupable") { + class Top extends Module { + val definition = Definition(new HasUserDefinedType) + val i0 = Instance(definition) + i0.simple.name should be("foo") + i0.parameterized.value should be(List(1, 2, 3)) + mark(i0.simple.data, "data") + mark(i0.simple.inst, "inst") + mark(i0.parameterized.inst, "inst2") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + (annos.collect { case c: MarkAnnotation => c } should contain).allOf( + MarkAnnotation("~Top|Top/i0:HasUserDefinedType>wire".rt, "data"), + MarkAnnotation("~Top|Top/i0:HasUserDefinedType/inst0:AddOne".it, "inst"), + MarkAnnotation("~Top|Top/i0:HasUserDefinedType/inst1:AddOne".it, "inst2") + ) + } } describe("(2) Annotations on designs not in the same chisel compilation") { it("(2.a): should work on an innerWire, marked in a different compilation") { @@ -486,6 +503,22 @@ class InstanceSpec extends ChiselFunSpec with Utils { MarkAnnotation("~Top|Top/i:HasPublicUnit>y_1".rt, "y_1") ) } + it("(3.t): should work on Tuple5 with a Module in it") { + class Top() extends Module { + val i = Instance(Definition(new HasTuple5())) + val (3, w: UInt, "hi", inst: Instance[AddOne], l) = i.tup + l should be(List(1, 2, 3)) + mark(w, "wire") + mark(inst, "inst") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos.collect { case c: MarkAnnotation => c } should contain( + MarkAnnotation("~Top|Top/i:HasTuple5>wire".rt, "wire") + ) + annos.collect { case c: MarkAnnotation => c } should contain( + MarkAnnotation("~Top|Top/i:HasTuple5/inst:AddOne".it, "inst") + ) + } } describe("(4) toInstance") { it("(4.a): should work on modules") {