hdl.rec: migrate Record from UserValue to ValueCastable.
[nmigen.git] / tests / test_hdl_rec.py
1 from enum import Enum
2
3 from nmigen.hdl.ast import *
4 from nmigen.hdl.rec import *
5
6 from .utils import *
7
8
9 class UnsignedEnum(Enum):
10 FOO = 1
11 BAR = 2
12 BAZ = 3
13
14
15 class LayoutTestCase(FHDLTestCase):
16 def assertFieldEqual(self, field, expected):
17 (shape, dir) = field
18 shape = Shape.cast(shape)
19 self.assertEqual((shape, dir), expected)
20
21 def test_fields(self):
22 layout = Layout.cast([
23 ("cyc", 1),
24 ("data", signed(32)),
25 ("stb", 1, DIR_FANOUT),
26 ("ack", 1, DIR_FANIN),
27 ("info", [
28 ("a", 1),
29 ("b", 1),
30 ])
31 ])
32
33 self.assertFieldEqual(layout["cyc"], ((1, False), DIR_NONE))
34 self.assertFieldEqual(layout["data"], ((32, True), DIR_NONE))
35 self.assertFieldEqual(layout["stb"], ((1, False), DIR_FANOUT))
36 self.assertFieldEqual(layout["ack"], ((1, False), DIR_FANIN))
37 sublayout = layout["info"][0]
38 self.assertEqual(layout["info"][1], DIR_NONE)
39 self.assertFieldEqual(sublayout["a"], ((1, False), DIR_NONE))
40 self.assertFieldEqual(sublayout["b"], ((1, False), DIR_NONE))
41
42 def test_enum_field(self):
43 layout = Layout.cast([
44 ("enum", UnsignedEnum),
45 ("enum_dir", UnsignedEnum, DIR_FANOUT),
46 ])
47 self.assertFieldEqual(layout["enum"], ((2, False), DIR_NONE))
48 self.assertFieldEqual(layout["enum_dir"], ((2, False), DIR_FANOUT))
49
50 def test_range_field(self):
51 layout = Layout.cast([
52 ("range", range(0, 7)),
53 ])
54 self.assertFieldEqual(layout["range"], ((3, False), DIR_NONE))
55
56 def test_slice_tuple(self):
57 layout = Layout.cast([
58 ("a", 1),
59 ("b", 2),
60 ("c", 3)
61 ])
62 expect = Layout.cast([
63 ("a", 1),
64 ("c", 3)
65 ])
66 self.assertEqual(layout["a", "c"], expect)
67
68 def test_repr(self):
69 self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", signed(2))])),
70 "Layout([('a', unsigned(1)), ('b', signed(2))])")
71 self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", [("c", signed(3))])])),
72 "Layout([('a', unsigned(1)), "
73 "('b', Layout([('c', signed(3))]))])")
74
75 def test_wrong_field(self):
76 with self.assertRaisesRegex(TypeError,
77 (r"^Field \(1,\) has invalid layout: should be either \(name, shape\) or "
78 r"\(name, shape, direction\)$")):
79 Layout.cast([(1,)])
80
81 def test_wrong_name(self):
82 with self.assertRaisesRegex(TypeError,
83 r"^Field \(1, 1\) has invalid name: should be a string$"):
84 Layout.cast([(1, 1)])
85
86 def test_wrong_name_duplicate(self):
87 with self.assertRaisesRegex(NameError,
88 r"^Field \('a', 2\) has a name that is already present in the layout$"):
89 Layout.cast([("a", 1), ("a", 2)])
90
91 def test_wrong_direction(self):
92 with self.assertRaisesRegex(TypeError,
93 (r"^Field \('a', 1, 0\) has invalid direction: should be a Direction "
94 r"instance like DIR_FANIN$")):
95 Layout.cast([("a", 1, 0)])
96
97 def test_wrong_shape(self):
98 with self.assertRaisesRegex(TypeError,
99 (r"^Field \('a', 'x'\) has invalid shape: should be castable to Shape or "
100 r"a list of fields of a nested record$")):
101 Layout.cast([("a", "x")])
102
103
104 class RecordTestCase(FHDLTestCase):
105 def test_basic(self):
106 r = Record([
107 ("stb", 1),
108 ("data", 32),
109 ("info", [
110 ("a", 1),
111 ("b", 1),
112 ])
113 ])
114
115 self.assertEqual(repr(r), "(rec r stb data (rec r__info a b))")
116 self.assertEqual(len(r), 35)
117 self.assertIsInstance(r.stb, Signal)
118 self.assertEqual(r.stb.name, "r__stb")
119 self.assertEqual(r["stb"].name, "r__stb")
120
121 self.assertTrue(hasattr(r, "stb"))
122 self.assertFalse(hasattr(r, "xxx"))
123
124 def test_unnamed(self):
125 r = [Record([
126 ("stb", 1)
127 ])][0]
128
129 self.assertEqual(repr(r), "(rec <unnamed> stb)")
130 self.assertEqual(r.stb.name, "stb")
131
132 def test_iter(self):
133 r = Record([
134 ("data", 4),
135 ("stb", 1),
136 ])
137
138 self.assertEqual(repr(r[0]), "(slice (cat (sig r__data) (sig r__stb)) 0:1)")
139 self.assertEqual(repr(r[0:3]), "(slice (cat (sig r__data) (sig r__stb)) 0:3)")
140
141 def test_wrong_field(self):
142 r = Record([
143 ("stb", 1),
144 ("ack", 1),
145 ])
146 with self.assertRaisesRegex(AttributeError,
147 r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
148 r["en"]
149 with self.assertRaisesRegex(AttributeError,
150 r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
151 r.en
152
153 def test_wrong_field_unnamed(self):
154 r = [Record([
155 ("stb", 1),
156 ("ack", 1),
157 ])][0]
158 with self.assertRaisesRegex(AttributeError,
159 r"^Unnamed record does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
160 r.en
161
162 def test_construct_with_fields(self):
163 ns = Signal(1)
164 nr = Record([
165 ("burst", 1)
166 ])
167 r = Record([
168 ("stb", 1),
169 ("info", [
170 ("burst", 1)
171 ])
172 ], fields={
173 "stb": ns,
174 "info": nr
175 })
176 self.assertIs(r.stb, ns)
177 self.assertIs(r.info, nr)
178
179 def test_like(self):
180 r1 = Record([("a", 1), ("b", 2)])
181 r2 = Record.like(r1)
182 self.assertEqual(r1.layout, r2.layout)
183 self.assertEqual(r2.name, "r2")
184 r3 = Record.like(r1, name="foo")
185 self.assertEqual(r3.name, "foo")
186 r4 = Record.like(r1, name_suffix="foo")
187 self.assertEqual(r4.name, "r1foo")
188
189 def test_like_modifications(self):
190 r1 = Record([("a", 1), ("b", [("s", 1)])])
191 self.assertEqual(r1.a.name, "r1__a")
192 self.assertEqual(r1.b.name, "r1__b")
193 self.assertEqual(r1.b.s.name, "r1__b__s")
194 r1.a.reset = 1
195 r1.b.s.reset = 1
196 r2 = Record.like(r1)
197 self.assertEqual(r2.a.reset, 1)
198 self.assertEqual(r2.b.s.reset, 1)
199 self.assertEqual(r2.a.name, "r2__a")
200 self.assertEqual(r2.b.name, "r2__b")
201 self.assertEqual(r2.b.s.name, "r2__b__s")
202
203 def test_slice_tuple(self):
204 r1 = Record([("a", 1), ("b", 2), ("c", 3)])
205 r2 = r1["a", "c"]
206 self.assertEqual(r2.layout, Layout([("a", 1), ("c", 3)]))
207 self.assertIs(r2.a, r1.a)
208 self.assertIs(r2.c, r1.c)
209
210 def test_enum_decoder(self):
211 r1 = Record([("a", UnsignedEnum)])
212 self.assertEqual(r1.a.decoder(UnsignedEnum.FOO), "FOO/1")
213
214
215 class ConnectTestCase(FHDLTestCase):
216 def setUp_flat(self):
217 self.core_layout = [
218 ("addr", 32, DIR_FANOUT),
219 ("data_r", 32, DIR_FANIN),
220 ("data_w", 32, DIR_FANIN),
221 ]
222 self.periph_layout = [
223 ("addr", 32, DIR_FANOUT),
224 ("data_r", 32, DIR_FANIN),
225 ("data_w", 32, DIR_FANIN),
226 ]
227
228 def setUp_nested(self):
229 self.core_layout = [
230 ("addr", 32, DIR_FANOUT),
231 ("data", [
232 ("r", 32, DIR_FANIN),
233 ("w", 32, DIR_FANIN),
234 ]),
235 ]
236 self.periph_layout = [
237 ("addr", 32, DIR_FANOUT),
238 ("data", [
239 ("r", 32, DIR_FANIN),
240 ("w", 32, DIR_FANIN),
241 ]),
242 ]
243
244 def test_flat(self):
245 self.setUp_flat()
246
247 core = Record(self.core_layout)
248 periph1 = Record(self.periph_layout)
249 periph2 = Record(self.periph_layout)
250
251 stmts = core.connect(periph1, periph2)
252 self.assertRepr(stmts, """(
253 (eq (sig periph1__addr) (sig core__addr))
254 (eq (sig periph2__addr) (sig core__addr))
255 (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r)))
256 (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w)))
257 )""")
258
259 def test_flat_include(self):
260 self.setUp_flat()
261
262 core = Record(self.core_layout)
263 periph1 = Record(self.periph_layout)
264 periph2 = Record(self.periph_layout)
265
266 stmts = core.connect(periph1, periph2, include={"addr": True})
267 self.assertRepr(stmts, """(
268 (eq (sig periph1__addr) (sig core__addr))
269 (eq (sig periph2__addr) (sig core__addr))
270 )""")
271
272 def test_flat_exclude(self):
273 self.setUp_flat()
274
275 core = Record(self.core_layout)
276 periph1 = Record(self.periph_layout)
277 periph2 = Record(self.periph_layout)
278
279 stmts = core.connect(periph1, periph2, exclude={"addr": True})
280 self.assertRepr(stmts, """(
281 (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r)))
282 (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w)))
283 )""")
284
285 def test_nested(self):
286 self.setUp_nested()
287
288 core = Record(self.core_layout)
289 periph1 = Record(self.periph_layout)
290 periph2 = Record(self.periph_layout)
291
292 stmts = core.connect(periph1, periph2)
293 self.maxDiff = None
294 self.assertRepr(stmts, """(
295 (eq (sig periph1__addr) (sig core__addr))
296 (eq (sig periph2__addr) (sig core__addr))
297 (eq (sig core__data__r) (| (sig periph1__data__r) (sig periph2__data__r)))
298 (eq (sig core__data__w) (| (sig periph1__data__w) (sig periph2__data__w)))
299 )""")
300
301 def test_wrong_include_exclude(self):
302 self.setUp_flat()
303
304 core = Record(self.core_layout)
305 periph = Record(self.periph_layout)
306
307 with self.assertRaisesRegex(AttributeError,
308 r"^Cannot include field 'foo' because it is not present in record 'core'$"):
309 core.connect(periph, include={"foo": True})
310
311 with self.assertRaisesRegex(AttributeError,
312 r"^Cannot exclude field 'foo' because it is not present in record 'core'$"):
313 core.connect(periph, exclude={"foo": True})
314
315 def test_wrong_direction(self):
316 recs = [Record([("x", 1)]) for _ in range(2)]
317
318 with self.assertRaisesRegex(TypeError,
319 (r"^Cannot connect field 'x' of unnamed record because it does not have "
320 r"a direction$")):
321 recs[0].connect(recs[1])
322
323 def test_wrong_missing_field(self):
324 core = Record([("addr", 32, DIR_FANOUT)])
325 periph = Record([])
326
327 with self.assertRaisesRegex(AttributeError,
328 (r"^Cannot connect field 'addr' of record 'core' to subordinate record 'periph' "
329 r"because the subordinate record does not have this field$")):
330 core.connect(periph)