Skip to content

Commit 9cdac48

Browse files
author
Jean-François Nguyen
committed
periph: add a PeripheralInfo class for metadata.
1 parent 517638c commit 9cdac48

File tree

2 files changed

+280
-0
lines changed

2 files changed

+280
-0
lines changed

Diff for: nmigen_soc/periph.py

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from collections.abc import Mapping
2+
3+
from .memory import MemoryMap
4+
from .event import EventMap
5+
6+
7+
__all__ = ["ConfigDict", "PeripheralInfo"]
8+
9+
10+
class ConfigDict(Mapping):
11+
"""Configuration dictionary.
12+
13+
A read-only container for static configuration data (e.g. named constants) needed by the
14+
firmware to use a peripheral. Dictionaries may be nested in order to provide namespaces. Keys
15+
are iterated in ascending order.
16+
17+
Parameters
18+
----------
19+
other : :class:`collections.abc.Mapping` or ``None``
20+
Source dictionary, whose items are extracted into this one.
21+
"""
22+
def __init__(self, other=None, *, _parents=()):
23+
if other is None:
24+
other = dict()
25+
if not isinstance(other, Mapping):
26+
raise TypeError("Source dictionary must be an instance of Mapping, not {!r}"
27+
.format(other))
28+
self._storage = dict()
29+
for key, value in other.items():
30+
if not isinstance(key, str) or not key:
31+
raise ValueError("Key must be a non-empty string, not {!r}{}"
32+
.format(key, "; parents={}".format(_parents) if _parents else ""))
33+
if isinstance(value, Mapping):
34+
self._storage[key] = ConfigDict(value, _parents=(*_parents, key))
35+
else:
36+
self._storage[key] = value
37+
38+
def __getitem__(self, key):
39+
return self._storage[key]
40+
41+
def __iter__(self):
42+
yield from sorted(self._storage)
43+
44+
def __len__(self):
45+
return len(self._storage)
46+
47+
def all_items(self, *, root="", separator=":"):
48+
"""Recursively iterate all items.
49+
50+
Arguments
51+
---------
52+
root : str
53+
Root namespace. Optional.
54+
separator : str
55+
Namespace delimiter. Optional. ``':'`` by default.
56+
57+
Yield values
58+
------------
59+
A tuple ``key, value`` corresponding to a configuration item. The key is prefixed by its
60+
full namespace.
61+
"""
62+
if not isinstance(root, str):
63+
raise TypeError("Root namespace must be a string, not {!r}"
64+
.format(root))
65+
if not isinstance(separator, str):
66+
raise TypeError("Separator must be a string, not {!r}"
67+
.format(separator))
68+
for key, value in self.items():
69+
key = "{}{}".format(root + separator if root else "", key)
70+
if isinstance(value, ConfigDict):
71+
yield from value.all_items(root=key, separator=separator)
72+
else:
73+
yield key, value
74+
75+
76+
class PeripheralInfo:
77+
"""Peripheral metadata.
78+
79+
A description of the resources used by a peripheral.
80+
81+
Parameters
82+
----------
83+
memory_map : :class:`MemoryMap`
84+
Memory map. Describes the CSR registers and/or memory windows residing in the address space
85+
of the peripheral.
86+
event_map : :class:`EventMap`
87+
Event map. Optional. Describes the events monitored by the peripheral.
88+
config : :class:`ConfigDict`
89+
Configuration dictionary. Optional. Contains static data needed to use the peripheral.
90+
91+
Attributes
92+
----------
93+
memory_map : :class:`MemoryMap`
94+
Memory map.
95+
event_map : :class:`EventMap`
96+
Event map.
97+
config : :class:`ConfigDict`
98+
Configuration dictionary.
99+
"""
100+
def __init__(self, *, memory_map, event_map=None, config=None):
101+
if not isinstance(memory_map, MemoryMap):
102+
raise TypeError("Memory map must be an instance of MemoryMap, not {!r}"
103+
.format(memory_map))
104+
memory_map.freeze()
105+
self.memory_map = memory_map
106+
107+
if event_map is None:
108+
event_map = EventMap()
109+
if not isinstance(event_map, EventMap):
110+
raise TypeError("Event map must be an instance of EventMap, not {!r}"
111+
.format(event_map))
112+
event_map.freeze()
113+
self.event_map = event_map
114+
115+
if config is None:
116+
config = dict()
117+
self.config = ConfigDict(config)

Diff for: nmigen_soc/test/test_periph.py

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import unittest
2+
3+
from ..periph import ConfigDict, PeripheralInfo
4+
from ..memory import MemoryMap
5+
from ..event import Source, EventMap
6+
7+
8+
class ConfigDictTestCase(unittest.TestCase):
9+
def test_init(self):
10+
config = ConfigDict({"foo": {"bar": 1}, "baz": 2})
11+
self.assertEqual(config, {"foo": {"bar": 1}, "baz": 2})
12+
13+
def test_init_none(self):
14+
config = ConfigDict(other=None)
15+
self.assertEqual(len(config), 0)
16+
self.assertEqual(list(config.keys()), [])
17+
self.assertEqual(config, {})
18+
19+
def test_init_wrong(self):
20+
with self.assertRaisesRegex(TypeError,
21+
r"Source dictionary must be an instance of Mapping, not 'foo'"):
22+
config = ConfigDict("foo")
23+
24+
def test_init_wrong_key(self):
25+
with self.assertRaisesRegex(ValueError,
26+
r"Key must be a non-empty string, not 1"):
27+
config = ConfigDict({1: None})
28+
29+
def test_init_wrong_key_empty(self):
30+
with self.assertRaisesRegex(ValueError,
31+
r"Key must be a non-empty string, not ''"):
32+
config = ConfigDict({'': None})
33+
34+
def test_init_wrong_key_nested(self):
35+
with self.assertRaisesRegex(ValueError,
36+
r"Key must be a non-empty string, not 1; parents=\('foo', 'bar'\)"):
37+
config = ConfigDict({"foo": {"bar": {1: None}}})
38+
39+
def test_getitem(self):
40+
config = ConfigDict({"foo": 1})
41+
self.assertEqual(config["foo"], 1)
42+
43+
def test_getitem_wrong(self):
44+
config = ConfigDict()
45+
with self.assertRaisesRegex(KeyError, r"'foo'"):
46+
config["foo"]
47+
48+
def test_setitem_wrong(self):
49+
config = ConfigDict()
50+
with self.assertRaisesRegex(TypeError,
51+
r"'ConfigDict' object does not support item assignment"):
52+
config["foo"] = 1
53+
54+
def test_iter(self):
55+
config = ConfigDict({"foo": {"oof": 0},
56+
"bar": 1,
57+
"baz": 2})
58+
self.assertEqual(list(config), ["bar", "baz", "foo"])
59+
60+
def test_len(self):
61+
config = ConfigDict({"foo": {"oof": 0},
62+
"bar": 1,
63+
"baz": 2})
64+
self.assertEqual(len(config), 3)
65+
66+
def test_iter_all_items(self):
67+
config = ConfigDict({"a": {"e": "f",
68+
"b": {"c": "d"}},
69+
"g": "h"})
70+
self.assertEqual(list(config.all_items()), [
71+
("a:b:c", "d"),
72+
("a:e", "f"),
73+
("g", "h"),
74+
])
75+
76+
def test_iter_all_items_root(self):
77+
config = ConfigDict({"a": {"e": "f",
78+
"b": {"c": "d"}},
79+
"g": "h"})
80+
self.assertEqual(list(config.all_items(root="foo")), [
81+
("foo:a:b:c", "d"),
82+
("foo:a:e", "f"),
83+
("foo:g", "h"),
84+
])
85+
86+
def test_iter_all_items_separator(self):
87+
config = ConfigDict({"a": {"e": "f",
88+
"b": {"c": "d"}},
89+
"g": "h"})
90+
self.assertEqual(list(config.all_items(separator="__")), [
91+
("a__b__c", "d"),
92+
("a__e", "f"),
93+
("g", "h"),
94+
])
95+
96+
def test_iter_all_items_wrong_root(self):
97+
config = ConfigDict()
98+
with self.assertRaisesRegex(TypeError,
99+
r"Root namespace must be a string, not 0"):
100+
next(config.all_items(root=0))
101+
102+
def test_iter_all_items_wrong_separator(self):
103+
config = ConfigDict()
104+
with self.assertRaisesRegex(TypeError,
105+
r"Separator must be a string, not 0"):
106+
next(config.all_items(separator=0))
107+
108+
109+
class PeripheralInfoTestCase(unittest.TestCase):
110+
def test_default(self):
111+
memory_map = MemoryMap(addr_width=1, data_width=8)
112+
info = PeripheralInfo(memory_map=memory_map)
113+
self.assertIs(info.memory_map, memory_map)
114+
self.assertIsInstance(info.event_map, EventMap)
115+
self.assertEqual(info.event_map.size, 0)
116+
self.assertIsInstance(info.config, ConfigDict)
117+
self.assertEqual(info.config, {})
118+
119+
def test_memory_map_wrong(self):
120+
with self.assertRaisesRegex(TypeError,
121+
r"Memory map must be an instance of MemoryMap, not 'foo'"):
122+
info = PeripheralInfo(memory_map="foo")
123+
124+
def test_event_map(self):
125+
memory_map = MemoryMap(addr_width=1, data_width=8)
126+
event_map = EventMap()
127+
info = PeripheralInfo(memory_map=memory_map, event_map=event_map)
128+
self.assertIs(info.memory_map, memory_map)
129+
130+
def test_event_map_none(self):
131+
memory_map = MemoryMap(addr_width=1, data_width=8)
132+
info = PeripheralInfo(memory_map=memory_map, event_map=None)
133+
self.assertEqual(info.event_map.size, 0)
134+
135+
def test_event_map_wrong(self):
136+
memory_map = MemoryMap(addr_width=1, data_width=8)
137+
with self.assertRaisesRegex(TypeError,
138+
r"Event map must be an instance of EventMap, not 'foo'"):
139+
info = PeripheralInfo(memory_map=memory_map, event_map="foo")
140+
141+
def test_config(self):
142+
memory_map = MemoryMap(addr_width=1, data_width=8)
143+
info = PeripheralInfo(memory_map=memory_map, config={"foo": 1})
144+
self.assertIsInstance(info.config, ConfigDict)
145+
self.assertEqual(info.config, {"foo": 1})
146+
147+
def test_config_none(self):
148+
memory_map = MemoryMap(addr_width=1, data_width=8)
149+
info = PeripheralInfo(memory_map=memory_map, config=None)
150+
self.assertIsInstance(info.config, ConfigDict)
151+
self.assertEqual(info.config, {})
152+
153+
def test_frozen(self):
154+
memory_map = MemoryMap(addr_width=1, data_width=8)
155+
event_map = EventMap()
156+
info = PeripheralInfo(memory_map=memory_map, event_map=event_map)
157+
with self.assertRaisesRegex(ValueError,
158+
r"Memory map has been frozen. Address width cannot be extended further"):
159+
memory_map.add_resource("a", size=3, extend=True)
160+
with self.assertRaisesRegex(ValueError,
161+
r"Event map has been frozen. Cannot add source."):
162+
sub = Source()
163+
event_map.add(sub)

0 commit comments

Comments
 (0)