-
Notifications
You must be signed in to change notification settings - Fork 21
Introduction to Rocket Chip code style
Rocket Chip code employs some advanced programming techniques. The goal of the techniques is to implement a powerful parameterization system, that can easily change sizes of buses, change number of connections going to or from circuit elements, and so forth.
The team behind Rocket Chip chose to do this with four inter-related code patterns:
So, Mixins and LazyModule are base mechanics. Then Cake Pattern is a layer above those two. The Cake Pattern is how code gets connected together -- it is like plumbing in the code space. The way that a circuit ends up wired to another circuit, involves using the Cake pattern. It can be used on its own, but is required (I believe) for Diplomacy. Lastly, Diplomacy is a massive package of infrastructure, which is all about TileLink -- it’s the way that buses get interconnected in the on-chip network -- so connecting core to memory controller, and so on.
I'll give a brief explanation of each of these, then show how they interrelate. The individual pages are available for reference and more detail.
A “mixin” is a form of inheritance -- it’s how you get multiple inheritance in Scala. To create a mixin, use the "extends" keyword for the first thing to inherit, and then use “with” keyword to inherit more things. However, there's a special form of class, called a "trait", whose constructor takes no parameters -- "with" can only be used to inherit "trait"s.
Next, LazyModule is a pattern that is tightly coupled to the Cake pattern. The basic idea of LazyModule is that you don’t actually create the instance until the instance is used. So, that gives you time to calculate the parameters to pass. In other words, inside a class’s constructor, call it Class A, you can say: val foo = LazyModule( some_params )
and this will only create a place holder. Later, when you try to read the value of foo or wire foo to something else, that is when the constructor is called that actually creates foo.
Like this:
class A( paramsA )
{ ...
Lazy val foo = new Circuit( paramsA )
...
}
Then somewhere else: val bar = new A( someParams )
And then, later, do bar.foo :=
to wire up the foo. That foo was lazy.. So, it is only at the point that wiring happens that the constructor for foo is called. So, it is only at the point of wiring it up that the parameters to foo are accessed.. Which means you have all the time between constructing bar and accessing foo to calculate the actual parameters that you want to pass in to foo! The construction and wiring can be in widely separated places, with a lot of stuff happening between.
== So, now, that is the basis underneath Cake Pattern. What Cake does is make “twins” -- an outer twin and an inner twin.. In this version is also has a “bundle” We just need an easy name, when talking about them.. And they always have to be in pairs.. So “twin” is a natural word to use.. “Module” is too confusing, it’s overloaded in used in many different ways, so is implementation, and it’s awkward language..
Okay.. so the twins are used in inheritance tree -- this is the mixin mechanism.. Here’s some code: class RocketTile(val rocketParams: RocketTileParams, val hartid: Int)(implicit p: Parameters) extends BaseTile(rocketParams)(p) with HasLazyRoCC // implies CanHaveSharedFPU with CanHavePTW with HasHellaCache with CanHaveScratchpad { // implies CanHavePTW with HasHellaCache with HasICacheFrontend
So, HasLazyRoCC is a cake outer twin, and CanHaveScratchpad is also an outer twin RocketTile itself, is also an outer twin!
These twins being mixed-in, is how circuit code gets placed inside a RocketTile. Here’s one path through the code: trait HasLazyRoCC extends CanHaveSharedFPU with CanHavePTW with HasTileLinkMasterPort { This mixes in three more outer twins. One of them is CanHavePTW: trait CanHavePTW extends HasHellaCache { Which mixes in yet another outer twin: HasHellaCache: trait HasHellaCache extends HasTileLinkMasterPort with HasTileParameters { This time, the mixed-in twins are just Rocket plumbing -- they’re not circuit related.. BUT, inside HasHellaCache is this: val dcache = HellaCache(hartid, tileParams.dcache.get.nMSHRs == 0, findScratchpadFromICache _)
This is where an actual circuit is created! So, that is how a cache gets included inside RocketTile. Separately, that cache has to be wired up..
This wiring happens back inside RocketTile: dcachePorts += core.io.dmem
And that’s how the cake pattern works.. We’re created a diagram of the code: http://dev1.intensivate.com:8080/intensivate/?https=0#G1w5LD3RJFXGNl2pRhaX_F-cp3cP_5GdMW
This shows the cake pattern linkages -- the double lined boxes with sharp edges are outer twins The doubled line boxes with rounded corners are inner twins And the rounded corner single line boxes are actual circuit instantiation code.
So, each arrow corresponds one-to-one with “extends” or “with” keyword The red arrows are for outer twins inheritance The thin black arrows are for inner twin inheritance.
Make sense?
This diagram provides quick navigation of the code, to find the spot that you need to modify -- so if you’re in RocketTile and see “dcache.something” then you can look in the diagram, and find “val dcache = “ and then you know where the code for dcache is.
And if you’re thinking about making a change, you can look at the diagram and see what will have to be changed, and then go straight to those spots, then fix up the diagram after.
It will be important to keep the diagram in sync, of course.. But it feels to me as though that is a small overhead, for a large benefit of having a visual map of the code..
What do you think?
I agree. It is good to keep the diagram up to date, so all of us can see how the code evolves.
Great. To do an example, to insert ctxt unit.. It looks like it will fit inside “core” which is inside RocketTileModule
So, the other thing is that, when wiring things up, “outer.foobar” is used.. The “outer” is the outer twin. The wiring happens inside the inner twin (so that details about bus widths, and so forth can be calculated before performing the actual wiring operation and generating the nodes in the FIRRTL graph)
So, “frontend” doesn’t appear in RocketTile class def.. That means it’s mixed in, through cake pattern.. Okay, we need to go over the diagram again.. The idea is that “frontend” is a “val fronted = “ somewhere, in one of the mixins.. So we should be able to look for a single walled rounded box whose title is “frontend” and that will be the file where the code is that tells us what kind of object frontend is..