Skip to content

Commit

Permalink
Add a new DynamicObject.apply method to create Class instances. (#3792)…
Browse files Browse the repository at this point in the history
… (#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 04535d3)

Co-authored-by: Mike Urbach <mike.urbach@sifive.com>
Co-authored-by: Jack Koenig <koenig@sifive.com>
  • Loading branch information
3 people committed Jan 30, 2024
1 parent da8783a commit 1eee017
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 6 deletions.
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

0 comments on commit 1eee017

Please sign in to comment.