Skip to content

Commit 30ad1e0

Browse files
committed
memory: split the names of MemoryMap resources into multiple parts.
1 parent e7f6554 commit 30ad1e0

File tree

8 files changed

+323
-142
lines changed

8 files changed

+323
-142
lines changed

amaranth_soc/csr/bus.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ def _check_memory_map(self, memory_map):
569569
reg.signature.members["element"].flow == Out and
570570
reg.signature.members["element"].is_signature and
571571
isinstance(reg.signature.members["element"].signature, Element.Signature)):
572-
raise AttributeError(f"Signature of CSR register '{reg_name}' must have a "
572+
raise AttributeError(f"Signature of CSR register {reg_name} must have a "
573573
f"csr.Element.Signature member named 'element' and oriented "
574574
f"as wiring.Out")
575575

amaranth_soc/csr/event.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ def __init__(self, event_map, *, trigger="level", data_width, alignment=0, name=
6666
reg_size = (event_map.size + data_width - 1) // data_width
6767
addr_width = 1 + max(ceil_log2(reg_size), alignment)
6868
memory_map = MemoryMap(addr_width=addr_width, data_width=data_width, alignment=alignment)
69-
memory_map.add_resource(self._enable, size=reg_size, name="enable")
70-
memory_map.add_resource(self._pending, size=reg_size, name="pending")
69+
memory_map.add_resource(self._enable, size=reg_size, name=("enable",))
70+
memory_map.add_resource(self._pending, size=reg_size, name=("pending",))
7171

7272
self._mux = Multiplexer(memory_map)
7373

amaranth_soc/csr/reg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,7 @@ def traverse_path(path, obj):
884884

885885
reg_size = (register.element.width + data_width - 1) // data_width
886886
reg_name = "__".join(path)
887-
memory_map.add_resource(register, name=reg_name, size=reg_size, addr=reg_addr,
887+
memory_map.add_resource(register, name=(reg_name,), size=reg_size, addr=reg_addr,
888888
alignment=reg_alignment)
889889

890890
self._map = register_map

amaranth_soc/memory.py

Lines changed: 108 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import bisect
22

33
from amaranth.lib import wiring
4-
from amaranth.utils import bits_for
54

65

76
__all__ = ["ResourceInfo", "MemoryMap"]
@@ -48,6 +47,60 @@ def items(self):
4847
yield key, self._values[key]
4948

5049

50+
class _Namespace:
51+
"""Namespace.
52+
53+
A set of path-like names that identify locations in a hierarchical structure.
54+
"""
55+
def __init__(self):
56+
self._assignments = dict()
57+
58+
def is_available(self, *names, reasons=None):
59+
for name in names:
60+
assert name and isinstance(name, tuple)
61+
assert all(part and isinstance(part, str) for part in name)
62+
63+
conflicts = False
64+
for name_idx, name in enumerate(names):
65+
# Also check for conflicts between the queried names.
66+
reserved_names = sorted(self._assignments.keys() | set(names[name_idx + 1:]))
67+
68+
for reserved_name in reserved_names:
69+
# A conflict happens in the following cases:
70+
# - `name` is equal to `reserved_name`
71+
# - `name` is a prefix of `reserved_name`
72+
# - `reserved_name` is a prefix of `name`.
73+
for part_idx, part in enumerate(name):
74+
if part != reserved_name[part_idx]:
75+
# Available. `name` and `reserved_name` are not equal and are not prefixes
76+
# of each other.
77+
break
78+
if part_idx == min(len(name), len(reserved_name)) - 1:
79+
# `name` and `reserved_name` are equal so far, but we have reached the end
80+
# of at least one of them. In either case, they are in conflict.
81+
conflicts = True
82+
# Conflicts between the queried names should never happen.
83+
assert reserved_name in self._assignments
84+
if reasons is not None:
85+
reasons.append(f"{name} conflicts with local name {reserved_name} "
86+
f"assigned to {self._assignments[reserved_name]!r}")
87+
break
88+
89+
return not conflicts
90+
91+
def assign(self, name, obj):
92+
assert self.is_available(name)
93+
self._assignments[name] = obj
94+
95+
def extend(self, other):
96+
assert isinstance(other, _Namespace)
97+
assert self.is_available(*other.names())
98+
self._assignments.update(other._assignments)
99+
100+
def names(self):
101+
yield from self._assignments.keys()
102+
103+
51104
class ResourceInfo:
52105
"""Resource metadata.
53106
@@ -57,7 +110,7 @@ class ResourceInfo:
57110
----------
58111
resource : :class:`wiring.Component`
59112
A resource located in the memory map. See :meth:`MemoryMap.add_resource` for details.
60-
path : iter(str)
113+
path : :class:`tuple` of (:class:`str` or (:class:`tuple` of :class:`str`))
61114
Path of the resource. It is composed of the names of each window sitting between
62115
the resource and the memory map from which this :class:`ResourceInfo` was obtained.
63116
See :meth:`MemoryMap.add_window` for details.
@@ -71,9 +124,12 @@ class ResourceInfo:
71124
is located behind a window that uses sparse addressing.
72125
"""
73126
def __init__(self, resource, path, start, end, width):
74-
if not path or not all(isinstance(part, str) and part for part in path):
75-
raise TypeError("Path must be a non-empty sequence of non-empty strings, not {!r}"
76-
.format(path))
127+
flattened_path = []
128+
for name in path:
129+
flattened_path += name if name and isinstance(name, tuple) else [name]
130+
if not (path and isinstance(path, tuple) and
131+
all(name and isinstance(name, str) for name in flattened_path)):
132+
raise TypeError(f"Path must be a non-empty tuple of non-empty strings, not {path!r}")
77133
if not isinstance(start, int) or start < 0:
78134
raise TypeError("Start address must be a non-negative integer, not {!r}"
79135
.format(start))
@@ -162,7 +218,7 @@ def __init__(self, *, addr_width, data_width, alignment=0, name=None):
162218
self._ranges = _RangeMap()
163219
self._resources = dict()
164220
self._windows = dict()
165-
self._namespace = dict()
221+
self._namespace = _Namespace()
166222

167223
self._next_addr = 0
168224
self._frozen = False
@@ -266,8 +322,8 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None):
266322
---------
267323
resource : :class:`wiring.Component`
268324
The resource to be added.
269-
name : str
270-
Name of the resource. It must not collide with the name of other resources or windows
325+
name : :class:`tuple` of (:class:`str`)
326+
Name of the resource. It must not conflict with the name of other resources or windows
271327
present in this memory map.
272328
addr : int
273329
Address of the resource. Optional. If ``None``, the implicit next address will be used.
@@ -295,7 +351,8 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None):
295351
:exc:`ValueError`
296352
If the resource has already been added to this memory map.
297353
:exc:`ValueError`
298-
If the name of the resource is already present in the namespace of this memory map.
354+
If the resource name conflicts with the name of other resources or windows present in
355+
this memory map.
299356
"""
300357
if self._frozen:
301358
raise ValueError("Memory map has been frozen. Cannot add resource {!r}"
@@ -309,10 +366,17 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None):
309366
raise ValueError("Resource {!r} is already added at address range {:#x}..{:#x}"
310367
.format(resource, addr_range.start, addr_range.stop))
311368

312-
if not isinstance(name, str) or not name:
313-
raise TypeError("Name must be a non-empty string, not {!r}".format(name))
314-
if name in self._namespace:
315-
raise ValueError("Name {} is already used by {!r}".format(name, self._namespace[name]))
369+
if not (name and isinstance(name, tuple) and
370+
all(part and isinstance(part, str) for part in name)):
371+
raise TypeError(f"Resource name must be a non-empty tuple of non-empty strings, not "
372+
f"{name!r}")
373+
374+
reasons = []
375+
if not self._namespace.is_available(name, reasons=reasons):
376+
reasons_as_string = "".join(f"\n- {reason}" for reason in reasons)
377+
raise ValueError(f"Resource {resource!r} cannot be added to the local namespace:" +
378+
reasons_as_string)
379+
del reasons
316380

317381
if alignment is not None:
318382
if not isinstance(alignment, int) or alignment < 0:
@@ -325,7 +389,7 @@ def add_resource(self, resource, *, name, size, addr=None, alignment=None):
325389
addr_range = self._compute_addr_range(addr, size, alignment=alignment)
326390
self._ranges.insert(addr_range, resource)
327391
self._resources[id(resource)] = resource, name, addr_range
328-
self._namespace[name] = resource
392+
self._namespace.assign(name, resource)
329393
self._next_addr = addr_range.stop
330394
return addr_range.start, addr_range.stop
331395

@@ -382,17 +446,24 @@ def add_window(self, window, *, addr=None, sparse=None):
382446
383447
Exceptions
384448
----------
385-
Raises :exn:`ValueError` if one of the following occurs:
386-
387-
- this memory map is frozen;
388-
- the requested address and size, after alignment, would overlap with any resources or
389-
windows that have already been added, or would be out of bounds;
390-
- the added memory map has a wider datapath than this memory map;
391-
- dense address translation is used and the datapath width of this memory map is not an
392-
integer multiple of the datapath of the added memory map;
393-
- the name of the added memory map is already present in the namespace of this memory map;
394-
- the added memory map has no name, and the name of one of its subordinate resources or
395-
windows is already present in the namespace of this memory map;
449+
:exc:`ValueError`
450+
If the memory map is frozen.
451+
:exc:`TypeError`
452+
If the added memory map is not a :class:`MemoryMap`.
453+
:exc:`ValueError`
454+
If the requested address and size, after alignment, would overlap with any resources or
455+
windows that have already been added, or would be out of bounds.
456+
:exc:`ValueError`
457+
If the added memory map has a wider datapath than this memory map.
458+
:exc:`ValueError`
459+
If dense address translation is used and the datapath width of this memory map is not
460+
an integer multiple of the datapath of the added memory map.
461+
:exc:`ValueError`
462+
If the name of the added memory map conflicts with the name of other resources or
463+
windows present in this memory map.
464+
:exc:`ValueError`
465+
If the added memory map has no name, and the name of one of its resources or windows
466+
conflicts with the name of others present in this memory map.
396467
"""
397468
if not isinstance(window, MemoryMap):
398469
raise TypeError("Window must be a MemoryMap, not {!r}"
@@ -423,17 +494,13 @@ def add_window(self, window, *, addr=None, sparse=None):
423494
"data width {}"
424495
.format(self.data_width, window.data_width))
425496

426-
if window.name is None:
427-
name_conflicts = sorted(self._namespace.keys() & window._namespace.keys())
428-
if name_conflicts:
429-
name_conflict_descrs = ["{} is used by {!r}".format(name, self._namespace[name])
430-
for name in name_conflicts]
431-
raise ValueError("The following names are already used: {}"
432-
.format("; ".join(name_conflict_descrs)))
433-
else:
434-
if window.name in self._namespace:
435-
raise ValueError("Name {} is already used by {!r}"
436-
.format(window.name, self._namespace[window.name]))
497+
queries = window._namespace.names() if window.name is None else ((window.name,),)
498+
reasons = []
499+
if not self._namespace.is_available(*queries, reasons=reasons):
500+
reasons_as_string = "".join(f"\n- {reason}" for reason in reasons)
501+
raise ValueError(f"Window {window!r} cannot be added to the local namespace:" +
502+
reasons_as_string)
503+
del queries, reasons
437504

438505
if not sparse:
439506
ratio = self.data_width // window.data_width
@@ -452,9 +519,9 @@ def add_window(self, window, *, addr=None, sparse=None):
452519
self._ranges.insert(addr_range, window)
453520
self._windows[id(window)] = window, addr_range
454521
if window.name is None:
455-
self._namespace.update(window._namespace)
522+
self._namespace.extend(window._namespace)
456523
else:
457-
self._namespace[window.name] = window
524+
self._namespace.assign((window.name,), window)
458525
self._next_addr = addr_range.stop
459526
return addr_range.start, addr_range.stop, addr_range.step
460527

@@ -587,3 +654,6 @@ def decode_address(self, address):
587654
return assignment.decode_address((address - addr_range.start) // addr_range.step)
588655
else:
589656
assert False # :nocov:
657+
658+
def __repr__(self):
659+
return f"MemoryMap(name={self.name!r})"

tests/test_csr_bus.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ def test_memory_map(self):
187187
reg_8_rw = _MockRegister(8, "rw")
188188

189189
memory_map = MemoryMap(addr_width=2, data_width=4)
190-
memory_map.add_resource(reg_4_rw, name="reg_4_rw", size=1)
191-
memory_map.add_resource(reg_8_rw, name="reg_8_rw", size=2)
190+
memory_map.add_resource(reg_4_rw, name=("reg_4_rw",), size=1)
191+
memory_map.add_resource(reg_8_rw, name=("reg_8_rw",), size=2)
192192
memory_map.freeze()
193193

194194
dut = csr.Multiplexer(memory_map)
@@ -207,30 +207,31 @@ class _Reg(wiring.Component):
207207
pass
208208
# wrong name
209209
map_0 = MemoryMap(addr_width=1, data_width=8)
210-
map_0.add_resource(_Reg({"foo": Out(csr.Element.Signature(8, "rw"))}), name="a", size=1)
210+
map_0.add_resource(_Reg({"foo": Out(csr.Element.Signature(8, "rw"))}), name=("a",), size=1)
211211
with self.assertRaisesRegex(AttributeError,
212-
r"Signature of CSR register 'a' must have a csr\.Element\.Signature member "
212+
r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member "
213213
r"named 'element' and oriented as wiring\.Out"):
214214
csr.Multiplexer(map_0)
215215
# wrong direction
216216
map_1 = MemoryMap(addr_width=1, data_width=8)
217-
map_1.add_resource(_Reg({"element": In(csr.Element.Signature(8, "rw"))}), name="a", size=1)
217+
map_1.add_resource(_Reg({"element": In(csr.Element.Signature(8, "rw"))}), name=("a",),
218+
size=1)
218219
with self.assertRaisesRegex(AttributeError,
219-
r"Signature of CSR register 'a' must have a csr\.Element\.Signature member "
220+
r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member "
220221
r"named 'element' and oriented as wiring\.Out"):
221222
csr.Multiplexer(map_1)
222223
# wrong member type
223224
map_2 = MemoryMap(addr_width=1, data_width=8)
224-
map_2.add_resource(_Reg({"element": Out(unsigned(8))}), name="a", size=1)
225+
map_2.add_resource(_Reg({"element": Out(unsigned(8))}), name=("a",), size=1)
225226
with self.assertRaisesRegex(AttributeError,
226-
r"Signature of CSR register 'a' must have a csr\.Element\.Signature member "
227+
r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member "
227228
r"named 'element' and oriented as wiring\.Out"):
228229
csr.Multiplexer(map_2)
229230
# wrong member signature
230231
map_3 = MemoryMap(addr_width=1, data_width=8)
231-
map_3.add_resource(_Reg({"element": Out(wiring.Signature({}))}), name="a", size=1)
232+
map_3.add_resource(_Reg({"element": Out(wiring.Signature({}))}), name=("a",), size=1)
232233
with self.assertRaisesRegex(AttributeError,
233-
r"Signature of CSR register 'a' must have a csr\.Element\.Signature member "
234+
r"Signature of CSR register \('a',\) must have a csr\.Element\.Signature member "
234235
r"named 'element' and oriented as wiring\.Out"):
235236
csr.Multiplexer(map_3)
236237

@@ -249,9 +250,9 @@ def test_sim(self):
249250
reg_16_rw = _MockRegister(16, "rw")
250251

251252
memory_map = MemoryMap(addr_width=16, data_width=8)
252-
memory_map.add_resource(reg_4_r, name="reg_4_r", size=1)
253-
memory_map.add_resource(reg_8_w, name="reg_8_w", size=1)
254-
memory_map.add_resource(reg_16_rw, name="reg_16_rw", size=2)
253+
memory_map.add_resource(reg_4_r, name=("reg_4_r",), size=1)
254+
memory_map.add_resource(reg_8_w, name=("reg_8_w",), size=1)
255+
memory_map.add_resource(reg_16_rw, name=("reg_16_rw",), size=2)
255256

256257
dut = csr.Multiplexer(memory_map, shadow_overlaps=shadow_overlaps)
257258
bus = dut.bus
@@ -336,7 +337,7 @@ def test_sim(self):
336337
with self.subTest(shadow_overlaps=shadow_overlaps):
337338
reg_20_rw = _MockRegister(20, "rw")
338339
memory_map = MemoryMap(addr_width=16, data_width=8, alignment=2)
339-
memory_map.add_resource(reg_20_rw, name="reg_20_rw", size=3)
340+
memory_map.add_resource(reg_20_rw, name=("reg_20_rw",), size=3)
340341

341342
dut = csr.Multiplexer(memory_map, shadow_overlaps=shadow_overlaps)
342343
bus = dut.bus
@@ -411,10 +412,10 @@ def test_sim(self):
411412
reg_2 = _MockRegister(8, "rw")
412413

413414
memory_map_1 = MemoryMap(addr_width=10, data_width=8)
414-
memory_map_1.add_resource(reg_1, name="reg_1", size=1)
415+
memory_map_1.add_resource(reg_1, name=("reg_1",), size=1)
415416

416417
memory_map_2 = MemoryMap(addr_width=10, data_width=8)
417-
memory_map_2.add_resource(reg_2, name="reg_2", size=1, addr=2)
418+
memory_map_2.add_resource(reg_2, name=("reg_2",), size=1, addr=2)
418419

419420
mux_1 = csr.Multiplexer(memory_map_1)
420421
mux_2 = csr.Multiplexer(memory_map_2)

0 commit comments

Comments
 (0)