Skip to content

Commit 5d77efa

Browse files
author
Jean-François Nguyen
committed
event: add event management primitives.
1 parent 967a65f commit 5d77efa

File tree

2 files changed

+421
-0
lines changed

2 files changed

+421
-0
lines changed

nmigen_soc/event.py

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import enum
2+
from collections import OrderedDict
3+
4+
from nmigen import *
5+
6+
7+
__all__ = ["Source", "EventMap", "Monitor"]
8+
9+
10+
class Source(Record):
11+
class Trigger(enum.Enum):
12+
"""Event trigger mode."""
13+
LEVEL = "level"
14+
RISE = "rise"
15+
FALL = "fall"
16+
17+
"""Event source interface.
18+
19+
Parameters
20+
----------
21+
trigger : :class:`Trigger`
22+
Trigger mode. An event can be edge- or level-triggered by the input line.
23+
name: str
24+
Name of the underlying record.
25+
26+
Attributes
27+
----------
28+
i : Signal()
29+
Input line. Sampled in order to detect an event.
30+
trg : Signal()
31+
Event trigger. Asserted when an event occurs, according to the trigger mode.
32+
"""
33+
def __init__(self, *, trigger="level", name=None, src_loc_at=0):
34+
choices = ("level", "rise", "fall")
35+
if not isinstance(trigger, Source.Trigger) and trigger not in choices:
36+
raise ValueError("Invalid trigger mode {!r}; must be one of {}"
37+
.format(trigger, ", ".join(choices)))
38+
self.trigger = Source.Trigger(trigger)
39+
self._map = None
40+
41+
super().__init__([
42+
("i", 1),
43+
("trg", 1),
44+
], name=name, src_loc_at=1 + src_loc_at)
45+
46+
@property
47+
def event_map(self):
48+
"""Event map.
49+
50+
Return value
51+
------------
52+
A :class:`EventMap` describing subordinate event sources.
53+
54+
Exceptions
55+
----------
56+
Raises :exn:`NotImplementedError` if the source does not have an event map.
57+
"""
58+
if self._map is None:
59+
raise NotImplementedError("Event source {!r} does not have an event map"
60+
.format(self))
61+
return self._map
62+
63+
@event_map.setter
64+
def event_map(self, event_map):
65+
if not isinstance(event_map, EventMap):
66+
raise TypeError("Event map must be an instance of EventMap, not {!r}"
67+
.format(event_map))
68+
event_map.freeze()
69+
self._map = event_map
70+
71+
# FIXME: get rid of this
72+
__hash__ = object.__hash__
73+
74+
75+
class EventMap:
76+
"""Event map.
77+
78+
An event map is a description of a set of events. It is built by adding event sources
79+
and can be queried later to determine their index. Event indexing is done implicitly by
80+
increment, starting at 0.
81+
"""
82+
def __init__(self):
83+
self._sources = OrderedDict()
84+
self._frozen = False
85+
86+
@property
87+
def size(self):
88+
"""Size of the event map.
89+
90+
Return value
91+
------------
92+
The number of event sources in the map.
93+
"""
94+
return len(self._sources)
95+
96+
def freeze(self):
97+
"""Freeze the event map.
98+
99+
Once the event map is frozen, sources cannot be added anymore.
100+
"""
101+
self._frozen = True
102+
103+
def add(self, src):
104+
"""Add an event source.
105+
106+
Arguments
107+
---------
108+
src : :class:`Source`
109+
Event source.
110+
111+
Exceptions
112+
----------
113+
Raises :exn:`ValueError` if the event map is frozen.
114+
"""
115+
if self._frozen:
116+
raise ValueError("Event map has been frozen. Cannot add source.")
117+
if not isinstance(src, Source):
118+
raise TypeError("Event source must be an instance of event.Source, not {!r}"
119+
.format(src))
120+
if src not in self._sources:
121+
self._sources[src] = self.size
122+
123+
def index(self, src):
124+
"""Get the index corresponding to an event source.
125+
126+
Arguments
127+
---------
128+
src : :class:`Source`
129+
Event source.
130+
131+
Return value
132+
------------
133+
The index of the source.
134+
135+
Exceptions
136+
----------
137+
Raises :exn:`KeyError` if the source is not found.
138+
"""
139+
if not isinstance(src, Source):
140+
raise TypeError("Event source must be an instance of event.Source, not {!r}"
141+
.format(src))
142+
return self._sources[src]
143+
144+
def sources(self):
145+
"""Iterate event sources.
146+
147+
Yield values
148+
------------
149+
A tuple ``src, index`` corresponding to an event source and its index.
150+
"""
151+
for src, index in self._sources.items():
152+
yield src, index
153+
154+
155+
class Monitor(Elaboratable):
156+
"""Event monitor.
157+
158+
A monitor for subordinate event sources.
159+
160+
Parameters
161+
----------
162+
event_map : :class:`EventMap`
163+
Event map.
164+
trigger : :class:`Source.Trigger`
165+
Trigger mode. See :class:`Source`.
166+
167+
Attributes
168+
----------
169+
src : :class:`Source`
170+
Event source. Its input is asserted when a subordinate event is enabled and pending.
171+
enable : Signal(event_map.size), bit mask, in
172+
Enabled events.
173+
pending : Signal(event_map.size), bit mask, out
174+
Pending events.
175+
clear : Signal(event_map.size), bit mask, in
176+
Clear selected pending events.
177+
"""
178+
def __init__(self, event_map, *, trigger="level"):
179+
self.src = Source(trigger=trigger)
180+
self.src.event_map = event_map
181+
182+
self.enable = Signal(event_map.size)
183+
self.pending = Signal(event_map.size)
184+
self.clear = Signal(event_map.size)
185+
186+
def elaborate(self, platform):
187+
m = Module()
188+
189+
for sub, index in self.src.event_map.sources():
190+
if sub.trigger != Source.Trigger.LEVEL:
191+
sub_i_r = Signal.like(sub.i, name_suffix="_r")
192+
m.d.sync += sub_i_r.eq(sub.i)
193+
194+
if sub.trigger == Source.Trigger.LEVEL:
195+
m.d.comb += sub.trg.eq(sub.i)
196+
elif sub.trigger == Source.Trigger.RISE:
197+
m.d.comb += sub.trg.eq(~sub_i_r & sub.i)
198+
elif sub.trigger == Source.Trigger.FALL:
199+
m.d.comb += sub.trg.eq( sub_i_r & ~sub.i)
200+
else:
201+
assert False # :nocov:
202+
203+
with m.If(sub.trg):
204+
m.d.sync += self.pending[index].eq(1)
205+
with m.Elif(self.clear[index]):
206+
m.d.sync += self.pending[index].eq(0)
207+
208+
m.d.comb += self.src.i.eq((self.enable & self.pending).any())
209+
210+
return m

0 commit comments

Comments
 (0)