-
Notifications
You must be signed in to change notification settings - Fork 17
Lazy Modules
A lazy module in Scala is one that has a delay between the point during the run where new MyLazyModule
runs, and the point where the constructor for that instance actually runs. The constructor is delayed until the first point at which a function is called on the module.
In Rocket-chip, this is used to solve a problem. The problem is that when connecting things with TileLink, sometimes one end of the connection has a variable size, while the other end has a definite size. In this case, the variable end adapts itself to be the same size as what's on the other end. If the new
for the variable end runs first, then the information about the other end is not yet known, and we're stuck.
The solution is to use the lazy module feature. It allows new
to run for both ends first, and so the details of both ends are known, and after that, the parameters for both ends are calculated. This happens before any wires are connected. Because no wires have been connected, no constructors have run yet. This way the parameters that the constructors need are calculated after both ends are known, but before the constructors execute. That's what a lazy module enables.
Comments in the code below illustrate the mechanism and details:
// See LICENSE.SiFive for license details.
package freechips.rocketchip.system
import chisel3._
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.devices.debug.Debug
import freechips.rocketchip.diplomacy.LazyModule
import freechips.rocketchip.util.AsyncResetReg
class TestHarness()(implicit p: Parameters) extends Module {
val io = IO(new Bundle {
//note: IO automatically comes with clock and reset signals
val success = Output(Bool())
})
val ldut = LazyModule(new ExampleRocketSystem)
val dut = Module(ldut.module)
// Allow the debug ndreset to reset the dut, but not until the initial reset has completed
dut.reset := (reset.asBool | dut.debug.map { debug => AsyncResetReg(debug.ndreset) }.getOrElse(false.B)).asBool
dut.dontTouchPorts()
dut.tieOffInterrupts()
SimAXIMem.connectMem(ldut)
SimAXIMem.connectMMIO(ldut)
ldut.l2_frontend_bus_axi4.foreach(_.tieoff)
Debug.connectDebug(dut.debug, dut.resetctrl, dut.psd, clock, reset.asBool, io.success)
}
Note: extends Module
causes circuit generation -- if don't extend Module class, then will be just plain Scala code -- will generate no Verilog
Note: IO -- IO
in caps is a method defined in Module -- when it defines the "top" inputs and outputs
Note: LazyModule
is the same as Module
, only difference is LazyModule
makes Scala wait for the class
//Example of Lazy Module
class TestNumber1()(implicit p: Parameters) extends Module {
//note: unlike Java, just start writing code -- it is implicitly constructor code unless define a function
val io = IO(new Bundle {
val success = Output(Bool())
})
val testHarnessInstance = new TestHarness(p) //normally constructor of TestHarness runs here, but it's Lazy, so waits
someFunctionThatAddsKeysTo_p()
testHarnessInstance.dut.reset := 0.U //when this line runs is what triggers the constructor of TestHarness to run
// note that p is a struct. We call that the param struct or just p struct.
// The actual parameter values are retrieved from the p struct either directly, p.someParam, or by using keys, p(key).
// As of Dec 7 2021 we do not fully understand Diplomacy, but the working hypothesis is that new keys can be added
// by someFunctionThatAddsKeysTo_p.
// Ex; someFunctionThatAddsKeysTo_p adds key "foo". This means that, at the point that "new Testharness(p)" was run,
// there did not exist a "foo" key in p struct. But at the point that testHarnessInstance.dut.reset runs, there _is_
// a "foo" key in p struct. So the constructor of TestHarness is able to retrieve a value for foo key at the point
// that the constructor actually runs, whereas it would not have been able to retrieve a value for foo key at the
// point that new Testharness() ran.