Skip to content

Commit d377e68

Browse files
committed
csr.bus: instantiate a Multiplexer from a MemoryMap.
Before this commit, CSR elements were added with `Multiplexer.add()`. Now, a memory map of elements is populated beforehand, then given as argument to `Multiplexer.__init__()`. This decouples the definition of a group of neighboring registers from their implementation by a CSR Multiplexer.
1 parent 68c0c59 commit d377e68

File tree

4 files changed

+109
-130
lines changed

4 files changed

+109
-130
lines changed

amaranth_soc/csr/bus.py

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ def width(self):
155155
def access(self):
156156
return self.signature.access
157157

158+
def add_to(self, memory_map, *, name, addr=None, alignment=None):
159+
"""Add register to memory map.
160+
161+
See :meth:`MemoryMap.add_resource` for details.
162+
"""
163+
if not isinstance(memory_map, MemoryMap):
164+
raise TypeError("Memory map must be an instance of MemoryMap, not {!r}"
165+
.format(memory_map))
166+
size = (self.width + memory_map.data_width - 1) // memory_map.data_width
167+
return memory_map.add_resource(self, size=size, addr=addr, alignment=alignment,
168+
name=name)
169+
158170
def __repr__(self):
159171
return f"csr.Element({self.signature!r})"
160172

@@ -519,31 +531,25 @@ def chunks(self):
519531
are possible for connecting the CSR bus to the CPU:
520532
* The CPU could access the CSR bus directly (with no intervening logic other than simple
521533
translation of control signals). In this case, the register alignment should be set
522-
to 1 (i.e. `alignment` should be set to 0), and each *w*-bit register would occupy
523-
*ceil(w/n)* addresses from the CPU perspective, requiring the same amount of memory
524-
instructions to access.
534+
to 1 (i.e. `memory_map.alignment` should be set to 0), and each *w*-bit register would
535+
occupy *ceil(w/n)* addresses from the CPU perspective, requiring the same amount of
536+
memory instructions to access.
525537
* The CPU could also access the CSR bus through a width down-converter, which would issue
526538
*k/n* CSR accesses for each CPU access. In this case, the register alignment should be
527539
set to *k/n*, and each *w*-bit register would occupy *ceil(w/k)* addresses from the CPU
528540
perspective, requiring the same amount of memory instructions to access.
529541
530-
If the register alignment (i.e. `2 ** alignment`) is greater than 1, it affects which CSR bus
531-
write is considered a write to the last register chunk. For example, if a 24-bit register is
532-
used with a 8-bit CSR bus and a CPU with a 32-bit datapath, a write to this register requires
533-
4 CSR bus writes to complete and the 4th write is the one that actually writes the value to
534-
the register. This allows determining write latency solely from the amount of addresses the
535-
register occupies in the CPU address space, and the width of the CSR bus.
542+
If the register alignment (i.e. `2 ** memory_map.alignment`) is greater than 1, it affects
543+
which CSR bus write is considered a write to the last register chunk. For example, if a 24-bit
544+
register is used with a 8-bit CSR bus and a CPU with a 32-bit datapath, a write to this
545+
register requires 4 CSR bus writes to complete and the 4th write is the one that actually
546+
writes the value to the register. This allows determining write latency solely from the amount
547+
of addresses the register occupies in the CPU address space, and the width of the CSR bus.
536548
537549
Parameters
538550
----------
539-
addr_width : int
540-
Address width. See :class:`Interface`.
541-
data_width : int
542-
Data width. See :class:`Interface`.
543-
alignment : int, power-of-2 exponent
544-
Register alignment. See :class:`..memory.MemoryMap`.
545-
name : :class:`str`
546-
Window name. Optional. See :class:`..memory.MemoryMap`.
551+
memory_map : :class:`..memory.MemoryMap`
552+
Memory map of the CSR bus.
547553
shadow_overlaps : int
548554
Maximum number of CSR registers that can share a chunk of a shadow register.
549555
Optional. If ``None``, any number of CSR registers can share a shadow chunk.
@@ -554,31 +560,24 @@ def chunks(self):
554560
bus : :class:`Interface`
555561
CSR bus providing access to registers.
556562
"""
557-
def __init__(self, *, addr_width, data_width, alignment=0, name=None, shadow_overlaps=None):
558-
super().__init__({"bus": In(Signature(addr_width=addr_width, data_width=data_width))})
559-
self.bus.memory_map = MemoryMap(addr_width=addr_width, data_width=data_width,
560-
alignment=alignment, name=name)
561-
self._r_shadow = Multiplexer._Shadow(data_width, shadow_overlaps, name="r_shadow")
562-
self._w_shadow = Multiplexer._Shadow(data_width, shadow_overlaps, name="w_shadow")
563-
564-
def align_to(self, alignment):
565-
"""Align the implicit address of the next register.
566-
567-
See :meth:`MemoryMap.align_to` for details.
568-
"""
569-
return self.bus.memory_map.align_to(alignment)
570-
571-
def add(self, elem, *, name, addr=None, alignment=None):
572-
"""Add a register.
573-
574-
See :meth:`MemoryMap.add_resource` for details.
575-
"""
576-
if not isinstance(elem, Element):
577-
raise TypeError(f"Element must be an instance of csr.Element, not {elem!r}")
578-
579-
size = (elem.width + self.bus.memory_map.data_width - 1) // self.bus.memory_map.data_width
580-
return self.bus.memory_map.add_resource(elem, size=size, addr=addr, alignment=alignment,
581-
name=name)
563+
def __init__(self, memory_map, *, shadow_overlaps=None):
564+
if not isinstance(memory_map, MemoryMap):
565+
raise TypeError("Memory map must be an instance of MemoryMap, not {!r}"
566+
.format(memory_map))
567+
for elem, elem_name, (elem_start, elem_end) in memory_map.resources():
568+
if not isinstance(elem, Element):
569+
raise TypeError("Memory map resource must be an instance of csr.Element, not {!r}"
570+
.format(elem))
571+
if list(memory_map.windows()):
572+
raise ValueError("Memory map cannot have windows")
573+
574+
super().__init__({
575+
"bus": In(Signature(addr_width=memory_map.addr_width,
576+
data_width=memory_map.data_width))
577+
})
578+
self.bus.memory_map = memory_map
579+
self._r_shadow = self._Shadow(memory_map.data_width, shadow_overlaps, name="r_shadow")
580+
self._w_shadow = self._Shadow(memory_map.data_width, shadow_overlaps, name="w_shadow")
582581

583582
def elaborate(self, platform):
584583
m = Module()

amaranth_soc/csr/event.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@ def __init__(self, event_map, *, trigger="level", data_width, alignment=0, name=
5959

6060
elem_size = ceil(event_map.size / data_width)
6161
addr_width = 1 + max(ceil_log2(elem_size), alignment)
62-
self._mux = Multiplexer(addr_width=addr_width, data_width=data_width,
63-
alignment=alignment)
64-
self._mux.add(self._enable, name="enable")
65-
self._mux.add(self._pending, name="pending")
62+
memory_map = MemoryMap(addr_width=addr_width, data_width=data_width, alignment=alignment)
63+
64+
self._enable .add_to(memory_map, name="enable")
65+
self._pending.add_to(memory_map, name="pending")
66+
self._mux = Multiplexer(memory_map)
6667

6768
super().__init__({
6869
"src": Out(self._monitor.src.signature),

tests/test_csr_bus.py

Lines changed: 49 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -176,62 +176,53 @@ def test_set_wrong_map_data_width(self):
176176

177177

178178
class MultiplexerTestCase(unittest.TestCase):
179-
def setUp(self):
180-
self.dut = csr.Multiplexer(addr_width=16, data_width=8)
181-
182-
def test_add_4b(self):
183-
elem_4b = csr.Element(4, "rw", path=("elem_4b",))
184-
self.assertEqual(self.dut.add(elem_4b, name="elem_4b"), (0, 1))
179+
def test_memory_map(self):
180+
elem_4_rw = csr.Element(4, "rw", path=("elem_4_rw",))
181+
elem_8_rw = csr.Element(8, "rw", path=("elem_8_rw",))
185182

186-
def test_add_8b(self):
187-
elem_8b = csr.Element(8, "rw", path=("elem_8b",))
188-
self.assertEqual(self.dut.add(elem_8b, name="elem_8b"), (0, 1))
183+
memory_map = MemoryMap(addr_width=2, data_width=4)
184+
memory_map.add_resource(elem_4_rw, name="elem_4_rw", size=1)
185+
memory_map.add_resource(elem_8_rw, name="elem_8_rw", size=2)
186+
memory_map.freeze()
189187

190-
def test_add_12b(self):
191-
elem_12b = csr.Element(12, "rw", path=("elem_12b",))
192-
self.assertEqual(self.dut.add(elem_12b, name="elem_8b"), (0, 2))
188+
dut = csr.Multiplexer(memory_map)
193189

194-
def test_add_16b(self):
195-
elem_16b = csr.Element(16, "rw", path=("elem_16b",))
196-
self.assertEqual(self.dut.add(elem_16b, name="elem_16b"), (0, 2))
190+
self.assertIs(dut.bus.memory_map, memory_map)
191+
self.assertEqual(dut.bus.addr_width, 2)
192+
self.assertEqual(dut.bus.data_width, 4)
197193

198-
def test_add_two(self):
199-
elem_8b = csr.Element( 8, "rw", path=("elem_8b",))
200-
elem_16b = csr.Element(16, "rw", path=("elem_16b",))
201-
self.assertEqual(self.dut.add(elem_16b, name="elem_16b"), (0, 2))
202-
self.assertEqual(self.dut.add(elem_8b, name="elem_8b"), (2, 3))
203-
204-
def test_add_wrong(self):
194+
def test_wrong_memory_map(self):
205195
with self.assertRaisesRegex(TypeError,
206-
r"Element must be an instance of csr\.Element, not 'foo'"):
207-
self.dut.add(elem="foo", name="elem_4b")
196+
r"Memory map must be an instance of MemoryMap, not 'foo'"):
197+
csr.Multiplexer("foo")
208198

209-
def test_align_to(self):
210-
elem_0 = csr.Element(8, "rw", path=("elem_0",))
211-
elem_1 = csr.Element(8, "rw", path=("elem_1",))
212-
self.assertEqual(self.dut.add(elem_0, name="elem_0"), (0, 1))
213-
self.assertEqual(self.dut.align_to(2), 4)
214-
self.assertEqual(self.dut.add(elem_1, name="elem_1"), (4, 5))
199+
def test_wrong_memory_map_resource(self):
200+
memory_map = MemoryMap(addr_width=1, data_width=8)
201+
memory_map.add_resource("foo", name="foo", size=1)
202+
with self.assertRaisesRegex(TypeError,
203+
r"Memory map resource must be an instance of csr\.Element, not 'foo'"):
204+
csr.Multiplexer(memory_map)
215205

216-
def test_add_wrong_out_of_bounds(self):
217-
elem = csr.Element(8, "rw", path=("elem",))
218-
with self.assertRaisesRegex(ValueError,
219-
r"Address range 0x10000\.\.0x10001 out of bounds for memory map spanning "
220-
r"range 0x0\.\.0x10000 \(16 address bits\)"):
221-
self.dut.add(elem, name="elem", addr=0x10000)
206+
def test_wrong_memory_map_windows(self):
207+
memory_map_0 = MemoryMap(addr_width=1, data_width=8)
208+
memory_map_1 = MemoryMap(addr_width=1, data_width=8)
209+
memory_map_0.add_window(memory_map_1)
210+
with self.assertRaisesRegex(ValueError, r"Memory map cannot have windows"):
211+
csr.Multiplexer(memory_map_0)
222212

223213
def test_sim(self):
224214
for shadow_overlaps in [None, 0, 1]:
225215
with self.subTest(shadow_overlaps=shadow_overlaps):
226-
dut = csr.Multiplexer(addr_width=16, data_width=8, shadow_overlaps=shadow_overlaps)
227-
228-
elem_4_r = csr.Element(4, "r", path=("elem_4_r",))
229-
dut.add(elem_4_r, name="elem_4_r")
230-
elem_8_w = csr.Element(8, "w", path=("elem_8_w",))
231-
dut.add(elem_8_w, name="elem_8_w")
216+
elem_4_r = csr.Element( 4, "r", path=("elem_4_r",))
217+
elem_8_w = csr.Element( 8, "w", path=("elem_8_w",))
232218
elem_16_rw = csr.Element(16, "rw", path=("elem_16_rw",))
233-
dut.add(elem_16_rw, name="elem_16_rw")
234219

220+
memory_map = MemoryMap(addr_width=16, data_width=8)
221+
memory_map.add_resource(elem_4_r, name="elem_4_r", size=1)
222+
memory_map.add_resource(elem_8_w, name="elem_8_w", size=1)
223+
memory_map.add_resource(elem_16_rw, name="elem_16_rw", size=2)
224+
225+
dut = csr.Multiplexer(memory_map, shadow_overlaps=shadow_overlaps)
235226
bus = dut.bus
236227

237228
def sim_test():
@@ -309,38 +300,14 @@ def sim_test():
309300

310301

311302
class MultiplexerAlignedTestCase(unittest.TestCase):
312-
def setUp(self):
313-
self.dut = csr.Multiplexer(addr_width=16, data_width=8, alignment=2)
314-
315-
def test_add_two(self):
316-
elem_0 = csr.Element( 8, "rw", path=("elem_0",))
317-
elem_1 = csr.Element(16, "rw", path=("elem_1",))
318-
self.assertEqual(self.dut.add(elem_0, name="elem_0"), (0, 4))
319-
self.assertEqual(self.dut.add(elem_1, name="elem_1"), (4, 8))
320-
321-
def test_over_align_to(self):
322-
elem_0 = csr.Element(8, "rw", path=("elem_0",))
323-
elem_1 = csr.Element(8, "rw", path=("elem_1",))
324-
self.assertEqual(self.dut.add(elem_0, name="elem_0"), (0, 4))
325-
self.assertEqual(self.dut.align_to(3), 8)
326-
self.assertEqual(self.dut.add(elem_1, name="elem_1"), (8, 12))
327-
328-
def test_under_align_to(self):
329-
elem_0 = csr.Element(8, "rw", path=("elem_0",))
330-
elem_1 = csr.Element(8, "rw", path=("elem_1",))
331-
self.assertEqual(self.dut.add(elem_0, name="elem_0"), (0, 4))
332-
self.assertEqual(self.dut.align_to(alignment=1), 4)
333-
self.assertEqual(self.dut.add(elem_1, name="elem_1"), (4, 8))
334-
335303
def test_sim(self):
336304
for shadow_overlaps in [None, 0, 1]:
337305
with self.subTest(shadow_overlaps=shadow_overlaps):
338-
dut = csr.Multiplexer(addr_width=16, data_width=8, alignment=2,
339-
shadow_overlaps=shadow_overlaps)
340-
341306
elem_20_rw = csr.Element(20, "rw", path=("elem_20_rw",))
342-
dut.add(elem_20_rw, name="elem_20_rw")
307+
memory_map = MemoryMap(addr_width=16, data_width=8, alignment=2)
308+
memory_map.add_resource(elem_20_rw, name="elem_20_rw", size=3)
343309

310+
dut = csr.Multiplexer(memory_map, shadow_overlaps=shadow_overlaps)
344311
bus = dut.bus
345312

346313
def sim_test():
@@ -392,7 +359,7 @@ def test_add_wrong_sub_bus(self):
392359
self.dut.add(sub_bus=1)
393360

394361
def test_add_wrong_data_width(self):
395-
mux = csr.Multiplexer(addr_width=10, data_width=16)
362+
mux = csr.Multiplexer(MemoryMap(addr_width=10, data_width=16))
396363
Fragment.get(mux, platform=None) # silence UnusedElaboratable
397364

398365
with self.assertRaisesRegex(ValueError,
@@ -409,14 +376,19 @@ def test_add_wrong_out_of_bounds(self):
409376
self.dut.add(iface)
410377

411378
def test_sim(self):
412-
mux_1 = csr.Multiplexer(addr_width=10, data_width=8)
413379
elem_1 = csr.Element(8, "rw", path=("elem_1",))
414-
mux_1.add(elem_1, name="elem_1")
415-
self.dut.add(mux_1.bus)
416-
417-
mux_2 = csr.Multiplexer(addr_width=10, data_width=8)
418380
elem_2 = csr.Element(8, "rw", path=("elem_2",))
419-
mux_2.add(elem_2, name="elem_2", addr=2)
381+
382+
memory_map_1 = MemoryMap(addr_width=10, data_width=8)
383+
memory_map_1.add_resource(elem_1, name="elem_1", size=1)
384+
385+
memory_map_2 = MemoryMap(addr_width=10, data_width=8)
386+
memory_map_2.add_resource(elem_2, name="elem_2", size=1, addr=2)
387+
388+
mux_1 = csr.Multiplexer(memory_map_1)
389+
mux_2 = csr.Multiplexer(memory_map_2)
390+
391+
self.dut.add(mux_1.bus)
420392
self.dut.add(mux_2.bus)
421393

422394
elem_1_info = self.dut.bus.memory_map.find_resource(elem_1)

tests/test_csr_wishbone.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from amaranth_soc import csr
88
from amaranth_soc.csr.wishbone import *
9+
from amaranth_soc.memory import MemoryMap
910

1011

1112
class MockRegister(Elaboratable):
@@ -42,12 +43,15 @@ def test_wrong_csr_bus_data_width(self):
4243
WishboneCSRBridge(csr_bus)
4344

4445
def test_narrow(self):
45-
mux = csr.Multiplexer(addr_width=10, data_width=8)
46-
reg_1 = MockRegister(8, name="reg_1")
47-
mux.add(reg_1.element, name="reg_1")
46+
reg_1 = MockRegister( 8, name="reg_1")
4847
reg_2 = MockRegister(16, name="reg_2")
49-
mux.add(reg_2.element, name="reg_2")
50-
dut = WishboneCSRBridge(mux.bus)
48+
49+
memory_map = MemoryMap(addr_width=10, data_width=8)
50+
reg_1.element.add_to(memory_map, name="reg_1")
51+
reg_2.element.add_to(memory_map, name="reg_2")
52+
53+
mux = csr.Multiplexer(memory_map)
54+
dut = WishboneCSRBridge(mux.bus)
5155

5256
def sim_test():
5357
yield dut.wb_bus.cyc.eq(1)
@@ -143,9 +147,12 @@ def sim_test():
143147
sim.run()
144148

145149
def test_wide(self):
146-
mux = csr.Multiplexer(addr_width=10, data_width=8)
147150
reg = MockRegister(32, name="reg")
148-
mux.add(reg.element, name="reg")
151+
152+
memory_map = MemoryMap(addr_width=10, data_width=8)
153+
reg.element.add_to(memory_map, name="reg")
154+
155+
mux = csr.Multiplexer(memory_map)
149156
dut = WishboneCSRBridge(mux.bus, data_width=32)
150157

151158
def sim_test():

0 commit comments

Comments
 (0)