Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new DynamicObject.apply method to create Class instances. (backport #3792) #3797

Merged
merged 2 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/src/main/scala/chisel3/RawModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
}
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/chisel3/properties/Class.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
72 changes: 68 additions & 4 deletions core/src/main/scala/chisel3/properties/Object.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down
43 changes: 42 additions & 1 deletion src/test/scala/chiselTests/properties/ObjectSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down
Loading