6.6. Keys, Traits, and Configs¶
You have probably seen snippets of Chisel referencing keys, traits, and configs by this point. This section aims to elucidate the interactions between these Chisel/Scala components, and provide best practices for how these should be used to create a parameterized design and configure it.
We will continue to use the GCD example.
6.6.1. Keys¶
Keys specify some parameter which controls some custom widget. Keys should typically be implemented as Option types, with a default value of None
that means no change in the system. In other words, the default behavior when the user does not explicitly set the key should be a no-op.
Keys should be defined and documented in sub-projects, since they generally deal with some specific block, and not system-level integration. (We make an exception for the example GCD widget).
The object within a key is typically a case class XXXParams
, which defines a set of parameters which some block accepts. For example, the GCD widget’s GCDParams
parameterizes its address, operand widths, whether the widget should be connected by Tilelink or AXI4, and whether the widget should use the blackbox-Verilog implementation, or the Chisel implementation.
Accessing the value stored in the key is easy in Chisel, as long as the implicit p: Parameters
object is being passed through to the relevant module. For example, p(GCDKey).get.address
returns the address field of GCDParams
. Note this only works if GCDKey
was not set to None
, so your Chisel should check for that case!
6.6.2. Traits¶
Typically, most custom blocks will need to modify the behavior of some pre-existing block. For example, the GCD widget needs the Top
module to instantiate and connect the widget via Tilelink, generate a top-level gcd_busy
port, and connect that to the module as well. Traits let us do this without modifying the existing code for the Top
, and enables compartmentalization of code for different custom blocks.
Top-level traits specify that the Top
has been parameterized to read some custom key and optionally instantiate and connect a widget defined by that key. Traits should not mandate the instantiation of custom logic. In other words, traits should be written with CanHave
semantics, where the default behavior when the key is unset is a no-op.
Top-level traits should be defined and documented in subprojects, alongside their corresponding keys. The traits should then be added to the Top
being used by Chipyard.
Below we see the traits for the GCD example. The Lazy trait connects the GCD module to the Diplomacy graph, while the Implementation trait causes the Top
to instantiate an additional port and concretely connect it to the GCD module.
These traits are added to the default Top
in Chipyard.
class Top(implicit p: Parameters) extends System
with testchipip.CanHaveTraceIO // Enables optionally adding trace IO
with testchipip.CanHaveBackingScratchpad // Enables optionally adding a backing scratchpad
with testchipip.CanHavePeripheryBlockDevice // Enables optionally adding the block device
with testchipip.CanHavePeripherySerial // Enables optionally adding the TSI serial-adapter and port
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 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
{
override lazy val module = new TopModule(this)
}
class TopModule[+L <: Top](l: L) extends SystemModule(l)
with testchipip.CanHaveTraceIOModuleImp
with testchipip.CanHavePeripheryBlockDeviceModuleImp
with testchipip.CanHavePeripherySerialModuleImp
with sifive.blocks.devices.uart.HasPeripheryUARTModuleImp
with sifive.blocks.devices.gpio.HasPeripheryGPIOModuleImp
with icenet.CanHavePeripheryIceNICModuleImp
with chipyard.example.CanHavePeripheryGCDModuleImp
with freechips.rocketchip.util.DontTouch
6.6.3. Config Fragments¶
Config fragments set the keys to a non-default value. Together, the collection of config fragments which define a configuration generate the values for all the keys used by the generator.
For example, the WithGCD
config fragment is parameterized by the type of GCD widget you want to instantiate. When this config fragment is added to a config, the GCDKey
is set to a instance of GCDParams
, informing the previously mentioned traits to instantiate and connect the GCD widget appropriately.
We can use this config fragment when composing our configs.
Note
Readers who want more information on the configuration system may be interested in reading Context-Dependent-Environments.