Skip to content

Commit 8ebe0f6

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

File tree

2 files changed

+379
-0
lines changed

2 files changed

+379
-0
lines changed

nmigen_soc/event.py

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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+
40+
super().__init__([
41+
("i", 1),
42+
("trg", 1),
43+
], name=name, src_loc_at=1 + src_loc_at)
44+
45+
# FIXME: get rid of this
46+
__hash__ = object.__hash__
47+
48+
49+
class EventMap:
50+
"""Event map.
51+
52+
An event map is a description of a set of events. It is built by adding event sources
53+
and can be queried later to determine their index. Event indexing is done implicitly by
54+
increment, starting at 0.
55+
"""
56+
def __init__(self):
57+
self._sources = OrderedDict()
58+
self._frozen = False
59+
60+
@property
61+
def size(self):
62+
"""Size of the event map.
63+
64+
Return value
65+
------------
66+
The number of event sources in the map.
67+
"""
68+
return len(self._sources)
69+
70+
def freeze(self):
71+
"""Freeze the event map.
72+
73+
Once the event map is frozen, sources cannot be added anymore.
74+
"""
75+
self._frozen = True
76+
77+
def add(self, src):
78+
"""Add an event source.
79+
80+
Arguments
81+
---------
82+
src : :class:`Source`
83+
Event source.
84+
85+
Exceptions
86+
----------
87+
Raises :exn:`ValueError` if the event map is frozen.
88+
"""
89+
if self._frozen:
90+
raise ValueError("Event map has been frozen. Cannot add source.")
91+
if not isinstance(src, Source):
92+
raise TypeError("Event source must be an instance of event.Source, not {!r}"
93+
.format(src))
94+
if src not in self._sources:
95+
self._sources[src] = self.size
96+
97+
def index(self, src):
98+
"""Get the index corresponding to an event source.
99+
100+
Arguments
101+
---------
102+
src : :class:`Source`
103+
Event source.
104+
105+
Return value
106+
------------
107+
The index of the source.
108+
109+
Exceptions
110+
----------
111+
Raises :exn:`KeyError` if the source is not found.
112+
"""
113+
if not isinstance(src, Source):
114+
raise TypeError("Event source must be an instance of event.Source, not {!r}"
115+
.format(src))
116+
return self._sources[src]
117+
118+
def sources(self):
119+
"""Iterate event sources.
120+
121+
Yield values
122+
------------
123+
A tuple ``src, index`` corresponding to an event source and its index.
124+
"""
125+
for src, index in self._sources.items():
126+
yield src, index
127+
128+
129+
class Monitor(Elaboratable):
130+
"""Event monitor.
131+
132+
A monitor for event sources sharing access to an interrupt request line.
133+
134+
Parameters
135+
----------
136+
event_map : :class:`EventMap`
137+
Event map.
138+
139+
Attributes
140+
----------
141+
event_map : :class:`EventMap`
142+
Event map.
143+
enable : Signal(event_map.size), one-hot, in
144+
Enabled events.
145+
pending : Signal(event_map.size), one-hot, out
146+
Pending events.
147+
clear : Signal(event_map.size), one-hot, in
148+
Clear the selected pending events.
149+
irq : Signal(), out
150+
Interrupt request. Asserted when an event is both enabled and pending.
151+
"""
152+
def __init__(self, event_map):
153+
if not isinstance(event_map, EventMap):
154+
raise TypeError("Event map must be an instance of EventMap, not {!r}"
155+
.format(event_map))
156+
event_map.freeze()
157+
self.event_map = event_map
158+
159+
self.enable = Signal(event_map.size)
160+
self.pending = Signal(event_map.size)
161+
self.clear = Signal(event_map.size)
162+
163+
self.irq = Signal()
164+
165+
def elaborate(self, platform):
166+
m = Module()
167+
168+
for src, index in self.event_map.sources():
169+
if src.trigger != Source.Trigger.LEVEL:
170+
src_i_r = Signal.like(src.i, name_suffix="_r")
171+
m.d.sync += src_i_r.eq(src.i)
172+
173+
if src.trigger == Source.Trigger.LEVEL:
174+
m.d.comb += src.trg.eq(src.i)
175+
elif src.trigger == Source.Trigger.RISE:
176+
m.d.comb += src.trg.eq(~src_i_r & src.i)
177+
elif src.trigger == Source.Trigger.FALL:
178+
m.d.comb += src.trg.eq( src_i_r & ~src.i)
179+
else:
180+
assert False # :nocov:
181+
182+
with m.If(src.trg):
183+
m.d.sync += self.pending[index].eq(1)
184+
with m.Elif(self.clear[index]):
185+
m.d.sync += self.pending[index].eq(0)
186+
187+
m.d.comb += self.irq.eq((self.enable & self.pending).any())
188+
189+
return m

nmigen_soc/test/test_event.py

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# nmigen: UnusedElaboratable=no
2+
3+
import unittest
4+
from nmigen import *
5+
from nmigen.back.pysim import *
6+
7+
from ..event import *
8+
9+
10+
def simulation_test(dut, process):
11+
with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim:
12+
sim.add_clock(1e-6)
13+
sim.add_sync_process(process)
14+
sim.run()
15+
16+
17+
class SourceTestCase(unittest.TestCase):
18+
def test_level(self):
19+
src = Source(trigger="level")
20+
self.assertEqual(src.trigger, Source.Trigger.LEVEL)
21+
22+
def test_rise(self):
23+
src = Source(trigger="rise")
24+
self.assertEqual(src.trigger, Source.Trigger.RISE)
25+
26+
def test_fall(self):
27+
src = Source(trigger="fall")
28+
self.assertEqual(src.trigger, Source.Trigger.FALL)
29+
30+
def test_trigger_wrong(self):
31+
with self.assertRaisesRegex(ValueError,
32+
r"Invalid trigger mode 'foo'; must be one of level, rise, fall"):
33+
src = Source(trigger="foo")
34+
35+
36+
class EventMapTestCase(unittest.TestCase):
37+
def test_add(self):
38+
src_0 = Source()
39+
src_1 = Source()
40+
event_map = EventMap()
41+
event_map.add(src_0)
42+
event_map.add(src_1)
43+
self.assertTrue(src_0 in event_map._sources)
44+
self.assertTrue(src_1 in event_map._sources)
45+
46+
def test_add_wrong(self):
47+
event_map = EventMap()
48+
with self.assertRaisesRegex(TypeError,
49+
r"Event source must be an instance of event.Source, not 'foo'"):
50+
event_map.add("foo")
51+
52+
def test_add_wrong_frozen(self):
53+
event_map = EventMap()
54+
event_map.freeze()
55+
with self.assertRaisesRegex(ValueError,
56+
r"Event map has been frozen. Cannot add source."):
57+
event_map.add(Source())
58+
59+
def test_size(self):
60+
event_map = EventMap()
61+
event_map.add(Source())
62+
event_map.add(Source())
63+
self.assertEqual(event_map.size, 2)
64+
65+
def test_index(self):
66+
src_0 = Source()
67+
src_1 = Source()
68+
event_map = EventMap()
69+
event_map.add(src_0)
70+
event_map.add(src_1)
71+
self.assertEqual(event_map.index(src_0), 0)
72+
self.assertEqual(event_map.index(src_1), 1)
73+
74+
def test_index_add_twice(self):
75+
src = Source()
76+
event_map = EventMap()
77+
event_map.add(src)
78+
event_map.add(src)
79+
self.assertEqual(event_map.index(src), 0)
80+
self.assertEqual(event_map.size, 1)
81+
82+
def test_index_wrong(self):
83+
event_map = EventMap()
84+
with self.assertRaisesRegex(TypeError,
85+
r"Event source must be an instance of event.Source, not 'foo'"):
86+
event_map.index("foo")
87+
88+
def test_index_not_found(self):
89+
src = Source()
90+
event_map = EventMap()
91+
with self.assertRaises(KeyError):
92+
event_map.index(src)
93+
94+
def test_iter_sources(self):
95+
src_0 = Source()
96+
src_1 = Source()
97+
event_map = EventMap()
98+
event_map.add(src_0)
99+
event_map.add(src_1)
100+
self.assertEqual(list(event_map.sources()), [
101+
(src_0, 0),
102+
(src_1, 1),
103+
])
104+
105+
106+
class MonitorTestCase(unittest.TestCase):
107+
def test_simple(self):
108+
src_0 = Source()
109+
src_1 = Source()
110+
event_map = EventMap()
111+
event_map.add(src_0)
112+
event_map.add(src_1)
113+
dut = Monitor(event_map)
114+
self.assertEqual(dut.enable.width, 2)
115+
self.assertEqual(dut.pending.width, 2)
116+
self.assertEqual(dut.clear.width, 2)
117+
118+
def test_event_map_wrong(self):
119+
with self.assertRaisesRegex(TypeError,
120+
r"Event map must be an instance of EventMap, not 'foo'"):
121+
dut = Monitor("foo")
122+
123+
def test_events(self):
124+
src_0 = Source(trigger="level")
125+
src_1 = Source(trigger="rise")
126+
src_2 = Source(trigger="fall")
127+
event_map = EventMap()
128+
event_map.add(src_0)
129+
event_map.add(src_1)
130+
event_map.add(src_2)
131+
dut = Monitor(event_map)
132+
133+
def process():
134+
yield src_0.i.eq(1)
135+
yield src_1.i.eq(0)
136+
yield src_2.i.eq(1)
137+
yield
138+
self.assertEqual((yield src_0.trg), 1)
139+
self.assertEqual((yield src_1.trg), 0)
140+
self.assertEqual((yield src_2.trg), 0)
141+
yield
142+
self.assertEqual((yield dut.pending), 0b001)
143+
self.assertEqual((yield dut.irq), 0)
144+
145+
yield dut.enable.eq(0b111)
146+
yield
147+
self.assertEqual((yield dut.irq), 1)
148+
149+
yield dut.clear.eq(0b001)
150+
yield
151+
self.assertEqual((yield dut.pending), 0b001)
152+
self.assertEqual((yield dut.irq), 1)
153+
154+
yield src_0.i.eq(0)
155+
yield
156+
self.assertEqual((yield src_0.trg), 0)
157+
self.assertEqual((yield src_1.trg), 0)
158+
self.assertEqual((yield src_2.trg), 0)
159+
yield
160+
self.assertEqual((yield dut.pending), 0b000)
161+
self.assertEqual((yield dut.irq), 0)
162+
163+
yield src_1.i.eq(1)
164+
yield
165+
self.assertEqual((yield src_0.trg), 0)
166+
self.assertEqual((yield src_1.trg), 1)
167+
self.assertEqual((yield src_2.trg), 0)
168+
yield
169+
self.assertEqual((yield dut.pending), 0b010)
170+
self.assertEqual((yield dut.irq), 1)
171+
172+
yield src_2.i.eq(0)
173+
yield
174+
self.assertEqual((yield src_0.trg), 0)
175+
self.assertEqual((yield src_1.trg), 0)
176+
self.assertEqual((yield src_2.trg), 1)
177+
yield
178+
self.assertEqual((yield dut.pending), 0b110)
179+
self.assertEqual((yield dut.irq), 1)
180+
181+
yield dut.clear.eq(0b110)
182+
yield
183+
self.assertEqual((yield src_0.trg), 0)
184+
self.assertEqual((yield src_1.trg), 0)
185+
self.assertEqual((yield src_2.trg), 0)
186+
yield
187+
self.assertEqual((yield dut.pending), 0b000)
188+
self.assertEqual((yield dut.irq), 0)
189+
190+
simulation_test(dut, process)

0 commit comments

Comments
 (0)