diff --git a/amaranth_soc/csr/bus.py b/amaranth_soc/csr/bus.py index 87b9da3..0ba354b 100644 --- a/amaranth_soc/csr/bus.py +++ b/amaranth_soc/csr/bus.py @@ -636,18 +636,16 @@ class Decoder(wiring.Component): Data width. See :class:`Interface`. alignment : int, power-of-2 exponent Window alignment. See :class:`..memory.MemoryMap`. - name : :class:`str` - Window name. Optional. See :class:`..memory.MemoryMap`. Attributes ---------- bus : :class:`Interface` CSR bus providing access to subordinate buses. """ - def __init__(self, *, addr_width, data_width, alignment=0, name=None): + def __init__(self, *, addr_width, data_width, alignment=0): super().__init__({"bus": In(Signature(addr_width=addr_width, data_width=data_width))}) self.bus.memory_map = MemoryMap(addr_width=addr_width, data_width=data_width, - alignment=alignment, name=name) + alignment=alignment) self._subs = dict() def align_to(self, alignment): @@ -657,10 +655,10 @@ def align_to(self, alignment): """ return self.bus.memory_map.align_to(alignment) - def add(self, sub_bus, *, addr=None): + def add(self, sub_bus, *, name=None, addr=None): """Add a window to a subordinate bus. - See :meth:`MemoryMap.add_resource` for details. + See :meth:`MemoryMap.add_window` for details. """ if isinstance(sub_bus, wiring.FlippedInterface): sub_bus_unflipped = flipped(sub_bus) @@ -673,7 +671,7 @@ def add(self, sub_bus, *, addr=None): raise ValueError(f"Subordinate bus has data width {sub_bus.data_width}, which is not " f"the same as decoder data width {self.bus.data_width}") self._subs[sub_bus.memory_map] = sub_bus - return self.bus.memory_map.add_window(sub_bus.memory_map, addr=addr) + return self.bus.memory_map.add_window(sub_bus.memory_map, name=name, addr=addr) def elaborate(self, platform): m = Module() @@ -682,7 +680,7 @@ def elaborate(self, platform): r_data_fanin = 0 with m.Switch(self.bus.addr): - for sub_map, (sub_pat, sub_ratio) in self.bus.memory_map.window_patterns(): + for sub_map, sub_name, (sub_pat, sub_ratio) in self.bus.memory_map.window_patterns(): assert sub_ratio == 1 sub_bus = self._subs[sub_map] diff --git a/amaranth_soc/csr/event.py b/amaranth_soc/csr/event.py index 3c29b0f..945f98f 100644 --- a/amaranth_soc/csr/event.py +++ b/amaranth_soc/csr/event.py @@ -42,8 +42,6 @@ class EventMonitor(wiring.Component): CSR bus data width. See :class:`..csr.Interface`. alignment : int, power-of-2 exponent CSR address alignment. See :class:`..memory.MemoryMap`. - name : str - Window name. Optional. See :class:`..memory.MemoryMap`. Attributes ---------- @@ -75,9 +73,7 @@ def __init__(self, event_map, *, trigger="level", data_width, alignment=0, name= "src": Out(self._monitor.src.signature), "bus": In(self._mux.bus.signature), }) - self.bus.memory_map = MemoryMap(addr_width=addr_width, data_width=data_width, - alignment=alignment, name=name) - self.bus.memory_map.add_window(self._mux.bus.memory_map) + self.bus.memory_map = self._mux.bus.memory_map def elaborate(self, platform): m = Module() diff --git a/amaranth_soc/csr/reg.py b/amaranth_soc/csr/reg.py index da3c81a..c7515a4 100644 --- a/amaranth_soc/csr/reg.py +++ b/amaranth_soc/csr/reg.py @@ -596,8 +596,6 @@ class Builder: Data width. granularity : :class:`int` Granularity. Optional, defaults to 8 bits. - name : :class:`str` - Name of the address range. Optional. Raises ------ @@ -609,10 +607,8 @@ class Builder: If ``granularity`` is not a positive integer. :exc:`ValueError` If ``granularity`` is not a divisor of ``data_width`` - :exc:`TypeError` - If ``name`` is not a string, or is empty. """ - def __init__(self, *, addr_width, data_width, granularity=8, name=None): + def __init__(self, *, addr_width, data_width, granularity=8): if not isinstance(addr_width, int) or addr_width <= 0: raise TypeError(f"Address width must be a positive integer, not {addr_width!r}") if not isinstance(data_width, int) or data_width <= 0: @@ -624,13 +620,9 @@ def __init__(self, *, addr_width, data_width, granularity=8, name=None): raise ValueError(f"Granularity {granularity} is not a divisor of data width " f"{data_width}") - if name is not None and not (isinstance(name, str) and name): - raise TypeError(f"Name must be a non-empty string, not {name!r}") - self._addr_width = addr_width self._data_width = data_width self._granularity = granularity - self._name = name self._registers = dict() self._scope_stack = [] @@ -648,10 +640,6 @@ def data_width(self): def granularity(self): return self._granularity - @property - def name(self): - return self._name - def freeze(self): """Freeze the builder. @@ -765,16 +753,13 @@ def Index(self, index): def as_memory_map(self): self.freeze() - memory_map = MemoryMap(addr_width=self.addr_width, data_width=self.data_width, - name=self.name) + memory_map = MemoryMap(addr_width=self.addr_width, data_width=self.data_width) for reg, reg_name, reg_offset in self._registers.values(): if reg_offset is not None: reg_addr = (reg_offset * self.granularity) // self.data_width else: reg_addr = None reg_size = (reg.element.width + self.data_width - 1) // self.data_width - # TBD: should integers be allowed inside resource names? - reg_name = tuple(str(part) for part in reg_name) memory_map.add_resource(reg, name=reg_name, addr=reg_addr, size=reg_size, alignment=ceil_log2(reg_size)) memory_map.freeze() diff --git a/amaranth_soc/csr/wishbone.py b/amaranth_soc/csr/wishbone.py index 7088a16..3961640 100644 --- a/amaranth_soc/csr/wishbone.py +++ b/amaranth_soc/csr/wishbone.py @@ -29,8 +29,8 @@ class WishboneCSRBridge(wiring.Component): CSR bus driven by the bridge. data_width : int Wishbone bus data width. Optional. If ``None``, defaults to ``csr_bus.data_width``. - name : str - Window name. Optional. See :class:`..memory.MemoryMap`. + name : :class:`..memory.MemoryMap.Name` + Window name. Optional. Attributes ---------- @@ -59,10 +59,10 @@ def __init__(self, csr_bus, *, data_width=None, name=None): super().__init__({"wb_bus": In(wb_sig)}) self.wb_bus.memory_map = MemoryMap(addr_width=csr_bus.addr_width, - data_width=csr_bus.data_width, name=name) + data_width=csr_bus.data_width) # Since granularity of the Wishbone interface matches the data width of the CSR bus, # no width conversion is performed, even if the Wishbone data width is greater. - self.wb_bus.memory_map.add_window(csr_bus.memory_map) + self.wb_bus.memory_map.add_window(csr_bus.memory_map, name=name) self._csr_bus = csr_bus diff --git a/amaranth_soc/gpio.py b/amaranth_soc/gpio.py index 8a73a08..d95c641 100644 --- a/amaranth_soc/gpio.py +++ b/amaranth_soc/gpio.py @@ -237,8 +237,6 @@ def __init__(self, pin_count): CSR bus address width. data_width : :class:`int` CSR bus data width. - name : :class:`str` - CSR bus window name. Optional. input_stages : :class:`int` Number of synchronization stages between pin inputs and the :class:`~Peripheral.Input` register. Optional. Defaults to ``2``. @@ -259,13 +257,13 @@ def __init__(self, pin_count): :exc:`TypeError` If ``input_stages`` is not a non-negative integer. """ - def __init__(self, *, pin_count, addr_width, data_width, name=None, input_stages=2): + 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}") if not isinstance(input_stages, int) or input_stages < 0: raise TypeError(f"Input stages must be a non-negative integer, not {input_stages!r}") - regs = csr.Builder(addr_width=addr_width, data_width=data_width, name=name) + regs = csr.Builder(addr_width=addr_width, data_width=data_width) self._mode = regs.add("Mode", self.Mode(pin_count)) self._input = regs.add("Input", self.Input(pin_count)) diff --git a/amaranth_soc/memory.py b/amaranth_soc/memory.py index 5d691b2..dddfac6 100644 --- a/amaranth_soc/memory.py +++ b/amaranth_soc/memory.py @@ -56,14 +56,13 @@ def __init__(self): self._assignments = dict() def is_available(self, *names, reasons=None): - for name in names: - assert name and isinstance(name, tuple) - assert all(part and isinstance(part, str) for part in name) + names = tuple(MemoryMap.Name(name) for name in names) conflicts = False for name_idx, name in enumerate(names): # Also check for conflicts between the queried names. - reserved_names = sorted(self._assignments.keys() | set(names[name_idx + 1:])) + reserved_names = sorted(self._assignments.keys() | set(names[name_idx + 1:]), + key=lambda name: tuple(str(part) for part in name)) for reserved_name in reserved_names: # A conflict happens in the following cases: @@ -90,7 +89,7 @@ def is_available(self, *names, reasons=None): def assign(self, name, obj): assert self.is_available(name) - self._assignments[name] = obj + self._assignments[MemoryMap.Name(name)] = obj def extend(self, other): assert isinstance(other, _Namespace) @@ -100,6 +99,9 @@ def extend(self, other): def names(self): yield from self._assignments.keys() + def __repr__(self): + return repr(self._assignments) + class ResourceInfo: """Resource metadata. @@ -110,7 +112,7 @@ class ResourceInfo: ---------- resource : :class:`wiring.Component` A resource located in the memory map. See :meth:`MemoryMap.add_resource` for details. - path : :class:`tuple` of (:class:`str` or (:class:`tuple` of :class:`str`)) + 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. See :meth:`MemoryMap.add_window` for details. @@ -124,12 +126,8 @@ class ResourceInfo: is located behind a window that uses sparse addressing. """ def __init__(self, resource, path, start, end, width): - flattened_path = [] - for name in path: - flattened_path += name if name and isinstance(name, tuple) else [name] - if not (path and isinstance(path, tuple) and - all(name and isinstance(name, str) for name in flattened_path)): - raise TypeError(f"Path must be a non-empty tuple of non-empty strings, not {path!r}") + if not isinstance(path, tuple) or len(path) == 0: + raise TypeError(f"Path must be a non-empty tuple, not {path!r}") if not isinstance(start, int) or start < 0: raise TypeError(f"Start address must be a non-negative integer, not {start!r}") if not isinstance(end, int) or end <= start: @@ -139,7 +137,7 @@ def __init__(self, resource, path, start, end, width): raise TypeError(f"Width must be a non-negative integer, not {width!r}") self._resource = resource - self._path = tuple(path) + self._path = tuple(MemoryMap.Name(name) for name in path) self._start = start self._end = end self._width = width @@ -166,6 +164,25 @@ def width(self): class MemoryMap: + class Name(tuple): + """Name of a resource or window located in a memory map.""" + def __new__(cls, name, /): + if isinstance(name, str): + name = (name,) + if not isinstance(name, tuple) or len(name) == 0: + raise TypeError(f"Name must be a non-empty tuple, not {name!r}") + for part in name: + if isinstance(part, str) and part: + continue + if isinstance(part, int) and part >= 0: + continue + raise TypeError(f"Name {name!r} must be composed of non-empty strings or " + f"non-negative integers, not {part!r}") + return tuple.__new__(MemoryMap.Name, name) + + def __repr__(self): + return "Name({})".format(", ".join(f"{part!r}" for part in self)) + """Memory map. A memory map is a hierarchical description of an address space, describing the structure of @@ -192,23 +209,18 @@ class MemoryMap: Range alignment. Each added resource and window will be placed at an address that is a multiple of ``2 ** alignment``, and its size will be rounded up to be a multiple of ``2 ** alignment``. - name : str - Name of the address range. Optional. """ - def __init__(self, *, addr_width, data_width, alignment=0, name=None): + def __init__(self, *, addr_width, data_width, alignment=0): if not isinstance(addr_width, int) or addr_width <= 0: raise ValueError(f"Address width must be a positive integer, not {addr_width!r}") if not isinstance(data_width, int) or data_width <= 0: raise ValueError(f"Data width must be a positive integer, not {data_width!r}") if not isinstance(alignment, int) or alignment < 0: raise ValueError(f"Alignment must be a non-negative integer, not {alignment!r}") - if name is not None and not (isinstance(name, str) and name): - raise ValueError(f"Name must be a non-empty string, not {name!r}") self._addr_width = addr_width self._data_width = data_width self._alignment = alignment - self._name = name self._ranges = _RangeMap() self._resources = dict() @@ -230,10 +242,6 @@ def data_width(self): def alignment(self): return self._alignment - @property - def name(self): - return self._name - def freeze(self): """Freeze the memory map. @@ -295,7 +303,7 @@ def _compute_addr_range(self, addr, size, step=1, *, alignment): overlap_descrs.append(f"resource {overlap!r} at {resource_range.start:#x}.." f"{resource_range.stop:#x}") if id(overlap) in self._windows: - _, window_range = self._windows[id(overlap)] + _, _, window_range = self._windows[id(overlap)] overlap_descrs.append(f"window {overlap!r} at {window_range.start:#x}.." f"{window_range.stop:#x}") raise ValueError(f"Address range {addr:#x}..{addr + size:#x} overlaps with " + @@ -313,7 +321,7 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None): --------- resource : :class:`wiring.Component` The resource to be added. - name : :class:`tuple` of (:class:`str`) + 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 @@ -356,10 +364,7 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None): raise ValueError(f"Resource {resource!r} is already added at address range " f"{addr_range.start:#x}..{addr_range.stop:#x}") - if not (name and isinstance(name, tuple) and - all(part and isinstance(part, str) for part in name)): - raise TypeError(f"Resource name must be a non-empty tuple of non-empty strings, not " - f"{name!r}") + name = MemoryMap.Name(name) reasons = [] if not self._namespace.is_available(name, reasons=reasons): @@ -399,7 +404,7 @@ def is_resource(item): _, resource_name, _ = self._resources[id(resource)] yield resource, resource_name, (resource_range.start, resource_range.stop) - def add_window(self, window, *, addr=None, sparse=None): + def add_window(self, window, *, name=None, addr=None, sparse=None): """Add a window. A window is a device on a bus that provides access to a different bus, i.e. a bus bridge. @@ -422,6 +427,9 @@ def add_window(self, window, *, addr=None, sparse=None): 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. + 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 @@ -472,7 +480,7 @@ def add_window(self, window, *, addr=None, sparse=None): raise ValueError(f"Memory map has been frozen. Cannot add window {window!r}") if id(window) in self._windows: - _, addr_range = self._windows[id(window)] + _, _, addr_range = self._windows[id(window)] raise ValueError(f"Window {window!r} is already added at address range " f"{addr_range.start:#x}..{addr_range.stop:#x}") @@ -489,7 +497,10 @@ def add_window(self, window, *, addr=None, sparse=None): f"data width {self.data_width} is not an integer multiple of " f"window data width {window.data_width}") - queries = window._namespace.names() if window.name is None else ((window.name,),) + if name is not None: + name = MemoryMap.Name(name) + + queries = window._namespace.names() if name is None else (name,) reasons = [] if not self._namespace.is_available(*queries, reasons=reasons): reasons_as_string = "".join(f"\n- {reason}" for reason in reasons) @@ -523,11 +534,11 @@ def add_window(self, window, *, addr=None, sparse=None): addr_range = self._compute_addr_range(addr, size, ratio, alignment=alignment) window.freeze() self._ranges.insert(addr_range, window) - self._windows[id(window)] = window, addr_range - if window.name is None: + self._windows[id(window)] = window, name, addr_range + if name is None: self._namespace.extend(window._namespace) else: - self._namespace.assign((window.name,), window) + self._namespace.assign(name, window) self._next_addr = addr_range.stop return addr_range.start, addr_range.stop, addr_range.step @@ -538,7 +549,7 @@ def windows(self): Yield values ------------ - A tuple ``window, (start, end, ratio)`` describing the address range assigned to + 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. @@ -547,7 +558,8 @@ def is_window(item): addr_range, assignment = item return id(assignment) in self._windows for window_range, window in filter(is_window, self._ranges.items()): - yield window, (window_range.start, window_range.stop, window_range.step) + _, window_name, _ = self._windows[id(window)] + yield window, window_name, (window_range.start, window_range.stop, window_range.step) def window_patterns(self): """Iterate local windows and patterns that match their address ranges. @@ -556,24 +568,24 @@ def window_patterns(self): Yield values ------------ - A tuple ``window, (pattern, ratio)`` describing the address range assigned to the window. - ``pattern`` is a ``self.addr_width`` wide pattern that may be used in ``Case`` or ``match`` - to determine if an address signal is within the address range of ``window``. When bridging - buses of unequal data width, ``ratio`` is the amount of contiguous addresses on + 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. """ - for window, (window_start, window_stop, window_ratio) in self.windows(): + for window, window_name, (window_start, window_stop, window_ratio) in self.windows(): const_bits = self.addr_width - window.addr_width if const_bits > 0: const_pat = f"{window_start >> window.addr_width:0{const_bits}b}" else: const_pat = "" pattern = const_pat + "-" * window.addr_width - yield window, (pattern, window_ratio) + yield window, window_name, (pattern, window_ratio) @staticmethod - def _translate(resource_info, window, window_range): + def _translate(resource_info, window, window_name, window_range): # When a resource is accessed through a dense window, its size and address on the wider # bus must be integer multiples of the number of addresses on the narrower bus that are # accessed for each transaction. @@ -583,7 +595,7 @@ def _translate(resource_info, window, window_range): # layouts that cannot be easily represented, so reject those. assert window_range.step == 1 or resource_info.width == window.data_width - path = resource_info.path if window.name is None else (window.name, *resource_info.path) + path = resource_info.path if window_name is None else (window_name, *resource_info.path) size = (resource_info.end - resource_info.start) // window_range.step start = (resource_info.start // window_range.step) + window_range.start width = resource_info.width * window_range.step @@ -606,8 +618,9 @@ def all_resources(self): yield ResourceInfo(assignment, resource_path, addr_range.start, addr_range.stop, self.data_width) elif id(assignment) in self._windows: + _, window_name, _ = self._windows[id(assignment)] for resource_info in assignment.all_resources(): - yield self._translate(resource_info, assignment, addr_range) + yield self._translate(resource_info, assignment, window_name, addr_range) else: assert False # :nocov: @@ -636,9 +649,10 @@ def find_resource(self, resource): return ResourceInfo(resource, resource_path, resource_range.start, resource_range.stop, self.data_width) - for window, window_range in self._windows.values(): + for window, window_name, window_range in self._windows.values(): try: - return self._translate(window.find_resource(resource), window, window_range) + resource_info = window.find_resource(resource) + return self._translate(resource_info, window, window_name, window_range) except KeyError: pass @@ -663,10 +677,10 @@ def decode_address(self, address): if id(assignment) in self._resources: return assignment elif id(assignment) in self._windows: - _, addr_range = self._windows[id(assignment)] + _, _, addr_range = self._windows[id(assignment)] return assignment.decode_address((address - addr_range.start) * addr_range.step) else: assert False # :nocov: def __repr__(self): - return f"MemoryMap(name={self.name!r})" + return f"MemoryMap({self._namespace!r})" diff --git a/amaranth_soc/wishbone/bus.py b/amaranth_soc/wishbone/bus.py index 393fca4..581936e 100644 --- a/amaranth_soc/wishbone/bus.py +++ b/amaranth_soc/wishbone/bus.py @@ -274,8 +274,6 @@ class Decoder(wiring.Component): Optional signal set. See :class:`Signature`. alignment : int, power-of-2 exponent Window alignment. Optional. See :class:`..memory.MemoryMap`. - name : :class:`str` - Window name. Optional. See :class:`..memory.MemoryMap`. Attributes ---------- @@ -290,7 +288,7 @@ def __init__(self, *, addr_width, data_width, granularity=None, features=frozens granularity=granularity, features=features))}) self.bus.memory_map = MemoryMap( addr_width=max(1, addr_width + exact_log2(data_width // granularity)), - data_width=granularity, alignment=alignment, name=name) + data_width=granularity, alignment=alignment) self._subs = dict() def align_to(self, alignment): @@ -300,7 +298,7 @@ def align_to(self, alignment): """ return self.bus.memory_map.align_to(alignment) - def add(self, sub_bus, *, addr=None, sparse=False): + 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 @@ -338,7 +336,8 @@ def add(self, sub_bus, *, addr=None, sparse=False): f"decoder does not have a corresponding input") self._subs[sub_bus.memory_map] = sub_bus - return self.bus.memory_map.add_window(sub_bus.memory_map, addr=addr, sparse=sparse) + return self.bus.memory_map.add_window(sub_bus.memory_map, name=name, addr=addr, + sparse=sparse) def elaborate(self, platform): m = Module() @@ -349,7 +348,7 @@ def elaborate(self, platform): stall_fanin = 0 with m.Switch(self.bus.adr): - for sub_map, (sub_pat, sub_ratio) in self.bus.memory_map.window_patterns(): + for sub_map, sub_name, (sub_pat, sub_ratio) in self.bus.memory_map.window_patterns(): sub_bus = self._subs[sub_map] m.d.comb += [ diff --git a/tests/test_csr_bus.py b/tests/test_csr_bus.py index db838b5..84206c0 100644 --- a/tests/test_csr_bus.py +++ b/tests/test_csr_bus.py @@ -206,30 +206,30 @@ class _Reg(wiring.Component): 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) with self.assertRaisesRegex(AttributeError, - r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member " - r"named 'element' and oriented as wiring\.Out"): + r"Signature of CSR register Name\('a'\) must have a csr\.Element\.Signature " + r"member named 'element' and oriented as wiring\.Out"): 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",), size=1) with self.assertRaisesRegex(AttributeError, - r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member " - r"named 'element' and oriented as wiring\.Out"): + r"Signature of CSR register Name\('a'\) must have a csr\.Element\.Signature " + r"member named 'element' and oriented as wiring\.Out"): 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) with self.assertRaisesRegex(AttributeError, - r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member " - r"named 'element' and oriented as wiring\.Out"): + r"Signature of CSR register Name\('a'\) must have a csr\.Element\.Signature " + r"member named 'element' and oriented as wiring\.Out"): 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) with self.assertRaisesRegex(AttributeError, - r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member " - r"named 'element' and oriented as wiring\.Out"): + r"Signature of CSR register Name\('a'\) must have a csr\.Element\.Signature " + r"member named 'element' and oriented as wiring\.Out"): csr.Multiplexer(map_3) def test_wrong_memory_map_windows(self): diff --git a/tests/test_csr_reg.py b/tests/test_csr_reg.py index 765db46..24d68d6 100644 --- a/tests/test_csr_reg.py +++ b/tests/test_csr_reg.py @@ -1,3 +1,5 @@ +# amaranth: UnusedElaboratable=no + import unittest import warnings from amaranth import * @@ -684,18 +686,16 @@ def __repr__(self): class BuilderTestCase(unittest.TestCase): def test_init(self): - # default name & granularity + # default granularity regs_0 = Builder(addr_width=30, data_width=32) self.assertEqual(regs_0.addr_width, 30) self.assertEqual(regs_0.data_width, 32) self.assertEqual(regs_0.granularity, 8) - self.assertEqual(regs_0.name, None) - # custom name & granularity - regs_1 = Builder(addr_width=31, data_width=32, granularity=16, name="periph") + # custom granularity + regs_1 = Builder(addr_width=31, data_width=32, granularity=16) self.assertEqual(regs_1.addr_width, 31) self.assertEqual(regs_1.data_width, 32) self.assertEqual(regs_1.granularity, 16) - self.assertEqual(regs_1.name, "periph") def test_init_wrong_addr_width(self): with self.assertRaisesRegex(TypeError, @@ -717,14 +717,6 @@ def test_init_granularity_divisor(self): r"Granularity 7 is not a divisor of data width 32"): Builder(addr_width=30, data_width=32, granularity=7) - def test_init_wrong_name(self): - with self.assertRaisesRegex(TypeError, - r"Name must be a non-empty string, not 8"): - Builder(addr_width=30, data_width=32, name=8) - with self.assertRaisesRegex(TypeError, - r"Name must be a non-empty string, not ''"): - Builder(addr_width=30, data_width=32, name="") - def test_add(self): regs = Builder(addr_width=30, data_width=32) ra = regs.add("a", _MockRegister("a")) @@ -856,7 +848,7 @@ def test_array_wrong_index(self): pass def test_memory_map(self): - regs = Builder(addr_width=30, data_width=32, name="foo") + regs = Builder(addr_width=30, data_width=32) ra = regs.add("a", _MockRegister("ra")) # offset=0x0 with regs.Cluster("b"): rc = regs.add("c", _MockRegister("rc"), offset=0xc) @@ -868,7 +860,6 @@ def test_memory_map(self): rg = regs.add("g", _MockRegister("rg", width=48)) # offset=0x18 memory_map = regs.as_memory_map() - self.assertEqual(memory_map.name, "foo") self.assertEqual(memory_map.addr_width, 30) self.assertEqual(memory_map.data_width, 32) self.assertEqual(memory_map.alignment, 0) @@ -893,11 +884,11 @@ def test_memory_map(self): self.assertEqual(results[3][2], (3, 4)) self.assertIs(results[4][0], rf) - self.assertEqual(results[4][1], ("b", "0", "f")) + self.assertEqual(results[4][1], ("b", 0, "f")) self.assertEqual(results[4][2], (4, 5)) self.assertIs(results[5][0], rg) - self.assertEqual(results[5][1], ("b", "1", "g")) + self.assertEqual(results[5][1], ("b", 1, "g")) self.assertEqual(results[5][2], (6, 8)) def test_memory_map_name_conflicts(self): @@ -907,7 +898,8 @@ def test_memory_map_name_conflicts(self): regs_0.add("a", _MockRegister("bar")) with self.assertRaisesRegex(ValueError, r"Resource _MockRegister\('bar'\) cannot be added to the local namespace:" - r"\n- \('a',\) conflicts with local name \('a',\) assigned to _MockRegister\('foo'\)"): + r"\n- Name\('a'\) conflicts with local name Name\('a'\) assigned to " + r"_MockRegister\('foo'\)"): regs_0.as_memory_map() # register/cluster regs_1 = Builder(addr_width=8, data_width=32) @@ -916,7 +908,8 @@ def test_memory_map_name_conflicts(self): regs_1.add("b", _MockRegister("bar")) with self.assertRaisesRegex(ValueError, r"Resource _MockRegister\('bar'\) cannot be added to the local namespace:" - r"\n- \('a', 'b'\) conflicts with local name \('a',\) assigned to _MockRegister\('foo'\)"): + r"\n- Name\('a', 'b'\) conflicts with local name Name\('a'\) assigned to " + r"_MockRegister\('foo'\)"): regs_1.as_memory_map() # cluster/register regs_2 = Builder(addr_width=8, data_width=32) @@ -925,7 +918,8 @@ def test_memory_map_name_conflicts(self): regs_2.add("a", _MockRegister("bar")) with self.assertRaisesRegex(ValueError, r"Resource _MockRegister\('bar'\) cannot be added to the local namespace:" - r"\n- \('a',\) conflicts with local name \('a', 'b'\) assigned to _MockRegister\('foo'\)"): + r"\n- Name\('a'\) conflicts with local name Name\('a', 'b'\) assigned to " + r"_MockRegister\('foo'\)"): regs_2.as_memory_map() diff --git a/tests/test_memory.py b/tests/test_memory.py index 3dd9c96..94ddd6a 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -88,20 +88,20 @@ def test_is_available(self): reasons_0 = [] self.assertFalse(namespace.is_available(("a", "b"), reasons=reasons_0)) self.assertEqual(reasons_0, [ - "('a', 'b') conflicts with local name ('a', 'b') assigned to 'foo'", + "Name('a', 'b') conflicts with local name Name('a', 'b') assigned to 'foo'", ]) # name is a prefix of local name reasons_1 = [] self.assertFalse(namespace.is_available(("a",), reasons=reasons_1)) self.assertEqual(reasons_1, [ - "('a',) conflicts with local name ('a', 'b') assigned to 'foo'", - "('a',) conflicts with local name ('a', 'c') assigned to 'bar'", + "Name('a') conflicts with local name Name('a', 'b') assigned to 'foo'", + "Name('a') conflicts with local name Name('a', 'c') assigned to 'bar'", ]) # local name is a prefix of name reasons_2 = [] self.assertFalse(namespace.is_available(("a", "b", "c"), reasons=reasons_2)) self.assertEqual(reasons_2, [ - "('a', 'b', 'c') conflicts with local name ('a', 'b') assigned to 'foo'", + "Name('a', 'b', 'c') conflicts with local name Name('a', 'b') assigned to 'foo'", ]) # no conflicts self.assertTrue(namespace.is_available(("b", "a"), ("a", "d"), ("aaa", "bbb"))) @@ -126,17 +126,6 @@ def test_extend_conflict(self): with self.assertRaises(AssertionError): namespace_1.extend(namespace_2) - def test_is_available_arg_type(self): - namespace = _Namespace() - with self.assertRaises(AssertionError): - namespace.is_available(()) - with self.assertRaises(AssertionError): - namespace.is_available(("a",), ("b", "")) - with self.assertRaises(AssertionError): - namespace.is_available("a") - with self.assertRaises(AssertionError): - namespace.is_available(("a", 1)) - def test_is_available_arg_conflict(self): namespace = _Namespace() with self.assertRaises(AssertionError): @@ -156,28 +145,19 @@ def test_names(self): class ResourceInfoTestCase(unittest.TestCase): def test_simple(self): - info = ResourceInfo("a", path=("foo", ("bar",)), start=0, end=1, width=8) - self.assertEqual(info.path, ("foo", ("bar",))) + info = ResourceInfo("a", path=(("foo",), ("bar",)), start=0, end=1, width=8) + self.assertEqual(info.path, (("foo",), ("bar",))) self.assertEqual(info.start, 0) self.assertEqual(info.end, 1) self.assertEqual(info.width, 8) def test_wrong_path(self): with self.assertRaisesRegex(TypeError, - r"Path must be a non-empty tuple of non-empty strings, not \(1,\)"): - ResourceInfo("a", path=(1,), start=0, end=1, width=8) + r"Path must be a non-empty tuple, not 'foo'"): + ResourceInfo("a", path="foo", start=0, end=1, width=8) with self.assertRaisesRegex(TypeError, - r"Path must be a non-empty tuple of non-empty strings, not \(\)"): + r"Path must be a non-empty tuple, not \(\)"): ResourceInfo("a", path=(), start=0, end=1, width=8) - with self.assertRaisesRegex(TypeError, - r"Path must be a non-empty tuple of non-empty strings, not \('foo', ''\)"): - ResourceInfo("a", path=("foo", ""), start=0, end=1, width=8) - with self.assertRaisesRegex(TypeError, - r"Path must be a non-empty tuple of non-empty strings, not \('foo', \(\)\)"): - ResourceInfo("a", path=("foo", ()), start=0, end=1, width=8) - with self.assertRaisesRegex(TypeError, - r"Path must be a non-empty tuple of non-empty strings, not \('foo', \('bar', ''\)\)"): - ResourceInfo("a", path=("foo", ("bar", "")), start=0, end=1, width=8) def test_wrong_start_addr(self): with self.assertRaisesRegex(TypeError, @@ -204,23 +184,37 @@ def test_wrong_width(self): ResourceInfo("a", path=("b",), start=0, end=1, width=-1) -class MemoryMapTestCase(unittest.TestCase): - def test_name(self): - memory_map_0 = MemoryMap(addr_width=1, data_width=8) - memory_map_1 = MemoryMap(addr_width=1, data_width=8, name=None) - memory_map_2 = MemoryMap(addr_width=1, data_width=8, name="foo") - self.assertEqual(memory_map_0.name, None) - self.assertEqual(memory_map_1.name, None) - self.assertEqual(memory_map_2.name, "foo") - - def test_wrong_name(self): - with self.assertRaisesRegex(ValueError, - r"Name must be a non-empty string, not 1"): - MemoryMap(addr_width=1, data_width=8, name=1) - with self.assertRaisesRegex(ValueError, - r"Name must be a non-empty string, not ''"): - MemoryMap(addr_width=1, data_width=8, name="") +class MemoryMapNameTestCase(unittest.TestCase): + def test_init(self): + name_0 = MemoryMap.Name(("foo", 0, "bar", "baz", 4, 5)) + self.assertEqual(name_0, ("foo", 0, "bar", "baz", 4, 5)) + name_1 = MemoryMap.Name("foo") + self.assertEqual(name_1, ("foo",)) + name_2 = MemoryMap.Name(name_1) + self.assertEqual(name_2, ("foo",)) + + def test_init_wrong_name(self): + with self.assertRaisesRegex(TypeError, r"Name must be a non-empty tuple, not \(\)"): + MemoryMap.Name(()) + with self.assertRaisesRegex(TypeError, r"Name must be a non-empty tuple, not 123"): + MemoryMap.Name(123) + def test_init_wrong_part(self): + with self.assertRaisesRegex(TypeError, + r"Name \('foo', 1\.0\) must be composed of non-empty strings or non-negative " + r"integers, not 1\.0"): + MemoryMap.Name(("foo", 1.0)) + with self.assertRaisesRegex(TypeError, + r"Name \('foo', ''\) must be composed of non-empty strings or non-negative " + r"integers, not ''"): + MemoryMap.Name(("foo", "")) + with self.assertRaisesRegex(TypeError, + r"Name \('foo', -1\) must be composed of non-empty strings or non-negative " + r"integers, not -1"): + MemoryMap.Name(("foo", -1)) + + +class MemoryMapTestCase(unittest.TestCase): def test_wrong_addr_width(self): with self.assertRaisesRegex(ValueError, r"Address width must be a positive integer, not -1"): @@ -287,19 +281,6 @@ def test_add_resource_wrong_type(self): r"Resource must be a wiring\.Component, not 'foo'"): memory_map.add_resource("foo", name="bar", size=1) - def test_add_resource_wrong_name(self): - memory_map = MemoryMap(addr_width=1, data_width=8) - res1 = _MockResource("res1") - with self.assertRaisesRegex(TypeError, - r"Resource name must be a non-empty tuple of non-empty strings, not 1"): - memory_map.add_resource(res1, name=1, size=0) - with self.assertRaisesRegex(TypeError, - r"Resource name must be a non-empty tuple of non-empty strings, not \(\)"): - memory_map.add_resource(res1, name=(), size=0) - with self.assertRaisesRegex(TypeError, - r"Resource name must be a non-empty tuple of non-empty strings, not \('',\)"): - memory_map.add_resource(res1, name=("",), size=0) - def test_add_resource_wrong_name_conflict(self): memory_map = MemoryMap(addr_width=1, data_width=8) res1 = _MockResource("res1") @@ -307,7 +288,8 @@ def test_add_resource_wrong_name_conflict(self): memory_map.add_resource(res1, name=("foo",), size=0) with self.assertRaisesRegex(ValueError, r"Resource _MockResource\('res2'\) cannot be added to the local namespace:" - r"\n- \('foo',\) conflicts with local name \('foo',\) assigned to _MockResource\('res1'\)"): + r"\n- Name\('foo'\) conflicts with local name Name\('foo'\) assigned to " + r"_MockResource\('res1'\)"): memory_map.add_resource(res2, name=("foo",), size=0) def test_add_resource_wrong_address(self): @@ -407,7 +389,7 @@ def test_add_window_wrong_frozen(self): memory_map = MemoryMap(addr_width=2, data_width=8) memory_map.freeze() with self.assertRaisesRegex(ValueError, - r"Memory map has been frozen. Cannot add window MemoryMap\(name=None\)"): + r"Memory map has been frozen. Cannot add window MemoryMap\({}\)"): memory_map.add_window(MemoryMap(addr_width=1, data_width=8)) def test_add_window_wrong_window(self): @@ -463,7 +445,7 @@ def test_add_window_wrong_overlap(self): memory_map = MemoryMap(addr_width=16, data_width=8) memory_map.add_window(MemoryMap(addr_width=10, data_width=8)) with self.assertRaisesRegex(ValueError, - r"Address range 0x200\.\.0x600 overlaps with window MemoryMap\(name=None\) at " + r"Address range 0x200\.\.0x600 overlaps with window MemoryMap\({}\) at " r"0x0\.\.0x400"): memory_map.add_window(MemoryMap(addr_width=10, data_width=8), addr=0x200) @@ -472,18 +454,19 @@ def test_add_window_wrong_twice(self): window = MemoryMap(addr_width=10, data_width=8) memory_map.add_window(window) with self.assertRaisesRegex(ValueError, - r"Window MemoryMap\(name=None\) is already added at address range 0x0\.\.0x400"): + r"Window MemoryMap\({}\) is already added at address range 0x0\.\.0x400"): memory_map.add_window(window) def test_add_window_wrong_name_conflict(self): memory_map = MemoryMap(addr_width=2, data_width=8) res1 = _MockResource("res1") memory_map.add_resource(res1, name=("foo",), size=0) - window = MemoryMap(addr_width=1, data_width=8, name="foo") + window = MemoryMap(addr_width=1, data_width=8) with self.assertRaisesRegex(ValueError, - r"Window MemoryMap\(name='foo'\) cannot be added to the local namespace:" - r"\n- \('foo',\) conflicts with local name \('foo',\) assigned to _MockResource\('res1'\)"): - memory_map.add_window(window) + r"Window MemoryMap\({}\) cannot be added to the local namespace:" + r"\n- Name\('foo'\) conflicts with local name Name\('foo'\) assigned to " + r"_MockResource\('res1'\)"): + memory_map.add_window(window, name=("foo",)) def test_add_window_wrong_name_conflict_subordinate(self): memory_map = MemoryMap(addr_width=2, data_width=8) @@ -493,13 +476,15 @@ def test_add_window_wrong_name_conflict_subordinate(self): res4 = _MockResource("res4") memory_map.add_resource(res1, name=("foo",), size=0) memory_map.add_resource(res2, name=("bar",), size=0) - window = MemoryMap(addr_width=1, data_width=8, name=None) + window = MemoryMap(addr_width=1, data_width=8) window.add_resource(res3, name=("foo",), size=0) window.add_resource(res4, name=("bar",), size=0) with self.assertRaisesRegex(ValueError, - r"Window MemoryMap\(name=None\) cannot be added to the local namespace:" - r"\n- \('foo',\) conflicts with local name \('foo',\) assigned to _MockResource\('res1'\)" - r"\n- \('bar',\) conflicts with local name \('bar',\) assigned to _MockResource\('res2'\)"): + r"Window MemoryMap\({.*}\) cannot be added to the local namespace:" + r"\n- Name\('foo'\) conflicts with local name Name\('foo'\) assigned to " + r"_MockResource\('res1'\)" + r"\n- Name\('bar'\) conflicts with local name Name\('bar'\) assigned to " + r"_MockResource\('res2'\)"): memory_map.add_window(window) def test_iter_windows(self): @@ -511,9 +496,9 @@ def test_iter_windows(self): memory_map.add_window(window_2) memory_map.add_window(window_3, sparse=False, addr=0x400) self.assertEqual(list(memory_map.windows()), [ - (window_1, (0x0000, 0x0200, 2)), - (window_3, (0x0400, 0x0600, 2)), - (window_2, (0x1000, 0x2000, 1)), + (window_1, None, (0x0000, 0x0200, 2)), + (window_3, None, (0x0400, 0x0600, 2)), + (window_2, None, (0x1000, 0x2000, 1)), ]) def test_iter_window_patterns(self): @@ -525,9 +510,9 @@ def test_iter_window_patterns(self): memory_map.add_window(window_2) memory_map.add_window(window_3, sparse=False, addr=0x400) self.assertEqual(list(memory_map.window_patterns()), [ - (window_1, ("000000----------", 2)), - (window_3, ("000001----------", 2)), - (window_2, ("0001------------", 1)), + (window_1, None, ("000000----------", 2)), + (window_3, None, ("000001----------", 2)), + (window_2, None, ("0001------------", 1)), ]) def test_iter_window_patterns_covered(self): @@ -535,7 +520,7 @@ def test_iter_window_patterns_covered(self): window = MemoryMap(addr_width=16, data_width=8) memory_map.add_window(window) self.assertEqual(list(memory_map.window_patterns()), [ - (window, ("----------------", 1)), + (window, None, ("----------------", 1)), ]) def test_align_to(self): @@ -570,12 +555,12 @@ def setUp(self): self.res5 = _MockResource("res5") self.win2.add_resource(self.res5, name=("name5",), size=16) self.root.add_window(self.win2, sparse=True) - self.win3 = MemoryMap(addr_width=16, data_width=8, alignment=2, name="win3") + self.win3 = MemoryMap(addr_width=16, data_width=8, alignment=2) self.res6 = _MockResource("res6") self.res7 = _MockResource("res7") self.win3.add_resource(self.res6, name=("name6",), size=16) self.win3.add_resource(self.res7, name=("name7",), size=1) - self.root.add_window(self.win3, sparse=False) + self.root.add_window(self.win3, sparse=False, name=("win3",)) def test_iter_all_resources(self): res_info = list(self.root.all_resources()) @@ -611,13 +596,13 @@ def test_iter_all_resources(self): self.assertEqual(res_info[4].width, 8) self.assertIs(res_info[5].resource, self.res6) - self.assertEqual(res_info[5].path, ("win3", ("name6",))) + self.assertEqual(res_info[5].path, (("win3",), ("name6",))) self.assertEqual(res_info[5].start, 0x00040000) self.assertEqual(res_info[5].end, 0x00040004) self.assertEqual(res_info[5].width, 32) self.assertIs(res_info[6].resource, self.res7) - self.assertEqual(res_info[6].path, ("win3", ("name7",))) + self.assertEqual(res_info[6].path, (("win3",), ("name7",))) self.assertEqual(res_info[6].start, 0x00040004) self.assertEqual(res_info[6].end, 0x00040005) self.assertEqual(res_info[6].width, 32)