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

Refactor Lookupable #4519

Merged
merged 1 commit into from
Nov 20, 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
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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(",")
)

Expand Down
6 changes: 5 additions & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we point to website docs on this?

"Chisel 7.0.0"
)
def toInstance: Instance[T] = new Instance(Proto(i))
}
}
331 changes: 243 additions & 88 deletions core/src/main/scala/chisel3/experimental/hierarchy/core/Lookupable.scala

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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
}
164 changes: 150 additions & 14 deletions docs/src/cookbooks/hierarchy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
jackkoenig marked this conversation as resolved.
Show resolved Hide resolved
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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -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")
Expand All @@ -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") {
Expand Down
34 changes: 34 additions & 0 deletions src/test/scala/chiselTests/experimental/hierarchy/Examples.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
jackkoenig marked this conversation as resolved.
Show resolved Hide resolved
}
@instantiable
class HasHasTarget() extends Module {
val sram = SRAM(1024, UInt(8.W), 1, 1, 0)
@public val x: HasTarget = sram.underlying.get
Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -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") {
Expand Down