Skip to content

Conversation

fabianschuiki
Copy link
Contributor

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.

Contributor Checklist

  • Did you add Scaladoc to every public function/method?
  • Did you add at least one test demonstrating the PR?
  • Did you delete any extraneous printlns/debugging code?
  • Did you specify the type of improvement?
  • Did you add appropriate documentation in docs/src?
  • Did you request a desired merge strategy?
  • Did you add text to be included in the Release Notes for this change?

Type of Improvement

  • Feature (or new API)

Desired Merge Strategy

  • Squash: The PR will be squashed and merged (choose this if you have no preference).

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)

  • Did you add the appropriate labels? (Select the most appropriate one based on the "Type of Improvement")
  • Did you mark the proper milestone (Bug fix: 3.6.x, 5.x, or 6.x depending on impact, API modification or big change: 7.0)?
  • Did you review?
  • Did you check whether all relevant Contributor checkboxes have been checked?
  • Did you do one of the following when ready to merge:
    • Squash: You/ the contributor Enable auto-merge (squash), clean up the commit message, and label with Please Merge.
    • Merge: Ensure that contributor has cleaned up their commit history, then merge with Create a merge commit.

Copy link

linux-foundation-easycla bot commented Oct 17, 2024

CLA Signed

The committers listed above are authorized under a signed CLA.

  • ✅ login: fabianschuiki / name: Fabian Schuiki (442ff71)

@sequencer
Copy link
Member

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?

@jackkoenig jackkoenig added the Feature New feature, will be included in release notes label Oct 18, 2024
Copy link
Contributor

@jackkoenig jackkoenig left a 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:

  1. Done correctly, we should be able to implement the hooking up of ImplicitClock and ImplicitReset with this API.
  2. I think this should work for Instance[_] (like the module where afterModuleBuilt is itself being instantiated as an Instance[_]). 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 to afterModuleBuilt probably should be Instance[T] => Unit so that you can access any public fields of the instance.
  3. What happens if you use afterModuleBuilt in the top module? For implementing (1), it should be a no-op, so maybe the type should be Option[Instance[T]] => Unit.
  4. 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" 🤷‍♀️

@mwachs5
Copy link
Contributor

mwachs5 commented Oct 18, 2024

Is this something that would let you drive the IOs of the module to default values / DontCare?

@jackkoenig
Copy link
Contributor

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 ImplicitClock and ImplicitReset which requiring connecting them up in the instantiating module.

@fabianschuiki
Copy link
Contributor Author

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 Instance argument, or know about the instantiation site at all.

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 afterDefinitionAvailable, withDefinition, onceDefined, or similar, to avoid confusion?

@fabianschuiki
Copy link
Contributor Author

fabianschuiki commented Oct 22, 2024

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)

Copy link
Member

@seldridge seldridge left a 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.
@fabianschuiki
Copy link
Contributor Author

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 😿

@seldridge seldridge enabled auto-merge (squash) November 8, 2024 22:02
@seldridge seldridge merged commit 7f90d72 into chipsalliance:main Nov 8, 2024
14 checks passed
@fabianschuiki fabianschuiki deleted the afterModuleBuilt branch November 14, 2024 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature New feature, will be included in release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants