Skip to content

Commit a0efbbb

Browse files
committed
docs/csr/bus: add guide-level documentation.
1 parent f21b31e commit a0efbbb

File tree

2 files changed

+197
-69
lines changed

2 files changed

+197
-69
lines changed

amaranth_soc/csr/bus.py

+11-59
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
class Element(wiring.PureInterface):
14-
"""Peripheral-side CSR interface.
14+
"""CSR register interface.
1515
1616
A low-level interface to a single atomically readable and writable register in a peripheral.
1717
This interface supports any register width and semantics, provided that both reads and writes
@@ -24,7 +24,7 @@ class Element(wiring.PureInterface):
2424
access : :class:`Element.Access`
2525
Register access mode.
2626
path : iterable of :class:`str`
27-
Path to this CSR interface. Optional. See :class:`amaranth.lib.wiring.PureInterface`.
27+
Path to this interface. Optional. See :class:`amaranth.lib.wiring.PureInterface`.
2828
"""
2929

3030
class Access(enum.Enum):
@@ -58,7 +58,7 @@ def writable(self):
5858
return self == self.W or self == self.RW
5959

6060
class Signature(wiring.Signature):
61-
"""Peripheral-side CSR signature.
61+
"""CSR register signature.
6262
6363
Arguments
6464
---------
@@ -179,7 +179,7 @@ def __repr__(self):
179179

180180

181181
class Signature(wiring.Signature):
182-
"""CPU-side CSR signature.
182+
"""CSR bus signature.
183183
184184
Arguments
185185
---------
@@ -272,22 +272,10 @@ def __repr__(self):
272272

273273

274274
class Interface(wiring.PureInterface):
275-
"""CPU-side CSR interface.
275+
"""CSR bus interface.
276276
277277
A low-level interface to a set of atomically readable and writable peripheral CSR registers.
278278
279-
.. note::
280-
281-
CSR registers mapped to the CSR bus are split into chunks according to the bus data width.
282-
Each chunk is assigned a consecutive address on the bus. This allows accessing CSRs of any
283-
size using any datapath width.
284-
285-
When the first chunk of a register is read, the value of a register is captured, and reads
286-
from subsequent chunks of the same register return the captured values. When any chunk
287-
except the last chunk of a register is written, the written value is captured; a write to
288-
the last chunk writes the captured value to the register. This allows atomically accessing
289-
CSRs larger than datapath width.
290-
291279
Arguments
292280
---------
293281
addr_width : :class:`int`
@@ -326,11 +314,15 @@ def data_width(self):
326314
def memory_map(self):
327315
"""Memory map of the bus.
328316
329-
.. todo:: setter
330-
331317
Returns
332318
-------
333319
:class:`~.memory.MemoryMap` or ``None``
320+
321+
Raises
322+
------
323+
:exc:`ValueError`
324+
If set to a memory map that does not have the same address and data widths as the bus
325+
interface.
334326
"""
335327
if self._memory_map is None:
336328
raise AttributeError(f"{self!r} does not have a memory map")
@@ -366,31 +358,6 @@ class Multiplexer(wiring.Component):
366358
367359
Writes are registered, and are performed 1 cycle after ``w_stb`` is asserted.
368360
369-
.. note::
370-
371-
Because the CSR bus conserves logic and routing resources, it is common to e.g. access
372-
a CSR bus with an *n*-bit data path from a CPU with a *k*-bit datapath (*k>n*) in cases
373-
where CSR access latency is less important than resource usage.
374-
375-
In this case, two strategies are possible for connecting the CSR bus to the CPU:
376-
377-
* The CPU could access the CSR bus directly (with no intervening logic other than
378-
simple translation of control signals). In this case, the register alignment should
379-
be set to 1 (i.e. ``memory_map.alignment`` should be set to 0), and each *w*-bit
380-
register would occupy *ceil(w/n)* addresses from the CPU perspective, requiring the
381-
same amount of memory instructions to access.
382-
* The CPU could also access the CSR bus through a width down-converter, which would
383-
issue *k/n* CSR accesses for each CPU access. In this case, the register alignment
384-
should be set to *k/n*, and each *w*-bit register would occupy *ceil(w/k)* addresses
385-
from the CPU perspective, requiring the same amount of memory instructions to access.
386-
387-
If the register alignment (i.e. ``2 ** memory_map.alignment``) is greater than 1, it affects
388-
which CSR bus write is considered a write to the last register chunk. For example, if a 24-bit
389-
register is used with a 8-bit CSR bus and a CPU with a 32-bit datapath, a write to this
390-
register requires 4 CSR bus writes to complete, and the 4th write is the one that actually
391-
writes the value to the register. This allows determining write latency solely from the amount
392-
of addresses the register occupies in the CPU address space, and the width of the CSR bus.
393-
394361
Arguments
395362
---------
396363
memory_map : :class:`~.memory.MemoryMap`
@@ -681,19 +648,6 @@ class Decoder(wiring.Component):
681648
682649
An address decoder for subordinate CSR buses.
683650
684-
.. note::
685-
686-
Although there is no functional difference between adding a set of registers directly to
687-
a :class:`Multiplexer` and adding a set of registers to multiple :class:`Multiplexer`\\ s
688-
that are aggregated with a :class:`Decoder`, hierarchical CSR buses are useful for
689-
organizing a hierarchical design.
690-
691-
If many peripherals are directly served by a single :class:`Multiplexer`, a very large
692-
amount of ports will connect the peripheral registers with the :class:`Decoder`, and the
693-
cost of decoding logic would not be attributed to specific peripherals. With a
694-
:class:`Decoder`, only five signals per peripheral will be used, and the logic could be
695-
kept together with the peripheral.
696-
697651
Arguments
698652
---------
699653
addr_width : :class:`int`
@@ -733,8 +687,6 @@ def add(self, sub_bus, *, addr=None):
733687
734688
See :meth:`~.memory.MemoryMap.add_window` for details.
735689
736-
.. todo:: include exceptions raised in :meth:`~.memory.MemoryMap.add_window`
737-
738690
Returns
739691
-------
740692
:class:`tuple` of (:class:`int`, :class:`int`, :class:`int`)

docs/csr/bus.rst

+186-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,187 @@
1-
CSR bus primitives
2-
------------------
1+
CSR bus
2+
-------
33

4-
.. warning::
4+
.. py:module:: amaranth_soc.csr.bus
55
6-
This manual is a work in progress and is seriously incomplete!
6+
The :mod:`amaranth_soc.csr.bus` module contains primitives to implement and access the registers of peripherals through a bus interface.
77

8-
.. py:module:: amaranth_soc.csr.bus
8+
.. testsetup::
9+
10+
from amaranth import *
11+
12+
from amaranth_soc import csr
13+
from amaranth_soc.memory import *
14+
15+
.. _csr-bus-introduction:
16+
17+
Introduction
18+
============
19+
20+
The CSR Bus API aims to provide unopinionated building blocks for accessing the *Control and Status Registers* of SoC peripherals, with an emphasis on safety and resource efficiency. It is composed of low-level :ref:`register interfaces <csr-bus-element>` suitable for a wide range of implementations, :ref:`register multiplexers <csr-bus-multiplexer>` that provide access to the registers of a peripheral, and :ref:`bus decoders <csr-bus-decoder>` that provide access to the registers of multiple peripherals.
21+
22+
.. _csr-bus-element:
23+
24+
Creating registers
25+
++++++++++++++++++
26+
27+
A CSR register is a :class:`~amaranth.lib.wiring.Component` with an :class:`Element` member in its interface, oriented as input and named ``"element"``.
28+
29+
For example, this component is a read/write register with a configurable width:
30+
31+
.. testcode::
32+
33+
class MyRegister(wiring.Component):
34+
def __init__(self, width):
35+
super().__init__({
36+
"element": In(csr.Element.Signature(width, "rw")),
37+
"data": Out(width),
38+
})
39+
40+
def elaborate(self, platform):
41+
m = Module()
42+
storage = Signal.like(self.data)
43+
44+
with m.If(self.element.w_stb):
45+
m.d.sync += storage.eq(self.element.w_data)
46+
47+
m.d.comb += [
48+
self.element.r_data.eq(storage),
49+
self.data.eq(storage),
50+
]
51+
52+
return m
53+
54+
CSR bus transactions go through the :class:`Element` port and always target the entire register. Transactions are completed in one clock cycle, regardless of the register width. A read and a write access can be part of the same transaction.
55+
56+
.. _csr-bus-multiplexer:
57+
58+
Accessing registers
59+
+++++++++++++++++++
60+
61+
A :class:`Multiplexer` can provide access to a group of registers from a CSR bus. Registers must first be added to a :class:`MemoryMap`, before using it to instantiate a CSR multiplexer.
62+
63+
In the following example, a :class:`Multiplexer` provides access to two registers over an 8-bit bus:
64+
65+
.. testcode::
66+
67+
reg_foo = MyRegister(24)
68+
reg_bar = MyRegister(32)
69+
70+
memory_map = MemoryMap(addr_width=3, data_width=8, alignment=2)
71+
memory_map.add_resource(reg_foo, size=3, name=("foo",))
72+
memory_map.add_resource(reg_bar, size=4, name=("bar",))
73+
74+
csr_mux = csr.Multiplexer(memory_map)
75+
76+
.. doctest::
77+
78+
>>> for res_info in csr_mux.bus.memory_map.all_resources()):
79+
... print(res_info)
80+
ResourceInfo(path=(('foo',),), start=0x0, end=0x4, width=8)
81+
ResourceInfo(path=(('bar',),), start=0x4, end=0x8, width=8)
982

10-
The :mod:`amaranth_soc.csr.bus` module provides CSR bus primitives.
83+
Registers mapped to a CSR bus are accessed atomically, regardless of their size. Each register is split into chunks according to the bus data width. Each chunk is assigned a consecutive address on the bus.
84+
85+
When the first chunk of a register is read, the values of all chunks of the register are captured, and reads from subsequent chunks return the captured values. When any chunk except the last chunk of a register is written, the written value is captured; a write to the last chunk writes all captured values to the register.
86+
87+
.. wavedrom:: csr/bus/example_mux
88+
89+
{
90+
"signal": [
91+
{"name": "clk",
92+
"wave": "0P......."},
93+
[
94+
"csr_mux",
95+
{"name": "bus.addr",
96+
"wave": "x====x..|",
97+
"data": [0,1,2,3]},
98+
{"name": "bus.r_stb",
99+
"wave": "01...0..|"},
100+
{"name": "bus.r_data",
101+
"wave": "0.3330..|",
102+
"data": ["A", "B", "C"]},
103+
{"name": "bus.w_stb",
104+
"wave": "01...0..|"},
105+
{"name": "bus.w_data",
106+
"wave": "x4444x..|",
107+
"data": ["E", "F", "G", "H"]}
108+
],
109+
{},
110+
[
111+
"reg_foo",
112+
{"name": "element.r_stb",
113+
"wave": "010.....|"},
114+
{"name": "element.r_data",
115+
"wave": "3.....4.|",
116+
"data": ["CBA", "GFE"]},
117+
{"name": "element.w_stb",
118+
"wave": "0....10.|"},
119+
{"name": "element.w_data",
120+
"wave": "x....4x.|",
121+
"data": ["GFE"]}
122+
]
123+
]
124+
}
125+
126+
A :class:`Multiplexer` adds a latency of one clock cycle to read and write accesses.
127+
128+
.. important::
129+
130+
To safely access registers over the bus interface of a :class:`Multiplexer`:
131+
* the bus initiator must have exclusive ownership over the address range of the multiplexer until the register transaction is either completed or aborted.
132+
* the bus initiator must access a register in ascending order of addresses, but it may abort the transaction after any bus cycle.
133+
134+
.. note::
135+
136+
Because the CSR bus conserves logic and routing resources, it is common to e.g. bridge a CSR bus with a narrow *N*-bit datapath to a CPU with a wider *W*-bit datapath (*W>N*) in cases where CSR access latency is less important than resource usage.
137+
138+
In this case, two strategies are possible for connecting the CSR bus to the CPU:
139+
140+
* The CPU could access the CSR bus directly (with no intervening logic other than simple translation of control signals). The register alignment should be set to 1 (i.e. ``memory_map.alignment`` should be 0), and each *R*-bit register would occupy *ceil(R/N)* addresses from the CPU perspective, requiring the same amount of memory instructions to access.
141+
* The CPU could access the CSR bus through a width down-converter, which would issue *W/N* CSR accesses for each CPU access. The register alignment should be set to *W/N*, and each *R*-bit register would occupy *ceil(R/K)* addresses from the CPU perspective, requiring the same amount of memory instructions to access.
142+
143+
If the register alignment is greater than 1, it affects which CSR bus write is considered a write to the last register chunk. For example, if a 24-bit register is accessed through an 8-bit CSR bus and a CPU with a 32-bit datapath, a write to this register requires 4 CSR bus writes to complete, and the last write is the one that actually writes the value to the register. This allows determining write latency solely from the amount of addresses occupied by the register in the CPU address space, and the CSR bus data width.
144+
145+
.. _csr-bus-decoder:
146+
147+
Accessing a hierarchy of registers
148+
++++++++++++++++++++++++++++++++++
149+
150+
A :class:`Decoder` can provide access to group of :class:`Multiplexer`\ s and subordinate :class:`Decoder`\ s, forming a hierarchical address space of CSR registers.
151+
152+
In the following example, a :class:`Decoder` provides access to the registers of two peripherals:
153+
154+
.. testcode::
155+
156+
reg_foo = MyRegister(24)
157+
reg_bar = MyRegister(32)
158+
159+
uart_memory_map = MemoryMap(addr_width=2, data_width=8, alignment=2, name="uart")
160+
uart_memory_map.add_resource(reg_foo, size=3, name=("foo",))
161+
uart_csr_mux = csr.Multiplexer(uart_memory_map)
162+
163+
gpio_memory_map = MemoryMap(addr_width=2, data_width=8, alignment=2, name="gpio")
164+
gpio_memory_map.add_resource(reg_bar, size=4, name=("bar",))
165+
gpio_csr_mux = csr.Multiplexer(gpio_memory_map)
166+
167+
csr_dec = csr.Decoder(addr_width=16, data_width=8)
168+
csr_dec.add(uart_csr_mux.bus, addr=0x0000)
169+
csr_dec.add(gpio_csr_mux.bus, addr=0x1000)
170+
171+
.. doctest::
172+
173+
>>> for res_info in csr_dec.bus.memory_map.all_resources()):
174+
... print(res_info)
175+
ResourceInfo(path=('uart', ('foo',)), start=0x0, end=0x4, width=8)
176+
ResourceInfo(path=('gpio', ('bar',)), start=0x1000, end=0x1004, width=8)
177+
178+
Although there is no functional difference between adding a group of registers directly to a
179+
:class:`Multiplexer` and adding them to multiple :class:`Multiplexer`\ s that are aggregated with a :class:`Decoder`, hierarchical CSR buses are useful for organizing a hierarchical design.
180+
181+
If many peripherals are directly served by a single :class:`Multiplexer`, a very large amount of ports will connect the peripheral registers to the multiplexer, and the cost of decoding logic would not be attributed to specific peripherals. With a :class:`Decoder`, only five signals per peripheral will be used, and the logic could be kept together with the peripheral.
182+
183+
Register interface
184+
==================
11185

12186
.. autoclass:: amaranth_soc.csr.bus::Element.Access()
13187
:no-members:
@@ -32,21 +206,23 @@ The :mod:`amaranth_soc.csr.bus` module provides CSR bus primitives.
32206
.. autoattribute:: width
33207
.. autoattribute:: access
34208

209+
Bus interface
210+
=============
211+
35212
.. autoclass:: Signature()
36213
:no-members:
37214

38-
.. autoattribute:: addr_width
39-
.. autoattribute:: data_width
40215
.. automethod:: create
41216
.. automethod:: __eq__
42217

43218
.. autoclass:: Interface()
44219
:no-members:
45220

46-
.. autoattribute:: addr_width
47-
.. autoattribute:: data_width
48221
.. autoattribute:: memory_map
49222

223+
Bus primitives
224+
==============
225+
50226
.. autoclass:: Multiplexer()
51227
:no-members:
52228

0 commit comments

Comments
 (0)