Skip to content

Commit b405880

Browse files
Jean-François NguyenJean-François Nguyen
Jean-François Nguyen
authored and
Jean-François Nguyen
committed
periph: add a ConstantMap container for configuration constants.
Resolves #24.
1 parent e352e2d commit b405880

File tree

2 files changed

+258
-4
lines changed

2 files changed

+258
-4
lines changed

nmigen_soc/periph.py

+143-3
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,147 @@
1+
from collections import OrderedDict
2+
from collections.abc import Mapping
3+
4+
from nmigen.utils import bits_for
5+
16
from .memory import MemoryMap
27
from . import event
38

49

5-
__all__ = ["PeripheralInfo"]
10+
__all__ = ["ConstantValue", "ConstantBool", "ConstantInt", "ConstantMap", "PeripheralInfo"]
11+
12+
13+
class ConstantValue:
14+
pass
15+
16+
17+
class ConstantBool(ConstantValue):
18+
"""Boolean constant.
19+
20+
Parameters
21+
----------
22+
value : bool
23+
Constant value.
24+
"""
25+
def __init__(self, value):
26+
if not isinstance(value, bool):
27+
raise TypeError("Value must be a bool, not {!r}".format(value))
28+
self._value = value
29+
30+
@property
31+
def value(self):
32+
return self._value
33+
34+
def __repr__(self):
35+
return "ConstantBool({})".format(self.value)
36+
37+
38+
class ConstantInt(ConstantValue):
39+
"""Integer constant.
40+
41+
Parameters
42+
----------
43+
value : int
44+
Constant value.
45+
width : int
46+
Width in bits. Optional. ``bits_for(value)`` by default.
47+
signed : bool
48+
Signedness. Optional. ``value < 0`` by default.
49+
"""
50+
def __init__(self, value, *, width=None, signed=None):
51+
if not isinstance(value, int):
52+
raise TypeError("Value must be an integer, not {!r}"
53+
.format(value))
54+
self._value = value
55+
56+
if width is None:
57+
width = bits_for(value)
58+
if not isinstance(width, int):
59+
raise TypeError("Width must be an integer, not {!r}"
60+
.format(width))
61+
if width < bits_for(value):
62+
raise ValueError("Width must be greater than or equal to the number of bits needed to"
63+
" represent {}".format(value))
64+
self._width = width
65+
66+
if signed is None:
67+
signed = value < 0
68+
if not isinstance(signed, bool):
69+
raise TypeError("Signedness must be a bool, not {!r}"
70+
.format(signed))
71+
self._signed = signed
72+
73+
@property
74+
def value(self):
75+
return self._value
76+
77+
@property
78+
def width(self):
79+
return self._width
80+
81+
@property
82+
def signed(self):
83+
return self._signed
84+
85+
def __repr__(self):
86+
return "ConstantInt({}, width={}, signed={})".format(self.value, self.width, self.signed)
87+
88+
89+
class ConstantMap(Mapping):
90+
"""Named constant map.
91+
92+
A read-only container for named constants. Keys are iterated in insertion order.
93+
94+
Parameters
95+
----------
96+
**constants : dict(str : :class:`ConstantValue`)
97+
Named constants.
98+
99+
Examples
100+
--------
101+
>>> ConstantMap(RX_FIFO_DEPTH=16)
102+
ConstantMap([('RX_FIFO_DEPTH', ConstantInt(16, width=5, signed=False))])
103+
"""
104+
def __init__(self, **constants):
105+
self._storage = OrderedDict()
106+
for key, value in constants.items():
107+
if isinstance(value, bool):
108+
value = ConstantBool(value)
109+
if isinstance(value, int):
110+
value = ConstantInt(value)
111+
if not isinstance(value, ConstantValue):
112+
raise TypeError("Constant value must be an instance of ConstantValue, not {!r}"
113+
.format(value))
114+
self._storage[key] = value
115+
116+
def __getitem__(self, key):
117+
return self._storage[key]
118+
119+
def __iter__(self):
120+
yield from self._storage
121+
122+
def __len__(self):
123+
return len(self._storage)
124+
125+
def __repr__(self):
126+
return "ConstantMap({})".format(list(self._storage.items()))
6127

7128

8129
class PeripheralInfo:
9130
"""Peripheral metadata.
10131
11132
A unified description of the local resources of a peripheral. It may be queried in order to
12-
recover its memory windows, CSR registers and event sources.
133+
recover its memory windows, CSR registers, event sources and configuration constants.
13134
14135
Parameters
15136
----------
16137
memory_map : :class:`MemoryMap`
17138
Memory map of the peripheral.
18139
irq : :class:`event.Source`
19140
IRQ line of the peripheral. Optional.
141+
constant_map : :class:`ConstantMap`
142+
Constant map of the peripheral. Optional.
20143
"""
21-
def __init__(self, *, memory_map, irq=None):
144+
def __init__(self, *, memory_map, irq=None, constant_map=None):
22145
if not isinstance(memory_map, MemoryMap):
23146
raise TypeError("Memory map must be an instance of MemoryMap, not {!r}"
24147
.format(memory_map))
@@ -30,6 +153,13 @@ def __init__(self, *, memory_map, irq=None):
30153
.format(irq))
31154
self._irq = irq
32155

156+
if constant_map is None:
157+
constant_map = ConstantMap()
158+
if not isinstance(constant_map, ConstantMap):
159+
raise TypeError("Constant map must be an instance of ConstantMap, not {!r}"
160+
.format(constant_map))
161+
self._constant_map = constant_map
162+
33163
@property
34164
def memory_map(self):
35165
"""Memory map.
@@ -57,3 +187,13 @@ def irq(self):
57187
raise NotImplementedError("Peripheral info does not have an IRQ line"
58188
.format(self))
59189
return self._irq
190+
191+
@property
192+
def constant_map(self):
193+
"""Constant map.
194+
195+
Return value
196+
------------
197+
A :class:`ConstantMap` containing configuration constants of the peripheral.
198+
"""
199+
return self._constant_map

nmigen_soc/test/test_periph.py

+115-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,100 @@
11
import unittest
22

3-
from ..periph import PeripheralInfo
3+
from ..periph import *
44
from ..memory import MemoryMap
55
from .. import event
66

77

8+
class ConstantBoolTestCase(unittest.TestCase):
9+
def test_init(self):
10+
a = ConstantBool(True)
11+
b = ConstantBool(False)
12+
self.assertTrue(a.value)
13+
self.assertFalse(b.value)
14+
15+
def test_value_wrong(self):
16+
with self.assertRaisesRegex(TypeError, r"Value must be a bool, not 'foo'"):
17+
ConstantBool("foo")
18+
19+
def test_repr(self):
20+
self.assertEqual(repr(ConstantBool(True)), "ConstantBool(True)")
21+
22+
23+
class ConstantIntTestCase(unittest.TestCase):
24+
def test_init(self):
25+
c = ConstantInt(5, width=8, signed=True)
26+
self.assertEqual(c.value, 5)
27+
self.assertEqual(c.width, 8)
28+
self.assertEqual(c.signed, True)
29+
30+
def test_init_default(self):
31+
c = ConstantInt(5)
32+
self.assertEqual(c.value, 5)
33+
self.assertEqual(c.width, 3)
34+
self.assertEqual(c.signed, False)
35+
36+
def test_value_wrong(self):
37+
with self.assertRaisesRegex(TypeError, r"Value must be an integer, not 'foo'"):
38+
ConstantInt("foo")
39+
40+
def test_width_wrong(self):
41+
with self.assertRaisesRegex(TypeError, r"Width must be an integer, not 'foo'"):
42+
ConstantInt(5, width="foo")
43+
44+
def test_width_overflow(self):
45+
with self.assertRaisesRegex(ValueError,
46+
r"Width must be greater than or equal to the number of bits needed to represent 5"):
47+
ConstantInt(5, width=1)
48+
49+
def test_signed_wrong(self):
50+
with self.assertRaisesRegex(TypeError, r"Signedness must be a bool, not 'foo'"):
51+
ConstantInt(5, signed="foo")
52+
53+
def test_repr(self):
54+
self.assertEqual(
55+
repr(ConstantInt(-5, width=8, signed=True)),
56+
"ConstantInt(-5, width=8, signed=True)"
57+
)
58+
59+
60+
class ConstantMapTestCase(unittest.TestCase):
61+
def test_init(self):
62+
constant_map = ConstantMap(A=5, B=True, C=ConstantBool(False))
63+
self.assertEqual(
64+
repr(constant_map), "ConstantMap(["
65+
"('A', ConstantInt(5, width=3, signed=False)), "
66+
"('B', ConstantBool(True)), "
67+
"('C', ConstantBool(False))])",
68+
)
69+
70+
def test_init_wrong_value(self):
71+
with self.assertRaisesRegex(TypeError,
72+
r"Constant value must be an instance of ConstantValue, not \('foo', 'bar'\)"):
73+
ConstantMap(A=("foo", "bar"))
74+
75+
def test_getitem(self):
76+
a = ConstantInt(1)
77+
b = ConstantBool(False)
78+
constant_map = ConstantMap(A=a, B=b)
79+
self.assertIs(constant_map["A"], a)
80+
self.assertIs(constant_map["B"], b)
81+
82+
def test_iter(self):
83+
a = ConstantInt(1)
84+
b = ConstantBool(False)
85+
constant_map = ConstantMap(B=b, A=a)
86+
self.assertEqual(list(constant_map.items()), [
87+
("B", b),
88+
("A", a),
89+
])
90+
91+
def test_len(self):
92+
a = ConstantInt(1)
93+
b = ConstantBool(False)
94+
constant_map = ConstantMap(B=b, A=a)
95+
self.assertEqual(len(constant_map), 2)
96+
97+
898
class PeripheralInfoTestCase(unittest.TestCase):
999
def test_memory_map(self):
10100
memory_map = MemoryMap(addr_width=1, data_width=8)
@@ -48,3 +138,27 @@ def test_irq_wrong(self):
48138
with self.assertRaisesRegex(TypeError,
49139
r"IRQ line must be an instance of event.Source, not 'foo'"):
50140
info = PeripheralInfo(memory_map=memory_map, irq="foo")
141+
142+
def test_constant_map(self):
143+
constant_map = ConstantMap()
144+
memory_map = MemoryMap(addr_width=1, data_width=8)
145+
info = PeripheralInfo(memory_map=memory_map, constant_map=constant_map)
146+
self.assertIs(info.constant_map, constant_map)
147+
148+
def test_constant_map_none(self):
149+
memory_map = MemoryMap(addr_width=1, data_width=8)
150+
info = PeripheralInfo(memory_map=memory_map, constant_map=None)
151+
self.assertIsInstance(info.constant_map, ConstantMap)
152+
self.assertEqual(info.constant_map, {})
153+
154+
def test_constant_map_default(self):
155+
memory_map = MemoryMap(addr_width=1, data_width=8)
156+
info = PeripheralInfo(memory_map=memory_map)
157+
self.assertIsInstance(info.constant_map, ConstantMap)
158+
self.assertEqual(info.constant_map, {})
159+
160+
def test_constant_map_wrong(self):
161+
memory_map = MemoryMap(addr_width=1, data_width=8)
162+
with self.assertRaisesRegex(TypeError,
163+
r"Constant map must be an instance of ConstantMap, not 'foo'"):
164+
info = PeripheralInfo(memory_map=memory_map, constant_map="foo")

0 commit comments

Comments
 (0)