From fc9c6cf67c8a3ae195fff5255c5cde515aab93cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Tue, 9 Apr 2024 04:38:51 +0200 Subject: [PATCH 1/5] docs: add reference-level documentation. --- amaranth_soc/csr/action.py | 126 ++++++------ amaranth_soc/csr/bus.py | 358 +++++++++++++++++++++-------------- amaranth_soc/csr/event.py | 20 +- amaranth_soc/csr/reg.py | 293 ++++++++++++++-------------- amaranth_soc/csr/wishbone.py | 10 +- amaranth_soc/gpio.py | 257 +++++++++++++------------ amaranth_soc/memory.py | 268 ++++++++++++++++---------- amaranth_soc/wishbone/bus.py | 233 ++++++++++++++++------- docs/_static/custom.css | 2 +- docs/conf.py | 13 +- docs/csr.rst | 13 ++ docs/csr/action.rst | 20 ++ docs/csr/bus.rst | 57 ++++++ docs/csr/reg.rst | 81 ++++++++ docs/gpio.rst | 14 ++ docs/index.rst | 8 + docs/memory.rst | 30 +++ docs/wishbone.rst | 14 ++ docs/wishbone/bus.rst | 67 +++++++ pyproject.toml | 1 + 20 files changed, 1252 insertions(+), 633 deletions(-) create mode 100644 docs/csr.rst create mode 100644 docs/csr/action.rst create mode 100644 docs/csr/bus.rst create mode 100644 docs/csr/reg.rst create mode 100644 docs/gpio.rst create mode 100644 docs/memory.rst create mode 100644 docs/wishbone.rst create mode 100644 docs/wishbone/bus.rst diff --git a/amaranth_soc/csr/action.py b/amaranth_soc/csr/action.py index 6dc9b96..78d79e1 100644 --- a/amaranth_soc/csr/action.py +++ b/amaranth_soc/csr/action.py @@ -8,21 +8,21 @@ class R(FieldAction): - """A read-only field action. + """A read-only :class:`~.csr.reg.FieldAction`. - Parameters - ---------- + Arguments + --------- shape : :ref:`shape-like object ` Shape of the field. - Interface attributes - -------------------- - port : :class:`FieldPort` + Members + ------- + port : :py:`In(csr.reg.FieldPort.Signature(shape, "r"))` Field port. - r_data : Signal(shape) - Read data. Drives ``port.r_data``. See :class:`FieldPort`. - r_stb : Signal() - Read strobe. Driven by ``port.r_stb``. See :class:`FieldPort`. + r_data : :py:`In(shape)` + Read data. Drives ``port.r_data``. + r_stb : :py:`Out(1)` + Read strobe. Driven by ``port.r_stb``. """ def __init__(self, shape): super().__init__(shape, access="r", members={ @@ -40,21 +40,21 @@ def elaborate(self, platform): class W(FieldAction): - """A write-only field action. + """A write-only :class:`~.csr.reg.FieldAction`. - Parameters - ---------- + Arguments + --------- shape : :ref:`shape-like object ` Shape of the field. - Interface attributes - -------------------- - port : :class:`FieldPort` + Members + ------- + port : :py:`In(csr.reg.FieldPort.Signature(shape, "w"))` Field port. - w_data : Signal(shape) - Write data. Driven by ``port.w_data``. See :class:`FieldPort`. - w_stb : Signal() - Write strobe. Driven by ``port.w_stb``. See :class:`FieldPort`. + w_data : :py:`Out(shape)` + Write data. Driven by ``port.w_data``. + w_stb : :py:`Out(1)` + Write strobe. Driven by ``port.w_stb``. """ def __init__(self, shape): super().__init__(shape, access="w", members={ @@ -72,23 +72,23 @@ def elaborate(self, platform): class RW(FieldAction): - """A read/write field action, with built-in storage. + """A read/write :class:`~.csr.reg.FieldAction`, with built-in storage. Storage is updated with the value of ``port.w_data`` one clock cycle after ``port.w_stb`` is asserted. - Parameters - ---------- + Arguments + --------- shape : :ref:`shape-like object ` Shape of the field. init : :class:`int` Storage initial value. - Interface attributes - -------------------- - port : :class:`FieldPort` + Members + ------- + port : :py:`In(csr.reg.FieldPort.Signature(shape, "rw"))` Field port. - data : Signal(shape) + data : :py:`Out(shape)` Storage output. """ def __init__(self, shape, *, init=0): @@ -100,6 +100,12 @@ def __init__(self, shape, *, init=0): @property def init(self): + """Storage initial value. + + Returns + ------- + :class:`int` + """ return self._init def elaborate(self, platform): @@ -117,28 +123,29 @@ def elaborate(self, platform): class RW1C(FieldAction): - """A read/write-one-to-clear field action, with built-in storage. + """A read/write-one-to-clear :class:`~.csr.reg.FieldAction`, with built-in storage. Storage bits are: + * cleared by high bits in ``port.w_data``, one clock cycle after ``port.w_stb`` is asserted; * set by high bits in ``set``, one clock cycle after they are asserted. If a storage bit is set and cleared on the same clock cycle, setting it has precedence. - Parameters - ---------- + Arguments + --------- shape : :ref:`shape-like object ` Shape of the field. init : :class:`int` Storage initial value. - Interface attributes - -------------------- - port : :class:`FieldPort` + Members + ------- + port : :py:`In(csr.reg.FieldPort.Signature(shape, "rw"))` Field port. - data : Signal(shape) + data : :py:`Out(shape)` Storage output. - set : Signal(shape) + set : :py:`In(shape)` Mask to set storage bits. """ def __init__(self, shape, *, init=0): @@ -151,6 +158,12 @@ def __init__(self, shape, *, init=0): @property def init(self): + """Storage initial value. + + Returns + ------- + :class:`int` + """ return self._init def elaborate(self, platform): @@ -171,40 +184,47 @@ def elaborate(self, platform): class RW1S(FieldAction): - """A read/write-one-to-set field action, with built-in storage. + """A read/write-one-to-set :class:`~.csr.reg.FieldAction`, with built-in storage. Storage bits are: + * set by high bits in ``port.w_data``, one clock cycle after ``port.w_stb`` is asserted; * cleared by high bits in ``clear``, one clock cycle after they are asserted. If a storage bit is set and cleared on the same clock cycle, setting it has precedence. - Parameters - ---------- + Arguments + --------- shape : :ref:`shape-like object ` Shape of the field. init : :class:`int` Storage initial value. - Interface attributes - -------------------- - port : :class:`FieldPort` + Members + ------- + port : :py:`In(csr.reg.FieldPort.Signature(shape, "rw"))` Field port. - data : Signal(shape) + data : :py:`Out(shape)` Storage output. - clear : Signal(shape) + clear : :py:`In(shape)` Mask to clear storage bits. """ def __init__(self, shape, *, init=0): super().__init__(shape, access="rw", members={ - "clear": In(shape), "data": Out(shape), + "clear": In(shape), }) self._storage = Signal(shape, init=init) self._init = init @property def init(self): + """Storage initial value. + + Returns + ------- + :class:`int` + """ return self._init def elaborate(self, platform): @@ -228,14 +248,14 @@ class _Reserved(FieldAction): _doc_template = """ {description} - Parameters - ---------- + Arguments + --------- shape : :ref:`shape-like object ` Shape of the field. - Interface attributes - -------------------- - port : :class:`FieldPort` + Members + ------- + port : :py:`In(csr.reg.FieldPort.Signature(shape, "nc"))` Field port. """ def __init__(self, shape): @@ -247,23 +267,23 @@ def elaborate(self, platform): class ResRAW0(_Reserved): __doc__ = _Reserved._doc_template.format(description=""" - A reserved read-any/write-zero field action. + A reserved read-any/write-zero :class:`~.csr.reg.FieldAction`. """) class ResRAWL(_Reserved): __doc__ = _Reserved._doc_template.format(description=""" - A reserved read-any/write-last field action. + A reserved read-any/write-last :class:`~.csr.reg.FieldAction`. """) class ResR0WA(_Reserved): __doc__ = _Reserved._doc_template.format(description=""" - A reserved read-zero/write-any field action. + A reserved read-zero/write-any :class:`~.csr.reg.FieldAction`. """) class ResR0W0(_Reserved): __doc__ = _Reserved._doc_template.format(description=""" - A reserved read-zero/write-zero field action. + A reserved read-zero/write-zero :class:`~.csr.reg.FieldAction`. """) diff --git a/amaranth_soc/csr/bus.py b/amaranth_soc/csr/bus.py index 87b9da3..ca7077d 100644 --- a/amaranth_soc/csr/bus.py +++ b/amaranth_soc/csr/bus.py @@ -11,6 +11,22 @@ class Element(wiring.PureInterface): + """Peripheral-side CSR interface. + + A low-level interface to a single atomically readable and writable register in a peripheral. + This interface supports any register width and semantics, provided that both reads and writes + always succeed and complete in one cycle. + + Arguments + --------- + width : :class:`int` + Width of the register. + access : :class:`Element.Access` + Register access mode. + path : iterable of :class:`str` + Path to this CSR interface. Optional. See :class:`amaranth.lib.wiring.PureInterface`. + """ + class Access(enum.Enum): """Register access mode. @@ -22,31 +38,45 @@ class Access(enum.Enum): RW = "rw" def readable(self): + """Readable access mode. + + Returns + ------- + :class:`bool` + ``True`` if `self` is equal to :attr:`R` or :attr:`RW`. + """ return self == self.R or self == self.RW def writable(self): + """Writable access mode. + + Returns + ------- + :class:`bool` + ``True`` if `self` is equal to :attr:`W` or :attr:`RW`. + """ return self == self.W or self == self.RW class Signature(wiring.Signature): """Peripheral-side CSR signature. - Parameters - ---------- - width : int + Arguments + --------- + width : :class:`int` Width of the register. - access : :class:`Access` + access : :class:`Element.Access` Register access mode. - Interface attributes - -------------------- - r_data : Signal(width) + Members + ------- + r_data : :py:`In(width)` Read data. Must be always valid, and is sampled when ``r_stb`` is asserted. - r_stb : Signal() - Read strobe. Registers with read side effects should perform the read side effect when this - strobe is asserted. - w_data : Signal(width) + r_stb : :py:`Out(1)` + Read strobe. Registers with read side effects should perform the read side effect when + this strobe is asserted. + w_data : :py:`Out(width)` Write data. Valid only when ``w_stb`` is asserted. - w_stb : Signal() + w_stb : :py:`Out(1)` Write strobe. Registers should update their value or perform the write side effect when this strobe is asserted. """ @@ -79,20 +109,32 @@ def __init__(self, width, access): @property def width(self): + """Width of the register. + + Returns + ------- + :class:`int` + """ return self._width @property def access(self): + """Register access mode. + + Returns + ------- + :class:`Element.Access` + """ return self._access def create(self, *, path=None, src_loc_at=0): """Create a compatible interface. - See :meth:`wiring.Signature.create` for details. + See :meth:`amaranth.lib.wiring.Signature.create` for details. Returns ------- - An :class:`Element` object using this signature. + :class:`Element`. """ return Element(self.width, self.access, path=path, src_loc_at=1 + src_loc_at) @@ -108,30 +150,28 @@ def __eq__(self, other): def __repr__(self): return f"csr.Element.Signature({self.members!r})" - """Peripheral-side CSR interface. - - A low-level interface to a single atomically readable and writable register in a peripheral. - This interface supports any register width and semantics, provided that both reads and writes - always succeed and complete in one cycle. - - Parameters - ---------- - width : :class:`int` - Width of the register. - access : :class:`Element.Access` - Register access mode. - path : iter(:class:`str`) - Path to this CSR interface. Optional. See :class:`wiring.PureInterface`. - """ def __init__(self, width, access, *, path=None, src_loc_at=0): - super().__init__(Element.Signature(width=width, access=access), path=path, src_loc_at=1 + src_loc_at) + super().__init__(Element.Signature(width=width, access=access), path=path, + src_loc_at=1 + src_loc_at) @property def width(self): + """Width of the register. + + Returns + ------- + :class:`int` + """ return self.signature.width @property def access(self): + """Register access mode. + + Returns + ------- + :class:`Element.Access` + """ return self.signature.access def __repr__(self): @@ -141,28 +181,28 @@ def __repr__(self): class Signature(wiring.Signature): """CPU-side CSR signature. - Parameters - ---------- + Arguments + --------- addr_width : :class:`int` Address width. At most ``(2 ** addr_width) * data_width`` register bits will be available. data_width : :class:`int` Data width. Registers are accessed in ``data_width`` sized chunks. - Interface attributes - -------------------- - addr : Signal(addr_width) + Members + ------- + addr : :py:`Out(addr_width)` Address for reads and writes. - r_data : Signal(data_width) + r_data : :py:`In(data_width)` Read data. Valid on the next cycle after ``r_stb`` is asserted. Otherwise, zero. (Keeping read data of an unused interface at zero simplifies multiplexers.) - r_stb : Signal() + r_stb : :py:`Out(1)` Read strobe. If ``addr`` points to the first chunk of a register, captures register value and causes read side effects to be performed (if any). If ``addr`` points to any chunk of a register, latches the captured value to ``r_data``. Otherwise, latches zero to ``r_data``. - w_data : Signal(data_width) + w_data : :py:`Out(data_width)` Write data. Must be valid when ``w_stb`` is asserted. - w_stb : Signal() + w_stb : :py:`Out(1)` Write strobe. If ``addr`` points to the last chunk of a register, writes captured value to the register and causes write side effects to be performed (if any). If ``addr`` points to any chunk of a register, latches ``w_data`` to the captured value. Otherwise, does @@ -188,20 +228,32 @@ def __init__(self, *, addr_width, data_width): @property def addr_width(self): + """Address width. + + Returns + ------- + :class:`int` + """ return self._addr_width @property def data_width(self): + """Data width. + + Returns + ------- + :class:`int` + """ return self._data_width def create(self, *, path=None, src_loc_at=0): """Create a compatible interface. - See :meth:`wiring.Signature.create` for details. + See :meth:`amaranth.lib.wiring.Signature.create` for details. Returns ------- - An :class:`Interface` object using this signature. + :class:`Interface` """ return Interface(addr_width=self.addr_width, data_width=self.data_width, path=path, src_loc_at=1 + src_loc_at) @@ -224,32 +276,26 @@ class Interface(wiring.PureInterface): A low-level interface to a set of atomically readable and writable peripheral CSR registers. - Operation - --------- + .. note:: - CSR registers mapped to the CSR bus are split into chunks according to the bus data width. - Each chunk is assigned a consecutive address on the bus. This allows accessing CSRs of any - size using any datapath width. + CSR registers mapped to the CSR bus are split into chunks according to the bus data width. + Each chunk is assigned a consecutive address on the bus. This allows accessing CSRs of any + size using any datapath width. - When the first chunk of a register is read, the value of a register is captured, and reads - from subsequent chunks of the same register 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 the captured value to the register. This allows atomically accessing CSRs larger - than datapath width. + When the first chunk of a register is read, the value of a register is captured, and reads + from subsequent chunks of the same register 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 the captured value to the register. This allows atomically accessing + CSRs larger than datapath width. - Parameters - ---------- + Arguments + --------- addr_width : :class:`int` Address width. See :class:`Signature`. data_width : :class:`int` Data width. See :class:`Signature`. - path : iter(:class:`str`) - Path to this CSR interface. Optional. See :class:`wiring.PureInterface`. - - Attributes - ---------- - memory_map: :class:`MemoryMap` - Memory map of the bus. Optional. + path : iterable of :class:`str` + Path to this CSR interface. Optional. See :class:`amaranth.lib.wiring.PureInterface`. """ def __init__(self, *, addr_width, data_width, path=None, src_loc_at=0): super().__init__(Signature(addr_width=addr_width, data_width=data_width), @@ -258,14 +304,34 @@ def __init__(self, *, addr_width, data_width, path=None, src_loc_at=0): @property def addr_width(self): + """Address width. + + Returns + ------- + :class:`int` + """ return self.signature.addr_width @property def data_width(self): + """Data width. + + Returns + ------- + :class:`int` + """ return self.signature.data_width @property def memory_map(self): + """Memory map of the bus. + + .. todo:: setter + + Returns + ------- + :class:`~.memory.MemoryMap` or ``None`` + """ if self._memory_map is None: raise AttributeError(f"{self!r} does not have a memory map") return self._memory_map @@ -287,6 +353,58 @@ def __repr__(self): class Multiplexer(wiring.Component): + """CSR register multiplexer. + + An address-based multiplexer for CSR registers implementing atomic updates. + + This implementation assumes the following from the CSR bus: + + * an initiator must have exclusive ownership over the multiplexer for the full duration of + a register transaction; + * an initiator must access a register in ascending order of addresses, but it may abort a + transaction after any bus cycle. + + Writes are registered, and are performed 1 cycle after ``w_stb`` is asserted. + + .. note:: + + Because the CSR bus conserves logic and routing resources, it is common to e.g. access + a CSR bus with an *n*-bit data path from a CPU with a *k*-bit datapath (*k>n*) in cases + where CSR access latency is less important than resource usage. + + In this case, two strategies are possible for connecting the CSR bus to the CPU: + + * The CPU could access the CSR bus directly (with no intervening logic other than + simple translation of control signals). In this case, the register alignment should + be set to 1 (i.e. ``memory_map.alignment`` should be set to 0), and each *w*-bit + register would occupy *ceil(w/n)* addresses from the CPU perspective, requiring the + same amount of memory instructions to access. + * The CPU could also access the CSR bus through a width down-converter, which would + issue *k/n* CSR accesses for each CPU access. In this case, the register alignment + should be set to *k/n*, and each *w*-bit register would occupy *ceil(w/k)* addresses + from the CPU perspective, requiring the same amount of memory instructions to access. + + If the register alignment (i.e. ``2 ** memory_map.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 used with a 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 4th write is the one that actually + writes the value to the register. This allows determining write latency solely from the amount + of addresses the register occupies in the CPU address space, and the width of the CSR bus. + + Arguments + --------- + memory_map : :class:`~.memory.MemoryMap` + Memory map of CSR registers. + shadow_overlaps : :class:`int` + Maximum number of CSR registers that can share a chunk of a shadow register. + Optional. If ``None``, any number of CSR registers can share a shadow chunk. + + Members + ------- + bus : :py:`In(csr.Signature(memory_map.addr_width, memory_map.data_width))` + CSR bus providing access to registers. + """ + class _Shadow: class Chunk: """The interface between a CSR multiplexer and a shadow register chunk.""" @@ -456,59 +574,6 @@ def chunks(self): for chunk_offset, chunk in self._chunks.items(): yield chunk_offset, chunk - """CSR register multiplexer. - - An address-based multiplexer for CSR registers implementing atomic updates. - - This implementation assumes the following from the CSR bus: - * an initiator must have exclusive ownership over the multiplexer for the full duration of - a register transaction; - * an initiator must access a register in ascending order of addresses, but it may abort a - transaction after any bus cycle. - - Latency - ------- - - Writes are registered, and are performed 1 cycle after ``w_stb`` is asserted. - - Alignment - --------- - - Because the CSR bus conserves logic and routing resources, it is common to e.g. access - a CSR bus with an *n*-bit data path from a CPU with a *k*-bit datapath (*k>n*) in cases - where CSR access latency is less important than resource usage. In this case, two strategies - are possible for connecting the CSR bus to the CPU: - * The CPU could access the CSR bus directly (with no intervening logic other than simple - translation of control signals). In this case, the register alignment should be set - to 1 (i.e. `memory_map.alignment` should be set to 0), and each *w*-bit register would - occupy *ceil(w/n)* addresses from the CPU perspective, requiring the same amount of - memory instructions to access. - * The CPU could also access the CSR bus through a width down-converter, which would issue - *k/n* CSR accesses for each CPU access. In this case, the register alignment should be - set to *k/n*, and each *w*-bit register would occupy *ceil(w/k)* addresses from the CPU - perspective, requiring the same amount of memory instructions to access. - - If the register alignment (i.e. `2 ** memory_map.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 used with a 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 4th write is the one that actually - writes the value to the register. This allows determining write latency solely from the amount - of addresses the register occupies in the CPU address space, and the width of the CSR bus. - - Parameters - ---------- - memory_map : :class:`..memory.MemoryMap` - Memory map of CSR registers. - shadow_overlaps : int - Maximum number of CSR registers that can share a chunk of a shadow register. - Optional. If ``None``, any number of CSR registers can share a shadow chunk. - See :class:`Multiplexer._Shadow` for details. - - Attributes - ---------- - bus : :class:`Interface` - CSR bus providing access to registers. - """ def __init__(self, memory_map, *, shadow_overlaps=None): self._check_memory_map(memory_map) self._r_shadow = self._Shadow(memory_map.data_width, shadow_overlaps, name="r_shadow") @@ -616,32 +681,33 @@ class Decoder(wiring.Component): An address decoder for subordinate CSR buses. - Usage - ----- - - Although there is no functional difference between adding a set of registers directly to - a :class:`Multiplexer` and adding a set of registers to multiple :class:`Multiplexer`s that are - aggregated with a :class:`Decoder`, hierarchical CSR buses are useful for organizing - a hierarchical design. If many peripherals are directly served by a single - :class:`Multiplexer`, a very large amount of ports will connect the peripheral registers with - the decoder, and the cost of decoding logic would not be attributed to specific peripherals. - With a decoder, only five signals per peripheral will be used, and the logic could be kept - together with the peripheral. - - Parameters - ---------- - addr_width : int + .. note:: + + Although there is no functional difference between adding a set of registers directly to + a :class:`Multiplexer` and adding a set of registers to multiple :class:`Multiplexer`\\ s + that are aggregated with a :class:`Decoder`, hierarchical CSR buses are useful for + organizing a hierarchical design. + + If many peripherals are directly served by a single :class:`Multiplexer`, a very large + amount of ports will connect the peripheral registers with the :class:`Decoder`, 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. + + Arguments + --------- + addr_width : :class:`int` Address width. See :class:`Interface`. - data_width : int + data_width : :class:`int` Data width. See :class:`Interface`. - alignment : int, power-of-2 exponent - Window alignment. See :class:`..memory.MemoryMap`. + alignment : :class:`int`, power-of-2 exponent + Window alignment. See :class:`~.memory.MemoryMap`. name : :class:`str` - Window name. Optional. See :class:`..memory.MemoryMap`. + Window name. Optional. See :class:`~.memory.MemoryMap`. - Attributes - ---------- - bus : :class:`Interface` + Members + ------- + bus : :py:`In(csr.Signature(addr_width, data_width))` CSR bus providing access to subordinate buses. """ def __init__(self, *, addr_width, data_width, alignment=0, name=None): @@ -653,14 +719,32 @@ def __init__(self, *, addr_width, data_width, alignment=0, name=None): def align_to(self, alignment): """Align the implicit address of the next window. - See :meth:`MemoryMap.align_to` for details. + See :meth:`~.memory.MemoryMap.align_to` for details. + + Returns + ------- + :class:`int` + Implicit next address. """ return self.bus.memory_map.align_to(alignment) def add(self, sub_bus, *, addr=None): """Add a window to a subordinate bus. - See :meth:`MemoryMap.add_resource` for details. + See :meth:`~.memory.MemoryMap.add_window` for details. + + .. todo:: include exceptions raised in :meth:`~.memory.MemoryMap.add_window` + + Returns + ------- + :class:`tuple` of (:class:`int`, :class:`int`, :class:`int`) + A tuple ``(start, end, ratio)`` describing the address range assigned to the window. + ``ratio`` is always 1. + + Raises + ------ + :exc:`ValueError` + If the subordinate bus data width is not equal to the :class:`Decoder` data width. """ if isinstance(sub_bus, wiring.FlippedInterface): sub_bus_unflipped = flipped(sub_bus) diff --git a/amaranth_soc/csr/event.py b/amaranth_soc/csr/event.py index 3c29b0f..0c67048 100644 --- a/amaranth_soc/csr/event.py +++ b/amaranth_soc/csr/event.py @@ -28,29 +28,29 @@ class EventMonitor(wiring.Component): CSR registers ------------- enable : ``event_map.size``, read/write - Enabled events. See :meth:`..event.EventMap.sources` for layout. + Enabled events. See :meth:`.event.EventMap.sources` for layout. pending : ``event_map.size``, read/clear - Pending events. See :meth:`..event.EventMap.sources` for layout. + Pending events. See :meth:`.event.EventMap.sources` for layout. Parameters ---------- - event_map : :class:`..event.EventMap` + event_map : :class:`.event.EventMap` A collection of event sources. - trigger : :class:`..event.Source.Trigger` - Trigger mode. See :class:`..event.Source`. + trigger : :class:`.event.Source.Trigger` + Trigger mode. See :class:`.event.Source`. data_width : int - CSR bus data width. See :class:`..csr.Interface`. + CSR bus data width. See :class:`.csr.bus.Interface`. alignment : int, power-of-2 exponent - CSR address alignment. See :class:`..memory.MemoryMap`. + CSR address alignment. See :class:`~.memory.MemoryMap`. name : str - Window name. Optional. See :class:`..memory.MemoryMap`. + Window name. Optional. See :class:`~.memory.MemoryMap`. Attributes ---------- - src : :class:`..event.Source` + src : :class:`.event.Source` Event source. Its input line is asserted by the monitor when a subordinate event is enabled and pending. - bus : :class:`..csr.Interface` + bus : :class:`.csr.bus.Interface` CSR bus interface. """ def __init__(self, event_map, *, trigger="level", data_width, alignment=0, name=None): diff --git a/amaranth_soc/csr/reg.py b/amaranth_soc/csr/reg.py index da3c81a..e601914 100644 --- a/amaranth_soc/csr/reg.py +++ b/amaranth_soc/csr/reg.py @@ -17,6 +17,18 @@ class FieldPort(wiring.PureInterface): + """CSR register field port. + + An interface between a :class:`Register` and one of its fields. + + Arguments + --------- + signature : :class:`FieldPort.Signature` + Field port signature. + path : iterable of :class:`str` + Path to the field port. Optional. See :class:`amaranth.lib.wiring.PureInterface`. + """ + class Access(enum.Enum): """Field access mode.""" R = "r" @@ -25,31 +37,45 @@ class Access(enum.Enum): NC = "nc" def readable(self): + """Readable access mode. + + Returns + ------- + :class:`bool` + ``True`` if `self` is equal to :attr:`R` or :attr:`RW`. + """ return self == self.R or self == self.RW def writable(self): + """Writable access mode. + + Returns + ------- + :class:`bool` + ``True`` if `self` is equal to :attr:`W` or :attr:`RW`. + """ return self == self.W or self == self.RW class Signature(wiring.Signature): """CSR register field port signature. - Parameters - ---------- + Arguments + --------- shape : :ref:`shape-like object ` Shape of the field. access : :class:`FieldPort.Access` Field access mode. - Interface attributes - -------------------- - r_data : Signal(shape) + Members + ------- + r_data : :py:`In(shape)` Read data. Must always be valid, and is sampled when ``r_stb`` is asserted. - r_stb : Signal() + r_stb : :py:`Out(1)` Read strobe. Fields with read side effects should perform them when this strobe is asserted. - w_data : Signal(shape) + w_data : :py:`Out(shape)` Write data. Valid only when ``w_stb`` is asserted. - w_stb : Signal() + w_stb : :py:`Out(1)` Write strobe. Fields should update their value or perform the write side effect when this strobe is asserted. """ @@ -76,20 +102,32 @@ def __init__(self, shape, access): @property def shape(self): + """Field shape. + + Returns + ------- + :class:`amaranth.hdl.Shape` + """ return self._shape @property def access(self): + """Field access mode. + + Returns + ------- + :class:`FieldPort.Access` + """ return self._access def create(self, *, path=None, src_loc_at=0): """Create a compatible interface. - See :meth:`wiring.Signature.create` for details. + See :meth:`amaranth.lib.wiring.Signature.create` for details. Returns ------- - A :class:`FieldPort` object using this signature. + :class:`FieldPort` """ return FieldPort(self, path=path, src_loc_at=1 + src_loc_at) @@ -105,29 +143,6 @@ def __eq__(self, other): def __repr__(self): return f"csr.FieldPort.Signature({self.members!r})" - """CSR register field port. - - An interface between a CSR register and one of its fields. - - Parameters - ---------- - signature : :class:`FieldPort.Signature` - Field port signature. - path : iter(:class:`str`) - Path to the field port. Optional. See :class:`wiring.PureInterface`. - - Attributes - ---------- - shape : :ref:`shape-like object ` - Shape of the field. See :class:`FieldPort.Signature`. - access : :class:`FieldPort.Access` - Field access mode. See :class:`FieldPort.Signature`. - - Raises - ------ - :exc:`TypeError` - If ``signature`` is not a :class:`FieldPort.Signature`. - """ def __init__(self, signature, *, path=None, src_loc_at=0): if not isinstance(signature, FieldPort.Signature): raise TypeError(f"This interface requires a csr.FieldPort.Signature, not " @@ -136,10 +151,22 @@ def __init__(self, signature, *, path=None, src_loc_at=0): @property def shape(self): + """Field shape. + + Returns + ------- + :class:`amaranth.hdl.Shape` + """ return self.signature.shape @property def access(self): + """Field access mode. + + Returns + ------- + :class:`FieldPort.Access` + """ return self.signature.access def __repr__(self): @@ -149,19 +176,14 @@ def __repr__(self): class Field: """Description of a CSR register field. - Parameters - ---------- + Arguments + --------- action_cls : :class:`FieldAction` subclass The type of field action to be instantiated by :meth:`Field.create`. *args : :class:`tuple` Positional arguments passed to ``action_cls.__init__``. **kwargs : :class:`dict` Keyword arguments passed to ``action_cls.__init__``. - - Raises - ------ - :exc:`TypeError` - If ``action_cls`` is not a subclass of :class:`FieldAction`. """ def __init__(self, action_cls, *args, **kwargs): if not issubclass(action_cls, FieldAction): @@ -184,22 +206,21 @@ def create(self): class FieldAction(wiring.Component): """CSR register field action. - A :class:`~wiring.Component` mediating access between a CSR bus and a range of bits within a - :class:`Register`. + A component mediating access between a CSR bus and a range of bits within a :class:`Register`. - Parameters - ---------- + Arguments + --------- shape : :ref:`shape-like object ` Shape of the field. See :class:`FieldPort.Signature`. access : :class:`FieldPort.Access` Field access mode. See :class:`FieldPort.Signature`. - members : iterable of (:class:`str`, :class:`wiring.Member`) key/value pairs + members : iterable of (:class:`str`, :class:`amaranth.lib.wiring.Member`) key/value pairs Signature members. Optional, defaults to ``()``. A :class:`FieldPort.Signature` member named 'port' and oriented as input is always present in addition to these members. - Interface attributes - -------------------- - port : :class:`FieldPort` + Members + ------- + port : :py:`In(csr.reg.FieldPort.Signature(shape, access))` Field port. Raises @@ -221,23 +242,14 @@ def __init__(self, shape, access, members=()): class FieldActionMap(Mapping): """A mapping of field actions. - Parameters - ---------- + Arguments + --------- fields : :class:`dict` of :class:`str` to (:class:`Field` or :class:`dict` or :class:`list`) Register fields. Fields are instantiated according to their type: + - a :class:`Field` is instantiated as a :class:`FieldAction` (see :meth:`Field.create`); - a :class:`dict` is instantiated as a :class:`FieldActionMap`; - - a :class:`list` is instantiated as a :class:`FieldArrayMap`. - - Raises - ------ - :exc:`TypeError` - If ``fields`` is not a dict, or is empty. - :exc:`TypeError` - If ``fields`` has a key that is not a string, or is empty. - :exc:`TypeError` - If ``fields`` has a value that is neither a :class:`Field` object, a dict or a list of - :class:`Field` objects. + - a :class:`list` is instantiated as a :class:`FieldActionArray`. """ def __init__(self, fields): self._fields = {} @@ -326,7 +338,7 @@ def flatten(self): Yields ------ - iter(:class:`str`) + iterable of :class:`str` Path of the field. It is prefixed by the name of every nested :class:`FieldActionMap` or :class:`FieldActionArray`. :class:`FieldAction` @@ -343,21 +355,14 @@ def flatten(self): class FieldActionArray(Sequence): """An array of CSR register fields. - Parameters - ---------- + Arguments + --------- fields : :class:`list` of (:class:`Field` or :class:`dict` or :class:`list`) Register fields. Fields are instantiated according to their type: + - a :class:`Field` is instantiated as a :class:`FieldAction` (see :meth:`Field.create`); - a :class:`dict` is instantiated as a :class:`FieldActionMap`; - - a :class:`list` is instantiated as a :class:`FieldArrayMap`. - - Raises - ------ - :exc:`TypeError` - If ``fields`` is not a list, or is empty. - :exc:`TypeError` - If ``fields`` has an item that is neither a :class:`Field` object, a dict or a list of - :class:`Field` objects. + - a :class:`list` is instantiated as a :class:`FieldActionArray`. """ def __init__(self, fields): self._fields = [] @@ -403,7 +408,7 @@ def flatten(self): Yields ------ - iter(:class:`str`) + iterable of :class:`str` Path of the field. It is prefixed by the name of every nested :class:`FieldActionMap` or :class:`FieldActionArray`. :class:`FieldAction` @@ -421,41 +426,33 @@ class Register(wiring.Component): _doc_template = """ A CSR register. - Parameters - ---------- + Arguments + --------- fields : :class:`dict` or :class:`list` or :class:`Field` Collection of register fields. If ``None`` (default), a dict is populated from Python - :term:`variable annotations `. ``fields`` is used to create + :term:`variable annotations `. ``fields`` is used to create a :class:`FieldActionMap`, :class:`FieldActionArray`, or :class:`FieldAction`, - depending on its type (dict, list, or Field). - {parameters} - - Interface attributes - -------------------- - element : :class:`Element` - Interface between this register and a CSR bus primitive. + depending on its type (:class:`dict`, :class:`list`, or :class:`Field`). + {arguments} - Attributes - ---------- - field : :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction` - Collection of field instances. - f : :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction` - Shorthand for :attr:`Register.field`. + Members + ------- + element : :py:`Out(csr.Element.Signature(shape, access))` + Interface between this :class:`Register` and a CSR bus primitive. Raises ------ - :exc:`TypeError` - If ``fields`` is neither ``None``, a :class:`dict`, a :class:`list`, or a :class:`Field`. :exc:`ValueError` - If ``fields`` is not ``None`` and at least one variable annotation is a :class:`Field`. + If ``fields`` is not ``None`` and at least one :term:`variable annotation ` is a :class:`Field`. :exc:`ValueError` If ``element.access`` is not readable and at least one field is readable. :exc:`ValueError` If ``element.access`` is not writable and at least one field is writable. """ - __doc__ = _doc_template.format(parameters=""" - access : :class:`Element.Access` + __doc__ = _doc_template.format(arguments=""" + access : :class:`~.csr.bus.Element.Access` Element access mode. """) @@ -468,7 +465,6 @@ def __init_subclass__(cls, *, access=None, **kwargs): cls._access = Element.Access(access) except ValueError as e: raise ValueError(f"{access!r} is not a valid Element.Access") from e - cls.__doc__ = cls._doc_template.format(parameters="") super().__init_subclass__(**kwargs) def __init__(self, fields=None, access=None): @@ -530,10 +526,22 @@ def filter_fields(src): @property def field(self): + """Collection of field instances. + + Returns + ------- + :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction` + """ return self._field @property def f(self): + """Shorthand for :attr:`Register.field`. + + Returns + ------- + :class:`FieldActionMap` or :class:`FieldActionArray` or :class:`FieldAction` + """ return self._field def __iter__(self): @@ -541,7 +549,7 @@ def __iter__(self): Yields ------ - iter(:class:`str`) + iterable of :class:`str` Path of the field. It is prefixed by the name of every nested :class:`FieldActionMap` or :class:`FieldActionArray`. :class:`FieldAction` @@ -585,32 +593,24 @@ def elaborate(self, platform): class Builder: """CSR builder. - A CSR builder can organize a group of registers within an address range and convert it to a - :class:`MemoryMap` for consumption by other SoC primitives. + A CSR builder can organize a group of :class:`Register`\\ s within an address range and convert + it to a :class:`~.memory.MemoryMap` for consumption by other SoC primitives. - Parameters - ---------- + Arguments + --------- addr_width : :class:`int` Address width. data_width : :class:`int` Data width. granularity : :class:`int` Granularity. Optional, defaults to 8 bits. - name : :class:`str` + name : :class:`str` or ``None`` Name of the address range. Optional. Raises ------ - :exc:`TypeError` - If ``addr_width`` is not a positive integer. - :exc:`TypeError` - If ``data_width`` is not a positive integer. - :exc:`TypeError` - If ``granularity`` is not a positive integer. :exc:`ValueError` If ``granularity`` is not a divisor of ``data_width`` - :exc:`TypeError` - If ``name`` is not a string, or is empty. """ def __init__(self, *, addr_width, data_width, granularity=8, name=None): if not isinstance(addr_width, int) or addr_width <= 0: @@ -638,24 +638,48 @@ def __init__(self, *, addr_width, data_width, granularity=8, name=None): @property def addr_width(self): + """Address width. + + Returns + ------- + :class:`int` + """ return self._addr_width @property def data_width(self): + """Data width. + + Returns + ------- + :class:`int` + """ return self._data_width @property def granularity(self): + """Granularity. + + Returns + ------- + :class:`int` + """ return self._granularity @property def name(self): + """Name of the address range. + + Returns + ------- + :class:`str` or ``None`` + """ return self._name def freeze(self): """Freeze the builder. - Once the builder is frozen, CSR registers cannot be added anymore. + Once the builder is frozen, :class:`Register`\\ s cannot be added anymore. """ self._frozen = True @@ -681,14 +705,8 @@ def add(self, name, reg, *, offset=None): ------ :exc:`ValueError` If the builder is frozen. - :exc:`TypeError` - If ``name`` is not a string, or is empty. - :exc:`TypeError` - If ``reg` is not an instance of :class:`Register`. :exc:`ValueError` If ``reg`` is already added to the builder. - :exc:`TypeError` - If ``offset`` is not an integer, or is negative. :exc:`ValueError` If ``offset`` is not a multiple of ``self.data_width // self.granularity``. """ @@ -727,11 +745,6 @@ def Cluster(self, name): --------- name : :class:`str` Cluster name. - - Raises - ------ - :exc:`TypeError` - If ``name`` is not a string, or is empty. """ if not (isinstance(name, str) and name): raise TypeError(f"Cluster name must be a non-empty string, not {name!r}") @@ -749,11 +762,6 @@ def Index(self, index): --------- index : :class:`int` Array index. - - Raises - ------ - :exc:`TypeError` - If ``index`` is not an integer, or is negative. """ if not (isinstance(index, int) and index >= 0): raise TypeError(f"Array index must be a non-negative integer, not {index!r}") @@ -764,6 +772,14 @@ def Index(self, index): assert self._scope_stack.pop() == index def as_memory_map(self): + """Build a memory map. + + .. todo:: explain address/offset conversions + + Returns + ------- + :class:`~.memory.MemoryMap`. + """ self.freeze() memory_map = MemoryMap(addr_width=self.addr_width, data_width=self.data_width, name=self.name) @@ -784,28 +800,27 @@ def as_memory_map(self): class Bridge(wiring.Component): """CSR bridge. - Parameters - ---------- - memory_map : :class:`MemoryMap` - Memory map of CSR registers. + .. todo:: extended summary + + Arguments + --------- + memory_map : :class:`~.memory.MemoryMap` + Memory map of :class:`Register`\\ s. - Interface attributes - -------------------- - bus : :class:`Interface` + Members + ------- + bus : :py:`In(csr.Signature(memory_map.addr_width, memory_map.data_width))` CSR bus providing access to the contents of ``memory_map``. Raises ------ - :exc:`TypeError` - If ``memory_map`` is not a :class:`MemoryMap` object. :exc:`ValueError` If ``memory_map`` has windows. - :exc:`TypeError` - If ``memory_map`` has resources that are not :class:`Register` objects. """ def __init__(self, memory_map): if not isinstance(memory_map, MemoryMap): - raise TypeError(f"CSR bridge memory map must be an instance of MemoryMap, not {memory_map!r}") + raise TypeError(f"CSR bridge memory map must be an instance of MemoryMap, not " + f"{memory_map!r}") if list(memory_map.windows()): raise ValueError("CSR bridge memory map cannot have windows") for reg, reg_name, (reg_start, reg_end) in memory_map.resources(): diff --git a/amaranth_soc/csr/wishbone.py b/amaranth_soc/csr/wishbone.py index 7088a16..3aacd6d 100644 --- a/amaranth_soc/csr/wishbone.py +++ b/amaranth_soc/csr/wishbone.py @@ -23,18 +23,18 @@ class WishboneCSRBridge(wiring.Component): Reads and writes always take ``self.data_width // csr_bus.data_width + 1`` cycles to complete, regardless of the select inputs. Write side effects occur simultaneously with acknowledgement. - Parameters - ---------- - csr_bus : :class:`..csr.Interface` + Arguments + --------- + csr_bus : :class:`.csr.bus.Interface` CSR bus driven by the bridge. data_width : int Wishbone bus data width. Optional. If ``None``, defaults to ``csr_bus.data_width``. name : str - Window name. Optional. See :class:`..memory.MemoryMap`. + Window name. Optional. See :class:`~.memory.MemoryMap`. Attributes ---------- - wb_bus : :class:`..wishbone.Interface` + wb_bus : :class:`.wishbone.bus.Interface` Wishbone bus provided by the bridge. """ def __init__(self, csr_bus, *, data_width=None, name=None): diff --git a/amaranth_soc/gpio.py b/amaranth_soc/gpio.py index 8a73a08..ebcf957 100644 --- a/amaranth_soc/gpio.py +++ b/amaranth_soc/gpio.py @@ -18,73 +18,106 @@ class PinMode(enum.Enum, shape=unsigned(2)): #: `Input-only` mode. #: #: The pin output is disabled but remains connected to its :class:`Peripheral.Output` field. - #: Its :attr:`Peripheral.alt_mode` bit is wired to 0. + #: Its ``alt_mode`` bit is wired to 0. INPUT_ONLY = 0b00 #: `Push-pull` mode. #: #: The pin output is enabled and connected to its :class:`Peripheral.Output` field. Its - #: :attr:`Peripheral.alt_mode` bit is wired to 0. + #: ``alt_mode`` bit is wired to 0. PUSH_PULL = 0b01 #: `Open-drain` mode. #: #: The pin output is enabled when the value of its :class:`Peripheral.Output` field is 0, and - #: is itself wired to 0. Its :attr:`Peripheral.alt_mode` bit is wired to 0. + #: is itself wired to 0. Its ``alt_mode`` bit is wired to 0. OPEN_DRAIN = 0b10 #: `Alternate` mode. #: #: The pin output is disabled but remains connected to its :class:`Peripheral.Output` field. - #: Its :attr:`Peripheral.alt_mode` bit is wired to 1. + #: Its ``alt_mode`` bit is wired to 1. ALTERNATE = 0b11 class PinSignature(wiring.Signature): """GPIO pin signature. - Interface attributes - -------------------- - i : :class:`Signal` + Members + ------- + i : :py:`In(1)` Input. - o : :class:`Signal` + o : :py:`Out(1)` Output. - oe : :class:`Signal` + oe : :py:`Out(1)` Output enable. """ def __init__(self): super().__init__({ - "i": In(unsigned(1)), - "o": Out(unsigned(1)), - "oe": Out(unsigned(1)), + "i": In(1), + "o": Out(1), + "oe": Out(1), }) class Peripheral(wiring.Component): + """GPIO peripheral. + + Arguments + --------- + pin_count : :class:`int` + Number of GPIO pins. + addr_width : :class:`int` + CSR bus address width. + data_width : :class:`int` + CSR bus data width. + name : :class:`str` + CSR bus window name. Optional. + input_stages : :class:`int` + Number of synchronization stages between pin inputs and the :class:`~Peripheral.Input` + register. Optional. Defaults to ``2``. + + Members + ------- + bus : :py:`In(csr.Signature(addr_width, data_width))` + CSR bus interface providing access to registers. + pins : :py:`Out(PinSignature()).array(pin_count)` + GPIO pin interfaces. + alt_mode : :py:`Out(pin_count)` + Indicates which members of the ``pins`` array are in alternate mode. + """ + class Mode(csr.Register, access="rw"): - """Mode register. + __doc__ = """Mode register. - This :class:`csr.Register` contains an array of ``pin_count`` read/write fields. Each field - is 2-bit wide and its possible values are defined by the :class:`PinMode` enumeration. + This :class:`~.csr.reg.Register` contains an array of ``pin_count`` read/write fields. + Each field is 2-bit wide and its possible values are defined by the :class:`PinMode` + enumeration. - If ``pin_count`` is 8, then the register has the following fields: + ---- - .. bitfield:: - :bits: 16 + If ``pin_count`` is 8, then the :class:`~.csr.reg.Register` has the following fields: - [ - { "name": "pin[0]", "bits": 2, "attr": "RW" }, - { "name": "pin[1]", "bits": 2, "attr": "RW" }, - { "name": "pin[2]", "bits": 2, "attr": "RW" }, - { "name": "pin[3]", "bits": 2, "attr": "RW" }, - { "name": "pin[4]", "bits": 2, "attr": "RW" }, - { "name": "pin[5]", "bits": 2, "attr": "RW" }, - { "name": "pin[6]", "bits": 2, "attr": "RW" }, - { "name": "pin[7]", "bits": 2, "attr": "RW" }, - ] + .. wavedrom:: gpio_mode + + { + "reg": [ + { "name": "pin[0]", "bits": 2, "attr": "RW" }, + { "name": "pin[1]", "bits": 2, "attr": "RW" }, + { "name": "pin[2]", "bits": 2, "attr": "RW" }, + { "name": "pin[3]", "bits": 2, "attr": "RW" }, + { "name": "pin[4]", "bits": 2, "attr": "RW" }, + { "name": "pin[5]", "bits": 2, "attr": "RW" }, + { "name": "pin[6]", "bits": 2, "attr": "RW" }, + { "name": "pin[7]", "bits": 2, "attr": "RW" } + ], + "config": {"bits": 16} + } + + ---- - Parameters - ---------- + Arguments + --------- pin_count : :class:`int` Number of GPIO pins. """ @@ -96,31 +129,37 @@ def __init__(self, pin_count): class Input(csr.Register, access="r"): """Input register. - This :class:`csr.Register` contains an array of ``pin_count`` read-only fields. Each field - is 1-bit wide and driven by the input of its associated pin in the :attr:`Peripheral.pins` - array. + This :class:`~.csr.reg.Register` contains an array of ``pin_count`` read-only fields. Each + field is 1-bit wide and is driven by the input of its associated pin in the + :attr:`Peripheral.pins` array. Values sampled from pin inputs go through :attr:`Peripheral.input_stages` synchronization stages (on a rising edge of ``ClockSignal("sync")``) before reaching the register. - If ``pin_count`` is 8, then the register has the following fields: + ---- - .. bitfield:: - :bits: 8 + If ``pin_count`` is 8, then the :class:`~.csr.reg.Register` has the following fields: - [ - { "name": "pin[0]", "bits": 1, "attr": "R" }, - { "name": "pin[1]", "bits": 1, "attr": "R" }, - { "name": "pin[2]", "bits": 1, "attr": "R" }, - { "name": "pin[3]", "bits": 1, "attr": "R" }, - { "name": "pin[4]", "bits": 1, "attr": "R" }, - { "name": "pin[5]", "bits": 1, "attr": "R" }, - { "name": "pin[6]", "bits": 1, "attr": "R" }, - { "name": "pin[7]", "bits": 1, "attr": "R" }, - ] + .. wavedrom:: gpio_input + + { + "reg": [ + { "name": "pin[0]", "bits": 1, "attr": "R" }, + { "name": "pin[1]", "bits": 1, "attr": "R" }, + { "name": "pin[2]", "bits": 1, "attr": "R" }, + { "name": "pin[3]", "bits": 1, "attr": "R" }, + { "name": "pin[4]", "bits": 1, "attr": "R" }, + { "name": "pin[5]", "bits": 1, "attr": "R" }, + { "name": "pin[6]", "bits": 1, "attr": "R" }, + { "name": "pin[7]", "bits": 1, "attr": "R" } + ], + "config": {"bits": 8} + } + + ---- - Parameters - ---------- + Arguments + --------- pin_count : :class:`int` Number of GPIO pins. """ @@ -132,28 +171,34 @@ def __init__(self, pin_count): class Output(csr.Register, access="rw"): """Output register. - This :class:`csr.Register` contains an array of ``pin_count`` read/write fields. Each field - is 1-bit wide and drives the output of its associated pin in the :attr:`Peripheral.pins` - array, depending on its associated :class:`~Peripheral.Mode` field. + This :class:`~.csr.reg.Register` contains an array of ``pin_count`` read/write fields. Each + field is 1-bit wide and drives the output of its associated pin in the + :attr:`Peripheral.pins` array, depending on its associated :class:`~Peripheral.Mode` field. - If ``pin_count`` is 8, then the register has the following fields: + ---- - .. bitfield:: - :bits: 8 + If ``pin_count`` is 8, then the :class:`~.csr.reg.Register` has the following fields: - [ - { "name": "pin[0]", "bits": 1, "attr": "RW" }, - { "name": "pin[1]", "bits": 1, "attr": "RW" }, - { "name": "pin[2]", "bits": 1, "attr": "RW" }, - { "name": "pin[3]", "bits": 1, "attr": "RW" }, - { "name": "pin[4]", "bits": 1, "attr": "RW" }, - { "name": "pin[5]", "bits": 1, "attr": "RW" }, - { "name": "pin[6]", "bits": 1, "attr": "RW" }, - { "name": "pin[7]", "bits": 1, "attr": "RW" }, - ] + .. wavedrom:: gpio_output + + { + "reg": [ + { "name": "pin[0]", "bits": 1, "attr": "RW" }, + { "name": "pin[1]", "bits": 1, "attr": "RW" }, + { "name": "pin[2]", "bits": 1, "attr": "RW" }, + { "name": "pin[3]", "bits": 1, "attr": "RW" }, + { "name": "pin[4]", "bits": 1, "attr": "RW" }, + { "name": "pin[5]", "bits": 1, "attr": "RW" }, + { "name": "pin[6]", "bits": 1, "attr": "RW" }, + { "name": "pin[7]", "bits": 1, "attr": "RW" } + ], + "config": {"bits": 8} + } + + ---- - Parameters - ---------- + Arguments + --------- pin_count : :class:`int` Number of GPIO pins. """ @@ -161,8 +206,8 @@ class _FieldAction(csr.FieldAction): def __init__(self): super().__init__(shape=unsigned(1), access="rw", members=( ("data", Out(unsigned(1))), - ("set", In(unsigned(1))), - ("clr", In(unsigned(1))), + ("set", In(1)), + ("clr", In(1)), )) self._storage = Signal(unsigned(1)) @@ -189,32 +234,38 @@ def __init__(self, pin_count): class SetClr(csr.Register, access="w"): """Output set/clear register. - This :class:`csr.Register` contains an array of ``pin_count`` write-only fields. Each field - is 2-bit wide; writing it can modify its associated :class:`~Peripheral.Output` field as a - side-effect. + This :class:`~.csr.reg.Register` contains an array of ``pin_count`` write-only fields. Each + field is 2-bit wide; writing it can modify its associated :class:`~Peripheral.Output` field + as a side-effect. - If ``pin_count`` is 8, then the register has the following fields: + ---- - .. bitfield:: - :bits: 16 + If ``pin_count`` is 8, then the :class:`~.csr.reg.Register` has the following fields: - [ - { "name": "pin[0]", "bits": 2, "attr": "W" }, - { "name": "pin[1]", "bits": 2, "attr": "W" }, - { "name": "pin[2]", "bits": 2, "attr": "W" }, - { "name": "pin[3]", "bits": 2, "attr": "W" }, - { "name": "pin[4]", "bits": 2, "attr": "W" }, - { "name": "pin[5]", "bits": 2, "attr": "W" }, - { "name": "pin[6]", "bits": 2, "attr": "W" }, - { "name": "pin[7]", "bits": 2, "attr": "W" }, - ] + .. wavedrom:: gpio_setclr + + { + "reg": [ + { "name": "pin[0]", "bits": 2, "attr": "W" }, + { "name": "pin[1]", "bits": 2, "attr": "W" }, + { "name": "pin[2]", "bits": 2, "attr": "W" }, + { "name": "pin[3]", "bits": 2, "attr": "W" }, + { "name": "pin[4]", "bits": 2, "attr": "W" }, + { "name": "pin[5]", "bits": 2, "attr": "W" }, + { "name": "pin[6]", "bits": 2, "attr": "W" }, + { "name": "pin[7]", "bits": 2, "attr": "W" } + ], + "config": {"bits": 16} + } - Writing `0b01` to a field sets its associated :class:`~Peripheral.Output` field. - Writing `0b10` to a field clears its associated :class:`~Peripheral.Output` field. - Writing `0b00` or `0b11` to a field has no side-effect. - Parameters - ---------- + ---- + + Arguments + --------- pin_count : :class:`int` Number of GPIO pins. """ @@ -227,38 +278,6 @@ def __init__(self, pin_count): "pin": [pin_fields for _ in range(pin_count)], }) - """GPIO peripheral. - - Parameters - ---------- - pin_count : :class:`int` - Number of GPIO pins. - addr_width : :class:`int` - CSR bus address width. - data_width : :class:`int` - CSR bus data width. - name : :class:`str` - CSR bus window name. Optional. - input_stages : :class:`int` - Number of synchronization stages between pin inputs and the :class:`~Peripheral.Input` - register. Optional. Defaults to ``2``. - - Attributes - ---------- - bus : :class:`csr.Interface` - CSR bus interface providing access to registers. - pins : :class:`list` of :class:`wiring.PureInterface` of :class:`PinSignature` - GPIO pin interfaces. - alt_mode : :class:`Signal` - Indicates which members of the :attr:`Peripheral.pins` array are in alternate mode. - - Raises - ------ - :exc:`TypeError` - If ``pin_count`` is not a positive integer. - :exc:`TypeError` - If ``input_stages`` is not a non-negative integer. - """ def __init__(self, *, pin_count, addr_width, data_width, name=None, input_stages=2): if not isinstance(pin_count, int) or pin_count <= 0: raise TypeError(f"Pin count must be a positive integer, not {pin_count!r}") @@ -277,7 +296,7 @@ def __init__(self, *, pin_count, addr_width, data_width, name=None, input_stages super().__init__({ "bus": In(csr.Signature(addr_width=addr_width, data_width=data_width)), "pins": Out(PinSignature()).array(pin_count), - "alt_mode": Out(unsigned(pin_count)), + "alt_mode": Out(pin_count), }) self.bus.memory_map = self._bridge.bus.memory_map diff --git a/amaranth_soc/memory.py b/amaranth_soc/memory.py index 5d691b2..660268e 100644 --- a/amaranth_soc/memory.py +++ b/amaranth_soc/memory.py @@ -104,24 +104,25 @@ def names(self): class ResourceInfo: """Resource metadata. - A description of a memory map resource with its assigned path and address range. + A description of a :class:`MemoryMap` resource with its assigned path and address range. - Parameters - ---------- - resource : :class:`wiring.Component` - A resource located in the memory map. See :meth:`MemoryMap.add_resource` for details. + Arguments + --------- + resource : :class:`amaranth.lib.wiring.Component` + A resource located in the :class:`MemoryMap`. See :meth:`MemoryMap.add_resource` for + details. path : :class:`tuple` of (:class:`str` or (:class:`tuple` of :class:`str`)) Path of the resource. It is composed of the names of each window sitting between - the resource and the memory map from which this :class:`ResourceInfo` was obtained. + the resource and the :class:`MemoryMap` from which this :class:`ResourceInfo` was obtained. See :meth:`MemoryMap.add_window` for details. - start : int + start : :class:`int` Start of the address range assigned to the resource. - end : int + end : :class:`int` End of the address range assigned to the resource. - width : int + width : :class:`int` Amount of data bits accessed at each address. It may be equal to the data width of the - memory map from which this :class:`ResourceInfo` was obtained, or less if the resource - is located behind a window that uses sparse addressing. + :class:`MemoryMap` from which this :class:`ResourceInfo` was obtained, or less if the + resource is located behind a window that uses sparse addressing. """ def __init__(self, resource, path, start, end, width): flattened_path = [] @@ -146,53 +147,83 @@ def __init__(self, resource, path, start, end, width): @property def resource(self): + """The resource described by this :class:`ResourceInfo`. + + Returns + ------- + :class:`amaranth.lib.wiring.Component` + """ return self._resource @property def path(self): + """Path of the resource. + + Returns + ------- + :class:`tuple` of (:class:`str` or (:class:`tuple` of :class:`str`)) + """ return self._path @property def start(self): + """Start of the address range assigned to the resource. + + Returns + ------- + :class:`int` + """ return self._start @property def end(self): + """End of the address range assigned to the resource. + + Returns + ------- + :class:`int` + """ return self._end @property def width(self): + """Amount of data bits accessed at each address. + + Returns + ------- + :class:`int` + """ return self._width class MemoryMap: """Memory map. - A memory map is a hierarchical description of an address space, describing the structure of - address decoders of peripherals as well as bus bridges. It is built by adding resources - (range allocations for registers, memory, etc) and windows (range allocations for bus bridges), - and can be queried later to determine the address of any given resource from a specific - vantage point in the design. + A :class:`MemoryMap` is a hierarchical description of an address space, describing the + structure of address decoders of peripherals as well as bus bridges. - Address assignment - ------------------ + It is built by adding resources (range allocations for registers, memory, etc) and windows + (range allocations for bus bridges), and can be queried later to determine the address of + any given resource from a specific vantage point in the design. - To simplify address assignment, each memory map has an implicit next address, starting at 0. - If a resource or a window is added without specifying an address explicitly, the implicit next - address is used. In any case, the implicit next address is set to the address immediately - following the newly added resource or window. + .. note:: - Parameters - ---------- - addr_width : int + To simplify address assignment, each :class:`MemoryMap` has an implicit next address, + starting at 0. If a resource or a window is added without specifying an address explicitly, + the implicit next address is used. In any case, the implicit next address is set to the + address immediately following the newly added resource or window. + + Arguments + --------- + addr_width : :class:`int` Address width. - data_width : int + data_width : :class:`int` Data width. - alignment : int, power-of-2 exponent + alignment : :class:`int`, power-of-2 exponent Range alignment. Each added resource and window will be placed at an address that is a multiple of ``2 ** alignment``, and its size will be rounded up to be a multiple of ``2 ** alignment``. - name : str + name : :class:`str` Name of the address range. Optional. """ def __init__(self, *, addr_width, data_width, alignment=0, name=None): @@ -220,25 +251,49 @@ def __init__(self, *, addr_width, data_width, alignment=0, name=None): @property def addr_width(self): + """Address width. + + Returns + ------- + :class:`int` + """ return self._addr_width @property def data_width(self): + """Data width. + + Returns + ------- + :class:`int` + """ return self._data_width @property def alignment(self): + """Alignment. + + Returns + ------- + :class:`int` + """ return self._alignment @property def name(self): + """Name of the address range. + + Returns + ------- + :class:`str` + """ return self._name def freeze(self): - """Freeze the memory map. + """Freeze the :class:`MemoryMap`. - Once the memory map is frozen, its visible state becomes immutable. Resources and windows - cannot be added anymore. + Once the :class:`MemoryMap` is frozen, its visible state becomes immutable. Resources and + windows cannot be added anymore. """ self._frozen = True @@ -253,13 +308,14 @@ def align_to(self, alignment): Arguments --------- - alignment : int, power-of-2 exponent + alignment : :class:`int`, power-of-2 exponent Address alignment. The start of the implicit next address will be a multiple of ``2 ** max(alignment, self.alignment)``. - Return value - ------------ - Implicit next address. + Returns + ------- + :class:`int` + Implicit next address. """ if not isinstance(alignment, int) or alignment < 0: raise ValueError(f"Alignment must be a non-negative integer, not {alignment!r}") @@ -311,7 +367,7 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None): Arguments --------- - resource : :class:`wiring.Component` + resource : :class:`amaranth.lib.wiring.Component` The resource to be added. name : :class:`tuple` of (:class:`str`) Name of the resource. It must not conflict with the name of other resources or windows @@ -326,24 +382,25 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None): alignment : int, power-of-2 exponent Alignment of the resource. Optional. If ``None``, the memory map alignment is used. - Return value - ------------ - A tuple ``(start, end)`` describing the address range assigned to the resource. + Returns + ------- + :class:`tuple` of (:class:`int`, :class:`int`) + A tuple ``(start, end)`` describing the address range assigned to the resource. - Exceptions - ---------- + Raises + ------ :exc:`ValueError` - If the memory map is frozen. + If the :class:`MemoryMap` is frozen. :exc:`TypeError` If the resource is not a :class:`wiring.Component`. :exc:`ValueError` If the requested address and size, after alignment, would overlap with any resources or windows that have already been added, or would be out of bounds. :exc:`ValueError` - If the resource has already been added to this memory map. + If the resource has already been added to this :class:`MemoryMap`. :exc:`ValueError` If the resource name conflicts with the name of other resources or windows present in - this memory map. + this :class:`MemoryMap`. """ if self._frozen: raise ValueError(f"Memory map has been frozen. Cannot add resource {resource!r}") @@ -387,10 +444,12 @@ def resources(self): Non-recursively iterate resources in ascending order of their address. - Yield values - ------------ - A tuple ``resource, name, (start, end)`` describing the address range assigned to the - resource. + Yields + ------ + :class:`tuple` of (:class:`amaranth.lib.wiring.Component`, :class:`str`, :class:`tuple` \ + of (:class:`int`, :class:`int`)) + A tuple ``resource, name, (start, end)`` describing the address range assigned to the + resource. """ def is_resource(item): addr_range, assignment = item @@ -407,38 +466,42 @@ def add_window(self, window, *, addr=None, sparse=None): addresses; the memory map reflects this address translation when resources are looked up through the window. - Sparse addressing - ----------------- + .. note:: + + If a narrow bus is bridged to a wide bus, the bridge can perform *sparse* or *dense* + address translation. + + In the sparse case, each transaction on the wide bus results in one transaction on the + narrow bus; high data bits on the wide bus are ignored, and any contiguous resource on + the narrow bus becomes discontiguous on the wide bus. - If a narrow bus is bridged to a wide bus, the bridge can perform *sparse* or *dense* - address translation. In the sparse case, each transaction on the wide bus results in - one transaction on the narrow bus; high data bits on the wide bus are ignored, and any - contiguous resource on the narrow bus becomes discontiguous on the wide bus. In the dense - case, each transaction on the wide bus results in several transactions on the narrow bus, - and any contiguous resource on the narrow bus stays contiguous on the wide bus. + In the dense case, each transaction on the wide bus results in several transactions on + the narrow bus, and any contiguous resource on the narrow bus stays contiguous on the + wide bus. Arguments --------- window : :class:`MemoryMap` - A memory map describing the layout of the window. It is frozen as a side-effect of - being added to this memory map. - addr : int + A :class:`MemoryMap` describing the layout of the window. It is frozen as a side-effect + of being added to this memory map. + addr : :class:`int` Address of the window. Optional. If ``None``, the implicit next address will be used after aligning it to ``2 ** window.addr_width``. Otherwise, the exact specified address (which must be a multiple of ``2 ** window.addr_width``) will be used. - sparse : bool + sparse : :class:`bool` Address translation type. Optional. Ignored if the datapath widths of both memory maps are equal; must be specified otherwise. - Return value - ------------ - A tuple ``(start, end, ratio)`` describing the address range assigned to the window. - When bridging buses of unequal data width, ``ratio`` is the amount of contiguous addresses - on the narrower bus that are accessed for each transaction on the wider bus. Otherwise, - it is always 1. + Returns + ------- + :class:`tuple` of (:class:`int`, :class:`int`, :class:`int`) + A tuple ``(start, end, ratio)`` describing the address range assigned to the window. + When bridging buses of unequal data width, ``ratio`` is the amount of contiguous + addresses on the narrower bus that are accessed for each transaction on the wider bus. + Otherwise, it is always 1. - Exceptions - ---------- + Raises + ------ :exc:`ValueError` If the memory map is frozen. :exc:`ValueError` @@ -536,12 +599,14 @@ def windows(self): Non-recursively iterate windows in ascending order of their address. - Yield values - ------------ - A tuple ``window, (start, end, ratio)`` describing the address range assigned to - the window. When bridging buses of unequal data width, ``ratio`` is the amount of - contiguous addresses on the narrower bus that are accessed for each transaction on - the wider bus. Otherwise, it is always 1. + Yields + ------ + :class:`tuple` of (:class:`MemoryMap`, :class:`tuple` of (:class:`int`, :class:`int`, \ + :class:`int`)) + A tuple ``window, (start, end, ratio)`` describing the address range assigned to + the window. When bridging buses of unequal data widths, ``ratio`` is the amount of + contiguous addresses on the narrower bus that are accessed for each transaction on + the wider bus. Otherwise, it is always 1. """ def is_window(item): addr_range, assignment = item @@ -554,14 +619,15 @@ def window_patterns(self): Non-recursively iterate windows in ascending order of their address. - Yield values - ------------ - A tuple ``window, (pattern, ratio)`` describing the address range assigned to the window. - ``pattern`` is a ``self.addr_width`` wide pattern that may be used in ``Case`` or ``match`` - to determine if an address signal is within the address range of ``window``. When bridging - buses of unequal data width, ``ratio`` is the amount of contiguous addresses on - the narrower bus that are accessed for each transaction on the wider bus. Otherwise, - it is always 1. + Yields + ------ + :class:`tuple` of (:class:`MemoryMap`, :class:`tuple` of (:class:`str`, :class:`int`)) + A tuple ``window, (pattern, ratio)`` describing the address range assigned to the + window. ``pattern`` is a :attr:`addr_width` wide pattern that may be used in ``Case`` + or ``match`` to determine if an address signal is within the address range of + ``window``. When bridging buses of unequal data widths, ``ratio`` is the amount of + contiguous addresses on the narrower bus that are accessed for each transaction on + the wider bus. Otherwise, it is always 1. """ for window, (window_start, window_stop, window_ratio) in self.windows(): const_bits = self.addr_width - window.addr_width @@ -595,9 +661,10 @@ def all_resources(self): Recursively iterate all resources in ascending order of their address, performing address translation for resources that are located behind a window. - Yield values - ------------ - An instance of :class:`ResourceInfo` describing the resource and its address range. + Yields + ------ + :class:`ResourceInfo` + A description of the resource and its address range. """ for addr_range, assignment in self._ranges.items(): if id(assignment) in self._resources: @@ -619,16 +686,18 @@ def find_resource(self, resource): Arguments --------- - resource - Resource previously added to this memory map or any windows. - - Return value - ------------ - An instance of :class:`ResourceInfo` describing the resource and its address range. - - Exceptions - ---------- - Raises :exn:`KeyError` if the resource is not found. + resource : :class:`amaranth.lib.wiring.Component` + Resource previously added to this :class:`MemoryMap` or one of its windows. + + Returns + ------- + :class:`ResourceInfo` + A description of the resource and its address range. + + Raises + ------ + :exc:`KeyError` + If the resource is not found. """ if id(resource) in self._resources: _, resource_name, resource_range = self._resources[id(resource)] @@ -649,12 +718,13 @@ def decode_address(self, address): Arguments --------- - address : int + address : :class:`int` Address of interest. - Return value - ------------ - A resource mapped to the provided address, or ``None`` if there is no such resource. + Returns + ------- + :class:`amaranth.lib.wiring.Component` or ``None`` + A resource mapped to the provided address, or ``None`` if there is no such resource. """ assignment = self._ranges.get(address) if assignment is None: diff --git a/amaranth_soc/wishbone/bus.py b/amaranth_soc/wishbone/bus.py index 393fca4..c06d0f5 100644 --- a/amaranth_soc/wishbone/bus.py +++ b/amaranth_soc/wishbone/bus.py @@ -42,55 +42,57 @@ class Signature(wiring.Signature): of the Wishbone signals. The ``RST_I`` and ``CLK_I`` signals are provided as a part of the clock domain that drives the interface. - Parameters - ---------- - addr_width : int + The correspondence between the Amaranth-SoC signals and the Wishbone signals changes depending + on whether the interface acts as an initiator or a target. + + Arguments + --------- + addr_width : :class:`int` Width of the address signal. - data_width : ``8``, ``16``, ``32`` or ``64`` + data_width : :class:`int` Width of the data signals ("port size" in Wishbone terminology). - granularity : ``8``, ``16``, ``32``, ``64`` or ``None`` + One of 8, 16, 32, 64. + granularity : :class:`int` Granularity of select signals ("port granularity" in Wishbone terminology). + One of 8, 16, 32, 64. Optional. If ``None`` (by default), the granularity is equal to ``data_width``. - features : iter(:class:`Feature`) + features : iterable of :class:`Feature` Selects additional signals that will be a part of this interface. Optional. - Interface attributes - -------------------- - The correspondence between the Amaranth-SoC signals and the Wishbone signals changes depending - on whether the interface acts as an initiator or a target. - - adr : Signal(addr_width) + Members + ------- + adr : :py:`unsigned(addr_width)` Corresponds to Wishbone signal ``ADR_O`` (initiator) or ``ADR_I`` (target). - dat_w : Signal(data_width) + dat_w : :py:`unsigned(data_width)` Corresponds to Wishbone signal ``DAT_O`` (initiator) or ``DAT_I`` (target). - dat_r : Signal(data_width) + dat_r : :py:`unsigned(data_width)` Corresponds to Wishbone signal ``DAT_I`` (initiator) or ``DAT_O`` (target). - sel : Signal(data_width // granularity) + sel : :py:`unsigned(data_width // granularity)` Corresponds to Wishbone signal ``SEL_O`` (initiator) or ``SEL_I`` (target). - cyc : Signal() + cyc : :py:`unsigned(1)` Corresponds to Wishbone signal ``CYC_O`` (initiator) or ``CYC_I`` (target). - stb : Signal() + stb : :py:`unsigned(1)` Corresponds to Wishbone signal ``STB_O`` (initiator) or ``STB_I`` (target). - we : Signal() + we : :py:`unsigned(1)` Corresponds to Wishbone signal ``WE_O`` (initiator) or ``WE_I`` (target). - ack : Signal() + ack : :py:`unsigned(1)` Corresponds to Wishbone signal ``ACK_I`` (initiator) or ``ACK_O`` (target). - err : Signal() + err : :py:`unsigned(1)` Optional. Corresponds to Wishbone signal ``ERR_I`` (initiator) or ``ERR_O`` (target). - rty : Signal() + rty : :py:`unsigned(1)` Optional. Corresponds to Wishbone signal ``RTY_I`` (initiator) or ``RTY_O`` (target). - stall : Signal() + stall : :py:`unsigned(1)` Optional. Corresponds to Wishbone signal ``STALL_I`` (initiator) or ``STALL_O`` (target). - lock : Signal() + lock : :py:`unsigned(1)` Optional. Corresponds to Wishbone signal ``LOCK_O`` (initiator) or ``LOCK_I`` (target). Amaranth-SoC Wishbone support assumes that initiators that don't want bus arbitration to happen in between two transactions need to use ``lock`` feature to guarantee this. An initiator without the ``lock`` feature may be arbitrated in between two transactions even if ``cyc`` is kept high. - cti : Signal() + cti : :py:`unsigned(1)` Optional. Corresponds to Wishbone signal ``CTI_O`` (initiator) or ``CTI_I`` (target). - bte : Signal() + bte : :py:`unsigned(1)` Optional. Corresponds to Wishbone signal ``BTE_O`` (initiator) or ``BTE_I`` (target). """ def __init__(self, *, addr_width, data_width, granularity=None, features=frozenset()): @@ -140,28 +142,52 @@ def __init__(self, *, addr_width, data_width, granularity=None, features=frozens @property def addr_width(self): + """Width of the address signal. + + Returns + ------- + :class:`int` + """ return self._addr_width @property def data_width(self): + """Width of the data signals ("port size" in Wishbone terminology). + + Returns + ------- + One of 8, 16, 32, 64. + """ return self._data_width @property def granularity(self): + """Granularity of select signals ("port granularity" in Wishbone terminology). + + Returns + ------- + One of 8, 16, 32, 64. + """ return self._granularity @property def features(self): + """Additional signals that will be a part of this interface. + + Returns + ------- + :class:`frozenset` of :class:`Feature` + """ return self._features def create(self, *, path=None, src_loc_at=0): """Create a compatible interface. - See :meth:`wiring.Signature.create` for details. + See :meth:`amaranth.lib.wiring.Signature.create` for details. Returns ------- - An :class:`Interface` object using this signature. + :class:`Interface` """ return Interface(addr_width=self.addr_width, data_width=self.data_width, granularity=self.granularity, features=self.features, @@ -186,27 +212,24 @@ def __repr__(self): class Interface(wiring.PureInterface): """Wishbone bus interface. - Note that the data width of the underlying memory map of the interface is equal to port - granularity, not port size. If port granularity is less than port size, then the address width - of the underlying memory map is extended to reflect that. + .. note:: + + The data width of the underlying :class:`.MemoryMap` of the interface is equal to port + granularity, not port size. If port granularity is less than port size, then the address + width of the underlying memory map is extended to reflect that. - Parameters - ---------- + Arguments + --------- addr_width : :class:`int` Width of the address signal. See :class:`Signature`. data_width : :class:`int` Width of the data signals. See :class:`Signature`. granularity : :class:`int` Granularity of select signals. Optional. See :class:`Signature`. - features : iter(:class:`Feature`) + features : iterable of :class:`Feature` Describes additional signals of this interface. Optional. See :class:`Signature`. path : iter(:class:`str`) - Path to this Wishbone interface. Optional. See :class:`wiring.PureInterface`. - - Attributes - ---------- - memory_map: :class:`MemoryMap` - Memory map of the bus. Optional. + Path to this Wishbone interface. Optional. See :class:`amaranth.lib.wiring.PureInterface`. """ def __init__(self, *, addr_width, data_width, granularity=None, features=frozenset(), path=None, src_loc_at=0): @@ -217,22 +240,54 @@ def __init__(self, *, addr_width, data_width, granularity=None, features=frozens @property def addr_width(self): + """Width of the address signal. + + Returns + ------- + :class:`int` + """ return self.signature.addr_width @property def data_width(self): + """Width of the data signals ("port size" in Wishbone terminology). + + Returns + ------- + One of 8, 16, 32, 64. + """ return self.signature.data_width @property def granularity(self): + """Granularity of select signals ("port granularity" in Wishbone terminology). + + Returns + ------- + One of 8, 16, 32, 64. + """ return self.signature.granularity @property def features(self): + """Additional signals that are part of this interface. + + Returns + ------- + :class:`frozenset` of :class:`Feature` + """ return self.signature.features @property def memory_map(self): + """Memory map of the bus. + + .. todo:: setter + + Returns + ------- + :class:`~.memory.MemoryMap` or ``None`` + """ if self._memory_map is None: raise AttributeError(f"{self!r} does not have a memory map") return self._memory_map @@ -262,24 +317,24 @@ class Decoder(wiring.Component): An address decoder for subordinate Wishbone buses. - Parameters - ---------- + Arguments + --------- addr_width : :class:`int` Address width. See :class:`Signature`. data_width : :class:`int` Data width. See :class:`Signature`. granularity : :class:`int` Granularity. See :class:`Signature` - features : iter(:class:`Feature`) + features : iterable of :class:`Feature` Optional signal set. See :class:`Signature`. - alignment : int, power-of-2 exponent - Window alignment. Optional. See :class:`..memory.MemoryMap`. + alignment : :class:`int`, power-of-2 exponent + Window alignment. Optional. See :class:`~.memory.MemoryMap`. name : :class:`str` - Window name. Optional. See :class:`..memory.MemoryMap`. + Window name. Optional. See :class:`~.memory.MemoryMap`. - Attributes - ---------- - bus : :class:`Interface` + Members + ------- + bus : :py:`In(wishbone.Signature(addr_width, data_width, granularity, features))` Wishbone bus providing access to subordinate buses. """ def __init__(self, *, addr_width, data_width, granularity=None, features=frozenset(), @@ -296,21 +351,55 @@ def __init__(self, *, addr_width, data_width, granularity=None, features=frozens def align_to(self, alignment): """Align the implicit address of the next window. - See :meth:`MemoryMap.align_to` for details. + See :meth:`~.memory.MemoryMap.align_to` for details. + + Returns + ------- + :class:`int` + Implicit next address. """ return self.bus.memory_map.align_to(alignment) def add(self, sub_bus, *, addr=None, sparse=False): """Add a window to a subordinate bus. - The decoder can perform either sparse or dense address translation. If dense address - translation is used (the default), the subordinate bus must have the same data width as - the decoder; the window will be contiguous. If sparse address translation is used, - the subordinate bus may have data width less than the data width of the decoder; - the window may be discontiguous. In either case, the granularity of the subordinate bus - must be equal to or less than the granularity of the decoder. + See :meth:`~.memory.MemoryMap.add_window` for details. + + .. note:: + + The :class:`Decoder` can perform either *sparse* or *dense* address translation: + + - If dense address translation is used (the default), the subordinate bus must have + the same data width as the :class:`Decoder`; the window will be contiguous. + - If sparse address translation is used, the subordinate bus may have data width less + than the data width of the :class:`Decoder`; the window may be discontiguous. - See :meth:`MemoryMap.add_resource` for details. + In either case, the granularity of the subordinate bus must be equal to or less than + the granularity of the :class:`Decoder`. + + .. todo:: include exceptions raised in :meth:`~.memory.MemoryMap.add_window` + + Returns + ------- + :class:`tuple` of (:class:`int`, :class:`int`, :class:`int`) + A tuple ``(start, end, ratio)`` describing the address range assigned to the window. + When bridging buses of unequal data width, ``ratio`` is the amount of contiguous + addresses on the narrower bus that are accessed for each transaction on the wider bus. + Otherwise, it is always 1. + + Raises + ------ + :exc:`ValueError` + If the subordinate bus granularity is greater than the :class:`Decoder` granularity. + :exc:`ValueError` + If dense address translation is used and the subordinate bus data width is not equal + to the :class:`Decoder` data width. + :exc:`ValueError` + If sparse address translation is used and the subordinate bus data width is not the + equal to the subordinate bus granularity. + :exc:`ValueError` + If the subordinate bus as an optional output signal that is not present in the + :class:`Decoder` interface. """ if isinstance(sub_bus, wiring.FlippedInterface): sub_bus_unflipped = flipped(sub_bus) @@ -396,20 +485,20 @@ class Arbiter(wiring.Component): A round-robin arbiter for initiators accessing a shared Wishbone bus. - Parameters - ---------- - addr_width : int + Arguments + --------- + addr_width : :class:`int` Address width. See :class:`Signature`. - data_width : int + data_width : :class:`int` Data width. See :class:`Signature`. - granularity : int + granularity : :class:`int` Granularity. See :class:`Signature` - features : iter(:class:`Feature`) + features : iterable of :class:`Feature` Optional signal set. See :class:`Signature`. - Attributes - ---------- - bus : :class:`Interface` + Members + ------- + bus : :py:`Out(wishbone.Signature(addr_width, data_width, granularity, features))` Shared Wishbone bus. """ def __init__(self, *, addr_width, data_width, granularity=None, features=frozenset()): @@ -420,9 +509,17 @@ def __init__(self, *, addr_width, data_width, granularity=None, features=frozens def add(self, intr_bus): """Add an initiator bus to the arbiter. - The initiator bus must have the same address width and data width as the arbiter. The - granularity of the initiator bus must be greater than or equal to the granularity of - the arbiter. + Raises + ------ + :exc:`ValueError` + If the initiator bus address width is not equal to the :class:`Arbiter` address width. + :exc:`ValueError` + If the initiator bus granularity is lesser than the :class:`Arbiter` granularity. + :exc:`ValueError` + If the initiator bus data width is not equal to the :class:`Arbiter` data width. + :exc:`ValueError` + If the :class:`Arbiter` has an optional output signal that is not present in the + initiator bus. """ if not isinstance(intr_bus, Interface): raise TypeError(f"Initiator bus must be an instance of wishbone.Interface, not " diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 69a6288..081ca01 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -11,7 +11,7 @@ a { text-decoration: underline; } /* Some of our section titles are looong */ @media screen and (min-width:769px) { .wy-nav-side, .wy-side-scroll, .wy-menu-vertical { width: 340px; } - .wy-side-nav-search { width: 325px; } + .wy-side-nav-search { width: 340px; margin-bottom: .0em; } .wy-nav-content-wrap { margin-left: 340px; } } diff --git a/docs/conf.py b/docs/conf.py index 5e6ef45..487374b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,6 +16,7 @@ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_rtd_theme", + "sphinxcontrib.yowasp_wavedrom", ] with open(".gitignore") as f: @@ -23,7 +24,10 @@ root_doc = "cover" -intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "amaranth": ("https://amaranth-lang.org/docs/amaranth/latest", None), +} todo_include_todos = True @@ -38,6 +42,11 @@ napoleon_use_ivar = True napoleon_include_init_with_doc = True napoleon_include_special_with_doc = True +napoleon_custom_sections = [ + ("Arguments", "params_style"), # by default displays as "Parameters" + ("Attributes", "params_style"), # by default displays as "Variables", which is confusing + ("Members", "params_style"), # `amaranth.lib.wiring` signature members +] html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] @@ -45,6 +54,6 @@ html_logo = "_static/logo.png" rst_prolog = """ -.. role:: pc(code) +.. role:: py(code) :language: python """ diff --git a/docs/csr.rst b/docs/csr.rst new file mode 100644 index 0000000..40645c9 --- /dev/null +++ b/docs/csr.rst @@ -0,0 +1,13 @@ +CSR +=== + +.. warning:: + + This manual is a work in progress and is seriously incomplete! + +.. toctree:: + :maxdepth: 2 + + csr/bus + csr/reg + csr/action diff --git a/docs/csr/action.rst b/docs/csr/action.rst new file mode 100644 index 0000000..883e05a --- /dev/null +++ b/docs/csr/action.rst @@ -0,0 +1,20 @@ +CSR field actions +----------------- + +.. warning:: + + This manual is a work in progress and is seriously incomplete! + +.. py:module:: amaranth_soc.csr.action + +The :mod:`amaranth_soc.csr.action` module provides built-in CSR field actions. + +.. autoclass:: R() +.. autoclass:: W() +.. autoclass:: RW() +.. autoclass:: RW1C() +.. autoclass:: RW1S() +.. autoclass:: ResRAW0() +.. autoclass:: ResRAWL() +.. autoclass:: ResR0WA() +.. autoclass:: ResR0W0() diff --git a/docs/csr/bus.rst b/docs/csr/bus.rst new file mode 100644 index 0000000..88f78f7 --- /dev/null +++ b/docs/csr/bus.rst @@ -0,0 +1,57 @@ +CSR bus primitives +------------------ + +.. warning:: + + This manual is a work in progress and is seriously incomplete! + +.. py:module:: amaranth_soc.csr.bus + +The :mod:`amaranth_soc.csr.bus` module provides CSR bus primitives. + +.. autoclass:: amaranth_soc.csr.bus::Element.Access() + :no-members: + + .. autoattribute:: amaranth_soc.csr.bus::Element.Access.R + .. autoattribute:: amaranth_soc.csr.bus::Element.Access.W + .. autoattribute:: amaranth_soc.csr.bus::Element.Access.RW + .. automethod:: amaranth_soc.csr.bus::Element.Access.readable + .. automethod:: amaranth_soc.csr.bus::Element.Access.writable + +.. autoclass:: amaranth_soc.csr.bus::Element.Signature() + :no-members: + + .. autoattribute:: amaranth_soc.csr.bus::Element.Signature.width + .. autoattribute:: amaranth_soc.csr.bus::Element.Signature.access + .. automethod:: amaranth_soc.csr.bus::Element.Signature.create + .. automethod:: amaranth_soc.csr.bus::Element.Signature.__eq__ + +.. autoclass:: Element() + :no-members: + + .. autoattribute:: width + .. autoattribute:: access + +.. autoclass:: Signature() + :no-members: + + .. autoattribute:: addr_width + .. autoattribute:: data_width + .. automethod:: create + .. automethod:: __eq__ + +.. autoclass:: Interface() + :no-members: + + .. autoattribute:: addr_width + .. autoattribute:: data_width + .. autoattribute:: memory_map + +.. autoclass:: Multiplexer() + :no-members: + +.. autoclass:: Decoder() + :no-members: + + .. automethod:: align_to + .. automethod:: add diff --git a/docs/csr/reg.rst b/docs/csr/reg.rst new file mode 100644 index 0000000..39f0b7a --- /dev/null +++ b/docs/csr/reg.rst @@ -0,0 +1,81 @@ +CSR registers +------------- + +.. warning:: + + This manual is a work in progress and is seriously incomplete! + +.. py:module:: amaranth_soc.csr.reg + +The :mod:`amaranth_soc.csr.reg` module provides CSR register primitives. + +.. autoclass:: amaranth_soc.csr.reg::FieldPort.Access() + :no-members: + + .. autoattribute:: amaranth_soc.csr.reg::FieldPort.Access.R + .. autoattribute:: amaranth_soc.csr.reg::FieldPort.Access.W + .. autoattribute:: amaranth_soc.csr.reg::FieldPort.Access.RW + .. autoattribute:: amaranth_soc.csr.reg::FieldPort.Access.NC + .. automethod:: amaranth_soc.csr.reg::FieldPort.Access.readable + .. automethod:: amaranth_soc.csr.reg::FieldPort.Access.writable + +.. autoclass:: amaranth_soc.csr.reg::FieldPort.Signature() + :no-members: + + .. autoattribute:: amaranth_soc.csr.reg::FieldPort.Signature.shape + .. autoattribute:: amaranth_soc.csr.reg::FieldPort.Signature.access + .. automethod:: amaranth_soc.csr.reg::FieldPort.Signature.create + .. automethod:: amaranth_soc.csr.reg::FieldPort.Signature.__eq__ + +.. autoclass:: FieldPort() + :no-members: + + .. autoattribute:: shape + .. autoattribute:: access + +.. autoclass:: Field() + :no-members: + + .. automethod:: create + +.. autoclass:: FieldAction() + :no-members: + +.. autoclass:: FieldActionMap() + :no-members: + + .. automethod:: __getitem__ + .. automethod:: __getattr__ + .. automethod:: __iter__ + .. automethod:: __len__ + .. automethod:: flatten + +.. autoclass:: FieldActionArray() + :no-members: + + .. automethod:: __getitem__ + .. automethod:: __len__ + .. automethod:: flatten + +.. autoclass:: Register() + :no-members: + + .. autoattribute:: field + .. autoattribute:: f + .. automethod:: __iter__ + +.. autoclass:: Builder() + :no-members: + + .. autoattribute:: addr_width + .. autoattribute:: data_width + .. autoattribute:: granularity + .. autoattribute:: name + .. automethod:: freeze + .. automethod:: add + .. automethod:: Cluster + .. automethod:: Index + .. automethod:: as_memory_map + +.. autoclass:: Bridge() + :no-members: diff --git a/docs/gpio.rst b/docs/gpio.rst new file mode 100644 index 0000000..c393de3 --- /dev/null +++ b/docs/gpio.rst @@ -0,0 +1,14 @@ +GPIO +==== + +.. warning:: + + This manual is a work in progress and is seriously incomplete! + +.. py:module:: amaranth_soc.gpio + +The :mod:`amaranth_soc.gpio` module provides a basic GPIO peripheral. + +.. autoclass:: PinMode() +.. autoclass:: PinSignature() +.. autoclass:: Peripheral() diff --git a/docs/index.rst b/docs/index.rst index 820df0e..6465ac5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,3 +4,11 @@ System on Chip toolkit .. warning:: This manual is a work in progress and is seriously incomplete! + +.. toctree:: + :maxdepth: 2 + + memory + wishbone + csr + gpio diff --git a/docs/memory.rst b/docs/memory.rst new file mode 100644 index 0000000..2af2168 --- /dev/null +++ b/docs/memory.rst @@ -0,0 +1,30 @@ +Memory maps +=========== + +.. warning:: + + This manual is a work in progress and is seriously incomplete! + +.. py:module:: amaranth_soc.memory + +The :mod:`amaranth_soc.memory` module provides primitives for organizing the address space of a bus interface. + +.. autoclass:: MemoryMap() + :no-members: + + .. autoattribute:: addr_width + .. autoattribute:: data_width + .. autoattribute:: alignment + .. autoattribute:: name + .. automethod:: freeze + .. automethod:: align_to + .. automethod:: add_resource + .. automethod:: resources + .. automethod:: add_window + .. automethod:: windows + .. automethod:: window_patterns + .. automethod:: all_resources + .. automethod:: find_resource + .. automethod:: decode_address + +.. autoclass:: ResourceInfo() diff --git a/docs/wishbone.rst b/docs/wishbone.rst new file mode 100644 index 0000000..9b05ee3 --- /dev/null +++ b/docs/wishbone.rst @@ -0,0 +1,14 @@ +Wishbone +======== + +.. warning:: + + This manual is a work in progress and is seriously incomplete! + +.. py:module:: amaranth_soc.wishbone + + +.. toctree:: + :maxdepth: 2 + + wishbone/bus diff --git a/docs/wishbone/bus.rst b/docs/wishbone/bus.rst new file mode 100644 index 0000000..9d32d93 --- /dev/null +++ b/docs/wishbone/bus.rst @@ -0,0 +1,67 @@ +Wishbone bus +============ + +.. warning:: + + This manual is a work in progress and is seriously incomplete! + +.. py:module:: amaranth_soc.wishbone.bus + +The :mod:`amaranth_soc.wishbone.bus` module provides Wishbone bus primitives. + +.. autoclass:: CycleType() + :no-members: + + .. autoattribute:: CLASSIC + .. autoattribute:: CONST_BURST + .. autoattribute:: INCR_BURST + .. autoattribute:: END_OF_BURST + +.. autoclass:: BurstTypeExt() + :no-members: + + .. autoattribute:: LINEAR + .. autoattribute:: WRAP_4 + .. autoattribute:: WRAP_8 + .. autoattribute:: WRAP_16 + +.. autoclass:: Feature() + :no-members: + + .. autoattribute:: ERR + .. autoattribute:: RTY + .. autoattribute:: STALL + .. autoattribute:: LOCK + .. autoattribute:: CTI + .. autoattribute:: BTE + +.. autoclass:: Signature() + :no-members: + + .. autoattribute:: addr_width + .. autoattribute:: data_width + .. autoattribute:: granularity + .. autoattribute:: features + .. automethod:: create + .. automethod:: __eq__ + + +.. autoclass:: Interface() + :no-members: + + .. autoattribute:: addr_width + .. autoattribute:: data_width + .. autoattribute:: granularity + .. autoattribute:: features + .. autoattribute:: memory_map + +.. autoclass:: Decoder() + :no-members: + + .. automethod:: align_to + .. automethod:: add + +.. autoclass:: Arbiter() + :no-members: + + .. automethod:: add diff --git a/pyproject.toml b/pyproject.toml index 22d910f..34bc2f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ test = [ ] docs = [ "sphinx~=7.1", + "sphinxcontrib-yowasp-wavedrom~=1.0", "sphinx-rtd-theme~=1.2", "sphinx-autobuild", ] From 1925de03eea77fb26e0df2049f63b25f6e9f30bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Tue, 14 May 2024 13:50:34 +0200 Subject: [PATCH 2/5] docs/memory: add guide-level documentation. --- amaranth_soc/memory.py | 55 ++++----- docs/memory.rst | 248 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 258 insertions(+), 45 deletions(-) diff --git a/amaranth_soc/memory.py b/amaranth_soc/memory.py index 660268e..9bbd437 100644 --- a/amaranth_soc/memory.py +++ b/amaranth_soc/memory.py @@ -195,6 +195,10 @@ def width(self): """ return self._width + def __repr__(self): + return f"ResourceInfo(path={self.path}, start={self.start:#x}, end={self.end:#x}, " \ + f"width={self.width})" + class MemoryMap: """Memory map. @@ -206,13 +210,6 @@ class MemoryMap: (range allocations for bus bridges), and can be queried later to determine the address of any given resource from a specific vantage point in the design. - .. note:: - - To simplify address assignment, each :class:`MemoryMap` has an implicit next address, - starting at 0. If a resource or a window is added without specifying an address explicitly, - the implicit next address is used. In any case, the implicit next address is set to the - address immediately following the newly added resource or window. - Arguments --------- addr_width : :class:`int` @@ -304,7 +301,7 @@ def _align_up(value, alignment): return value def align_to(self, alignment): - """Align the implicit next address. + """Align the :ref:`implicit next address `. Arguments --------- @@ -372,14 +369,14 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None): name : :class:`tuple` of (:class:`str`) Name of the resource. It must not conflict with the name of other resources or windows present in this memory map. - addr : int - Address of the resource. Optional. If ``None``, the implicit next address will be used. - Otherwise, the exact specified address (which must be a multiple of - ``2 ** max(alignment, self.alignment)``) will be used. - size : int + addr : :class:`int` + Address of the resource. Optional. If ``None``, the :ref:`implicit next address + ` will be used. Otherwise, the exact specified address + (which must be a multiple of ``2 ** max(alignment, self.alignment)``) will be used. + size : :class:`int` Size of the resource, in minimal addressable units. Rounded up to a multiple of ``2 ** max(alignment, self.alignment)``. - alignment : int, power-of-2 exponent + alignment : :class:`int`, power-of-2 exponent Alignment of the resource. Optional. If ``None``, the memory map alignment is used. Returns @@ -390,17 +387,15 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None): Raises ------ :exc:`ValueError` - If the :class:`MemoryMap` is frozen. - :exc:`TypeError` - If the resource is not a :class:`wiring.Component`. + If the memory map is frozen. :exc:`ValueError` If the requested address and size, after alignment, would overlap with any resources or windows that have already been added, or would be out of bounds. :exc:`ValueError` - If the resource has already been added to this :class:`MemoryMap`. + If ``resource`` has already been added to this memory map. :exc:`ValueError` - If the resource name conflicts with the name of other resources or windows present in - this :class:`MemoryMap`. + If the requested name would conflict with the name of other resources or windows that + have already been added. """ if self._frozen: raise ValueError(f"Memory map has been frozen. Cannot add resource {resource!r}") @@ -466,28 +461,16 @@ def add_window(self, window, *, addr=None, sparse=None): addresses; the memory map reflects this address translation when resources are looked up through the window. - .. note:: - - If a narrow bus is bridged to a wide bus, the bridge can perform *sparse* or *dense* - address translation. - - In the sparse case, each transaction on the wide bus results in one transaction on the - narrow bus; high data bits on the wide bus are ignored, and any contiguous resource on - the narrow bus becomes discontiguous on the wide bus. - - In the dense case, each transaction on the wide bus results in several transactions on - the narrow bus, and any contiguous resource on the narrow bus stays contiguous on the - wide bus. - Arguments --------- window : :class:`MemoryMap` A :class:`MemoryMap` describing the layout of the window. It is frozen as a side-effect of being added to this memory map. addr : :class:`int` - Address of the window. Optional. If ``None``, the implicit next address will be used - after aligning it to ``2 ** window.addr_width``. Otherwise, the exact specified address - (which must be a multiple of ``2 ** window.addr_width``) will be used. + Address of the window. Optional. If ``None``, the :ref:`implicit next address + ` will be used after aligning it to + ``2 ** window.addr_width``. Otherwise, the exact specified address (which must be a + multiple of ``2 ** window.addr_width``) will be used. sparse : :class:`bool` Address translation type. Optional. Ignored if the datapath widths of both memory maps are equal; must be specified otherwise. diff --git a/docs/memory.rst b/docs/memory.rst index 2af2168..1d02968 100644 --- a/docs/memory.rst +++ b/docs/memory.rst @@ -1,21 +1,250 @@ Memory maps -=========== - -.. warning:: - - This manual is a work in progress and is seriously incomplete! +########### .. py:module:: amaranth_soc.memory The :mod:`amaranth_soc.memory` module provides primitives for organizing the address space of a bus interface. +.. testsetup:: + + from amaranth import * + + from amaranth_soc import csr + from amaranth_soc.memory import * + +.. _memory-introduction: + +Introduction +============ + +The purpose of :class:`MemoryMap` is to provide a hierarchical description of the address space of a System-on-Chip, from its bus interconnect to the registers of its peripherals. It is composed of :ref:`resources ` (representing registers, memories, etc) and :ref:`windows ` (representing bus bridges), and may be :ref:`queried ` afterwards in order to enumerate its contents, or determine the address of a resource. + +.. _memory-resources: + +Resources +========= + +A *resource* is a :class:`~amaranth.lib.wiring.Component` previously added to a :class:`MemoryMap`. Each resource occupies an unique range of addresses within the memory map, and represents a device that is a target for bus transactions. + +Adding resources +++++++++++++++++ + +Resources are added with :meth:`MemoryMap.add_resource`, which returns a ``(start, end)`` tuple describing their address range: + +.. testcode:: + + memory_map = MemoryMap(addr_width=3, data_width=8) + + reg_ctrl = csr.Register(csr.Field(csr.action.RW, 32), "rw") + reg_data = csr.Register(csr.Field(csr.action.RW, 32), "rw") + +.. doctest:: + + >>> memory_map.add_resource(reg_ctrl, size=4, addr=0x0, name=("ctrl",)) + (0, 4) + >>> memory_map.add_resource(reg_data, size=4, addr=0x4, name=("data",)) + (4, 8) + +.. _memory-implicit-next-address: + +.. note:: + + The ``addr`` parameter of :meth:`MemoryMap.add_resource` and :meth:`MemoryMap.add_window` is optional. + + To simplify address assignment, each :class:`MemoryMap` has an *implicit next address*, starting at 0. If a resource or a window is added without an explicit address, the implicit next address is used. In any case, the implicit next address is set to the address immediately following the newly added resource or window. + +Accessing resources ++++++++++++++++++++ + +Memory map resources can be iterated with :meth:`MemoryMap.resources`: + +.. doctest:: + + >>> for resource, name, (start, end) in memory_map.resources(): + ... print(f"name={name}, start={start:#x}, end={end:#x}, resource={resource}") + name=('ctrl',), start=0x0, end=0x4, resource=<...> + name=('data',), start=0x4, end=0x8, resource=<...> + +A memory map can be queried with :meth:`MemoryMap.find_resource` to get the name and address range of a given resource: + +.. doctest:: + + >>> memory_map.find_resource(reg_ctrl) + ResourceInfo(path=(('ctrl',),), start=0x0, end=0x4, width=8) + +The resource located at a given address can be retrieved with :meth:`MemoryMap.decode_address`: + +.. doctest:: + + >>> memory_map.decode_address(0x4) is reg_data + True + +Alignment +========= + +The value of :attr:`MemoryMap.alignment` constrains the layout of a memory map. If unspecified, it defaults to 0. + +Each resource or window added to a memory map is placed at an address that is a multiple of ``2 ** alignment``, and its size is rounded up to a multiple of ``2 ** alignment``. + +For example, the resources of this memory map are 64-bit aligned: + +.. testcode:: + + memory_map = MemoryMap(addr_width=8, data_width=8, alignment=3) + + reg_foo = csr.Register(csr.Field(csr.action.RW, 32), "rw") + reg_bar = csr.Register(csr.Field(csr.action.RW, 32), "rw") + reg_baz = csr.Register(csr.Field(csr.action.RW, 32), "rw") + +.. doctest:: + + >>> memory_map.add_resource(reg_foo, size=4, name=("foo",)) + (0, 8) + >>> memory_map.add_resource(reg_bar, size=4, name=("bar",), addr=0x9) + Traceback (most recent call last): + ... + ValueError: Explicitly specified address 0x9 must be a multiple of 0x8 bytes + +:meth:`MemoryMap.add_resource` takes an optional ``alignment`` parameter. If a value greater than :attr:`MemoryMap.alignment` is given, it becomes the alignment of this resource: + +.. doctest:: + + >>> memory_map.add_resource(reg_bar, size=4, name=("bar",), alignment=4) + (16, 32) + +:meth:`MemoryMap.align_to` can be used to align the :ref:`implicit next address `. Its alignment is modified if a value greater than :attr:`MemoryMap.alignment` is given. + +.. doctest:: + + >>> memory_map.align_to(6) + 64 + >>> memory_map.add_resource(reg_baz, size=4, name=("baz",)) + (64, 72) + +.. note:: :meth:`MemoryMap.align_to` has no effect on the size of the next resource or window. + +.. _memory-windows: + +Windows +======= + +A *window* is a :class:`MemoryMap` nested inside another memory map. Each window occupies an unique range of addresses within the memory map, and represents a bridge to a subordinate bus. + +Adding windows +++++++++++++++ + +Windows are added with :meth:`MemoryMap.add_window`, which returns a ``(start, end, ratio)`` tuple describing their address range: + +.. testcode:: + + reg_ctrl = csr.Register(csr.Field(csr.action.RW, 32), "rw") + reg_rx_data = csr.Register(csr.Field(csr.action.RW, 32), "rw") + reg_tx_data = csr.Register(csr.Field(csr.action.RW, 32), "rw") + + memory_map = MemoryMap(addr_width=14, data_width=32) + rx_window = MemoryMap(addr_width=12, data_width=32, name="rx") + tx_window = MemoryMap(addr_width=12, data_width=32, name="tx") + +.. doctest:: + + >>> memory_map.add_resource(reg_ctrl, size=1, name=("ctrl",)) + (0, 1) + + >>> rx_window.add_resource(reg_rx_data, size=1, name=("data",)) + (0, 1) + >>> memory_map.add_window(rx_window) + (4096, 8192, 1) + +The third value returned by :meth:`MemoryMap.add_window` represents the number of addresses that are accessed in the bus described by ``rx_window`` for one transaction in the bus described by ``memory_map``. It is 1 in this case, as both busses have the same width. + +.. doctest:: + + >>> tx_window.add_resource(reg_tx_data, size=1, name=("data",)) + (0, 1) + >>> memory_map.add_window(tx_window) + (8192, 12288, 1) + +.. _memory-accessing-windows: + +Accessing windows +----------------- + +Memory map windows can be iterated with :meth:`MemoryMap.windows`: + +.. doctest:: + + >>> for window, (start, end, ratio) in memory_map.windows(): + ... print(f"{window}, start={start:#x}, end={end:#x}, ratio={ratio}") + MemoryMap(name='rx'), start=0x1000, end=0x2000, ratio=1 + MemoryMap(name='tx'), start=0x2000, end=0x3000, ratio=1 + +Windows can also be iterated with :meth:`MemoryMap.window_patterns`, which encodes their address ranges as bit patterns compatible with the :ref:`match operator ` and the :ref:`Case block `: + +.. doctest:: + + >>> for window, (pattern, ratio) in memory_map.window_patterns(): + ... print(f"{window}, pattern='{pattern}', ratio={ratio}") + MemoryMap(name='rx'), pattern='01------------', ratio=1 + MemoryMap(name='tx'), pattern='10------------', ratio=1 + +Memory map resources can be recursively iterated with :meth:`MemoryMap.all_resources`, which yields instances of :class:`ResourceInfo`: + +.. doctest:: + + >>> for res_info in memory_map.all_resources(): + ... print(res_info) + ResourceInfo(path=(('ctrl',),), start=0x0, end=0x1, width=32) + ResourceInfo(path=('rx', ('data',)), start=0x1000, end=0x1001, width=32) + ResourceInfo(path=('tx', ('data',)), start=0x2000, end=0x2001, width=32) + +Address translation ++++++++++++++++++++ + +When a memory map resource is accessed through a window, address translation may happen in three different modes. + +Transparent mode +---------------- + +In *transparent mode*, each transaction on the primary bus results in one transaction on the subordinate bus without loss of data. This mode is selected when :meth:`MemoryMap.add_window` is given ``sparse=None``, which will fail if the window and the memory map have a different data widths. + +.. note:: + + In practice, transparent mode is identical to other modes; it can only be used with equal data widths, which results in the same behavior regardless of the translation mode. However, it causes :meth:`MemoryMap.add_window` to fail if the data widths are different. + +Sparse mode +----------- + +In *sparse mode*, each transaction on the wide primary bus results in one transaction on the narrow subordinate bus. High data bits on the primary bus are ignored, and any contiguous resource on the subordinate bus becomes discontiguous on the primary bus. This mode is selected when :meth:`MemoryMap.add_window` is given ``sparse=True``. + +Dense mode +---------- + +In *dense mode*, each transaction on the wide primary bus results in several transactions on the narrow subordinate bus, and any contiguous resource on the subordinate bus stays contiguous on the primary bus. This mode is selected when :meth:`MemoryMap.add_window` is given ``sparse=False``. + +Freezing +======== + +The state of a memory map can become immutable by calling :meth:`MemoryMap.freeze`: + +.. testcode:: + + memory_map = MemoryMap(addr_width=3, data_width=8) + + reg_ctrl = csr.Register(csr.Field(csr.action.RW, 32), "rw") + +.. doctest:: + + >>> memory_map.freeze() + >>> memory_map.add_resource(reg_ctrl, size=4, addr=0x0, name=("ctrl",)) + Traceback (most recent call last): + ... + ValueError: Memory map has been frozen. Cannot add resource <...> + +It is recommended to freeze a memory map before passing it to external logic, as a preventive measure against TOCTTOU bugs. + .. autoclass:: MemoryMap() :no-members: - .. autoattribute:: addr_width - .. autoattribute:: data_width - .. autoattribute:: alignment - .. autoattribute:: name .. automethod:: freeze .. automethod:: align_to .. automethod:: add_resource @@ -28,3 +257,4 @@ The :mod:`amaranth_soc.memory` module provides primitives for organizing the add .. automethod:: decode_address .. autoclass:: ResourceInfo() + :no-members: From f21b31e0ddd03a21828233da891cff1e0279805c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Tue, 21 May 2024 06:42:03 +0200 Subject: [PATCH 3/5] csr.bus: fix incorrect flow of element members of register signatures. --- amaranth_soc/csr/bus.py | 4 ++-- amaranth_soc/csr/reg.py | 4 ++-- tests/test_csr_bus.py | 18 +++++++++--------- tests/test_csr_wishbone.py | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/amaranth_soc/csr/bus.py b/amaranth_soc/csr/bus.py index ca7077d..0c55b58 100644 --- a/amaranth_soc/csr/bus.py +++ b/amaranth_soc/csr/bus.py @@ -592,12 +592,12 @@ def _check_memory_map(self, memory_map): raise ValueError("CSR multiplexer memory map cannot have windows") for reg, reg_name, (reg_start, reg_end) in memory_map.resources(): if not ("element" in reg.signature.members and - reg.signature.members["element"].flow == Out and + reg.signature.members["element"].flow == In and reg.signature.members["element"].is_signature and isinstance(reg.signature.members["element"].signature, Element.Signature)): raise AttributeError(f"Signature of CSR register {reg_name} must have a " f"csr.Element.Signature member named 'element' and oriented " - f"as wiring.Out") + f"as wiring.In") def elaborate(self, platform): m = Module() diff --git a/amaranth_soc/csr/reg.py b/amaranth_soc/csr/reg.py index e601914..6d6f5aa 100644 --- a/amaranth_soc/csr/reg.py +++ b/amaranth_soc/csr/reg.py @@ -437,7 +437,7 @@ class Register(wiring.Component): Members ------- - element : :py:`Out(csr.Element.Signature(shape, access))` + element : :py:`In(csr.Element.Signature(shape, access))` Interface between this :class:`Register` and a CSR bus primitive. Raises @@ -522,7 +522,7 @@ def filter_fields(src): raise ValueError(f"Field {'__'.join(field_path)} is writable, but element access " f"mode is {access}") - super().__init__({"element": Out(Element.Signature(width, access))}) + super().__init__({"element": In(Element.Signature(width, access))}) @property def field(self): diff --git a/tests/test_csr_bus.py b/tests/test_csr_bus.py index d45415e..073df04 100644 --- a/tests/test_csr_bus.py +++ b/tests/test_csr_bus.py @@ -12,7 +12,7 @@ class _MockRegister(wiring.Component): def __init__(self, width, access): - super().__init__({"element": Out(csr.Element.Signature(width, access))}) + super().__init__({"element": In(csr.Element.Signature(width, access))}) class ElementSignatureTestCase(unittest.TestCase): @@ -204,32 +204,32 @@ class _Reg(wiring.Component): pass # wrong name map_0 = MemoryMap(addr_width=1, data_width=8) - map_0.add_resource(_Reg({"foo": Out(csr.Element.Signature(8, "rw"))}), name=("a",), size=1) + map_0.add_resource(_Reg({"foo": In(csr.Element.Signature(8, "rw"))}), name=("a",), size=1) with self.assertRaisesRegex(AttributeError, r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member " - r"named 'element' and oriented as wiring\.Out"): + r"named 'element' and oriented as wiring\.In"): csr.Multiplexer(map_0) # wrong direction map_1 = MemoryMap(addr_width=1, data_width=8) - map_1.add_resource(_Reg({"element": In(csr.Element.Signature(8, "rw"))}), name=("a",), + map_1.add_resource(_Reg({"element": Out(csr.Element.Signature(8, "rw"))}), name=("a",), size=1) with self.assertRaisesRegex(AttributeError, r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member " - r"named 'element' and oriented as wiring\.Out"): + r"named 'element' and oriented as wiring\.In"): csr.Multiplexer(map_1) # wrong member type map_2 = MemoryMap(addr_width=1, data_width=8) - map_2.add_resource(_Reg({"element": Out(unsigned(8))}), name=("a",), size=1) + map_2.add_resource(_Reg({"element": In(unsigned(8))}), name=("a",), size=1) with self.assertRaisesRegex(AttributeError, r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member " - r"named 'element' and oriented as wiring\.Out"): + r"named 'element' and oriented as wiring\.In"): csr.Multiplexer(map_2) # wrong member signature map_3 = MemoryMap(addr_width=1, data_width=8) - map_3.add_resource(_Reg({"element": Out(wiring.Signature({}))}), name=("a",), size=1) + map_3.add_resource(_Reg({"element": In(wiring.Signature({}))}), name=("a",), size=1) with self.assertRaisesRegex(AttributeError, r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member " - r"named 'element' and oriented as wiring\.Out"): + r"named 'element' and oriented as wiring\.In"): csr.Multiplexer(map_3) def test_wrong_memory_map_windows(self): diff --git a/tests/test_csr_wishbone.py b/tests/test_csr_wishbone.py index 717156e..3d7bf2f 100644 --- a/tests/test_csr_wishbone.py +++ b/tests/test_csr_wishbone.py @@ -14,7 +14,7 @@ class _MockRegister(wiring.Component): def __init__(self, width, name): super().__init__({ - "element": Out(csr.Element.Signature(width, "rw")), + "element": In(csr.Element.Signature(width, "rw")), "r_count": Out(unsigned(8)), "w_count": Out(unsigned(8)), "data": Out(width) From af7bda220e38ddb9c48e24c4b782808ed2f08e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Tue, 21 May 2024 13:52:35 +0200 Subject: [PATCH 4/5] docs/csr/bus: add guide-level documentation. --- amaranth_soc/csr/bus.py | 107 ++++++-------- docs/csr/bus.rst | 303 ++++++++++++++++++++++++++++++++++++++-- docs/memory.rst | 3 + 3 files changed, 335 insertions(+), 78 deletions(-) diff --git a/amaranth_soc/csr/bus.py b/amaranth_soc/csr/bus.py index 0c55b58..49247cb 100644 --- a/amaranth_soc/csr/bus.py +++ b/amaranth_soc/csr/bus.py @@ -11,7 +11,7 @@ class Element(wiring.PureInterface): - """Peripheral-side CSR interface. + """CSR register interface. A low-level interface to a single atomically readable and writable register in a peripheral. This interface supports any register width and semantics, provided that both reads and writes @@ -24,7 +24,7 @@ class Element(wiring.PureInterface): access : :class:`Element.Access` Register access mode. path : iterable of :class:`str` - Path to this CSR interface. Optional. See :class:`amaranth.lib.wiring.PureInterface`. + Path to this interface. Optional. See :class:`amaranth.lib.wiring.PureInterface`. """ class Access(enum.Enum): @@ -33,8 +33,14 @@ class Access(enum.Enum): Coarse access mode for the entire register. Individual fields can have more restrictive access mode, e.g. R/O fields can be a part of an R/W register. """ + + #: Read-only mode. R = "r" + + #: Write-only mode. W = "w" + + #: Read/write mode. RW = "rw" def readable(self): @@ -43,7 +49,7 @@ def readable(self): Returns ------- :class:`bool` - ``True`` if `self` is equal to :attr:`R` or :attr:`RW`. + ``True`` if equal to :attr:`R` or :attr:`RW`. """ return self == self.R or self == self.RW @@ -53,12 +59,12 @@ def writable(self): Returns ------- :class:`bool` - ``True`` if `self` is equal to :attr:`W` or :attr:`RW`. + ``True`` if equal to :attr:`W` or :attr:`RW`. """ return self == self.W or self == self.RW class Signature(wiring.Signature): - """Peripheral-side CSR signature. + """CSR register signature. Arguments --------- @@ -179,7 +185,7 @@ def __repr__(self): class Signature(wiring.Signature): - """CPU-side CSR signature. + """CSR bus signature. Arguments --------- @@ -272,22 +278,10 @@ def __repr__(self): class Interface(wiring.PureInterface): - """CPU-side CSR interface. + """CSR bus interface. A low-level interface to a set of atomically readable and writable peripheral CSR registers. - .. note:: - - CSR registers mapped to the CSR bus are split into chunks according to the bus data width. - Each chunk is assigned a consecutive address on the bus. This allows accessing CSRs of any - size using any datapath width. - - When the first chunk of a register is read, the value of a register is captured, and reads - from subsequent chunks of the same register 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 the captured value to the register. This allows atomically accessing - CSRs larger than datapath width. - Arguments --------- addr_width : :class:`int` @@ -326,11 +320,15 @@ def data_width(self): def memory_map(self): """Memory map of the bus. - .. todo:: setter - Returns ------- :class:`~.memory.MemoryMap` or ``None`` + + Raises + ------ + :exc:`ValueError` + If set to a memory map that does not have the same address and data widths as the bus + interface. """ if self._memory_map is None: raise AttributeError(f"{self!r} does not have a memory map") @@ -357,39 +355,33 @@ class Multiplexer(wiring.Component): An address-based multiplexer for CSR registers implementing atomic updates. - This implementation assumes the following from the CSR bus: - - * an initiator must have exclusive ownership over the multiplexer for the full duration of - a register transaction; - * an initiator must access a register in ascending order of addresses, but it may abort a - transaction after any bus cycle. - Writes are registered, and are performed 1 cycle after ``w_stb`` is asserted. .. note:: - Because the CSR bus conserves logic and routing resources, it is common to e.g. access - a CSR bus with an *n*-bit data path from a CPU with a *k*-bit datapath (*k>n*) in cases - where CSR access latency is less important than resource usage. - - In this case, two strategies are possible for connecting the CSR bus to the CPU: - - * The CPU could access the CSR bus directly (with no intervening logic other than - simple translation of control signals). In this case, the register alignment should - be set to 1 (i.e. ``memory_map.alignment`` should be set to 0), and each *w*-bit - register would occupy *ceil(w/n)* addresses from the CPU perspective, requiring the - same amount of memory instructions to access. - * The CPU could also access the CSR bus through a width down-converter, which would - issue *k/n* CSR accesses for each CPU access. In this case, the register alignment - should be set to *k/n*, and each *w*-bit register would occupy *ceil(w/k)* addresses - from the CPU perspective, requiring the same amount of memory instructions to access. - - If the register alignment (i.e. ``2 ** memory_map.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 used with a 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 4th write is the one that actually - writes the value to the register. This allows determining write latency solely from the amount - of addresses the register occupies in the CPU address space, and the width of the CSR bus. + 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. + + In this case, two strategies are possible for connecting the CSR bus to the CPU: + + * 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. + + * 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. + + 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. Arguments --------- @@ -681,19 +673,6 @@ class Decoder(wiring.Component): An address decoder for subordinate CSR buses. - .. note:: - - Although there is no functional difference between adding a set of registers directly to - a :class:`Multiplexer` and adding a set of registers to multiple :class:`Multiplexer`\\ s - that are aggregated with a :class:`Decoder`, hierarchical CSR buses are useful for - organizing a hierarchical design. - - If many peripherals are directly served by a single :class:`Multiplexer`, a very large - amount of ports will connect the peripheral registers with the :class:`Decoder`, 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. - Arguments --------- addr_width : :class:`int` @@ -733,8 +712,6 @@ def add(self, sub_bus, *, addr=None): See :meth:`~.memory.MemoryMap.add_window` for details. - .. todo:: include exceptions raised in :meth:`~.memory.MemoryMap.add_window` - Returns ------- :class:`tuple` of (:class:`int`, :class:`int`, :class:`int`) diff --git a/docs/csr/bus.rst b/docs/csr/bus.rst index 88f78f7..537d313 100644 --- a/docs/csr/bus.rst +++ b/docs/csr/bus.rst @@ -1,13 +1,291 @@ -CSR bus primitives ------------------- +CSR bus +------- + +.. py:module:: amaranth_soc.csr.bus + +The :mod:`amaranth_soc.csr.bus` module contains primitives to implement and access the registers of peripherals through a bus interface. + +.. testsetup:: + + from amaranth import * + from amaranth.lib import wiring + from amaranth.lib.wiring import In, Out, flipped, connect + + from amaranth_soc import csr + from amaranth_soc.memory import * + +.. _csr-bus-introduction: + +Introduction +============ + +The CSR bus API provides unopinionated primitives for defining and connecting 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 `, :ref:`register multiplexers ` that provide access to the registers of a peripheral, and :ref:`bus decoders ` that provide access to the registers of multiple peripherals. + +.. _csr-bus-element: + +Defining registers +++++++++++++++++++ + +A CSR register is a :class:`~amaranth.lib.wiring.Component` with an :class:`Element` member in its interface, oriented as input and named ``"element"``. + +For example, this component is a read/write register with a configurable width: + +.. testcode:: + + class MyRegister(wiring.Component): + def __init__(self, width): + super().__init__({ + "element": In(csr.Element.Signature(width, "rw")), + "data": Out(width), + }) + + def elaborate(self, platform): + m = Module() + storage = Signal.like(self.data) + + with m.If(self.element.w_stb): + m.d.sync += storage.eq(self.element.w_data) + + m.d.comb += [ + self.element.r_data.eq(storage), + self.data.eq(storage), + ] + + return m + +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. + +.. _csr-bus-multiplexer: + +Accessing registers ++++++++++++++++++++ + +A :class:`Multiplexer` can provide access to the registers of a peripheral through a CSR bus :class:`Interface`. Registers must first be added to a :class:`MemoryMap`, which is used to instantiate the multiplexer. + +The following example shows a very basic timer peripheral with an 8-bit CSR bus and two 24-bit registers, ``Cnt`` and ``Rst``. The value of ``Cnt`` is incremented every clock cycle, and can be reset by a CSR bus write to ``Rst``: + +.. testcode:: + + class BasicTimer(wiring.Component): + class Cnt(wiring.Component): + element: In(csr.Element.Signature(width=24, access="r")) + r_stb: Out(1) + r_data: In(unsigned(24)) + + def elaborate(self, platform): + m = Module() + m.d.comb += [ + self.r_stb.eq(self.element.r_stb), + self.element.r_data.eq(self.r_data), + ] + return m + + class Rst(wiring.Component): + element: In(csr.Element.Signature(width=24, access="w")) + w_stb: Out(1) + w_data: Out(unsigned(24)) + + def elaborate(self, platform): + m = Module() + m.d.comb += [ + self.w_stb.eq(self.element.w_stb), + self.w_data.eq(self.element.w_data), + ] + return m + + def __init__(self, name="timer"): + super().__init__({ + "csr_bus": In(csr.Signature(addr_width=3, data_width=8)), + }) + + self._reg_cnt = self.Cnt() + self._reg_rst = self.Rst() + + self.csr_bus.memory_map = MemoryMap(addr_width=3, data_width=8, alignment=2, + name=name) + self.csr_bus.memory_map.add_resource(self._reg_cnt, size=3, name=("cnt",)) + self.csr_bus.memory_map.add_resource(self._reg_rst, size=3, name=("rst",)) + + self._csr_mux = csr.Multiplexer(self.csr_bus.memory_map) + + def elaborate(self, platform): + m = Module() + m.submodules.reg_cnt = self._reg_cnt + m.submodules.reg_rst = self._reg_rst + m.submodules.csr_mux = self._csr_mux + + connect(m, flipped(self.csr_bus), self._csr_mux.bus) + + count = Signal(unsigned(24)) + + m.d.comb += self._reg_cnt.r_data.eq(count) + + with m.If(self._reg_rst.w_stb): + m.d.sync += count.eq(self._reg_rst.w_data) + with m.Else(): + m.d.sync += count.eq(count + 1) + + return m + +.. doctest:: + + >>> timer = BasicTimer() + >>> for res_info in timer.csr_bus.memory_map.all_resources(): + ... print(res_info) + ResourceInfo(path=(('cnt',),), start=0x0, end=0x4, width=8) + ResourceInfo(path=(('rst',),), start=0x4, end=0x8, width=8) + + +Registers are always accessed atomically, regardless of their size. Each register is split into chunks according to the CSR bus data width, and each chunk is assigned a consecutive address on the bus. + +In this example, the sizes of ``Cnt`` and ``Rst`` are extended from 24 to 32 bits, because they were added to ``csr_bus.memory_map`` with an :ref:`alignment ` of 32 bits. + +The following diagram shows a read transaction from the ``Cnt`` register: + +.. wavedrom:: csr/bus/example_mux_read + + { + "head": { + "tick": 0 + }, + "signal": [ + {"name": "clk", "wave": "p......"}, + {"node": ".AB...C"}, + ["csr_bus", + {"name": "addr", "wave": "x====x.", + "data": [0, 1, 2, 3]}, + {"name": "r_stb", "wave": "01...0."}, + {"name": "r_data", "wave": "0.45670", "node": "..1234.", + "data": ["0x01", "0x00", "0xa5", "0x00"]} + ], + {}, + ["csr_mux", + {"name": "r_shadow", "wave": "x.3...x", "node": "..b....", + "data": ["0xa50001"]} + ], + {}, + ["reg_cnt", + {"name": "r_stb", "wave": "010...."}, + {"name": "r_data", "wave": "=3=====", "node": ".a.....", + "data": ["0xa50000", "0xa50001", "0xa50002", "0xa50003", + "0xa50004", "0xa50005", "0xa50006"]} + ] + ], + "edge": [ + "A+B t1", + "a-~>b", + "B+C t2", + "b~>1" + ], + "config": { + "hscale": 2 + } + } + +The :class:`Multiplexer` adds a delay of 1 clock cycle to CSR bus reads (represented by *t1*) between the time of assertion of ``csr_bus.r_stb`` and the time the first chunk is transmitted to ``csr_bus.r_data``. + +A read transaction targeting ``Cnt`` requires 4 bus reads to complete and has a latency of 4 clock cycles (represented by *t2*). + +When the first chunk of ``Cnt`` is read, the value of all of its chunks (at point labelled *a*) is captured by a shadow register internal to the multiplexer (at point labelled *b*). Reads from any chunk return the captured values (at points labelled *1*, *2*, *3*, *4*). + +The following diagram shows a write transaction to the ``Rst`` register, which resets the value of the ``Cnt`` register as a side-effect: + +.. wavedrom:: csr/bus/example_mux_write + + { + "head": { + "tick": 0 + }, + "signal": [ + {"name": "clk", "wave": "p......"}, + {"node": ".A...BC"}, + ["csr_bus", + {"name": "addr", "wave": "x====x.", + "data": [4, 5, 6, 7]}, + {"name": "w_stb", "wave": "01...0."}, + {"name": "w_data", "wave": "x4567x.", "node": ".1234..", + "data": ["0x44", "0x55", "0x66", "0x00"]} + ], + {}, + ["csr_mux", + {"name": "w_shadow", "wave": "x.===3x", "node": "..a..b", + "data": ["0x000044", "0x005544", "0x665544", "0x665544"]} + ], + {}, + ["reg_rst", + {"name": "w_stb", "wave": "0....10"}, + {"name": "w_data", "wave": "x....3x", "node": ".....c.", + "data": ["0x665544"]} + ], + {}, + ["reg_cnt", + {"name": "r_data", "wave": "======3", "node": "......d", + "data": ["0xa50007", "0xa50008", "0xa50009", "0xa5000a", + "0xa5000b", "0xa5000c", "0x665544"]} + ], + {} + ], + "edge": [ + "A+B t1", + "1~->a", + "B+C t2", + "b~->c", "c~->d" + ], + "config": { + "hscale": 2 + } + } + + +A write transaction targeting ``Rst`` requires 4 bus writes to complete and has a latency of 4 clock cycles (represented by *t1*). + +When a chunk of ``Rst`` is written (at point labelled *1*), the written value is captured by a shadow register internal to the multiplexer (at point labelled *a*). A write to the last chunk (at point labelled *4*) causes all captured values to be written to the register (at point labelled *c*). + +The :class:`Multiplexer` adds a delay of 1 clock cycle to CSR bus writes (represented by *t2*) between the time of assertion of ``csr_bus.w_stb`` and the time of assertion of ``reg_rst.w_stb``. + +As a side-effect of the transaction, the next value of ``Cnt`` becomes the value that was written to ``Rst`` (at point labelled *d*). .. warning:: - This manual is a work in progress and is seriously incomplete! + To safely access registers over the bus interface of a :class:`Multiplexer`, the following + rules apply: -.. py:module:: amaranth_soc.csr.bus + 1. the bus initiator must have exclusive ownership over the address range of the multiplexer until the register transaction is either completed or aborted. + 2. the bus initiator must access a register in ascending order of addresses, but it may abort the transaction after any bus cycle. + +.. _csr-bus-decoder: + +Accessing a hierarchy of registers +++++++++++++++++++++++++++++++++++ + +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. + +In the following example, a CSR decoder provides access to the registers of two peripherals: -The :mod:`amaranth_soc.csr.bus` module provides CSR bus primitives. +.. testcode:: + + timer0 = BasicTimer(name="timer0") + timer1 = BasicTimer(name="timer1") + + csr_dec = csr.Decoder(addr_width=16, data_width=8) + csr_dec.add(timer0.csr_bus, addr=0x0000) + csr_dec.add(timer1.csr_bus, addr=0x1000) + +.. doctest:: + + >>> for res_info in csr_dec.bus.memory_map.all_resources(): + ... print(res_info) + ResourceInfo(path=('timer0', ('cnt',)), start=0x0, end=0x4, width=8) + ResourceInfo(path=('timer0', ('rst',)), start=0x4, end=0x8, width=8) + ResourceInfo(path=('timer1', ('cnt',)), start=0x1000, end=0x1004, width=8) + ResourceInfo(path=('timer1', ('rst',)), start=0x1004, end=0x1008, width=8) + +Although there is no functional difference between adding a group of registers directly to a :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. + +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. + +Register interface +================== .. autoclass:: amaranth_soc.csr.bus::Element.Access() :no-members: @@ -21,32 +299,31 @@ The :mod:`amaranth_soc.csr.bus` module provides CSR bus primitives. .. autoclass:: amaranth_soc.csr.bus::Element.Signature() :no-members: - .. autoattribute:: amaranth_soc.csr.bus::Element.Signature.width - .. autoattribute:: amaranth_soc.csr.bus::Element.Signature.access .. automethod:: amaranth_soc.csr.bus::Element.Signature.create .. automethod:: amaranth_soc.csr.bus::Element.Signature.__eq__ .. autoclass:: Element() :no-members: - .. autoattribute:: width - .. autoattribute:: access +.. _csr-bus-interface: + +Bus interface +============= .. autoclass:: Signature() :no-members: - .. autoattribute:: addr_width - .. autoattribute:: data_width .. automethod:: create .. automethod:: __eq__ .. autoclass:: Interface() :no-members: - .. autoattribute:: addr_width - .. autoattribute:: data_width .. autoattribute:: memory_map +Bus primitives +============== + .. autoclass:: Multiplexer() :no-members: diff --git a/docs/memory.rst b/docs/memory.rst index 1d02968..0808c31 100644 --- a/docs/memory.rst +++ b/docs/memory.rst @@ -79,6 +79,9 @@ The resource located at a given address can be retrieved with :meth:`MemoryMap.d >>> memory_map.decode_address(0x4) is reg_data True + +.. _memory-alignment: + Alignment ========= From 286e3fc3cd014171955317f7cefa24fd7a86f6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Tue, 28 May 2024 10:44:59 +0200 Subject: [PATCH 5/5] docs/_static/custom.css: sync with amaranth upstream. --- docs/_static/custom.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 081ca01..e058a22 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -20,3 +20,17 @@ a { text-decoration: underline; } /* Many of our diagnostics are even longer */ .rst-content pre.literal-block, .rst-content div[class^="highlight"] pre, .rst-content .linenodiv pre { white-space: pre-wrap; } + +/* Work around https://github.com/readthedocs/sphinx_rtd_theme/issues/1301 */ +.py.property { display: block !important; } + +/* Avoid excessively tiny font in the sidebar */ +.wy-menu-vertical li.toctree-l2, .wy-menu-vertical li.toctree-l3, .wy-menu-vertical li.toctree-l4 { font-size: 0.97em; } +/* For some cursed reason the RTD theme was decreasing the font size twice! */ +.wy-menu-vertical a { font-size: 100%; } + +/* Work around images in docstrings being glued to the paragraph underneath */ +.rst-content section dd>img { margin-bottom: 24px; } + +/* No switchable color schemes */ +img { color-scheme: light; }