Skip to content

Commit 2a634b3

Browse files
committed
csr.bus: add Multiplexer.
1 parent 120ea53 commit 2a634b3

File tree

3 files changed

+183
-9
lines changed

3 files changed

+183
-9
lines changed

nmigen_soc/csr/bus.py

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import enum
22
from nmigen import *
3+
from nmigen.utils import log2_int
34

45
from ..memory import MemoryMap
56

67

7-
__all__ = ["Element", "Interface", "Decoder"]
8+
__all__ = ["Element", "Interface", "Decoder", "Multiplexer"]
89

910

1011
class Element(Record):
@@ -103,15 +104,20 @@ class Interface(Record):
103104
Address width. At most ``(2 ** addr_width) * data_width`` register bits will be available.
104105
data_width : int
105106
Data width. Registers are accessed in ``data_width`` sized chunks.
107+
alignment : int
108+
Alignment. See :class:`MemoryMap`.
106109
name : str
107110
Name of the underlying record.
108111
109112
Attributes
110113
----------
114+
memory_map : MemoryMap
115+
Map of the bus.
111116
addr : Signal(addr_width)
112117
Address for reads and writes.
113118
r_data : Signal(data_width)
114-
Read data. Valid on the next cycle after ``r_stb`` is asserted.
119+
Read data. Valid on the next cycle after ``r_stb`` is asserted. Otherwise, zero. (Keeping
120+
read data of an unused interface at zero simplifies multiplexers.)
115121
r_stb : Signal()
116122
Read strobe. If ``addr`` points to the first chunk of a register, captures register value
117123
and causes read side effects to be performed (if any). If ``addr`` points to any chunk
@@ -126,7 +132,7 @@ class Interface(Record):
126132
nothing.
127133
"""
128134

129-
def __init__(self, *, addr_width, data_width, name=None):
135+
def __init__(self, *, addr_width, data_width, alignment=0, name=None):
130136
if not isinstance(addr_width, int) or addr_width <= 0:
131137
raise ValueError("Address width must be a positive integer, not {!r}"
132138
.format(addr_width))
@@ -135,6 +141,8 @@ def __init__(self, *, addr_width, data_width, name=None):
135141
.format(data_width))
136142
self.addr_width = addr_width
137143
self.data_width = data_width
144+
self.memory_map = MemoryMap(addr_width=addr_width, data_width=data_width,
145+
alignment=alignment)
138146

139147
super().__init__([
140148
("addr", addr_width),
@@ -185,17 +193,16 @@ class Decoder(Elaboratable):
185193
data_width : int
186194
Data width. See :class:`Interface`.
187195
alignment : int
188-
Register alignment. The address assigned to each register will be a multiple of
189-
``2 ** alignment``.
196+
Register alignment. See :class:`Interface`.
190197
191198
Attributes
192199
----------
193200
bus : :class:`Interface`
194201
CSR bus providing access to registers.
195202
"""
196203
def __init__(self, *, addr_width, data_width, alignment=0):
197-
self.bus = Interface(addr_width=addr_width, data_width=data_width)
198-
self._map = MemoryMap(addr_width=addr_width, data_width=data_width, alignment=alignment)
204+
self.bus = Interface(addr_width=addr_width, data_width=data_width, alignment=alignment)
205+
self._map = self.bus.memory_map
199206

200207
def align_to(self, alignment):
201208
"""Align the implicit address of the next register.
@@ -266,3 +273,91 @@ def elaborate(self, platform):
266273
m.d.comb += self.bus.r_data.eq(r_data_fanin)
267274

268275
return m
276+
277+
278+
class Multiplexer(Elaboratable):
279+
"""CSR bus multiplexer.
280+
281+
An address-based multiplexer for subordinate CSR buses.
282+
283+
Usage
284+
-----
285+
286+
Although there is no functional difference between adding a set of registers directly to
287+
a :class:`Decoder` and adding a set of reigsters to multiple :class:`Decoder`s that are
288+
aggregated with a :class:`Multiplexer`, hierarchical CSR buses are useful for organizing
289+
a hierarchical design. If many peripherals are directly served by a single :class:`Decoder`,
290+
a very large amount of ports will connect the peripheral registers with the decoder, and
291+
the cost of decoding logic would not be attributed to specific peripherals. With a multiplexer,
292+
only five signals per peripheral will be used, and the logic could be kept together with
293+
the peripheral.
294+
295+
Parameters
296+
----------
297+
addr_width : int
298+
Address width. See :class:`Interface`.
299+
data_width : int
300+
Data width. See :class:`Interface`.
301+
alignment : int
302+
Window alignment. See :class:`Interface`.
303+
304+
Attributes
305+
----------
306+
bus : :class:`Interface`
307+
CSR bus providing access to subordinate buses.
308+
"""
309+
def __init__(self, *, addr_width, data_width, alignment=0):
310+
self.bus = Interface(addr_width=addr_width, data_width=data_width, alignment=alignment)
311+
self._map = self.bus.memory_map
312+
self._subs = dict()
313+
314+
def align_to(self, alignment):
315+
"""Align the implicit address of the next window.
316+
317+
See :meth:`MemoryMap.align_to` for details.
318+
"""
319+
return self._map.align_to(alignment)
320+
321+
def add(self, sub_bus, *, addr=None):
322+
"""Add a window to a subordinate bus.
323+
324+
See :meth:`MemoryMap.add_resource` for details.
325+
"""
326+
if not isinstance(sub_bus, Interface):
327+
raise TypeError("Subordinate bus must be an instance of csr.Interface, not {!r}"
328+
.format(sub_bus))
329+
if sub_bus.data_width != self.bus.data_width:
330+
raise ValueError("Subordinate bus has data width {}, which is not the same as "
331+
"multiplexer data width {}"
332+
.format(sub_bus.data_width, self.bus.data_width))
333+
334+
start, end, ratio = window_range = self._map.add_window(sub_bus.memory_map, addr=addr)
335+
assert ratio == 1
336+
pattern = "{:0{}b}{}".format(start >> sub_bus.addr_width,
337+
self.bus.addr_width - sub_bus.addr_width,
338+
"-" * sub_bus.addr_width)
339+
self._subs[pattern] = sub_bus
340+
return window_range
341+
342+
def elaborate(self, platform):
343+
m = Module()
344+
345+
# See Decoder.elaborate above.
346+
r_data_fanin = 0
347+
348+
with m.Switch(self.bus.addr):
349+
for sub_pat, sub_bus in self._subs.items():
350+
m.d.comb += sub_bus.addr.eq(self.bus.addr[:sub_bus.addr_width])
351+
352+
# The CSR bus interface is defined to output zero when idle, allowing us to avoid
353+
# adding a multiplexer here.
354+
r_data_fanin |= sub_bus.r_data
355+
m.d.comb += sub_bus.w_data.eq(self.bus.w_data)
356+
357+
with m.Case(sub_pat):
358+
m.d.comb += sub_bus.r_stb.eq(self.bus.r_stb)
359+
m.d.comb += sub_bus.w_stb.eq(self.bus.w_stb)
360+
361+
m.d.comb += self.bus.r_data.eq(r_data_fanin)
362+
363+
return m

nmigen_soc/memory.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def add_resource(self, resource, *, size, addr=None, alignment=None):
166166
167167
Arguments
168168
---------
169-
resource
169+
resource : object
170170
Arbitrary object representing a resource.
171171
addr : int or None
172172
Address of the resource. If ``None``, the implicit next address will be used.
@@ -291,7 +291,12 @@ def add_window(self, window, *, addr=None, sparse=None):
291291
ratio = self.data_width // window.data_width
292292
else:
293293
ratio = 1
294-
size = (1 << window.addr_width) // ratio
294+
size = (1 << window.addr_width) // ratio
295+
# For resources, the alignment argument of add_resource() affects both address and size
296+
# of the resource; aligning only the address should be done using align_to(). For windows,
297+
# changing the size (beyond the edge case of the window size being smaller than alignment
298+
# of the bus) is unlikely to be useful, so there is no alignment argument. The address of
299+
# a window can still be aligned using align_to().
295300
alignment = max(self.alignment, window.addr_width // ratio)
296301

297302
addr_range = self._compute_addr_range(addr, size, ratio, alignment=alignment)

nmigen_soc/test/test_csr_bus.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,77 @@ def sim_test():
253253
sim.add_clock(1e-6)
254254
sim.add_sync_process(sim_test())
255255
sim.run()
256+
257+
258+
class MultiplexerTestCase(unittest.TestCase):
259+
def setUp(self):
260+
self.dut = Multiplexer(addr_width=16, data_width=8)
261+
Fragment.get(self.dut, platform=None) # silence UnusedElaboratable
262+
263+
def test_add_wrong_sub_bus(self):
264+
with self.assertRaisesRegex(TypeError,
265+
r"Subordinate bus must be an instance of csr\.Interface, not 1"):
266+
self.dut.add(1)
267+
268+
def test_add_wrong_data_width(self):
269+
decoder = Decoder(addr_width=10, data_width=16)
270+
Fragment.get(decoder, platform=None) # silence UnusedElaboratable
271+
272+
with self.assertRaisesRegex(ValueError,
273+
r"Subordinate bus has data width 16, which is not the same as "
274+
r"multiplexer data width 8"):
275+
self.dut.add(decoder.bus)
276+
277+
def test_sim(self):
278+
dec_1 = Decoder(addr_width=10, data_width=8)
279+
self.dut.add(dec_1.bus)
280+
elem_1 = Element(8, "rw")
281+
dec_1.add(elem_1)
282+
283+
dec_2 = Decoder(addr_width=10, data_width=8)
284+
self.dut.add(dec_2.bus)
285+
elem_2 = Element(8, "rw")
286+
dec_2.add(elem_2, addr=2)
287+
288+
elem_1_addr, _, _ = self.dut.bus.memory_map.find_resource(elem_1)
289+
elem_2_addr, _, _ = self.dut.bus.memory_map.find_resource(elem_2)
290+
self.assertEqual(elem_1_addr, 0x0000)
291+
self.assertEqual(elem_2_addr, 0x0402)
292+
293+
bus = self.dut.bus
294+
295+
def sim_test():
296+
yield bus.addr.eq(elem_1_addr)
297+
yield bus.w_stb.eq(1)
298+
yield bus.w_data.eq(0x55)
299+
yield
300+
yield bus.w_stb.eq(0)
301+
yield
302+
self.assertEqual((yield elem_1.w_data), 0x55)
303+
304+
yield bus.addr.eq(elem_2_addr)
305+
yield bus.w_stb.eq(1)
306+
yield bus.w_data.eq(0xaa)
307+
yield
308+
yield bus.w_stb.eq(0)
309+
yield
310+
self.assertEqual((yield elem_2.w_data), 0xaa)
311+
312+
yield elem_1.r_data.eq(0x55)
313+
yield elem_2.r_data.eq(0xaa)
314+
315+
yield bus.addr.eq(elem_1_addr)
316+
yield bus.r_stb.eq(1)
317+
yield
318+
yield bus.addr.eq(elem_2_addr)
319+
yield
320+
self.assertEqual((yield bus.r_data), 0x55)
321+
yield
322+
self.assertEqual((yield bus.r_data), 0xaa)
323+
324+
m = Module()
325+
m.submodules += self.dut, dec_1, dec_2
326+
with Simulator(m, vcd_file=open("test.vcd", "w")) as sim:
327+
sim.add_clock(1e-6)
328+
sim.add_sync_process(sim_test())
329+
sim.run()

0 commit comments

Comments
 (0)