Skip to content

Commit 7f90d72

Browse files
[Module] Add afterModuleBuilt hook (#4479)
Add the `afterModuleBuilt` hook to `RawModule`, similar to the existing `atModuleBodyEnd`. This hook is called after a module has been fully constructed and its resulting component definition has been added to the builder. Thus code inside the body of `afterModuleBuilt` can get the definition of the surrounding module and instantiate it, which is interesting for generating additional collateral like unit tests alongside a module.
1 parent 48866a9 commit 7f90d72

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

core/src/main/scala/chisel3/ModuleImpl.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ private[chisel3] trait ObjectModuleImpl {
114114
Builder.setPrefix(savePrefix)
115115
Builder.enabledLayers = saveEnabledLayers
116116

117+
module.moduleBuilt()
117118
module
118119
}
119120

@@ -644,6 +645,11 @@ package experimental {
644645
}
645646
}
646647

648+
/** Called once the module's definition has been fully built. At this point
649+
* the module can be instantiated through its definition.
650+
*/
651+
protected[chisel3] def moduleBuilt(): Unit = {}
652+
647653
//
648654
// Chisel Internals
649655
//

core/src/main/scala/chisel3/RawModule.scala

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,46 @@ abstract class RawModule extends BaseModule {
8282
}
8383
private val _atModuleBodyEnd = new ArrayBuffer[() => Unit]
8484

85+
/** Hook to invoke hardware generators after a Module has been constructed and closed.
86+
*
87+
* This is useful for running hardware generators after a Module's constructor has run and its Definition is available, while still having access to arguments and definitions in the constructor. The Module itself can no longer be modified at this point.
88+
*
89+
* An interesting application of this is the generation of unit tests whenever a module is instantiated. For example:
90+
*
91+
* {{{
92+
* class Example(N: int) extends RawModule {
93+
* private val someSecret: Int = ...
94+
*
95+
* afterModuleBuilt {
96+
* // Executes once the surrounding module is closed.
97+
* // We can get its definition at this point and pass it to another module.
98+
* Definition(ExampleTest(this.definition, someSecret))
99+
* }
100+
* }
101+
*
102+
* class ExampleTest(unitDef: Definition[Example], someSecret: Int) extends RawModule {
103+
* // Instantiate the generated module and test it.
104+
* val unit = Instance(unitDef)
105+
* ...
106+
* }
107+
*
108+
* class Parent extends RawModule {
109+
* Instantiate(Example(42))
110+
* }
111+
*
112+
* // Resulting modules:
113+
* // - Parent (top-level)
114+
* // - instantiates Example
115+
* // - ExampleTest (top-level)
116+
* // - instantiates Example
117+
* // - Example
118+
* }}}
119+
*/
120+
protected def afterModuleBuilt(gen: => Unit): Unit = {
121+
_afterModuleBuilt += { () => gen }
122+
}
123+
private val _afterModuleBuilt = new ArrayBuffer[() => Unit]
124+
85125
//
86126
// RTL construction internals
87127
//
@@ -253,6 +293,9 @@ abstract class RawModule extends BaseModule {
253293
Builder.currentBlock.get.addSecretCommand(rhs)
254294
}
255295

296+
// Evaluate any afterModuleBuilt generators.
297+
protected[chisel3] override def moduleBuilt(): Unit = _afterModuleBuilt.foreach(_())
298+
256299
private[chisel3] def initializeInParent(): Unit = {}
257300
}
258301

src/test/scala/chiselTests/RawModuleSpec.scala

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package chiselTests
44

55
import chisel3._
66
import chisel3.aop.Select
7-
import chisel3.experimental.hierarchy.Definition
7+
import chisel3.experimental.hierarchy.{instantiable, public, Definition, Instance}
88
import chisel3.reflect.DataMirror
99
import chisel3.testers.BasicTester
1010
import circt.stage.ChiselStage
@@ -170,4 +170,57 @@ class RawModuleSpec extends ChiselFlatSpec with Utils with MatchesAndOmits {
170170
}
171171
}
172172
}
173+
174+
"RawModule with afterModuleBuilt" should "be able to create other modules" in {
175+
val chirrtl = ChiselStage.emitCHIRRTL(new RawModule {
176+
override def desiredName = "Foo"
177+
val port0 = IO(Input(Bool()))
178+
179+
afterModuleBuilt {
180+
Module(new RawModule {
181+
override def desiredName = "Bar"
182+
val port1 = IO(Input(Bool()))
183+
})
184+
}
185+
})
186+
187+
matchesAndOmits(chirrtl)(
188+
"module Foo",
189+
"input port0 : UInt<1>",
190+
"module Bar",
191+
"input port1 : UInt<1>"
192+
)()
193+
}
194+
195+
"RawModule with afterModuleBuilt" should "be able to instantiate the surrounding module" in {
196+
@instantiable
197+
class Foo extends RawModule {
198+
override def desiredName = "Foo"
199+
@public val port0 = IO(Input(Bool()))
200+
201+
afterModuleBuilt {
202+
val fooDef = this.toDefinition
203+
Module(new RawModule {
204+
override def desiredName = "Bar"
205+
val port1 = IO(Input(Bool()))
206+
val foo1 = Instance(fooDef)
207+
val foo2 = Instance(fooDef)
208+
foo1.port0 := port1
209+
foo2.port0 := port1
210+
})
211+
}
212+
}
213+
val chirrtl = ChiselStage.emitCHIRRTL(new Foo)
214+
215+
matchesAndOmits(chirrtl)(
216+
"module Foo :",
217+
"input port0 : UInt<1>",
218+
"module Bar",
219+
"input port1 : UInt<1>",
220+
"inst foo1 of Foo",
221+
"inst foo2 of Foo",
222+
"connect foo1.port0, port1",
223+
"connect foo2.port0, port1"
224+
)()
225+
}
173226
}

0 commit comments

Comments
 (0)