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 0ba354b..d014bff 100644 --- a/amaranth_soc/csr/bus.py +++ b/amaranth_soc/csr/bus.py @@ -11,42 +11,76 @@ class Element(wiring.PureInterface): + """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 + 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 interface. Optional. See :class:`amaranth.lib.wiring.PureInterface`. + """ + class Access(enum.Enum): """Register access mode. 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): + """Readable access mode. + + Returns + ------- + :class:`bool` + ``True`` if 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 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. - 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 +113,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 +154,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): @@ -139,30 +183,30 @@ def __repr__(self): class Signature(wiring.Signature): - """CPU-side CSR signature. + """CSR bus 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 +232,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) @@ -220,36 +276,18 @@ 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. - Operation + Arguments --------- - - 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. - - Parameters - ---------- 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 +296,38 @@ 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. + + 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") return self._memory_map @@ -287,6 +349,52 @@ def __repr__(self): class Multiplexer(wiring.Component): + """CSR register multiplexer. + + An address-based multiplexer for CSR registers implementing atomic updates. + + 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. 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 + --------- + 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 +564,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") @@ -527,12 +582,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() @@ -616,30 +671,18 @@ 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 + 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`. - 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): @@ -651,14 +694,30 @@ def __init__(self, *, addr_width, data_width, alignment=0): 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, *, name=None, addr=None): """Add a window to a subordinate bus. - See :meth:`MemoryMap.add_window` for details. + See :meth:`~.memory.MemoryMap.add_window` for details. + + 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 945f98f..57081d4 100644 --- a/amaranth_soc/csr/event.py +++ b/amaranth_soc/csr/event.py @@ -28,27 +28,27 @@ 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`. 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 c7515a4..5c09878 100644 --- a/amaranth_soc/csr/reg.py +++ b/amaranth_soc/csr/reg.py @@ -17,39 +17,70 @@ 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.""" + + #: Read-only mode. R = "r" + #: Write-only mode. W = "w" + #: Read/write mode. RW = "rw" + #: Not connected. NC = "nc" def readable(self): + """Readable access mode. + + Returns + ------- + :class:`bool` + ``True`` if 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 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 +107,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 +148,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 +156,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 +181,14 @@ def __repr__(self): class Field: """Description of a CSR register field. - Parameters - ---------- - action_cls : :class:`FieldAction` subclass + Arguments + --------- + action_cls : subclass of :class:`FieldAction` 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,28 +211,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`. + Shape of the field. access : :class:`FieldPort.Access` - Field access mode. See :class:`FieldPort.Signature`. - members : iterable of (:class:`str`, :class:`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` - Field port. + Field access mode. + members : iterable of (:class:`str`, :class:`amaranth.lib.wiring.Member`) pairs, optional + Additional signature members. - Raises - ------ - :exc:`ValueError` - If the key 'port' is used in ``members``. + Members + ------- + port : :py:`In(csr.reg.FieldPort.Signature(shape, access))` + Field port. """ def __init__(self, shape, access, members=()): members = dict(members) @@ -221,23 +241,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:`Field` is instantiated as a :class:`FieldAction`; + - a :class:`dict` is instantiated as a :class:`FieldActionMap`; + - a :class:`list` is instantiated as a :class:`FieldActionArray`. """ def __init__(self, fields): self._fields = {} @@ -326,7 +337,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 +354,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:`Field` is instantiated as a :class:`FieldAction`; + - a :class:`dict` is instantiated as a :class:`FieldActionMap`; + - a :class:`list` is instantiated as a :class:`FieldActionArray`. """ def __init__(self, fields): self._fields = [] @@ -399,11 +403,11 @@ def __len__(self): return len(self._fields) def flatten(self): - """Iterate recursively over the field array. + """Recursively iterate over the field array. 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` @@ -418,47 +422,34 @@ def flatten(self): class Register(wiring.Component): - _doc_template = """ - A CSR register. - - Parameters - ---------- - 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 - a :class:`FieldActionMap`, :class:`FieldActionArray`, or :class:`FieldAction`, - depending on its type (dict, list, or Field). - {parameters} + """A CSR register. - Interface attributes - -------------------- - element : :class:`Element` - Interface between this register and a CSR bus primitive. + Arguments + --------- + fields : :class:`dict` or :class:`list` or :class:`Field`, optional + Collection of register fields. If omitted, a dict is populated from Python :term:`variable + annotations `. ``fields`` is used to create + a :class:`FieldActionMap`, :class:`FieldActionArray`, or :class:`FieldAction`, + depending on its type (:class:`dict`, :class:`list`, or :class:`Field`). + access : :class:`~.csr.bus.Element.Access` + Element access mode. - 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:`In(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` - Element access mode. - """) - def __init_subclass__(cls, *, access=None, **kwargs): if access is not None: # TODO(py3.9): Remove this. Python 3.8 and below use cls.__name__ in the error message @@ -468,7 +459,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): @@ -526,14 +516,26 @@ 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): + """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 +543,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,28 +587,22 @@ 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 collects a group of :class:`Register`\\ s within an address range with the goal + of producing a :class:`~.memory.MemoryMap` of the resulting layout. - Parameters - ---------- + Arguments + --------- addr_width : :class:`int` Address width. data_width : :class:`int` Data width. - granularity : :class:`int` - Granularity. Optional, defaults to 8 bits. + granularity : :class:`int`, optional + Granularity. Defaults to 8 bits. 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`` + If ``data_width`` is not a multiple of ``granularity``. """ def __init__(self, *, addr_width, data_width, granularity=8): if not isinstance(addr_width, int) or addr_width <= 0: @@ -630,25 +626,43 @@ def __init__(self, *, addr_width, data_width, granularity=8): @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 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 def add(self, name, reg, *, offset=None): - """Add a CSR register. + """Add a register. Arguments --------- @@ -669,14 +683,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``. """ @@ -715,11 +723,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}") @@ -737,11 +740,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}") @@ -752,6 +750,18 @@ def Index(self, index): assert self._scope_stack.pop() == index def as_memory_map(self): + """Build a memory map. + + If a register was added without an explicit ``offset``, the :ref:`implicit next address + ` of the memory map is used. Otherwise, the register address + is ``offset * granularity // data_width``. + + Registers are added to the memory map in the same order as they were added to the builder. + + Returns + ------- + :class:`~.memory.MemoryMap`. + """ self.freeze() memory_map = MemoryMap(addr_width=self.addr_width, data_width=self.data_width) for reg, reg_name, reg_offset in self._registers.values(): @@ -769,28 +779,25 @@ def as_memory_map(self): class Bridge(wiring.Component): """CSR bridge. - Parameters - ---------- - memory_map : :class:`MemoryMap` - Memory map of CSR registers. + 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 3961640..1b49b5c 100644 --- a/amaranth_soc/csr/wishbone.py +++ b/amaranth_soc/csr/wishbone.py @@ -23,9 +23,9 @@ 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``. @@ -34,7 +34,7 @@ class WishboneCSRBridge(wiring.Component): 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 d95c641..47c2541 100644 --- a/amaranth_soc/gpio.py +++ b/amaranth_soc/gpio.py @@ -18,73 +18,104 @@ 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. + 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 - Parameters - ---------- + { + "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} + } + + ---- + + Arguments + --------- pin_count : :class:`int` Number of GPIO pins. """ @@ -96,31 +127,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 ``pins`` array + of the peripheral. 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 +169,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 ``pins`` array of the + peripheral, 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 +204,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 +232,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,36 +276,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. - 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, 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}") @@ -275,7 +294,7 @@ def __init__(self, *, pin_count, addr_width, data_width, input_stages=2): 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 dddfac6..c1fcd88 100644 --- a/amaranth_soc/memory.py +++ b/amaranth_soc/memory.py @@ -106,24 +106,25 @@ def __repr__(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:`MemoryMap.Name` 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): if not isinstance(path, tuple) or len(path) == 0: @@ -144,24 +145,58 @@ 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:`MemoryMap.Name` + """ 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 + def __repr__(self): + return f"ResourceInfo(path={self.path}, start={self.start:#x}, end={self.end:#x}, " \ + f"width={self.width})" + class MemoryMap: class Name(tuple): @@ -185,27 +220,20 @@ def __repr__(self): """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. - - Address assignment - ------------------ + A :class:`MemoryMap` is a hierarchical description of an address space, describing the + structure of address decoders of peripherals as well as bus bridges. - 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. + 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. - Parameters - ---------- - addr_width : int + 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``. @@ -232,21 +260,39 @@ def __init__(self, *, addr_width, data_width, alignment=0): @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 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 @@ -257,17 +303,18 @@ def _align_up(value, alignment): return value def align_to(self, alignment): - """Align the implicit next address. + """Align the :ref:`implicit next address `. 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}") @@ -319,39 +366,38 @@ 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:`MemoryMap.Name` 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. - 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. - :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 ``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 memory map. + 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}") @@ -392,10 +438,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:`MemoryMap.Name`, \ + :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 @@ -412,41 +460,33 @@ def add_window(self, window, *, name=None, addr=None, sparse=None): addresses; the memory map reflects this address translation when resources are looked up through the window. - Sparse addressing - ----------------- - - 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 memory map describing the layout of the window. It is frozen as a side-effect of - being added to this memory map. + A :class:`MemoryMap` describing the layout of the window. It is frozen as a side-effect + of being added to this memory map. name : :class:`MemoryMap.Name` - Name of the window. Optional. It must not conflict with the name of other resources - or windows present in this memory map. - addr : 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 + Name of the window. Optional. It must not conflict with the name of other resources or + windows present in this memory map. + addr : :class:`int` + 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. - 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` @@ -547,12 +587,14 @@ def windows(self): Non-recursively iterate windows in ascending order of their address. - Yield values - ------------ - A tuple ``window, name, (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:`MemoryMap.Name`, :class:`tuple` of \ + (:class:`int`, :class:`int`, :class:`int`)) + A tuple ``window, name, (start, end, ratio)`` describing the address range assigned to + the window. When bridging busses 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 @@ -566,14 +608,16 @@ def window_patterns(self): Non-recursively iterate windows in ascending order of their address. - Yield values - ------------ - A tuple ``window, name, (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:`MemoryMap.Name`, :class:`tuple` of \ + (:class:`str`, :class:`int`)) + A tuple ``window, name, (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 a value is within the address range of ``window``. When + bridging busses 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_name, (window_start, window_stop, window_ratio) in self.windows(): const_bits = self.addr_width - window.addr_width @@ -607,9 +651,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: @@ -632,16 +677,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)] @@ -663,12 +710,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 581936e..2f8b14b 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,22 +317,22 @@ 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`. - 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(), @@ -294,21 +349,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, *, name=None, 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) @@ -395,20 +484,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()): @@ -419,9 +508,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..e058a22 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; } } @@ -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; } 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..1173efe --- /dev/null +++ b/docs/csr.rst @@ -0,0 +1,9 @@ +CSR +=== + +.. toctree:: + :maxdepth: 2 + + csr/bus + csr/reg + csr/action diff --git a/docs/csr/_images/csr-bus.png b/docs/csr/_images/csr-bus.png new file mode 100644 index 0000000..e4d2762 Binary files /dev/null and b/docs/csr/_images/csr-bus.png differ diff --git a/docs/csr/action.rst b/docs/csr/action.rst new file mode 100644 index 0000000..7107653 --- /dev/null +++ b/docs/csr/action.rst @@ -0,0 +1,77 @@ +CSR fields +---------- + +.. py:module:: amaranth_soc.csr.action + +The :mod:`amaranth_soc.csr.action` module provides built-in :class:`~amaranth_soc.csr.reg.FieldAction` implementations intended for common use cases, which are split in three categories: :ref:`basic fields ` for numerical values, :ref:`flag fields ` for arrays of bits, and :ref:`reserved fields ` to serve as placeholders for compatibility. + +.. _csr-action-basic: + +Basic fields +============ + +Such fields are either exclusively writable by a CSR bus initiator (e.g. :class:`W`, :class:`RW`) or the peripheral itself (e.g. :class:`R`). This effectively removes the possibility of a write conflict between a CSR bus initiator and the peripheral. + +.. autoclass:: R() +.. autoclass:: W() +.. autoclass:: RW() + +.. _csr-action-flag: + +Flag fields +=========== + +Flag fields may be concurrently written by a CSR bus initiator and the peripheral. Each bit of a flag field may be set or cleared independently of others. + +Suggested use cases ++++++++++++++++++++ + +- :class:`RW1C` flags may be used when a peripheral needs to notify the CPU of a given condition, such as an error or a pending interrupt. To acknowledge the notification, the CPU would then write 1 to the flag bit. + +- :class:`RW1S` flags may be used for self-clearing bits, such as the enable bit of a one-shot timer. When the timer reaches its maximum value, it would automatically disable itself by clearing its enable bit. + +- A pair of :class:`RW1C` and :class:`RW1S` flags may be used to target the same range of bits (e.g. that drives an array of GPIO pins). This allows a CSR bus initiator to set and clear bits in one write transaction (which is guaranteed to be atomic). If a single :class:`RW` field was used instead, a read-modify-write transaction would be needed, and would require locking to insure its atomicity in a multi-tasked environment. + +.. autoclass:: RW1C() +.. autoclass:: RW1S() + +.. _csr-action-reserved: + +Reserved fields +=============== + +Reserved fields may be defined to provide placeholders for past, future or undocumented functions of a peripheral. + +Suggested use cases ++++++++++++++++++++ + +Reserved for future use (as value) +.................................. + +A :class:`ResRAWL` field can be used as a placeholder to ensure forward compatibility of software binaries with future SoC revisions, where it may be replaced with a :ref:`basic field `. + +The value returned by reads (and written back) must have defined semantics (e.g. a no-op) that can be relied upon in future SoC revisions. When writing to this field, software drivers targeting the current SoC revision must set up an atomic read-modify-write transaction. + +Reserved for future use (as flag) +................................. + +If a field is expected to be implemented as a :ref:`flag ` in a future SoC revision, it can be defined as a :class:`ResRAW0` field in the current revision to ensure forward compatibility of software binaries. + +Software drivers targeting the current SoC revision should ignore the value returned by reads. Writing a value of 0 must be a no-op if the field is implemented in a future SoC revision. + +Defined but deprecated +...................... + +If a field was deprecated in a previous SoC revision, it can be replaced with a :class:`ResR0WA` field to ensure backward compatibility of software binaries. + +The value of 0 returned by reads (and written back) must retain the semantics defined in the SoC revision where this field was deprecated. + +Defined but unimplemented +......................... + +If a field is only implemented in some variants of a peripheral, it can be replaced by a :class:`ResR0W0` field in the others. + +.. 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..6013186 --- /dev/null +++ b/docs/csr/bus.rst @@ -0,0 +1,343 @@ +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 +============ + +Overview +++++++++ + +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:`multiplexers ` that provide access to the registers of a peripheral, and :ref:`bus decoders ` that provide access to subordinate bus interfaces. + +This diagram shows a CSR bus decoder being used to provide access to the registers of two peripherals: + +.. image:: _images/csr-bus.png + +Examples +======== + +.. _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): + 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) + 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=(Name('cnt'),), start=0x0, end=0x4, width=8) + ResourceInfo(path=(Name('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:: + + To safely access registers over the bus interface of a :class:`Multiplexer`, the following + rules apply: + + 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: + +.. testcode:: + + timer0 = BasicTimer() + timer1 = BasicTimer() + + csr_dec = csr.Decoder(addr_width=16, data_width=8) + csr_dec.add(timer0.csr_bus, addr=0x0000, name="timer0") + csr_dec.add(timer1.csr_bus, addr=0x1000, name="timer1") + +.. doctest:: + + >>> for res_info in csr_dec.bus.memory_map.all_resources(): + ... print(res_info) + ResourceInfo(path=(Name('timer0'), Name('cnt')), start=0x0, end=0x4, width=8) + ResourceInfo(path=(Name('timer0'), Name('rst')), start=0x4, end=0x8, width=8) + ResourceInfo(path=(Name('timer1'), Name('cnt')), start=0x1000, end=0x1004, width=8) + ResourceInfo(path=(Name('timer1'), Name('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 interfaces +=================== + +.. 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: + + .. automethod:: amaranth_soc.csr.bus::Element.Signature.create + .. automethod:: amaranth_soc.csr.bus::Element.Signature.__eq__ + +.. autoclass:: Element() + :no-members: + +.. _csr-bus-interface: + +Bus interfaces +============== + +.. autoclass:: Signature() + :no-members: + + .. automethod:: create + .. automethod:: __eq__ + +.. autoclass:: Interface() + :no-members: + + .. autoattribute:: memory_map + +Bus primitives +============== + +.. 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..b2af8d4 --- /dev/null +++ b/docs/csr/reg.rst @@ -0,0 +1,235 @@ +CSR registers +------------- + +.. py:module:: amaranth_soc.csr.reg + +The :mod:`amaranth_soc.csr.reg` module provides a way to define and create CSR registers and register fields. + +.. 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 * + +Introduction +============ + +Control and Status registers are commonly used as an interface between SoC peripherals and the firmware that operates them. + +This module provides the following functionality: + +1. Register field description and implementation via the :class:`Field` and :class:`FieldAction` classes. The :mod:`amaranth_soc.csr.action` module provides a built-in :class:`FieldAction` subclasses for common use cases. If needed, users can implement their own subclasses. +2. Composable layouts of register fields via :class:`FieldActionMap` and :class:`FieldActionArray`. These classes are not meant to be instantiated directly, but are useful when introspecting the layout of a register. +3. Register definitions via the :class:`Register` class. The fields of a register can be provided as :term:`variable annotations ` or as instance parameters. +4. A :class:`Builder` class to organize registers of a peripheral into a hierarchy of clusters and arrays, to be converted into a :class:`~amaranth_soc.memory.MemoryMap`. +5. A bridge between a CSR bus interface and the registers of a peripheral, via the :class:`Bridge` class. + +Examples +======== + +Defining a register declaratively ++++++++++++++++++++++++++++++++++ + +If its layout and access mode are known in advance, a register can be concisely defined using :term:`variable annotations `: + +.. testcode:: + + class Status(csr.Register, access="rw"): + rdy: csr.Field(csr.action.R, 1) + err: csr.Field(csr.action.RW1C, 1) + _unimp: csr.Field(csr.action.ResR0W0, 6) + +.. note:: + + By convention, names of :ref:`reserved fields ` (such as ``_unimp`` in the above example) should begin with an underscore. + +Defining a register with instance parameters +++++++++++++++++++++++++++++++++++++++++++++ + +If the layout or access mode of a register aren't known until instantiation, a :class:`Register` subclass can override them in ``__init__``: + +.. testcode:: + + class Data(csr.Register): + def __init__(self, width=8, access="w"): + super().__init__(fields={"data": csr.Field(csr.action.W, width)}, + access=access) + +Defining a single-field register +++++++++++++++++++++++++++++++++ + +In the previous example, the ``Data`` register has a single field named ``"Data.data"``, which is redundant. + +If no other fields are expected to be added in future revisions of the peripheral (or forward compatibility is not a concern), the field name can be omitted like so: + +.. testcode:: + + class Data(csr.Register, access="w"): + def __init__(self): + super().__init__(csr.Field(csr.action.W, 8)) + + +Defining a register with nested fields +++++++++++++++++++++++++++++++++++++++ + +Hierarchical layouts of register fields can be expressed using lists and dicts: + +.. testcode:: + + class SetClr(csr.Register, access="r"): + pin: [{"set": csr.Field(csr.action.W, 1), + "clr": csr.Field(csr.action.W, 1)} for _ in range(8)] + + +Connecting registers to a CSR bus ++++++++++++++++++++++++++++++++++ + +In this example, the registers of ``FooPeripheral`` are added to a :class:`Builder` to produce a memory map, and then bridged to a bus interface: + +.. testcode:: + + class FooPeripheral(wiring.Component): + class Ctrl(csr.Register, access="rw"): + enable: csr.Field(csr.action.RW, 1) + _unimp: csr.Field(csr.action.ResR0W0, 7) + + class Data(csr.Register, access="r"): + def __init__(self, width): + super().__init__(csr.Field(csr.action.R, width)) + + def __init__(self): + regs = csr.Builder(addr_width=4, data_width=8) + + reg_ctrl = regs.add("Ctrl", Ctrl()) + reg_data = regs.add("Data", Data(width=32), offset=4) + + self._bridge = csr.Bridge(regs.as_memory_map()) + + super().__init__({"csr_bus": In(csr.Signature(addr_width=4, data_width=8))}) + self.csr_bus.memory_map = self._bridge.bus.memory_map + + def elaborate(self, platform): + return Module() # ... + + +Defining a custom field action +++++++++++++++++++++++++++++++ + +If :mod:`amaranth_soc.csr.action` built-ins do not cover a desired use case, a custom :class:`FieldAction` may provide an alternative. + +This example shows a "read/write-0-to-set" field action: + +.. testcode:: + + class RW0S(csr.FieldAction): + def __init__(self, shape, init=0): + super().__init__(shape, access="rw", members={ + "data": Out(shape), + "clear": In(shape), + }) + self._storage = Signal(shape, init=init) + self._init = init + + @property + def init(self): + return self._init + + def elaborate(self, platform): + m = Module() + + for i, storage_bit in enumerate(self._storage): + with m.If(self.clear[i]): + m.d.sync += storage_bit.eq(0) + with m.If(self.port.w_stb & ~self.port.w_data[i]): + m.d.sync += storage_bit.eq(1) + + m.d.comb += [ + self.port.r_data.eq(self._storage), + self.data.eq(self._storage), + ] + + return m + + +``RW0S`` can then be passed to :class:`Field`: + +.. testcode:: + + class Foo(csr.Register, access="rw"): + mask: csr.Field(RW0S, 8) + data: csr.Field(csr.action.RW, 8) + + +Fields +====== + +.. 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: + + .. automethod:: amaranth_soc.csr.reg::FieldPort.Signature.create + .. automethod:: amaranth_soc.csr.reg::FieldPort.Signature.__eq__ + +.. autoclass:: FieldPort() + :no-members: + +.. autoclass:: Field() + :no-members: + + .. automethod:: create + +Field actions +============= + +.. 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 + +Registers +========= + +.. autoclass:: Register() + :no-members: + + .. autoattribute:: field + .. autoattribute:: f + .. automethod:: __iter__ + +.. autoclass:: Builder() + :no-members: + + .. 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..9fb6e6a --- /dev/null +++ b/docs/gpio.rst @@ -0,0 +1,67 @@ +GPIO +==== + +.. py:module:: amaranth_soc.gpio + +The :mod:`amaranth_soc.gpio` module provides a basic GPIO peripheral. + +.. testsetup:: + + from amaranth import * + from amaranth.lib import io, wiring + from amaranth.lib.wiring import In, Out, flipped, connect + + from amaranth_soc import csr, gpio + + +Introduction +------------ + +`GPIO `_ peripherals are commonly used +to interface a SoC (usually a microcontroller) with a variety of external circuitry. This module contains a GPIO peripheral which can be connected to a :ref:`CSR bus`. + +Example ++++++++ + +This example shows a GPIO peripheral being used to drive four LEDs: + +.. testcode:: + + class MySoC(wiring.Component): + def elaborate(self, platform): + m = Module() + + m.submodules.led_gpio = led_gpio = gpio.Peripheral(pin_count=4, addr_width=8, + data_width=8) + + for n in range(4): + led = io.Buffer("o", platform.request("led", n, dir="-")) + connect(m, led_gpio.pins[n], led) + + m.submodules.csr_decoder = csr_decoder = csr.Decoder(addr_width=31, data_width=8) + csr_decoder.add(led_gpio.bus, addr=0x1000, name="led_gpio") + + # ... + + return m + +Pin modes +--------- + +.. autoclass:: PinMode() + +Pin interface +------------- + +.. autoclass:: PinSignature() + +Peripheral +---------- + +.. autoclass:: amaranth_soc.gpio::Peripheral.Mode() +.. autoclass:: amaranth_soc.gpio::Peripheral.Input() +.. autoclass:: amaranth_soc.gpio::Peripheral.Output() +.. autoclass:: amaranth_soc.gpio::Peripheral.SetClr() + +.. autoclass:: Peripheral() + :no-members: 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..54810c3 --- /dev/null +++ b/docs/memory.rst @@ -0,0 +1,263 @@ +Memory maps +########### + +.. 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=Name('ctrl'), start=0x0, end=0x4, resource=<...> + name=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=(Name('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 + + +.. _memory-alignment: + +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) + tx_window = MemoryMap(addr_width=12, data_width=32) + +.. 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, name=("rx",)) + (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, name=("tx",)) + (8192, 12288, 1) + +.. _memory-accessing-windows: + +Accessing windows +----------------- + +Memory map windows can be iterated with :meth:`MemoryMap.windows`: + +.. doctest:: + + >>> for window, name, (start, end, ratio) in memory_map.windows(): + ... print(f"{name}, start={start:#x}, end={end:#x}, ratio={ratio}") + Name('rx'), start=0x1000, end=0x2000, ratio=1 + 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, name, (pattern, ratio) in memory_map.window_patterns(): + ... print(f"{name}, pattern='{pattern}', ratio={ratio}") + Name('rx'), pattern='01------------', ratio=1 + 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=(Name('ctrl'),), start=0x0, end=0x1, width=32) + ResourceInfo(path=(Name('rx'), Name('data')), start=0x1000, end=0x1001, width=32) + ResourceInfo(path=(Name('tx'), Name('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: + + .. 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() + :no-members: 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 761e9ac..ecae3bb 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", ] diff --git a/tests/test_csr_bus.py b/tests/test_csr_bus.py index 84206c0..abdf882 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 Name\('a'\) must have a csr\.Element\.Signature " - r"member named 'element' and oriented as wiring\.Out"): + r"member 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 Name\('a'\) must have a csr\.Element\.Signature " - r"member named 'element' and oriented as wiring\.Out"): + r"member 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 Name\('a'\) must have a csr\.Element\.Signature " - r"member named 'element' and oriented as wiring\.Out"): + r"member 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 Name\('a'\) must have a csr\.Element\.Signature " - r"member named 'element' and oriented as wiring\.Out"): + r"member 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 a437c07..6109120 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)