Revamp the config system for Top/Harness (#347)
* Refactor how Configs parameterize the Top and TestHarnesses * Bump sha3, testchipip, icenet, firesim
This commit is contained in:
142
docs/Customization/MMIO-Peripherals.rst
Normal file
142
docs/Customization/MMIO-Peripherals.rst
Normal file
@@ -0,0 +1,142 @@
|
||||
.. _mmio-accelerators:
|
||||
|
||||
MMIO Peripherals
|
||||
==================
|
||||
|
||||
The easiest way to create a MMIO peripheral is to use the ``TLRegisterRouter`` or ``AXI4RegisterRouter`` widgets, which abstracts away the details of handling the interconnect protocols and provides a convenient interface for specifying memory-mapped registers. Since Chipyard and Rocket Chip SoCs primarily use Tilelink as the on-chip interconnect protocol, this section will primarily focus on designing Tilelink-based peripherals. However, see ``generators/example/src/main/scala/GCD.scala`` for how an example AXI4 based peripheral is defined and connected to the Tilelink graph through converters.
|
||||
|
||||
To create a RegisterRouter-based peripheral, you will need to specify a parameter case class for the configuration settings, a bundle trait with the extra top-level ports, and a module implementation containing the actual RTL.
|
||||
|
||||
For this example, we will show how to connect a MMIO peripheral which computes the GCD.
|
||||
The full code can be found in ``generators/example/src/main/scala/GCD.scala``.
|
||||
|
||||
In this case we use a submodule ``GCDMMIOChiselModule`` to actually perform the GCD. The ``GCDModule`` class only creates the registers and hooks them up using ``regmap``.
|
||||
|
||||
.. literalinclude:: ../../generators/example/src/main/scala/GCD.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: GCD chisel
|
||||
:end-before: DOC include end: GCD chisel
|
||||
|
||||
.. literalinclude:: ../../generators/example/src/main/scala/GCD.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: GCD instance regmap
|
||||
:end-before: DOC include end: GCD instance regmap
|
||||
|
||||
Advanced Features of RegField Entries
|
||||
-------------------------------------
|
||||
|
||||
``RegField`` exposes polymorphic ``r`` and ``w`` methods
|
||||
that allow read- and write-only memory-mapped registers to be
|
||||
interfaced to hardware in multiple ways.
|
||||
|
||||
* ``RegField.r(2, status)`` is used to create a 2-bit, read-only register that captures the current value of the ``status`` signal when read.
|
||||
* ``RegField.r(params.width, gcd)`` "connects" the decoupled handshaking interface ``gcd`` to a read-only memory-mapped register. When this register is read via MMIO, the ``ready`` signal is asserted. This is in turn connected to ``output_ready`` on the GCD module through the glue logic.
|
||||
* ``RegField.w(params.width, x)`` exposes a plain register via MMIO, but makes it write-only.
|
||||
* ``RegField.w(params.width, y)`` associates the decoupled interface signal ``y`` with a write-only memory-mapped register, causing ``y.valid`` to be asserted when the register is written.
|
||||
|
||||
Since the ready/valid signals of ``y`` are connected to the
|
||||
``input_ready`` and ``input_valid`` signals of the GCD module,
|
||||
respectively, this register map and glue logic has the effect of
|
||||
triggering the GCD algorithm when ``y`` is written. Therefore, the
|
||||
algorithm is set up by first writing ``x`` and then performing a
|
||||
triggering write to ``y``. Polling can be used for status checks.
|
||||
|
||||
|
||||
Connecting by TileLink
|
||||
----------------------
|
||||
|
||||
Once you have these classes, you can construct the final peripheral by extending the ``TLRegisterRouter`` and passing the proper arguments.
|
||||
The first set of arguments determines where the register router will be placed in the global address map and what information will be put in its device tree entry.
|
||||
The second set of arguments is the IO bundle constructor, which we create by extending ``TLRegBundle`` with our bundle trait.
|
||||
The final set of arguments is the module constructor, which we create by extends ``TLRegModule`` with our module trait.
|
||||
Notice how we can create an analogous AXI4 version of our peripheral.
|
||||
|
||||
.. literalinclude:: ../../generators/example/src/main/scala/GCD.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: GCD router
|
||||
:end-before: DOC include end: GCD router
|
||||
|
||||
|
||||
|
||||
Top-level Traits
|
||||
----------------
|
||||
|
||||
After creating the module, we need to hook it up to our SoC.
|
||||
Rocket Chip accomplishes this using the cake pattern.
|
||||
This basically involves placing code inside traits.
|
||||
In the Rocket Chip cake, there are two kinds of traits: a ``LazyModule`` trait and a module implementation trait.
|
||||
|
||||
The ``LazyModule`` trait runs setup code that must execute before all the hardware gets elaborated.
|
||||
For a simple memory-mapped peripheral, this just involves connecting the peripheral's TileLink node to the MMIO crossbar.
|
||||
|
||||
.. literalinclude:: ../../generators/example/src/main/scala/GCD.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: GCD lazy trait
|
||||
:end-before: DOC include end: GCD lazy trait
|
||||
|
||||
Note that the ``GCDTL`` class we created from the register router is itself a ``LazyModule``.
|
||||
Register routers have a TileLink node simply named "node", which we can hook up to the Rocket Chip bus.
|
||||
This will automatically add address map and device tree entries for the peripheral.
|
||||
Also observe how we have to place additional AXI4 buffers and converters for the AXI4 version of this peripheral.
|
||||
|
||||
For peripherals which instantiate a concrete module, or which need to be connected to concrete IOs or wires, a matching concrete trait is necessary. We will make our GCD example output a ``gcd_busy`` signal as a top-level port to demonstrate. In the concrete module implementation trait, we instantiate the top level IO (a concrete object) and wire it to the IO of our lazy module.
|
||||
|
||||
|
||||
.. literalinclude:: ../../generators/example/src/main/scala/GCD.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: GCD imp trait
|
||||
:end-before: DOC include end: GCD imp trait
|
||||
|
||||
Constructing the Top and Config
|
||||
-------------------------------
|
||||
|
||||
Now we want to mix our traits into the system as a whole.
|
||||
This code is from ``generators/example/src/main/scala/Top.scala``.
|
||||
|
||||
.. literalinclude:: ../../generators/example/src/main/scala/Top.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: Top
|
||||
:end-before: DOC include end: Top
|
||||
|
||||
Just as we need separate traits for ``LazyModule`` and module implementation, we need two classes to build the system.
|
||||
The ``Top`` class contains the set of traits which parameterize and define the ``Top``. Typically these traits will optionally add IOs or peripherals to the ``Top``.
|
||||
The ``Top`` class includes the pre-elaboration code and also a ``lazy val`` to produce the module implementation (hence ``LazyModule``).
|
||||
The ``TopModule`` class is the actual RTL that gets synthesized.
|
||||
|
||||
|
||||
|
||||
And finally, we create a configuration class in ``generators/example/src/main/scala/Configs.scala`` that uses the ``WithGCD`` mixin defined earlier.
|
||||
|
||||
.. literalinclude:: ../../generators/example/src/main/scala/ConfigMixins.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: GCD mixin
|
||||
:end-before: DOC include end: GCD mixin
|
||||
|
||||
.. literalinclude:: ../../generators/example/src/main/scala/RocketConfigs.scala
|
||||
:language: scala
|
||||
:start-after: DOC include start: GCDTLRocketConfig
|
||||
:end-before: DOC include end: GCDTLRocketConfig
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
Now we can test that the GCD is working. The test program is in ``tests/gcd.c``.
|
||||
|
||||
.. literalinclude:: ../../tests/gcd.c
|
||||
:language: c
|
||||
|
||||
This just writes out to the registers we defined earlier.
|
||||
The base of the module's MMIO region is at 0x2000 by default.
|
||||
This will be printed out in the address map portion when you generate the verilog code.
|
||||
You can also see how this changes the emitted ``.json`` addressmap files in ``generated-src``.
|
||||
|
||||
Compiling this program with ``make`` produces a ``gcd.riscv`` executable.
|
||||
|
||||
Now with all of that done, we can go ahead and run our simulation.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
cd sims/verilator
|
||||
make CONFIG=GCDTLRocketConfig BINARY=../../tests/gcd.riscv run-binary
|
||||
|
||||
|
||||
Reference in New Issue
Block a user