-
Notifications
You must be signed in to change notification settings - Fork 637
[Module] Add afterModuleBuilt hook #4479
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
Conversation
|
4392a7e
to
a3a9cf9
Compare
Good idea! I have been thinking how to instantiating the corresponding TestBench from DUT. I think this PR gives us a acceptable solution. I wonder if there is any related API in CIRCT to elaborate Verilog for multiple top Verilog? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am interested in this API, and you're showing an interesting use case in the tests so far, but this presents a pretty big API surface area and we should make sure to work through some of the consequences:
- Done correctly, we should be able to implement the hooking up of
ImplicitClock
andImplicitReset
with this API. - I think this should work for
Instance[_]
(like the module whereafterModuleBuilt
is itself being instantiated as anInstance[_]
). This has pretty broad implications--it means you should not really be capturing anything from the body of the Module, but instead the type of the thunk passed toafterModuleBuilt
probably should beInstance[T] => Unit
so that you can access any public fields of the instance. - What happens if you use
afterModuleBuilt
in the top module? For implementing (1), it should be a no-op, so maybe the type should beOption[Instance[T]] => Unit
. - Unfortunately I'm not sure we can prevent people capturing values from the Definition that they shouldn't (maybe we can eventually in Scala 3 with Capture Checking), but perhaps this is just an advanced API and we tell users to be careful and "don't screw up" 🤷♀️
Is this something that would let you drive the IOs of the module to default values / DontCare? |
I believe it should make that possible, see my comment about implementing |
Let me think through some of the stuff @jackkoenig and @mwachs5 have been pointing out. Basically, the idea is to have a callback/lambda/block of code executed after a module's definition becomes available such that you can instantiate the module from tests. This expressly doesn't want to know about the concrete instance that triggered the module build, and it doesn't want to be able to interact with that instance in anyway (i.e. no connecting of ports). So I don't think the block should have any It's important that the callback is able to capture values from the original module body though, mainly parameters, constructor arguments, and anything else that's lying around. However, since the module is closed, you can't connect to any of the hardware or change it in any way. You can, however, inspect its types and the like. So all this does is give you the opportunity to construct additional sibling module definitions that themselves instantiate the surrounding module. I don't think this really interacts with any of things you pointed out @jackkoenig. Maybe the name should be something like |
The following is the concrete unit testing API you could build around this: trait Testable extends RawModule {
def unitTest(gen: => Unit): Unit = {
val parent = this
afterModuleBuilt {
Definition(new RawModule with Public {
override def desiredName = parent.desiredName + "UnitTest"
gen
})
}
}
}
@instantiable
class Adder(width: Int) extends RawModule {
override def desiredName = f"Adder${width}"
@public val a = IO(Input(UInt(width.W)))
@public val b = IO(Input(UInt(width.W)))
@public val z = IO(Output(UInt(widht.W)))
z := a + b
unitTest {
val dut = Instance(this.toDefinition)
dut.a := 1.U << (width - 1)
dut.b := 1.U
Assert(dut.z === 0.U, label = f"${width} bit adder should overflow")
}
}
class Top extends RawModule {
val a0 = Instantiate(Adder(5))
val a1 = Instantiate(Adder(42))
}
// Expected modules:
// - Top (instantiates Adder5 and Adder42)
// - Adder5
// - Adder42
// - Adder5UnitTest (instantiates Adder5)
// - Adder42UnitTest (instantiates Adder42) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
We are starting to want to play around with this for future testing APIs. This may evolve in the future, but this is good enough for me.
A few nits about the tests. Looks good otherwise.
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.
6d7db62
to
442ff71
Compare
Thanks for the feedback @seldridge! I've tweaked things a bit. Should be good to go. I don't think I have permission to merge this myself though 😿 |
Add the
afterModuleBuilt
hook toRawModule
, similar to the existingatModuleBodyEnd
. 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 ofafterModuleBuilt
can get the definition of the surrounding module and instantiate it, which is interesting for generating additional collateral like unit tests alongside a module.Contributor Checklist
docs/src
?Type of Improvement
Desired Merge Strategy
Release Notes
The new
afterModuleBuilt
hook can be used to schedule code to be executed once a module has been fully generated and its definition is available. This allows further collateral such as unit tests to be generated alongside a module.Reviewer Checklist (only modified by reviewer)
3.6.x
,5.x
, or6.x
depending on impact, API modification or big change:7.0
)?Enable auto-merge (squash)
, clean up the commit message, and label withPlease Merge
.Create a merge commit
.