-
Notifications
You must be signed in to change notification settings - Fork 17
MMIO Cake Pattern
The final step in writing the MMIO peripheral is to connect it with the rest of the SoC.
In Rocket-chip world this is done using common practice known as the Cake Pattern. Basically, this means there will be two more traits – one of them is called inner twin, the other outer twin. The former holds the implementation of our widget, i.e. all the stuff we wrote (ChiselModule
wrapped in HasRegMap
extension wrapped in TileLink Register Router node) and it’s pretty much unaware of the outside world (the rest of SoC). The latter extends LazyModule
, where lazy
stands for the code that gets executed before any of the hardware (implementation = inner twin) is elaborated.
In this, simplest of cases, the Cake Pattern is applied only to connect the widget’s TileLink interface to the MMIO crossbar.
The inner twin in this case is trivial:
trait CanHavePeripheryJustReadModuleImp extends LazyModuleImp {
val outer: CanHavePeripheryJustRead
}
as it just declares that there’s the outer twin and its type. Both twins should be named in accordance with Rocket-chip terminology, so for the MMIOs their class names should start with CanHavePeriphery
, with the inner twin being suffixed with ModuleImp
. Also, inner twin’s characteristic is that it inherits from LazyModuleImp
.
The outer twin is also trivial from Cake Pattern point of view, but does involve a bit more Scala/Diplomacy/TileLink code, so not so trivial from the VHDL/Verilog digital designer point of view:
trait CanHavePeripheryJustRead { this: BaseSubsystem =>
private val portName = "justreadPortName"
val justread = p(JustReadKey) match {
case Some(params) => {
val justread = LazyModule(new JustReadTL(params, pbus.beatBytes)(p))
pbus.toVariableWidthSlave(Some(portName)) { justread.node }
// i to je ono sto povezujemo na RocketChip TL magistralu
Some(justread)
}
case None => None
}
}
The node
in the above code snippet is the TileLink node of JustReadTL
that is instantiated in val justread
.
We pass the JustReadKey
to p
, and then two things can happen:
- if the
p
returnsparams
successfully, the implementation of our complete widget (JustReadTL
) is instantiated and its TileLink connector (called “node”) is used to connect the widget, or - if the
p
failsnull
is returned and there’s no implementation, no connection.
Note that the widget (JustReadTL
) is instantiated as a LazyModule
itself, which means that at runtime all the Scala code gets executed first, and only afterwards, after all the parameters are negotiated and determined the implementation is instantiated, i.e. hardware (RTL) is elaborated.
What's achieved in this way may not seem obvious in this simple example, but the main idea is that the parameter of JustRead
may be dynamically determined by some calculation done somewhere else in hardware and yet we don't need to change a line in our code, while everything will work. For example, let's say that the SoC for some reason requires that the width of 230 is not 32, rather 36; running this exact code will still work, because the Cake Pattern enables parameter negotiations before the hardware is emitted (generated).
Final state of this project is shown in Fig. MMIO.5.
Fig. MMIO.4. Inner and outer twin (Cake Pattern) in green