Skip to content

Commit

Permalink
Merge branch 'main' into circt-sram
Browse files Browse the repository at this point in the history
  • Loading branch information
unlsycn authored Nov 20, 2024
2 parents ded35ec + ec4e2d5 commit 830ee50
Show file tree
Hide file tree
Showing 9 changed files with 504 additions and 105 deletions.
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.",
"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.
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))
}
@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

0 comments on commit 830ee50

Please sign in to comment.