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