1.3. Configs, Parameters, Mixins, and Everything In Between
A significant portion of generators in the Chipyard framework use the Rocket Chip parameter system. This parameter system enables for the flexible configuration of the SoC without invasive RTL changes. In order to use the parameter system correctly, we will use several terms and conventions:
1.3.1. Parameters
It is important to note that a significant challenge with the Rocket parameter system is being able to identify the correct parameter to use, and the impact that parameter has on the overall system. We are still investigating methods to facilitate parameter exploration and discovery.
1.3.2. Configs
A config is a collection of multiple generator parameters being set to specific values.
Configs are additive, can override each other, and can be composed of other configs (sometimes referred to as config fragments).
The naming convention for an additive config or config fragment is With<YourConfigName>
, while the naming convention for a non-additive config will be <YourConfig>
.
Configs can take arguments which will in-turn set parameters in the design or reference other parameters in the design (see Parameters).
This example shows a basic config fragment class that takes in zero arguments and instead uses hardcoded values to set the RTL design parameters.
In this example, MyAcceleratorConfig
is a Scala case class that defines a set of variables that the generator can use when referencing the MyAcceleratorKey
in the design.
class WithMyAcceleratorParams extends Config((site, here, up) => {
case BusWidthBits => 128
case MyAcceleratorKey =>
MyAcceleratorConfig(
rows = 2,
rowBits = 64,
columns = 16,
hartId = 1,
someLength = 256)
})
This next example shows a “higher-level” additive config fragment that uses prior parameters that were set to derive other parameters.
class WithMyMoreComplexAcceleratorConfig extends Config((site, here, up) => {
case BusWidthBits => 128
case MyAcceleratorKey =>
MyAcceleratorConfig(
Rows = 2,
rowBits = site(SystemBusKey).beatBits,
hartId = up(RocketTilesKey, site).length)
})
The following example shows a non-additive config that combines or “assembles” the prior two config fragments using ++
.
The additive config fragments are applied from the right to left in the list (or bottom to top in the example).
Thus, the order of the parameters being set will first start with the DefaultExampleConfig
, then WithMyAcceleratorParams
, then WithMyMoreComplexAcceleratorConfig
.
class SomeAdditiveConfig extends Config(
new WithMyMoreComplexAcceleratorConfig ++
new WithMyAcceleratorParams ++
new DefaultExampleConfig
)
The site
, here
, and up
objects in WithMyMoreComplexAcceleratorConfig
are maps from configuration keys to their definitions.
The site
map gives you the definitions as seen from the root of the configuration hierarchy (in this example, SomeAdditiveConfig
).
The here
map gives the definitions as seen at the current level of the hierarchy (i.e. in WithMyMoreComplexAcceleratorConfig
itself).
The up
map gives the definitions as seen from the next level up from the current (i.e. from WithMyAcceleratorParams
).
1.3.3. Cake Pattern / Mixin
A cake pattern or mixin is a Scala programming pattern, which enable “mixing” of multiple traits or interface definitions (sometimes referred to as dependency injection). It is used in the Rocket Chip SoC library and Chipyard framework in merging multiple system components and IO interfaces into a large system component.
This example shows the Chipyard default top that composes multiple traits together into a fully-featured SoC with many optional components.
class DigitalTop(implicit p: Parameters) extends ChipyardSystem
with testchipip.tsi.CanHavePeripheryUARTTSI // Enables optional UART-based TSI transport
with testchipip.boot.CanHavePeripheryCustomBootPin // Enables optional custom boot pin
with testchipip.boot.CanHavePeripheryBootAddrReg // Use programmable boot address register
with testchipip.cosim.CanHaveTraceIO // Enables optionally adding trace IO
with testchipip.soc.CanHaveBankedScratchpad // Enables optionally adding a banked scratchpad
with testchipip.iceblk.CanHavePeripheryBlockDevice // Enables optionally adding the block device
with testchipip.serdes.CanHavePeripheryTLSerial // Enables optionally adding the tl-serial interface
with testchipip.serdes.old.CanHavePeripheryTLSerial // Enables optionally adding the DEPRECATED tl-serial interface
with testchipip.soc.CanHavePeripheryChipIdPin // Enables optional pin to set chip id for multi-chip configs
with sifive.blocks.devices.i2c.HasPeripheryI2C // Enables optionally adding the sifive I2C
with sifive.blocks.devices.timer.HasPeripheryTimer // Enables optionally adding the timer device
with sifive.blocks.devices.pwm.HasPeripheryPWM // Enables optionally adding the sifive PWM
with sifive.blocks.devices.uart.HasPeripheryUART // Enables optionally adding the sifive UART
with sifive.blocks.devices.gpio.HasPeripheryGPIO // Enables optionally adding the sifive GPIOs
with sifive.blocks.devices.spi.HasPeripherySPIFlash // Enables optionally adding the sifive SPI flash controller
with sifive.blocks.devices.spi.HasPeripherySPI // Enables optionally adding the sifive SPI port
with icenet.CanHavePeripheryIceNIC // Enables optionally adding the IceNIC for FireSim
with chipyard.example.CanHavePeripheryInitZero // Enables optionally adding the initzero example widget
with chipyard.example.CanHavePeripheryGCD // Enables optionally adding the GCD example widget
with chipyard.example.CanHavePeripheryStreamingFIR // Enables optionally adding the DSPTools FIR example widget
with chipyard.example.CanHavePeripheryStreamingPassthrough // Enables optionally adding the DSPTools streaming-passthrough example widget
with nvidia.blocks.dla.CanHavePeripheryNVDLA // Enables optionally having an NVDLA
with chipyard.clocking.HasChipyardPRCI // Use Chipyard reset/clock distribution
with chipyard.clocking.CanHaveClockTap // Enables optionally adding a clock tap output port
with fftgenerator.CanHavePeripheryFFT // Enables optionally having an MMIO-based FFT block
with constellation.soc.CanHaveGlobalNoC // Support instantiating a global NoC interconnect
with rerocc.CanHaveReRoCCTiles // Support tiles that instantiate rerocc-attached accelerators
{
override lazy val module = new DigitalTopModule(this)
}
class DigitalTopModule(l: DigitalTop) extends ChipyardSystemModule(l)
with freechips.rocketchip.util.DontTouch
There are two “cakes” or mixins here. One for the lazy module (ex. CanHavePeripheryTLSerial
) and one for the lazy module
implementation (ex. CanHavePeripheryTLSerialModuleImp
where Imp
refers to implementation). The lazy module defines
all the logical connections between generators and exchanges configuration information among them, while the
lazy module implementation performs the actual Chisel RTL elaboration.
In the DigitalTop
example class, the “outer” DigitalTop
instantiates the “inner”
DigitalTopModule
as a lazy module implementation. This delays immediate elaboration
of the module until all logical connections are determined and all configuration information is exchanged.
The System
outer base class, as well as the
CanHavePeriphery<X>
outer traits contain code to perform high-level logical
connections. For example, the CanHavePeripheryTLSerial
outer trait contains code
to optionally lazily instantiate the TLSerdesser
, and connect the TLSerdesser
‘s
TileLink node to the Front bus.
The ModuleImp
classes and traits perform elaboration of real RTL.
In the test harness, the SoC is elaborated with
val dut = p(BuildTop)(p)
.
After elaboration, the system submodule of ChipTop
will be a DigitalTop
module, which contains a
TLSerdesser
module (among others), if the config specified for that block to be instantiated.
From a high level, classes which extend LazyModule
must reference
their module implementation through lazy val module
, and they
may optionally reference other lazy modules (which will elaborate
as child modules in the module hierarchy). The “inner” modules
contain the implementation for the module, and may instantiate
other normal modules OR lazy modules (for nested Diplomacy
graphs, for example).
The naming convention for an additive mixin or trait is CanHave<YourMixin>
.
This is shown in the Top
class where things such as CanHavePeripheryTLSerial
connect a RTL component to a bus and expose signals to the top-level.
1.3.4. Additional References
Another description of traits/mixins and config fragments is given in Keys, Traits, and Configs. Additionally, a brief explanation of some of these topics (with slightly different naming) is given in the following video: https://www.youtube.com/watch?v=Eko86PGEoDY.
Note
Chipyard uses the name “config fragments” over “config mixins” to avoid confusion between a mixin applying to a config or to the system Top
(even though both are technically Scala mixins).