From 1eee017dbf134039453b82b429b643b6a44d5d09 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:55:03 -0800 Subject: [PATCH] Add a new DynamicObject.apply method to create Class instances. (#3792) (#3797) This is in addition to the existing support in Definition.apply. Sometimes it is not possible to use Definition.apply, for example, if you plan to bore ports through the Class being constructed. The new DynamicObject.apply method supports this, and creates a DynamicObject from the newly elaborated Class. (cherry picked from commit 04535d325984cc83a7bbf81d55371f4212e06ae1) Co-authored-by: Mike Urbach Co-authored-by: Jack Koenig --- core/src/main/scala/chisel3/RawModule.scala | 2 + .../chisel3/internal/firrtl/Converter.scala | 2 +- .../main/scala/chisel3/properties/Class.scala | 2 + .../scala/chisel3/properties/Object.scala | 72 +++++++++++++++++-- .../chiselTests/properties/ObjectSpec.scala | 43 ++++++++++- 5 files changed, 115 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 45a8329629e..30ddd4f2967 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -130,8 +130,10 @@ abstract class RawModule extends BaseModule { case id: DynamicObject => { // Force name of the DynamicObject, and set its Property[ClassType] type's ref to the DynamicObject. // The type's ref can't be set upon instantiation, because the DynamicObject hasn't been named yet. + // This also updates the source Class ref to the DynamicObject ref now that it's named. id.forceName(default = "_object", _namespace) id.getReference.setRef(id.getRef) + id.setSourceClassRef() } case id: StaticObject => { // Set the StaticObject's ref and Property[ClassType] type's ref to the BaseModule for the Class. diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index e0959969e2b..793dd45e5fc 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -477,7 +477,7 @@ private[chisel3] object Converter { fir.DefClass( convert(id._getSourceLocator), name, - ports.map(p => convert(p, typeAliases)), + (ports ++ ctx.secretPorts).map(p => convert(p, typeAliases)), convert(cmds, ctx, typeAliases) ) } diff --git a/core/src/main/scala/chisel3/properties/Class.scala b/core/src/main/scala/chisel3/properties/Class.scala index 5b4f4ba7f35..8fe94db2eae 100644 --- a/core/src/main/scala/chisel3/properties/Class.scala +++ b/core/src/main/scala/chisel3/properties/Class.scala @@ -36,8 +36,10 @@ class Class extends BaseModule { case id: DynamicObject => { // Force name of the Object, and set its Property[ClassType] type's ref to the Object. // The type's ref can't be set within instantiate, because the Object hasn't been named yet. + // This also updates the source Class ref to the DynamicObject ref now that it's named. id.forceName(default = "_object", _namespace) id.getReference.setRef(id.getRef) + id.setSourceClassRef() } case id: StaticObject => { // Set the StaticObject's ref and Property[ClassType] type's ref to the BaseModule for the Class. diff --git a/core/src/main/scala/chisel3/properties/Object.scala b/core/src/main/scala/chisel3/properties/Object.scala index d037f3eec2a..853fc5acdb2 100644 --- a/core/src/main/scala/chisel3/properties/Object.scala +++ b/core/src/main/scala/chisel3/properties/Object.scala @@ -2,9 +2,13 @@ package chisel3.properties -import chisel3.SpecifiedDirection -import chisel3.experimental.BaseModule -import chisel3.internal.{throwException, HasId, NamedComponent, ObjectFieldBinding} +import scala.language.experimental.macros + +import chisel3.{Module, RawModule, SpecifiedDirection} +import chisel3.experimental.{BaseModule, SourceInfo} +import chisel3.internal.firrtl.ir.{DefClass, DefObject} +import chisel3.internal.sourceinfo.InstTransform +import chisel3.internal.{throwException, Builder, HasId, NamedComponent, ObjectFieldBinding} import scala.collection.immutable.HashMap import scala.language.existentials @@ -14,13 +18,43 @@ import scala.language.existentials * This cannot be instantiated directly, instead see Class.unsafeGetDynamicObject. * * The DynamicObject is generally unsafe, in that its getField method does not check the name, type, or direction of - * the accessed field. It may be used with care, and a more typesafe version will be added. + * the accessed field. It may be used with care, and a more typesafe version called StaticObject has been added, which + * works with the Definition / Instance APIs. + * + * To create a DynamicObject directly, wrap a Class with DynamicObject.apply. For example: + * + * {{{ + * val obj = DynamicObject(new Class { + * override def desiredName = "Test" + * val in = IO(Input(Property[Int]())) + * val out = IO(Output(Property[Int]())) + * out := in + * }) + * }}} */ class DynamicObject private[chisel3] (val className: ClassType) extends HasId with NamedComponent { private val tpe = Property[className.Type]() _parent.foreach(_.addId(this)) + // Keep state for a reference to the Class from which the DynamicObject was created. + // This is used to update the Class ref to the DynamicObject ref, for Classes created via DynamicObject.apply. + private var _class: Class = null + protected[chisel3] def setSourceClass(cls: Class): Unit = { + require(_class == null, "Cannot set DynamicObject class multiple times") + _class = cls + } + private def getSourceClass: Option[Class] = Option(_class) + + /** Set the source Class ref to this DynamicObject's ref. + * + * After the DynamicObject is named, this must be called so the Class ref will be updated to the DynamicObject ref. + * This is needed for any secret ports that are bored in the class, which point to the Class ref. + */ + protected[chisel3] def setSourceClassRef(): Unit = { + getSourceClass.foreach(_.setRef(this.getRef, true)) + } + /** Get a reference to this Object, suitable for use Ports. */ def getReference: Property[ClassType] = tpe @@ -44,6 +78,36 @@ class DynamicObject private[chisel3] (val className: ClassType) extends HasId wi } } +object DynamicObject { + + /** A wrapper method to wrap Class instantiations and return a DynamicObject. + * + * This is necessary to help Chisel track internal state. This can be used instead of `Definition.apply` if a + * DynamicObject is required. If possible, it is safer to user `Definition.apply` and StaticObject. + * + * @param bc the Class being created + * + * @return a DynamicObject representing an instance of the Class + */ + def apply[T <: Class](bc: => T): DynamicObject = macro InstTransform.apply[T] + + /** @group SourceInfoTransformMacro */ + def do_apply[T <: Class](bc: => T)(implicit sourceInfo: SourceInfo): DynamicObject = { + // Instantiate the Class definition. + val cls = Module.evaluate[T](bc) + + // Build the DynamicObject with associated bindings. + val obj = Class.unsafeGetDynamicObject(cls.name) + + // Save the source Class for this DynamicObject. + // After the DynamicObject is named, the Class ref will be updated to the DynamicObject ref. + // This is needed for any secret ports that are bored in the class, which point to the Class ref. + obj.setSourceClass(cls) + + obj + } +} + /** Represents an instance of a Class. * * This exists to associate an Instance[Class] with a Property[ClassType] for that Class. diff --git a/src/test/scala/chiselTests/properties/ObjectSpec.scala b/src/test/scala/chiselTests/properties/ObjectSpec.scala index 1fb91f24620..6f7ce889cbc 100644 --- a/src/test/scala/chiselTests/properties/ObjectSpec.scala +++ b/src/test/scala/chiselTests/properties/ObjectSpec.scala @@ -3,8 +3,9 @@ package chiselTests.properties import chisel3._ -import chisel3.properties.{Class, Property} +import chisel3.properties.{Class, DynamicObject, Property} import chisel3.experimental.hierarchy.{Definition, Instance} +import chisel3.util.experimental.BoringUtils import chiselTests.{ChiselFlatSpec, MatchesAndOmits} import circt.stage.ChiselStage @@ -95,6 +96,46 @@ class ObjectSpec extends ChiselFlatSpec with MatchesAndOmits { )() } + it should "support creating DynamicObject from a Class with DynamicObject.apply" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val out = IO(Output(Property[Int]())) + + val obj = DynamicObject(new Class { + override def desiredName = "Test" + val in = IO(Input(Property[Int]())) + val out = IO(Output(Property[Int]())) + }) + + obj.getField[Int]("in") := Property(1) + + out := obj.getField[Int]("out") + }) + + matchesAndOmits(chirrtl)( + "object obj of Test", + "propassign obj.in, Integer(1)", + "propassign out, obj.out" + )() + } + + it should "support boring ports through a Class created with DynamicObject.apply" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RawModule { + val in = IO(Input(Property[Int]())) + + val obj = DynamicObject(new Class { + override def desiredName = "Test" + val out = IO(Output(Property[Int]())) + out := BoringUtils.bore(in) + }) + }) + + matchesAndOmits(chirrtl)( + "input out_bore : Integer", + "propassign out, out_bore", + "propassign obj.out_bore, in" + )() + } + behavior.of("StaticObject") it should "support Instances of Objects in Class ports" in {