Skip to content

Commit 2d18ac7

Browse files
Jean-François Nguyenwhitequark
authored andcommitted
memory: add support for address space extension.
* MemoryMap.add_resource(extend=True) (resp. MemoryMap.add_window()) can be used to add a resource (resp. window) that would otherwise not fit inside the memory map. The address space upper bound is raised accordingly, which increases addr_width. * MemoryMap.freeze() can be called to prevent a memory map from being extended further. It must be called before the memory map is used to instantiate anything, because its public attributes must not change from then on.
1 parent 3737595 commit 2d18ac7

File tree

2 files changed

+105
-15
lines changed

2 files changed

+105
-15
lines changed

nmigen_soc/memory.py

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import bisect
22

3+
from nmigen.utils import bits_for
4+
35

46
__all__ = ["MemoryMap"]
57

@@ -84,15 +86,50 @@ def __init__(self, *, addr_width, data_width, alignment=0):
8486
raise ValueError("Alignment must be a non-negative integer, not {!r}"
8587
.format(alignment))
8688

87-
self.addr_width = addr_width
88-
self.data_width = data_width
89-
self.alignment = alignment
89+
self._addr_width = addr_width
90+
self._data_width = data_width
91+
self._alignment = alignment
92+
93+
self._ranges = _RangeMap()
94+
self._resources = dict()
95+
self._windows = dict()
96+
97+
self._next_addr = 0
98+
self._frozen = False
99+
100+
@property
101+
def addr_width(self):
102+
return self._addr_width
90103

91-
self._ranges = _RangeMap()
92-
self._resources = dict()
93-
self._windows = dict()
104+
@addr_width.setter
105+
def addr_width(self, addr_width):
106+
if self._frozen:
107+
raise ValueError("Memory map has been frozen. Address width cannot be extended "
108+
"further")
109+
if not isinstance(addr_width, int) or addr_width <= 0:
110+
raise ValueError("Address width must be a positive integer, not {!r}"
111+
.format(addr_width))
112+
if addr_width < self._addr_width:
113+
raise ValueError("Address width {!r} must not be less than its previous value {!r}, "
114+
"because resources that were previously added may not fit anymore"
115+
.format(addr_width, self._addr_width))
116+
self._addr_width = addr_width
94117

95-
self._next_addr = 0
118+
@property
119+
def data_width(self):
120+
return self._data_width
121+
122+
@property
123+
def alignment(self):
124+
return self._alignment
125+
126+
def freeze(self):
127+
"""Freeze the memory map.
128+
129+
Once frozen, the memory map cannot be extended anymore. Resources and windows may
130+
still be added, as long as they fit within the address space bounds.
131+
"""
132+
self._frozen = True
96133

97134
@staticmethod
98135
def _align_up(value, alignment):
@@ -119,7 +156,7 @@ def align_to(self, alignment):
119156
self._next_addr = self._align_up(self._next_addr, max(alignment, self.alignment))
120157
return self._next_addr
121158

122-
def _compute_addr_range(self, addr, size, step=1, *, alignment):
159+
def _compute_addr_range(self, addr, size, step=1, *, alignment, extend):
123160
if addr is not None:
124161
if not isinstance(addr, int) or addr < 0:
125162
raise ValueError("Address must be a non-negative integer, not {!r}"
@@ -137,9 +174,12 @@ def _compute_addr_range(self, addr, size, step=1, *, alignment):
137174
size = self._align_up(size, alignment)
138175

139176
if addr > (1 << self.addr_width) or addr + size > (1 << self.addr_width):
140-
raise ValueError("Address range {:#x}..{:#x} out of bounds for memory map spanning "
141-
"range {:#x}..{:#x} ({} address bits)"
142-
.format(addr, addr + size, 0, 1 << self.addr_width, self.addr_width))
177+
if extend:
178+
self.addr_width = bits_for(addr + size)
179+
else:
180+
raise ValueError("Address range {:#x}..{:#x} out of bounds for memory map spanning "
181+
"range {:#x}..{:#x} ({} address bits)"
182+
.format(addr, addr + size, 0, 1 << self.addr_width, self.addr_width))
143183

144184
addr_range = range(addr, addr + size, step)
145185
overlaps = self._ranges.overlaps(addr_range)
@@ -159,7 +199,7 @@ def _compute_addr_range(self, addr, size, step=1, *, alignment):
159199

160200
return addr_range
161201

162-
def add_resource(self, resource, *, size, addr=None, alignment=None):
202+
def add_resource(self, resource, *, size, addr=None, alignment=None, extend=False):
163203
"""Add a resource.
164204
165205
A resource is any device on the bus that is a destination for bus transactions, e.g.
@@ -178,6 +218,9 @@ def add_resource(self, resource, *, size, addr=None, alignment=None):
178218
``2 ** max(alignment, self.alignment)``.
179219
alignment : int or None
180220
Alignment of the resource. If not specified, the memory map alignment is used.
221+
extend: bool
222+
Allow memory map extension. If ``True``, the upper bound of the address space is
223+
raised as needed, in order to fit a resource that would otherwise be out of bounds.
181224
182225
Return value
183226
------------
@@ -201,7 +244,7 @@ def add_resource(self, resource, *, size, addr=None, alignment=None):
201244
else:
202245
alignment = self.alignment
203246

204-
addr_range = self._compute_addr_range(addr, size, alignment=alignment)
247+
addr_range = self._compute_addr_range(addr, size, alignment=alignment, extend=extend)
205248
self._ranges.insert(addr_range, resource)
206249
self._resources[resource] = addr_range
207250
self._next_addr = addr_range.stop
@@ -219,7 +262,7 @@ def resources(self):
219262
for resource, resource_range in self._resources.items():
220263
yield resource, (resource_range.start, resource_range.stop)
221264

222-
def add_window(self, window, *, addr=None, sparse=None):
265+
def add_window(self, window, *, addr=None, sparse=None, extend=False):
223266
"""Add a window.
224267
225268
A window is a device on a bus that provides access to a different bus, i.e. a bus bridge.
@@ -248,6 +291,9 @@ def add_window(self, window, *, addr=None, sparse=None):
248291
sparse : bool or None
249292
Address translation type. Ignored if the datapath widths of both memory maps are
250293
equal; must be specified otherwise.
294+
extend : bool
295+
Allow memory map extension. If ``True``, the upper bound of the address space is
296+
raised as needed, in order to fit a window that would otherwise be out of bounds.
251297
252298
Return value
253299
------------
@@ -288,6 +334,8 @@ def add_window(self, window, *, addr=None, sparse=None):
288334
"data width {}"
289335
.format(self.data_width, window.data_width))
290336

337+
window.freeze()
338+
291339
if not sparse:
292340
ratio = self.data_width // window.data_width
293341
else:
@@ -300,7 +348,8 @@ def add_window(self, window, *, addr=None, sparse=None):
300348
# a window can still be aligned using align_to().
301349
alignment = max(self.alignment, window.addr_width // ratio)
302350

303-
addr_range = self._compute_addr_range(addr, size, ratio, alignment=alignment)
351+
addr_range = self._compute_addr_range(addr, size, ratio, alignment=alignment,
352+
extend=extend)
304353
self._ranges.insert(addr_range, window)
305354
self._windows[window] = addr_range
306355
self._next_addr = addr_range.stop

nmigen_soc/test/test_memory.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,27 @@ def test_wrong_alignment(self):
5656
r"Alignment must be a non-negative integer, not -1"):
5757
MemoryMap(addr_width=16, data_width=8, alignment=-1)
5858

59+
def test_set_addr_width_wrong(self):
60+
with self.assertRaisesRegex(ValueError,
61+
r"Address width must be a positive integer, not -1"):
62+
memory_map = MemoryMap(addr_width=1, data_width=8)
63+
memory_map.addr_width = -1
64+
65+
def test_set_addr_width_wrong_shrink(self):
66+
with self.assertRaisesRegex(ValueError,
67+
r"Address width 1 must not be less than its previous value 2, "
68+
r"because resources that were previously added may not fit anymore"):
69+
memory_map = MemoryMap(addr_width=2, data_width=8)
70+
memory_map.addr_width = 1
71+
72+
def test_set_addr_width_wrong_frozen(self):
73+
with self.assertRaisesRegex(ValueError,
74+
r"Memory map has been frozen. Address width cannot be extended "
75+
r"further"):
76+
memory_map = MemoryMap(addr_width=1, data_width=8)
77+
memory_map.freeze()
78+
memory_map.addr_width = 2
79+
5980
def test_add_resource(self):
6081
memory_map = MemoryMap(addr_width=16, data_width=8)
6182
self.assertEqual(memory_map.add_resource("a", size=1), (0, 1))
@@ -77,6 +98,12 @@ def test_add_resource_addr(self):
7798
self.assertEqual(memory_map.add_resource("a", size=1, addr=10), (10, 11))
7899
self.assertEqual(memory_map.add_resource("b", size=2), (11, 13))
79100

101+
def test_add_resource_extend(self):
102+
memory_map = MemoryMap(addr_width=16, data_width=8)
103+
self.assertEqual(memory_map.add_resource("a", size=1, addr=0x10000, extend=True),
104+
(0x10000, 0x10001))
105+
self.assertEqual(memory_map.addr_width, 17)
106+
80107
def test_add_resource_wrong_address(self):
81108
memory_map = MemoryMap(addr_width=16, data_width=8)
82109
with self.assertRaisesRegex(ValueError,
@@ -154,6 +181,13 @@ def test_add_window_dense(self):
154181
sparse=False),
155182
(0, 0x100, 4))
156183

184+
def test_add_window_extend(self):
185+
memory_map = MemoryMap(addr_width=16, data_width=8)
186+
self.assertEqual(memory_map.add_window(MemoryMap(addr_width=17, data_width=8),
187+
extend=True),
188+
(0, 0x20000, 1))
189+
self.assertEqual(memory_map.addr_width, 18)
190+
157191
def test_add_window_wrong_window(self):
158192
memory_map = MemoryMap(addr_width=16, data_width=8)
159193
with self.assertRaisesRegex(TypeError,
@@ -181,6 +215,13 @@ def test_add_window_wrong_ratio(self):
181215
r"16 is not an integer multiple of window data width 7"):
182216
memory_map.add_window(MemoryMap(addr_width=10, data_width=7), sparse=False)
183217

218+
def test_add_window_wrong_out_of_bounds(self):
219+
memory_map = MemoryMap(addr_width=16, data_width=8)
220+
with self.assertRaisesRegex(ValueError,
221+
r"Address range 0x0\.\.0x20000 out of bounds for memory map spanning "
222+
r"range 0x0\.\.0x10000 \(16 address bits\)"):
223+
memory_map.add_window(MemoryMap(addr_width=17, data_width=8))
224+
184225
def test_add_window_wrong_overlap(self):
185226
memory_map = MemoryMap(addr_width=16, data_width=8)
186227
memory_map.add_window(MemoryMap(addr_width=10, data_width=8))

0 commit comments

Comments
 (0)