get started on documenting TileLink/Diplomacy

This commit is contained in:
Howard Mao
2019-09-06 23:34:43 -07:00
parent c0f49a47d8
commit dfbf87061b
7 changed files with 541 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
Diplomacy Connectors
====================
Nodes in a diplomacy graph are connected to each other with edges. The diplomacy
library provides four operators that can be used to form edges between nodes.
:=
--
This is the basic connection operator. It is the same syntax as the Chisel
uni-directional connector, but it is not equivalent. This operator connects
diplomacy node, not Chisel bundles.
The basic connection operator always creates a single edge between the two
nodes.
:=*
---
This is a "query" type connection operator. It can create multiple edges
between nodes, with the number of edges determined by the client node
(the node on the right side of the operator). This can be useful if you
are connecting a multi-edge client to a nexus node or adapter node.
:*=
---
This is a "star" type connection operator. It also creates multiple edges,
but the number of edges is determined by the manager (left side of operator),
rather than the client. It's useful for connecting nexus nodes to multi-edge
manager nodes.
:*=*
----
This is a "flex" connection operator. It creates multiple edges based on
whichever side of the operator has a known number of edges. This can be used
in generators where the type of node on either side isn't known until runtime.

View File

@@ -0,0 +1,158 @@
TileLink Edge Object Methods
============================
The edge object associated with a TileLink node has several helpful methods
for constructing TileLink messages and retrieving data from them.
Get
---
Constructor for a TLBundleA encoding a ``Get`` message, which requests data
from memory. The D channel response to this message will be an
``AccessAckData``, which may have multiple beats.
**Arguments:**
- ``fromSource: UInt`` - Source ID for this transaction
- ``toAddress: UInt`` - The address to read from
- ``lgSize: UInt`` - Base two logarithm of the number of bytes to be read
Put
---
Constructor for a TLBundleA encoding a ``PutFull`` or ``PutPartial`` message,
which write data to memory. It will be a ``PutPartial`` if the ``mask`` is
specified and a ``PutFull`` if it is omitted. The put may require multiple
beats. If that is the case, only ``data`` and ``mask`` should change for each
beat. All other fields must be the same for all beats in the transaction,
including the address. The manager will respond to this message with a single
``AccessAck``.
**Arguments:**
- ``fromSource: UInt`` - Source ID for this transaction.
- ``toAddress: UInt`` - The address to write to.
- ``lgSize: UInt`` - Base two logarithm of the number of bytes to be written.
- ``data: UInt`` - The data to write on this beat.
- ``mask: UInt`` - (optional) The write mask for this beat.
Arithmetic
----------
Constructor for a TLBundleA encoding an ``Arithmetic`` message, which is an
atomic operation. The possible values for the ``atomic`` field are defined
in the ``TLAtomics`` object. It can be ``MIN``, ``MAX``, ``MINU``, ``MAXU``, or
``ADD``, which correspond to atomic minimum, maximum, unsigned minimum, unsigned
maximum, or addition operations, respectively. The previous value at the
memory location will be returned in the response, which will be in the form
of an ``AccessAckData``.
**Arguments:**
- ``fromSource: UInt`` - Source ID for this transaction.
- ``toAddress: UInt`` - The address to perform an arithmetic operation on.
- ``lgSize: UInt`` - Base two logarithm of the number of bytes to operate on.
- ``data: UInt`` - Right-hand operand of the arithmetic operation
- ``atomic: UInt`` - Arithmetic operation type (from ``TLAtomics``)
Logical
-------
Constructor for a TLBundleA encoding a ``Logical`` message, an atomic operation.
The possible values for the ``atomic`` field are ``XOR``, ``OR``, ``AND``, and
``SWAP``, which correspond to atomic bitwise exclusive or, bitwise inclusive or,
bitwise and, and swap operations, respectively. The previous value at the
memory location will be returned in an ``AccessAckData`` response.
**Arguments:**
- ``fromSource: UInt`` - Source ID for this transaction.
- ``toAddress: UInt`` - The address to perform a logical operation on.
- ``lgSize: UInt`` - Base two logarithm of the number of bytes to operate on.
- ``data: UInt`` - Right-hand operand of the logical operation
- ``atomic: UInt`` - Logical operation type (from ``TLAtomics``)
Hint
----
Constructor for a TLBundleA encoding a ``Hint`` message, which is used to
send prefetch hints to caches. The ``param`` argument determines what kind
of hint it is. The possible values come from the ``TLHints`` object and are
``PREFETCH_READ`` and ``PREFETCH_WRITE``. The first one tells caches to
acquire data in a shared state. The second one tells cache to acquire data
in an exclusive state. If the cache this message reaches is a last-level cache,
there won't be any difference. If the manager this message reaches is not a
cache, it will simply be ignored. In any case, a ``HintAck`` message will be
sent in response.
**Arguments:**
- ``fromSource: UInt`` - Source ID for this transaction.
- ``toAddress: UInt`` - The address to prefetch
- ``lgSize: UInt`` - Base two logarithm of the number of bytes to prefetch
- ``param: UInt`` - Hint type (from TLHints)
AccessAck
---------
Constructor for a TLBundleD encoding an ``AccessAck`` or ``AccessAckData``
message. If the optional ``data`` field is supplied, it will be an
``AccessAckData``. Otherwise, it will be an ``AccessAck``.
**Arguments**
- ``a: TLBundleA`` - The A channel message to acknowledge
- ``data: UInt`` - (optional) The data to send back
HintAck
-------
Constructor for a TLBundleD encoding a ``HintAck`` message.
**Arguments**
- ``a: TLBundleA`` - The A channel message to acknowledge
first/last/count
----------------
These methods take a decoupled channel (either the A channel or D channel)
and determines whether the current beat is the first of the transaction,
whether the current beat is the last in the transaction, or the count
(starting from 0) of the current beat in the transaction.
**Arguments:**
- ``x: DecoupledIO[TLChannel]`` - The decoupled channel to snoop on.
numBeats
---------
This method takes in a TileLink bundle and gives the number of beats expected
for the transaction.
**Arguments:**
- ``x: TLChannel`` - The TileLink bundle to get the number of beats from
numBeats1
---------
Similar to ``numBeats`` except it gives the number of beats minus one. If this
is what you need, you should use this instead of doing ``numBeats - 1.U``, as
this is more efficient.
**Arguments:**
- ``x: TLChannel`` - The TileLink bundle to get the number of beats from
hasData
--------
Determines whether the TileLink message contains data or not. This is true
if the message is a PutFull, PutPartial, Arithmetic, Logical, or AccessAckData.
**Arguments:**
- ``x: TLChannel`` - The TileLink bundle to check

View File

@@ -0,0 +1,311 @@
TileLink Node Types
===================
Diplomacy represents the different components of an SoC as nodes of a
directed acyclic graph. TileLink nodes can come in several different types.
Client Node
-----------
TileLink clients are modules that initiate TileLink transactions by sending
requests on the A channel and receive responses on the D channel. If the
client implements TL-C, it will receive probes on the B channel, send releases
on the C channel, and send grant acknowledgements on the E channel.
The L1 caches and DMA devices in RocketChip/Chipyard have client nodes.
You can add a TileLink client node to your LazyModule using the TLHelper
object from testchipip like so:
.. code-block:: scala
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.tilelink.{TLClientParameters}
import testchipip.TLHelper
class MyClient(implicit p: Parameters) extends LazyModule {
val node = TLHelper.makeClientNode(TLClientParameters(
name = "my-client",
sourceId = IdRange(0, 4),
requestFifo = true,
visibility = Seq(AddressSet(0, 0xffff))))
lazy val module = new LazyModuleImp(this) {
val (tl, edge) = node.out(0)
// Rest of code here
}
}
The ``name`` argument identifies the node in the diplomacy graph. It is the
only required argument for TLClientParameters.
The ``sourceId`` argument specifies the range of source identifiers that this
client will use. Since we have set the range to [0, 4) here, this client will
be able to send up to four requests in flight at a time. Each request will
have a distinct value in its source field. The default value for this field
is ``IdRange(0, 1)``, which means it would only be able to send a single
request inflight.
The ``requestFifo`` argument is a boolean option which defaults to false.
If it is set to true, the client will request that downstream managers that
support it send responses in FIFO order (that is, in the same order the
corresponding requests were sent).
The ``visibility`` argument specifies the address ranges that the client
will access. By default it is set to include all addresses. In this example,
we set it to contain a single address range ``AddressSet(0, 0xffff)``, which
means that the client will only access addresses in this range. Clients
normally do not specify this, but it can help downstream crossbar generators
optimize the hardware by not arbitrating the client to managers with address
ranges that don't overlap with its visibility.
Inside your lazy module implementation, you can call ``node.out`` to get a
list of bundle/edge pairs. If you used the TLHelper, you only specified a
single client edge, so this list will only have one pair.
The ``tl`` bundle is a Chisel hardware bundle that connects to the IO of this
module. It contains two (in the case of TL-UL and TL-UH) or five (in the case
of TL-C) decoupled bundles corresponding to the TileLink channels. This is
what you should connect your hardware logic to in order to actually send/receive
TileLink messages.
The ``edge`` object represents the edge of the diplomacy graph. It contains
some useful helper functions which will be documented in
:ref:`TileLink Edge Object Methods`.
Manager Node
------------
TileLink managers take requests from clients on the A channel and send
responses back on the D channel. You can create a manager node using the
TLHelper like so:
.. code-block:: scala
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.tilelink.{TLManagerParameters}
import testchipip.TLHelper
class MyManager(implicit p: Parameters) extends LazyModule {
val device = new SimpleDevice("my-device", Seq("tutorial,my-device0"))
val beatBytes = 8
val node = TLHelper.makeManagerNode(beatBytes, TLManagerParameters(
address = Seq(AddressSet(0x20000, 0xfff)),
resources = device.reg,
regionType = RegionType.UNCACHED,
executable = true,
supportsArithemetic = TransferSizes(1, beatBytes),
supportsLogical = TransferSizes(1, beatBytes),
supportsGet = TransferSizes(1, beatBytes),
supportsPutFull = TransferSizes(1, beatBytes),
supportsPutPartial = TransferSizes(1, beatBytes),
supportsHint = TransferSizes(1, beatBytes),
fifoId = Some(0)))
lazy val module = new LazyModuleImp(this) {
val (tl, edge) = node.in(0)
}
}
The ``makeManagerNode`` method takes two arguments. The first is ``beatBytes``,
which is the physical width of the TileLink interface in bytes. The second
is a TLManagerParameters object.
The only required argument for ``TLManagerParameters`` is the ``address``,
which is the set of address ranges that this manager will serve.
This information is used to route requests from the clients.
The second argument is ``resources``, which is usually retrieved from a
``Device`` object. In this case, we use a ``SimpleDevice`` object.
This argument is necessary if you want to add an entry to the DeviceTree in
the BootROM so that it can be read by a Linux driver. The two arguments to
``SimpleDevice`` are the name and compatibility list for the device tree
entry. For this manager, then, the device tree entry would look like
.. code-block:: text
L12: my-device@20000 {
compatible = "tutorial,my-device0";
reg = <0x20000 0x1000>;
};
The next argument is ``regionType``, which gives some information about
the caching behavior of the manager. There are seven region types, listed below:
1. ``CACHED`` - An intermediate agent may have cached a copy of the region for you.
2. ``TRACKED`` - The region may have been cached by another master, but coherence is being provided.
3. ``UNCACHED`` - The region has not been cached yet, but should be cached when possible.
4. ``IDEMPOTENT`` - Gets return most recently put content, but content should not be cached.
5. ``VOLATILE`` - Content may change without a put, but puts and gets have no side effects.
6. ``PUT_EFFECTS`` - Puts produce side effects and so must not be combined/delayed.
7. ``GET_EFFECTS`` - Gets produce side effects and so must not be issued speculatively.
Next is the ``executable`` argument, which determines if the CPU is allowed to
fetch instructions from this manager. By default it is false, which is what
most MMIO peripherals should set it to.
The next six arguments start with ``support`` and determine the different
A channel message types that the manager can accept. The definitions of the
message types are explained in :ref:`TileLink Edge Object Methods`.
The ``TransferSizes`` case class specifies the range of logical sizes (in bytes)
that the manager can accept for the particular message type. This is an inclusive
range and all logical sizes must be powers of two. So in this case, the manager
can accept requests with sizes of 1, 2, 4, or 8 bytes.
The final argument shown here is the ``fifoId`` setting, which determines
which FIFO domain (if any) the manager is in. If this argument is set to ``None``
(the default), the manager will not guarantee any ordering of the responses.
If the ``fifoId`` is set, it will share a FIFO domain with all other managers
that specify the same ``fifoId``. This means that client requests sent to
that FIFO domain will see responses in the same order.
Register Node
-------------
While you can directly specify a manager node and write all of the logic
to handle TileLink requests, it is usually much easier to use a register node.
This type of node provides a ``regmap`` method that allows you to specify
control/status registers and automatically generates the logic to handle the
TileLink protocol. More information about how to use register nodes can be
found in :ref:`Register Router`.
Identity Node
-------------
Unlike the previous node types, which had only inputs or only outputs, the
identity node has both. As its name suggests, it simply connects the inputs
to the outputs unchanged. This node is mainly used to combine multiple
nodes into a single node with multiple edges. For instance, say we have two
client lazy modules, each with their own client node.
.. code-block:: scala
class MyClient1(implicit p: Parameters) extends LazyModule {
val node = TLHelper.makeClientNode("my-client1", IdRange(0, 1))
// ...
}
class MyClient2(implicit p: Parameters) extends LazyModule {
val node = TLHelper.makeClientNode("my-client2", IdRange(0, 1))
// ...
}
Now we instantiate these two clients in another lazy module and expose their
nodes as a single node.
.. code-block:: scala
class MyClientGroup(implicit p: Parameters) extends LazyModule {
val client1 = LazyModule(new MyClient1)
val client2 = LazyModule(new MyClient2)
val node = TLIdentityNode()
node := client1.node
node := client2.node
// ...
}
We can also do the same for managers.
.. code-block:: scala
class MyManager1(beatBytes: Int)(implicit p: Parameters) extends LazyModule {
val node = TLHelper.makeManagerNode(beatBytes, TLManagerParameters(
address = Seq(AddressSet(0x0, 0xfff))))
// ...
}
class MyManager2(beatBytes: Int)(implicit p: Parameters) extends LazyModule {
val node = TLHelper.makeManagerNode(beatBytes, TLManagerParameters(
address = Seq(AddressSet(0x1000, 0xfff))))
// ...
}
class MyManagerGroup(beatBytes: Int)(implicit p: Parameters) extends LazyModule {
val man1 = LazyModule(new MyManager1(beatBytes))
val man2 = LazyModule(new MyManager2(beatBytes))
val node = TLIdentityNode()
man1.node := node
man2.node := node
}
If we want to connect the client and manager groups together, we can now do this.
.. code-block:: scala
class ClientManagerComplex(implicit p: Parameters) extends LazyModule {
val client = LazyModule(new MyClientGroup)
val manager = LazyModule(new MyManagerGroup(8))
manager.node :=* client.node
}
The meaning of the ``:=*`` operator is explained in more detail in the
:ref:`Diplomacy Connectors` section. In summary, it connects two nodes together
using multiple edges. The edges in the identity node are assigned in order,
so in this case ``client1.node`` will eventually connect to ``manager1.node``
and ``client2.node`` will connect to ``manager2.node``.
The number of inputs to an identity node should match the number of outputs.
A mismatch will cause an elaboration error.
Adapter Node
------------
Like the identity node, the adapter node takes some number of inputs and
produces the same number of outputs. However, unlike the identity node, the
adapter node does not simply pass the connections through unchanged.
It can change the logical and physical interfaces between input and output and
rewrite messages going through. RocketChip provides a library of adapters,
which are catalogued in :ref:`Diplomatic Widgets`.
You will rarely need to create an adapter node yourself, but the invocation is
as follows.
.. code-block:: scala
val node = TLAdapterNode(
clientFn = { cp =>
// ..
},
managerFn = { mp =>
// ..
})
The ``clientFn`` is a function that takes the ``TLClientPortParameters`` of
the input as an argument and returns the corresponding parameters for the
output. The ``managerFn`` takes the ``TLManagerPortParameters`` of the output
as an argument and returns the corresponding parameters for the input.
Nexus Node
----------
The nexus node is similar to the adapter node in that it has a different
output interface than input interface. But it can also have a different
number of inputs than it does outputs. This node type is mainly used by
the ``TLXbar`` widget, which provides a TileLink crossbar generator. You will
also likely not need to define this node type manually, but its invocation is
as follows.
.. code-block:: scala
val node = TLNexusNode(
clientFn = { seq =>
// ..
},
managerFn = { seq =>
// ..
})
This has similar arguments as the adapter node's constructor, but instead of
taking single parameters objects as arguments and returning single objects
as results, the functions take and return sequences of parameters. And as you
might expect, the size of the returned sequence need not be the same size as
the input sequence.

View File

@@ -0,0 +1,2 @@
Register Router
===============

View File

@@ -0,0 +1,2 @@
Diplomatic Widgets
==================

View File

@@ -0,0 +1,29 @@
TileLink and Diplomacy Reference
================================
TileLink is the cache coherence and memory protocol used by RocketChip and
other Chipyard generators. It is how different modules like caches, memories,
peripherals, and DMA devices communicate with each other.
TileLink is built on top of diplomacy, a framework for exchanging
configuration information among Chisel generators in a two-phase elaboration
scheme.
A brief overview of how to connect simple TileLink widgets can be found
in the :ref:`Adding-an-Accelerator` section. This section will provide a
detailed reference for the TileLink and Diplomacy functionality provided by
RocketChip.
A detailed specification of the TileLink 1.7 protocol can be found on the
`SiFive website <https://sifive.cdn.prismic.io/sifive%2F57f93ecf-2c42-46f7-9818-bcdd7d39400a_tilelink-spec-1.7.1.pdf>`.
.. toctree::
:maxdepth: 2
:caption: Reference
NodeTypes
Diplomacy-Connectors
EdgeFunctions
Register-Router
Widgets

View File

@@ -30,6 +30,7 @@ New to Chipyard? Jump to the :ref:`Chipyard Basics` page for more info.
Advanced-Usage/index
TileLink-Diplomacy-Reference/index
Indices and tables