142 lines
6.2 KiB
ReStructuredText
142 lines
6.2 KiB
ReStructuredText
Register Router
|
|
===============
|
|
|
|
Memory-mapped devices generally follow a common pattern. They expose a set
|
|
of registers to the CPUs. By writing to a register, the CPU can change the
|
|
device's settings or send a command. By reading from a register, the CPU can
|
|
query the device's state or retrieve results.
|
|
|
|
While designers can manually instantiate a manager node and write the logic
|
|
for exposing registers themselves, it's much easier to use RocketChip's
|
|
``regmap`` interface, which can generate most of the glue logic.
|
|
|
|
For TileLink devices, you can use the ``regmap`` interface by extending
|
|
the ``TLRegisterRouter`` class, as shown in :ref:`Adding An Accelerator/Device`,
|
|
or you can create a regular LazyModule and instantiate a ``TLRegisterNode``.
|
|
This section will focus on the second method.
|
|
|
|
Basic Usage
|
|
-----------
|
|
|
|
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
|
|
:language: scala
|
|
:start-after: DOC include start: MyDeviceController
|
|
:end-before: DOC include end: MyDeviceController
|
|
|
|
The code example above shows a simple lazy module that uses the ``TLRegisterNode``
|
|
to memory map hardware registers of different sizes. The constructor has
|
|
two required arguments: ``address``, which is the base address of the registers,
|
|
and ``device``, which is the device tree entry. There are also two optional
|
|
arguments. The ``beatBytes`` argument is the interface width in bytes.
|
|
The default value is 4 bytes. The ``concurrency`` argument is the size of the
|
|
internal queue for TileLink requests. By default, this value is 0, which means
|
|
there will be no queue. This value must be greater than 0 if you wish to
|
|
decoupled requests and responses for register accesses. This is discussed
|
|
in :ref:`Using Functions`.
|
|
|
|
The main way to interact with the node is to call the ``regmap`` method, which
|
|
takes a sequence of pairs. The first element of the pair is an offset from the
|
|
base address. The second is a sequence of ``RegField`` objects, each of
|
|
which maps a different register. The ``RegField`` constructor takes two
|
|
arguments. The first argument is the width of the register in bits.
|
|
The second is the register itself.
|
|
|
|
Since the argument is a sequence, you can associate multiple ``RegField``
|
|
objects with an offset. If you do, the registers are read or written in parallel
|
|
when the offset is accessed. The registers are in little endian order, so the
|
|
first register in the list corresponds to the least significant bits in the
|
|
value written. In this example, if the CPU wrote to offset 0x0E with the value
|
|
0xAB, ``smallReg0`` will get the value 0xB and ``smallReg1`` would get 0xA.
|
|
|
|
Decoupled Interfaces
|
|
--------------------
|
|
|
|
Sometimes you may want to do something other than read and write from a hardware
|
|
register. The ``RegField`` interface also provides support for reading
|
|
and writing ``DecoupledIO`` interfaces. For instance, you can implement a
|
|
hardware FIFO like so.
|
|
|
|
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
|
|
:language: scala
|
|
:start-after: DOC include start: MyQueueRegisters
|
|
:end-before: DOC include end: MyQueueRegisters
|
|
|
|
This variant of the ``RegField`` constructor takes three arguments instead of
|
|
two. The first argument is still the bit width. The second is the decoupled
|
|
interface to read from. The third is the decoupled interface to write to.
|
|
In this example, writing to the "register" will push the data into the queue
|
|
and reading from it will pop data from the queue.
|
|
|
|
You need not specify both read and write for a register. You can also create
|
|
read-only or write-only registers. So for the previous example, if you wanted
|
|
enqueue and dequeue to use different addresses, you could write the following.
|
|
|
|
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
|
|
:language: scala
|
|
:start-after: DOC include start: MySeparateQueueRegisters
|
|
:end-before: DOC include end: MySeparateQueueRegisters
|
|
|
|
The read-only register function can also be used to read signals
|
|
that aren't registers.
|
|
|
|
.. code-block:: scala
|
|
|
|
val constant = 0xf00d.U
|
|
|
|
node.regmap(
|
|
0x00 -> Seq(RegField.r(8, constant)))
|
|
|
|
Using Functions
|
|
---------------
|
|
|
|
You can also create registers using functions. Say, for instance, that you
|
|
want to create a counter that gets incremented on a write and decremented on
|
|
a read.
|
|
|
|
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
|
|
:language: scala
|
|
:start-after: DOC include start: MyCounterRegisters
|
|
:end-before: DOC include end: MyCounterRegisters
|
|
|
|
The functions here are essentially the same as a decoupled interface.
|
|
The read function gets passed the ``ready`` signal and returns the
|
|
``valid`` and ``bits`` signals. The write function gets passed ``valid` and
|
|
``bits`` and returns ``ready``.
|
|
|
|
You can also pass functions that decouple the read/write request and response.
|
|
The request will appear as a decoupled input and the response as a decoupled
|
|
output. So for instance, if we wanted to do this for the previous example.
|
|
|
|
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
|
|
:language: scala
|
|
:start-after: DOC include start: MyCounterReqRespRegisters
|
|
:end-before: DOC include end: MyCounterReqRespRegisters
|
|
|
|
In each function, we set up a state variable ``responding``. The function
|
|
is ready to take requests when this is false and is sending a response when
|
|
this is true.
|
|
|
|
In this variant, both read and write take an input valid and return an
|
|
output ready. The only different is that bits is an input for read and an
|
|
output for write.
|
|
|
|
In order to use this variant, you need to set ``concurrency`` to a value
|
|
larger than 0.
|
|
|
|
Register Routers for Other Protocols
|
|
------------------------------------
|
|
|
|
One useful feature of the register router interface is that you can easily
|
|
change the protocol being used. For instance, in the first example in
|
|
:ref:`Basic Usage`, you could simply change the ``TLRegisterNode`` to
|
|
and ``AXI4RegisterNode``.
|
|
|
|
.. literalinclude:: ../../generators/example/src/main/scala/RegisterNodeExample.scala
|
|
:language: scala
|
|
:start-after: DOC include start: MyAXI4DeviceController
|
|
:end-before: DOC include end: MyAXI4DeviceController
|
|
|
|
Other than the fact that AXI4 nodes don't take a ``device`` argument, and can
|
|
only have a single AddressSet instead of multiple, everything else is
|
|
unchanged.
|