.. _keys-traits-configs:

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.

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).

.. literalinclude:: ../../generators/example/src/main/scala/GCD.scala
    :language: scala
    :start-after: DOC include start: GCD key
    :end-before: DOC include end: GCD key

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.


.. literalinclude:: ../../generators/example/src/main/scala/GCD.scala
    :language: scala
    :start-after: DOC include start: GCD params
    :end-before: DOC include end: GCD params

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!

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.

.. literalinclude:: ../../generators/example/src/main/scala/GCD.scala
    :language: scala
    :start-after: DOC include start: GCD lazy trait
    :end-before: DOC include end: GCD imp trait

These traits are added to the default ``Top`` in Chipyard.

.. literalinclude:: ../../generators/example/src/main/scala/Top.scala
    :language: scala
    :start-after: DOC include start: Top
    :end-before: DOC include end: Top

Mixins
------

Mixins set the keys to a non-default value. Together, the collection of Mixins which define a configuration generate the values for all the keys used by the generator.

For example, the ``WithGCDMixin`` is parameterized by the type of GCD widget you want to instantiate. When this mixin 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.

.. literalinclude:: ../../generators/example/src/main/scala/ConfigMixins.scala
    :language: scala
    :start-after: DOC include start: GCD mixin
    :end-before: DOC include end: GCD mixin

We can use this mixin when composing our configs.

.. literalinclude:: ../../generators/example/src/main/scala/RocketConfigs.scala
    :language: scala
    :start-after: DOC include start: GCDTLRocketConfig
    :end-before: DOC include end: GCDTLRocketConfig


BuildTop
--------

The ``BuildTop`` key is special, because sometimes, we need to instantiate ``TestHarness`` modules to interface with a custom widget. The ``BuildTop`` key provides a function which can call some method of the Top to instantiate these ``TestHarness`` modules. Since the ``BuildTop`` key is called from the ``TestHarness``, these modules will appear in the ``TestHarness``. The config system also lets the ``BuildTop`` key look recursively into previous definitions of itself. This enables composability of the ``Top`` configurations.

For example, conside a config that contains the mixins ``WithGPIO ++ WithTSI``. We need to instantiate the TSI serial adapter, and connect it to the ``success`` signal of our ``TestHarness``. We also need to instantiate the GPIO pins, and tie their inputs to 0 in the ``TestHarness``, since we currently cannot drive the GPIOs in simulation.

.. literalinclude:: ../../generators/example/src/main/scala/ConfigMixins.scala
    :language: scala
    :start-after: DOC include start: tsi mixin
    :end-before: DOC include end: tsi mixin


.. literalinclude:: ../../generators/example/src/main/scala/ConfigMixins.scala
    :language: scala
    :start-after: DOC include start: gpio mixin
    :end-before: DOC include end: gpio mixin

When ``WithGPIO ++ WithTSI`` is evaluated right to left, the call to ``up(BuildTop, site)`` in ``WithGPIO`` will reference the function defined in the ``BuildTop`` key of ``WithTSI``. Thus, at elaboration time, when the ``BuildTop`` function is called by the ``TestHarness``, first the ``BuildTop`` function in ``WithTSI`` will be evaluated. This connects the ``success`` signal of the ``TestHarness`` to the ``SerialAdapter`` enabled by ``WithTSI``. Then, the rest of the code in the ``BuildTop`` function of ``WithGPIO`` will execute, tieing off the top-level GPIO input pins. Thus the evaluation of the ``BuildTop`` functions in a completed config is "right-to-left", matching how the evaluation of the mixins at compile-time is also "right-to-left".

.. warning::
   In some cases, the ordering and duplication of mixins which extend ``BuildTop`` will have unintended consequences.
   For example, ``WithTSI ++ WithTSI`` will attempt to generate and connect two ``SimSerial`` widgets in the ``TestHarness``,
   which will likely break the simulation.
   In general, you should avoid attaching multiple mixins which interface to the same top-level ports.

.. note::
   Readers who want more information on the configuration system may be interested in reading :ref:`cdes`.


