Skip to content

Commit 0c6405f

Browse files
author
Jean-François Nguyen
committed
periph: add a ConstantMap container for configuration constants.
Resolves amaranth-lang#24.
1 parent e352e2d commit 0c6405f

File tree

2 files changed

+257
-4
lines changed

2 files changed

+257
-4
lines changed

nmigen_soc/periph.py

+142-3
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,146 @@
1+
from collections.abc import Mapping
2+
3+
from nmigen.utils import bits_for
4+
15
from .memory import MemoryMap
26
from . import event
37

48

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

7127

8128
class PeripheralInfo:
9129
"""Peripheral metadata.
10130
11131
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.
132+
recover its memory windows, CSR registers, event sources and configuration constants.
13133
14134
Parameters
15135
----------
16136
memory_map : :class:`MemoryMap`
17137
Memory map of the peripheral.
18138
irq : :class:`event.Source`
19139
IRQ line of the peripheral. Optional.
140+
constant_map : :class:`ConstantMap`
141+
Constant map of the peripheral. Optional.
20142
"""
21-
def __init__(self, *, memory_map, irq=None):
143+
def __init__(self, *, memory_map, irq=None, constant_map=None):
22144
if not isinstance(memory_map, MemoryMap):
23145
raise TypeError("Memory map must be an instance of MemoryMap, not {!r}"
24146
.format(memory_map))
@@ -30,6 +152,13 @@ def __init__(self, *, memory_map, irq=None):
30152
.format(irq))
31153
self._irq = irq
32154

155+
if constant_map is None:
156+
constant_map = ConstantMap()
157+
if not isinstance(constant_map, ConstantMap):
158+
raise TypeError("Constant map must be an instance of ConstantMap, not {!r}"
159+
.format(constant_map))
160+
self._constant_map = constant_map
161+
33162
@property
34163
def memory_map(self):
35164
"""Memory map.
@@ -57,3 +186,13 @@ def irq(self):
57186
raise NotImplementedError("Peripheral info does not have an IRQ line"
58187
.format(self))
59188
return self._irq
189+
190+
@property
191+
def constant_map(self):
192+
"""Constant map.
193+
194+
Return value
195+
------------
196+
A :class:`ConstantMap` containing configuration constants of the peripheral.
197+
"""
198+
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),
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+
("A", a),
88+
("B", b),
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)