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 {