Skip to content

Commit b9ffd36

Browse files
author
Jean-François Nguyen
committed
csr.periph: add Peripheral base class.
1 parent 967a65f commit b9ffd36

File tree

3 files changed

+608
-0
lines changed

3 files changed

+608
-0
lines changed

nmigen_soc/csr/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .bus import *
2+
from .periph import *

nmigen_soc/csr/periph.py

+328
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
from nmigen import *
2+
from nmigen import tracer
3+
4+
from .bus import *
5+
6+
7+
__all__ = ["Peripheral", "IRQLine", "Event", "PeripheralBridge"]
8+
9+
10+
class Peripheral:
11+
"""CSR-capable peripheral.
12+
13+
A helper class to reduce the boilerplate needed to control a peripheral with a CSR interface.
14+
It provides facilities for instantiating CSR registers and sending interrupt requests to the
15+
CPU.
16+
17+
The ``Peripheral`` class is not meant to be instantiated as-is, but rather as a base class for
18+
actual peripherals.
19+
20+
Usage example
21+
-------------
22+
23+
```
24+
class ExamplePeripheral(csr.Peripheral, Elaboratable):
25+
def __init__(self):
26+
super().__init__()
27+
self._data = self.csr(8, "w")
28+
self._rdy = self.event(mode="rise")
29+
30+
self._bridge = self.csr_bridge()
31+
self.csr_bus = self._bridge.bus
32+
self.irq = self._bridge.irq
33+
34+
def elaborate(self, platform):
35+
m = Module()
36+
m.submodules.bridge = self._bridge
37+
# ...
38+
return m
39+
```
40+
41+
Parameters
42+
----------
43+
name : str
44+
Name of this peripheral. If ``None`` (default) the name is inferred from the variable
45+
name this peripheral is assigned to.
46+
47+
Attributes
48+
----------
49+
name : str
50+
Name of the peripheral.
51+
"""
52+
def __init__(self, name=None, src_loc_at=1):
53+
if name is not None and not isinstance(name, str):
54+
raise TypeError("Name must be a string, not {!r}".format(name))
55+
self.name = name or tracer.get_var_name(depth=2 + src_loc_at)
56+
57+
self._csr_regs = []
58+
self._csr_bus = None
59+
60+
self._events = []
61+
self._irq = None
62+
63+
@property
64+
def csr_bus(self):
65+
"""CSR bus providing access to registers.
66+
67+
Return value
68+
------------
69+
An instance of :class:`Interface`.
70+
71+
Exceptions
72+
----------
73+
Raises :exn:`NotImplementedError` if the peripheral does not have a CSR bus.
74+
"""
75+
if self._csr_bus is None:
76+
raise NotImplementedError("Peripheral {!r} does not have a CSR bus interface"
77+
.format(self))
78+
return self._csr_bus
79+
80+
@csr_bus.setter
81+
def csr_bus(self, csr_bus):
82+
if not isinstance(csr_bus, Interface):
83+
raise TypeError("CSR bus interface must be an instance of csr.Interface, not {!r}"
84+
.format(csr_bus))
85+
self._csr_bus = csr_bus
86+
87+
@property
88+
def irq(self):
89+
"""Interrupt request line.
90+
91+
Return value
92+
------------
93+
An instance of :class:`IRQLine`.
94+
95+
Exceptions
96+
----------
97+
Raises :exn:`NotImplementedError` if the peripheral does not have an IRQ line.
98+
"""
99+
if self._irq is None:
100+
raise NotImplementedError("Peripheral {!r} does not have an IRQ line"
101+
.format(self))
102+
return self._irq
103+
104+
@irq.setter
105+
def irq(self, irq):
106+
if not isinstance(irq, IRQLine):
107+
raise TypeError("IRQ line must be an instance of IRQLine, not {!r}"
108+
.format(irq))
109+
self._irq = irq
110+
111+
def csr(self, width, access, *, addr=None, alignment=0, name=None, src_loc_at=0):
112+
"""Request a CSR register.
113+
114+
Parameters
115+
----------
116+
width : int
117+
Width of the register. See :class:`Element`.
118+
access : :class:`Access`
119+
Register access mode. See :class:`Element`.
120+
addr : int
121+
Address of the register. See :meth:`Multiplexer.add`.
122+
alignment : int
123+
Register alignment. See :class:`Multiplexer`.
124+
name : str
125+
Name of the register. If ``None`` (default) the name is inferred from the variable
126+
name this register is assigned to.
127+
128+
Return value
129+
------------
130+
An instance of :class:`Element`.
131+
"""
132+
if name is not None and not isinstance(name, str):
133+
raise TypeError("Name must be a string, not {!r}".format(name))
134+
elem_name = name or tracer.get_var_name(depth=2 + src_loc_at)
135+
136+
elem = Element(width, access, name="{}_{}".format(self.name, elem_name))
137+
self._csr_regs.append((elem, addr, alignment))
138+
return elem
139+
140+
def event(self, *, mode="level", name=None, src_loc_at=0):
141+
"""Request an event source.
142+
143+
See :class:`Event` for details.
144+
145+
Return value
146+
------------
147+
An instance of :class:`Event`.
148+
"""
149+
event = Event(mode=mode, name=name, src_loc_at=1 + src_loc_at)
150+
self._events.append(event)
151+
return event
152+
153+
def csr_bridge(self, *, data_width=8, alignment=0):
154+
"""Request a bridge to the resources of the peripheral.
155+
156+
See :class:`PeripheralBridge` for details.
157+
158+
Return value
159+
------------
160+
An instance of :class:`PeripheralBridge` providing access to the registers
161+
of the peripheral and managing its event sources.
162+
"""
163+
return PeripheralBridge(self, data_width=data_width, alignment=alignment)
164+
165+
def csr_registers(self):
166+
"""Iterate requested CSR registers and their parameters.
167+
168+
Yield values
169+
------------
170+
A tuple ``elem, addr, alignment`` describing the register and its parameters.
171+
"""
172+
for elem, addr, alignment in self._csr_regs:
173+
yield elem, addr, alignment
174+
175+
def events(self):
176+
"""Iterate requested event sources.
177+
178+
Event sources are ordered by request order.
179+
180+
Yield values
181+
------------
182+
An instance of :class:`Event`.
183+
"""
184+
for event in self._events:
185+
yield event
186+
187+
188+
class IRQLine(Signal):
189+
"""Interrupt request line."""
190+
def __init__(self, *, name=None, src_loc_at=0):
191+
super().__init__(name=name, src_loc_at=1 + src_loc_at)
192+
193+
194+
class Event:
195+
"""Event source.
196+
197+
Parameters
198+
----------
199+
mode : ``"level"``, ``"rise"``, ``"fall"``
200+
Trigger mode. If ``"level"``, a notification is raised when the ``stb`` signal is high.
201+
If ``"rise"`` (or ``"fall"``) a notification is raised on a rising (or falling) edge
202+
of ``stb``.
203+
name : str
204+
Name of the event. If ``None`` (default) the name is inferred from the variable
205+
name this event source is assigned to.
206+
207+
Attributes
208+
----------
209+
name : str
210+
Name of the event
211+
mode : ``"level"``, ``"rise"``, ``"fall"``
212+
Trigger mode.
213+
stb : Signal, in
214+
Event strobe.
215+
"""
216+
def __init__(self, *, mode, name=None, src_loc_at=0):
217+
if name is not None and not isinstance(name, str):
218+
raise TypeError("Name must be a string, not {!r}".format(name))
219+
220+
choices = ("level", "rise", "fall")
221+
if mode not in choices:
222+
raise ValueError("Invalid trigger mode {!r}; must be one of {}"
223+
.format(mode, ", ".join(choices)))
224+
225+
self.name = name or tracer.get_var_name(depth=2 + src_loc_at)
226+
self.mode = mode
227+
self.stb = Signal(name="{}_stb".format(self.name))
228+
229+
230+
class PeripheralBridge(Elaboratable):
231+
"""Peripheral bridge.
232+
233+
A bridge providing access to the registers of a peripheral, and support for interrupt
234+
requests (IRQs) from its event sources.
235+
236+
CSR registers
237+
-------------
238+
ev_status : read-only
239+
Event status. Each bit displays the value of the ``stb`` signal of an event source.
240+
The register width is ``len(list(periph.events())`` bits. Event sources are ordered by
241+
request order.
242+
ev_pending : read/write
243+
Event pending. Each bit displays whether an event source has a pending notification.
244+
Writing 1 to a bit clears the notification.
245+
The register width is ``len(list(periph.events())`` bits. Event sources are ordered by
246+
request order.
247+
ev_enable : read/write
248+
Event enable. Writing 1 to a bit enables an event source. Writing 0 disables it.
249+
The register width is ``len(list(periph.events())`` bits. Event sources are ordered by
250+
request order.
251+
252+
Parameters
253+
----------
254+
periph : :class:`Peripheral`
255+
The peripheral whose resources are exposed by this bridge.
256+
data_width : int
257+
Data width of the CSR bus. See :class:`Multiplexer`.
258+
alignment : int
259+
Register alignment. See :class:`Multiplexer`.
260+
261+
Attributes
262+
----------
263+
bus : :class:`Interface`
264+
CSR bus providing access to the registers of the peripheral.
265+
irq : :class:`IRQLine` or None
266+
IRQ line providing notifications from local events to the CPU. It is raised if any
267+
event source is both enabled and has a pending notification. If the peripheral has
268+
no event sources, it is set to ``None``.
269+
"""
270+
def __init__(self, periph, *, data_width, alignment):
271+
if not isinstance(periph, Peripheral):
272+
raise TypeError("Peripheral must be an instance of Peripheral, not {!r}"
273+
.format(periph))
274+
275+
self._mux = Multiplexer(addr_width=1, data_width=data_width, alignment=alignment)
276+
for elem, elem_addr, elem_alignment in periph.csr_registers():
277+
self._mux.add(elem, addr=elem_addr, alignment=elem_alignment, extend=True)
278+
279+
self._events = list(periph.events())
280+
if len(self._events) > 0:
281+
width = len(self._events)
282+
self._ev_status = Element(width, "r", name="{}_ev_status".format(periph.name))
283+
self._ev_pending = Element(width, "rw", name="{}_ev_pending".format(periph.name))
284+
self._ev_enable = Element(width, "rw", name="{}_ev_enable".format(periph.name))
285+
self._mux.add(self._ev_status, extend=True)
286+
self._mux.add(self._ev_pending, extend=True)
287+
self._mux.add(self._ev_enable, extend=True)
288+
self.irq = IRQLine(name="{}_irq".format(periph.name))
289+
else:
290+
self.irq = None
291+
292+
self.bus = self._mux.bus
293+
294+
def elaborate(self, platform):
295+
m = Module()
296+
297+
m.submodules.mux = self._mux
298+
299+
if self.irq is not None:
300+
with m.If(self._ev_pending.w_stb):
301+
m.d.sync += self._ev_pending.r_data.eq( self._ev_pending.r_data
302+
& ~self._ev_pending.w_data)
303+
with m.If(self._ev_enable.w_stb):
304+
m.d.sync += self._ev_enable.r_data.eq(self._ev_enable.w_data)
305+
306+
for i, ev in enumerate(self._events):
307+
m.d.sync += self._ev_status.r_data[i].eq(ev.stb)
308+
309+
if ev.mode in ("rise", "fall"):
310+
ev_stb_r = Signal.like(ev.stb, name_suffix="_r")
311+
m.d.sync += ev_stb_r.eq(ev.stb)
312+
313+
ev_trigger = Signal(name="{}_trigger".format(ev.name))
314+
if ev.mode == "level":
315+
m.d.comb += ev_trigger.eq(ev.stb)
316+
elif ev.mode == "rise":
317+
m.d.comb += ev_trigger.eq(~ev_stb_r & ev.stb)
318+
elif ev.mode == "fall":
319+
m.d.comb += ev_trigger.eq( ev_stb_r & ~ev.stb)
320+
else:
321+
assert False # :nocov:
322+
323+
with m.If(ev_trigger):
324+
m.d.sync += self._ev_pending.r_data[i].eq(1)
325+
326+
m.d.comb += self.irq.eq((self._ev_pending.r_data & self._ev_enable.r_data).any())
327+
328+
return m

0 commit comments

Comments
 (0)