build.run: implement SSH remote builds using Paramiko.
[nmigen.git] / nmigen / test / test_hdl_dsl.py
1 # nmigen: UnusedElaboratable=no
2
3 from collections import OrderedDict
4 from enum import Enum
5
6 from ..hdl.ast import *
7 from ..hdl.cd import *
8 from ..hdl.dsl import *
9 from .utils import *
10
11
12 class DSLTestCase(FHDLTestCase):
13 def setUp(self):
14 self.s1 = Signal()
15 self.s2 = Signal()
16 self.s3 = Signal()
17 self.c1 = Signal()
18 self.c2 = Signal()
19 self.c3 = Signal()
20 self.w1 = Signal(4)
21
22 def test_cant_inherit(self):
23 with self.assertRaisesRegex(SyntaxError,
24 (r"^Instead of inheriting from `Module`, inherit from `Elaboratable` and "
25 r"return a `Module` from the `elaborate\(self, platform\)` method$")):
26 class ORGate(Module):
27 pass
28
29 def test_d_comb(self):
30 m = Module()
31 m.d.comb += self.c1.eq(1)
32 m._flush()
33 self.assertEqual(m._driving[self.c1], None)
34 self.assertRepr(m._statements, """(
35 (eq (sig c1) (const 1'd1))
36 )""")
37
38 def test_d_sync(self):
39 m = Module()
40 m.d.sync += self.c1.eq(1)
41 m._flush()
42 self.assertEqual(m._driving[self.c1], "sync")
43 self.assertRepr(m._statements, """(
44 (eq (sig c1) (const 1'd1))
45 )""")
46
47 def test_d_pix(self):
48 m = Module()
49 m.d.pix += self.c1.eq(1)
50 m._flush()
51 self.assertEqual(m._driving[self.c1], "pix")
52 self.assertRepr(m._statements, """(
53 (eq (sig c1) (const 1'd1))
54 )""")
55
56 def test_d_index(self):
57 m = Module()
58 m.d["pix"] += self.c1.eq(1)
59 m._flush()
60 self.assertEqual(m._driving[self.c1], "pix")
61 self.assertRepr(m._statements, """(
62 (eq (sig c1) (const 1'd1))
63 )""")
64
65 def test_d_no_conflict(self):
66 m = Module()
67 m.d.comb += self.w1[0].eq(1)
68 m.d.comb += self.w1[1].eq(1)
69
70 def test_d_conflict(self):
71 m = Module()
72 with self.assertRaisesRegex(SyntaxError,
73 (r"^Driver-driver conflict: trying to drive \(sig c1\) from d\.sync, but it "
74 r"is already driven from d\.comb$")):
75 m.d.comb += self.c1.eq(1)
76 m.d.sync += self.c1.eq(1)
77
78 def test_d_wrong(self):
79 m = Module()
80 with self.assertRaisesRegex(AttributeError,
81 r"^Cannot assign 'd\.pix' attribute; did you mean 'd.pix \+='\?$"):
82 m.d.pix = None
83
84 def test_d_asgn_wrong(self):
85 m = Module()
86 with self.assertRaisesRegex(SyntaxError,
87 r"^Only assignments and property checks may be appended to d\.sync$"):
88 m.d.sync += Switch(self.s1, {})
89
90 def test_comb_wrong(self):
91 m = Module()
92 with self.assertRaisesRegex(AttributeError,
93 r"^'Module' object has no attribute 'comb'; did you mean 'd\.comb'\?$"):
94 m.comb += self.c1.eq(1)
95
96 def test_sync_wrong(self):
97 m = Module()
98 with self.assertRaisesRegex(AttributeError,
99 r"^'Module' object has no attribute 'sync'; did you mean 'd\.sync'\?$"):
100 m.sync += self.c1.eq(1)
101
102 def test_attr_wrong(self):
103 m = Module()
104 with self.assertRaisesRegex(AttributeError,
105 r"^'Module' object has no attribute 'nonexistentattr'$"):
106 m.nonexistentattr
107
108 def test_d_suspicious(self):
109 m = Module()
110 with self.assertWarnsRegex(SyntaxWarning,
111 (r"^Using '<module>\.d\.submodules' would add statements to clock domain "
112 r"'submodules'; did you mean <module>\.submodules instead\?$")):
113 m.d.submodules += []
114
115 def test_clock_signal(self):
116 m = Module()
117 m.d.comb += ClockSignal("pix").eq(ClockSignal())
118 self.assertRepr(m._statements, """
119 (
120 (eq (clk pix) (clk sync))
121 )
122 """)
123
124 def test_reset_signal(self):
125 m = Module()
126 m.d.comb += ResetSignal("pix").eq(1)
127 self.assertRepr(m._statements, """
128 (
129 (eq (rst pix) (const 1'd1))
130 )
131 """)
132
133 def test_sample_domain(self):
134 m = Module()
135 i = Signal()
136 o1 = Signal()
137 o2 = Signal()
138 o3 = Signal()
139 m.d.sync += o1.eq(Past(i))
140 m.d.pix += o2.eq(Past(i))
141 m.d.pix += o3.eq(Past(i, domain="sync"))
142 f = m.elaborate(platform=None)
143 self.assertRepr(f.statements, """
144 (
145 (eq (sig o1) (sample (sig i) @ sync[1]))
146 (eq (sig o2) (sample (sig i) @ pix[1]))
147 (eq (sig o3) (sample (sig i) @ sync[1]))
148 )
149 """)
150
151 def test_If(self):
152 m = Module()
153 with m.If(self.s1):
154 m.d.comb += self.c1.eq(1)
155 m._flush()
156 self.assertRepr(m._statements, """
157 (
158 (switch (cat (sig s1))
159 (case 1 (eq (sig c1) (const 1'd1)))
160 )
161 )
162 """)
163
164 def test_If_Elif(self):
165 m = Module()
166 with m.If(self.s1):
167 m.d.comb += self.c1.eq(1)
168 with m.Elif(self.s2):
169 m.d.sync += self.c2.eq(0)
170 m._flush()
171 self.assertRepr(m._statements, """
172 (
173 (switch (cat (sig s1) (sig s2))
174 (case -1 (eq (sig c1) (const 1'd1)))
175 (case 1- (eq (sig c2) (const 1'd0)))
176 )
177 )
178 """)
179
180 def test_If_Elif_Else(self):
181 m = Module()
182 with m.If(self.s1):
183 m.d.comb += self.c1.eq(1)
184 with m.Elif(self.s2):
185 m.d.sync += self.c2.eq(0)
186 with m.Else():
187 m.d.comb += self.c3.eq(1)
188 m._flush()
189 self.assertRepr(m._statements, """
190 (
191 (switch (cat (sig s1) (sig s2))
192 (case -1 (eq (sig c1) (const 1'd1)))
193 (case 1- (eq (sig c2) (const 1'd0)))
194 (default (eq (sig c3) (const 1'd1)))
195 )
196 )
197 """)
198
199 def test_If_If(self):
200 m = Module()
201 with m.If(self.s1):
202 m.d.comb += self.c1.eq(1)
203 with m.If(self.s2):
204 m.d.comb += self.c2.eq(1)
205 m._flush()
206 self.assertRepr(m._statements, """
207 (
208 (switch (cat (sig s1))
209 (case 1 (eq (sig c1) (const 1'd1)))
210 )
211 (switch (cat (sig s2))
212 (case 1 (eq (sig c2) (const 1'd1)))
213 )
214 )
215 """)
216
217 def test_If_nested_If(self):
218 m = Module()
219 with m.If(self.s1):
220 m.d.comb += self.c1.eq(1)
221 with m.If(self.s2):
222 m.d.comb += self.c2.eq(1)
223 m._flush()
224 self.assertRepr(m._statements, """
225 (
226 (switch (cat (sig s1))
227 (case 1 (eq (sig c1) (const 1'd1))
228 (switch (cat (sig s2))
229 (case 1 (eq (sig c2) (const 1'd1)))
230 )
231 )
232 )
233 )
234 """)
235
236 def test_If_dangling_Else(self):
237 m = Module()
238 with m.If(self.s1):
239 m.d.comb += self.c1.eq(1)
240 with m.If(self.s2):
241 m.d.comb += self.c2.eq(1)
242 with m.Else():
243 m.d.comb += self.c3.eq(1)
244 m._flush()
245 self.assertRepr(m._statements, """
246 (
247 (switch (cat (sig s1))
248 (case 1
249 (eq (sig c1) (const 1'd1))
250 (switch (cat (sig s2))
251 (case 1 (eq (sig c2) (const 1'd1)))
252 )
253 )
254 (default
255 (eq (sig c3) (const 1'd1))
256 )
257 )
258 )
259 """)
260
261 def test_Elif_wrong(self):
262 m = Module()
263 with self.assertRaisesRegex(SyntaxError,
264 r"^Elif without preceding If$"):
265 with m.Elif(self.s2):
266 pass
267
268 def test_Else_wrong(self):
269 m = Module()
270 with self.assertRaisesRegex(SyntaxError,
271 r"^Else without preceding If\/Elif$"):
272 with m.Else():
273 pass
274
275 def test_If_wide(self):
276 m = Module()
277 with m.If(self.w1):
278 m.d.comb += self.c1.eq(1)
279 m._flush()
280 self.assertRepr(m._statements, """
281 (
282 (switch (cat (b (sig w1)))
283 (case 1 (eq (sig c1) (const 1'd1)))
284 )
285 )
286 """)
287
288 def test_If_signed_suspicious(self):
289 m = Module()
290 with self.assertWarnsRegex(SyntaxWarning,
291 (r"^Signed values in If\/Elif conditions usually result from inverting Python "
292 r"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
293 r"`not flag`\. \(If this is a false positive, silence this warning with "
294 r"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$")):
295 with m.If(~True):
296 pass
297
298 def test_Elif_signed_suspicious(self):
299 m = Module()
300 with m.If(0):
301 pass
302 with self.assertWarnsRegex(SyntaxWarning,
303 (r"^Signed values in If\/Elif conditions usually result from inverting Python "
304 r"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
305 r"`not flag`\. \(If this is a false positive, silence this warning with "
306 r"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$")):
307 with m.Elif(~True):
308 pass
309
310 def test_if_If_Elif_Else(self):
311 m = Module()
312 with self.assertRaisesRegex(SyntaxError,
313 r"^`if m\.If\(\.\.\.\):` does not work; use `with m\.If\(\.\.\.\)`$"):
314 if m.If(0):
315 pass
316 with m.If(0):
317 pass
318 with self.assertRaisesRegex(SyntaxError,
319 r"^`if m\.Elif\(\.\.\.\):` does not work; use `with m\.Elif\(\.\.\.\)`$"):
320 if m.Elif(0):
321 pass
322 with self.assertRaisesRegex(SyntaxError,
323 r"^`if m\.Else\(\.\.\.\):` does not work; use `with m\.Else\(\.\.\.\)`$"):
324 if m.Else():
325 pass
326
327 def test_Switch(self):
328 m = Module()
329 with m.Switch(self.w1):
330 with m.Case(3):
331 m.d.comb += self.c1.eq(1)
332 with m.Case("11--"):
333 m.d.comb += self.c2.eq(1)
334 with m.Case("1 0--"):
335 m.d.comb += self.c2.eq(1)
336 m._flush()
337 self.assertRepr(m._statements, """
338 (
339 (switch (sig w1)
340 (case 0011 (eq (sig c1) (const 1'd1)))
341 (case 11-- (eq (sig c2) (const 1'd1)))
342 (case 10-- (eq (sig c2) (const 1'd1)))
343 )
344 )
345 """)
346
347 def test_Switch_default_Case(self):
348 m = Module()
349 with m.Switch(self.w1):
350 with m.Case(3):
351 m.d.comb += self.c1.eq(1)
352 with m.Case():
353 m.d.comb += self.c2.eq(1)
354 m._flush()
355 self.assertRepr(m._statements, """
356 (
357 (switch (sig w1)
358 (case 0011 (eq (sig c1) (const 1'd1)))
359 (default (eq (sig c2) (const 1'd1)))
360 )
361 )
362 """)
363
364 def test_Switch_default_Default(self):
365 m = Module()
366 with m.Switch(self.w1):
367 with m.Case(3):
368 m.d.comb += self.c1.eq(1)
369 with m.Default():
370 m.d.comb += self.c2.eq(1)
371 m._flush()
372 self.assertRepr(m._statements, """
373 (
374 (switch (sig w1)
375 (case 0011 (eq (sig c1) (const 1'd1)))
376 (default (eq (sig c2) (const 1'd1)))
377 )
378 )
379 """)
380
381 def test_Switch_const_test(self):
382 m = Module()
383 with m.Switch(1):
384 with m.Case(1):
385 m.d.comb += self.c1.eq(1)
386 m._flush()
387 self.assertRepr(m._statements, """
388 (
389 (switch (const 1'd1)
390 (case 1 (eq (sig c1) (const 1'd1)))
391 )
392 )
393 """)
394
395 def test_Switch_enum(self):
396 class Color(Enum):
397 RED = 1
398 BLUE = 2
399 m = Module()
400 se = Signal(Color)
401 with m.Switch(se):
402 with m.Case(Color.RED):
403 m.d.comb += self.c1.eq(1)
404 self.assertRepr(m._statements, """
405 (
406 (switch (sig se)
407 (case 01 (eq (sig c1) (const 1'd1)))
408 )
409 )
410 """)
411
412 def test_Case_width_wrong(self):
413 class Color(Enum):
414 RED = 0b10101010
415 m = Module()
416 with m.Switch(self.w1):
417 with self.assertRaisesRegex(SyntaxError,
418 r"^Case pattern '--' must have the same width as switch value \(which is 4\)$"):
419 with m.Case("--"):
420 pass
421 with self.assertWarnsRegex(SyntaxWarning,
422 (r"^Case pattern '10110' is wider than switch value \(which has width 4\); "
423 r"comparison will never be true$")):
424 with m.Case(0b10110):
425 pass
426 with self.assertWarnsRegex(SyntaxWarning,
427 (r"^Case pattern '10101010' \(Color\.RED\) is wider than switch value "
428 r"\(which has width 4\); comparison will never be true$")):
429 with m.Case(Color.RED):
430 pass
431 self.assertRepr(m._statements, """
432 (
433 (switch (sig w1) )
434 )
435 """)
436
437 def test_Case_bits_wrong(self):
438 m = Module()
439 with m.Switch(self.w1):
440 with self.assertRaisesRegex(SyntaxError,
441 (r"^Case pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
442 r"and may include whitespace$")):
443 with m.Case("abc"):
444 pass
445
446 def test_Case_pattern_wrong(self):
447 m = Module()
448 with m.Switch(self.w1):
449 with self.assertRaisesRegex(SyntaxError,
450 r"^Case pattern must be an integer, a string, or an enumeration, not 1\.0$"):
451 with m.Case(1.0):
452 pass
453
454 def test_Case_outside_Switch_wrong(self):
455 m = Module()
456 with self.assertRaisesRegex(SyntaxError,
457 r"^Case is not permitted outside of Switch$"):
458 with m.Case():
459 pass
460
461 def test_If_inside_Switch_wrong(self):
462 m = Module()
463 with m.Switch(self.s1):
464 with self.assertRaisesRegex(SyntaxError,
465 (r"^If is not permitted directly inside of Switch; "
466 r"it is permitted inside of Switch Case$")):
467 with m.If(self.s2):
468 pass
469
470 def test_FSM_basic(self):
471 a = Signal()
472 b = Signal()
473 c = Signal()
474 m = Module()
475 with m.FSM():
476 with m.State("FIRST"):
477 m.d.comb += a.eq(1)
478 m.next = "SECOND"
479 with m.State("SECOND"):
480 m.d.sync += b.eq(~b)
481 with m.If(c):
482 m.next = "FIRST"
483 m._flush()
484 self.assertRepr(m._statements, """
485 (
486 (switch (sig fsm_state)
487 (case 0
488 (eq (sig a) (const 1'd1))
489 (eq (sig fsm_state) (const 1'd1))
490 )
491 (case 1
492 (eq (sig b) (~ (sig b)))
493 (switch (cat (sig c))
494 (case 1
495 (eq (sig fsm_state) (const 1'd0)))
496 )
497 )
498 )
499 )
500 """)
501 self.assertEqual({repr(k): v for k, v in m._driving.items()}, {
502 "(sig a)": None,
503 "(sig fsm_state)": "sync",
504 "(sig b)": "sync",
505 })
506
507 frag = m.elaborate(platform=None)
508 fsm = frag.find_generated("fsm")
509 self.assertIsInstance(fsm.state, Signal)
510 self.assertEqual(fsm.encoding, OrderedDict({
511 "FIRST": 0,
512 "SECOND": 1,
513 }))
514 self.assertEqual(fsm.decoding, OrderedDict({
515 0: "FIRST",
516 1: "SECOND"
517 }))
518
519 def test_FSM_reset(self):
520 a = Signal()
521 m = Module()
522 with m.FSM(reset="SECOND"):
523 with m.State("FIRST"):
524 m.d.comb += a.eq(0)
525 m.next = "SECOND"
526 with m.State("SECOND"):
527 m.next = "FIRST"
528 m._flush()
529 self.assertRepr(m._statements, """
530 (
531 (switch (sig fsm_state)
532 (case 0
533 (eq (sig a) (const 1'd0))
534 (eq (sig fsm_state) (const 1'd1))
535 )
536 (case 1
537 (eq (sig fsm_state) (const 1'd0))
538 )
539 )
540 )
541 """)
542
543 def test_FSM_ongoing(self):
544 a = Signal()
545 b = Signal()
546 m = Module()
547 with m.FSM() as fsm:
548 m.d.comb += b.eq(fsm.ongoing("SECOND"))
549 with m.State("FIRST"):
550 pass
551 m.d.comb += a.eq(fsm.ongoing("FIRST"))
552 with m.State("SECOND"):
553 pass
554 m._flush()
555 self.assertEqual(m._generated["fsm"].state.reset, 1)
556 self.maxDiff = 10000
557 self.assertRepr(m._statements, """
558 (
559 (eq (sig b) (== (sig fsm_state) (const 1'd0)))
560 (eq (sig a) (== (sig fsm_state) (const 1'd1)))
561 (switch (sig fsm_state)
562 (case 1
563 )
564 (case 0
565 )
566 )
567 )
568 """)
569
570 def test_FSM_empty(self):
571 m = Module()
572 with m.FSM():
573 pass
574 self.assertRepr(m._statements, """
575 ()
576 """)
577
578 def test_FSM_wrong_domain(self):
579 m = Module()
580 with self.assertRaisesRegex(ValueError,
581 r"^FSM may not be driven by the 'comb' domain$"):
582 with m.FSM(domain="comb"):
583 pass
584
585 def test_FSM_wrong_undefined(self):
586 m = Module()
587 with self.assertRaisesRegex(NameError,
588 r"^FSM state 'FOO' is referenced but not defined$"):
589 with m.FSM() as fsm:
590 fsm.ongoing("FOO")
591
592 def test_FSM_wrong_redefined(self):
593 m = Module()
594 with m.FSM():
595 with m.State("FOO"):
596 pass
597 with self.assertRaisesRegex(NameError,
598 r"^FSM state 'FOO' is already defined$"):
599 with m.State("FOO"):
600 pass
601
602 def test_FSM_wrong_next(self):
603 m = Module()
604 with self.assertRaisesRegex(SyntaxError,
605 r"^Only assignment to `m\.next` is permitted$"):
606 m.next
607 with self.assertRaisesRegex(SyntaxError,
608 r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
609 m.next = "FOO"
610 with self.assertRaisesRegex(SyntaxError,
611 r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
612 with m.FSM():
613 m.next = "FOO"
614
615 def test_If_inside_FSM_wrong(self):
616 m = Module()
617 with m.FSM():
618 with m.State("FOO"):
619 pass
620 with self.assertRaisesRegex(SyntaxError,
621 (r"^If is not permitted directly inside of FSM; "
622 r"it is permitted inside of FSM State$")):
623 with m.If(self.s2):
624 pass
625
626 def test_auto_pop_ctrl(self):
627 m = Module()
628 with m.If(self.w1):
629 m.d.comb += self.c1.eq(1)
630 m.d.comb += self.c2.eq(1)
631 self.assertRepr(m._statements, """
632 (
633 (switch (cat (b (sig w1)))
634 (case 1 (eq (sig c1) (const 1'd1)))
635 )
636 (eq (sig c2) (const 1'd1))
637 )
638 """)
639
640 def test_submodule_anon(self):
641 m1 = Module()
642 m2 = Module()
643 m1.submodules += m2
644 self.assertEqual(m1._anon_submodules, [m2])
645 self.assertEqual(m1._named_submodules, {})
646
647 def test_submodule_anon_multi(self):
648 m1 = Module()
649 m2 = Module()
650 m3 = Module()
651 m1.submodules += m2, m3
652 self.assertEqual(m1._anon_submodules, [m2, m3])
653 self.assertEqual(m1._named_submodules, {})
654
655 def test_submodule_named(self):
656 m1 = Module()
657 m2 = Module()
658 m1.submodules.foo = m2
659 self.assertEqual(m1._anon_submodules, [])
660 self.assertEqual(m1._named_submodules, {"foo": m2})
661
662 def test_submodule_named_index(self):
663 m1 = Module()
664 m2 = Module()
665 m1.submodules["foo"] = m2
666 self.assertEqual(m1._anon_submodules, [])
667 self.assertEqual(m1._named_submodules, {"foo": m2})
668
669 def test_submodule_wrong(self):
670 m = Module()
671 with self.assertRaisesRegex(TypeError,
672 r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
673 m.submodules.foo = 1
674 with self.assertRaisesRegex(TypeError,
675 r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
676 m.submodules += 1
677
678 def test_submodule_named_conflict(self):
679 m1 = Module()
680 m2 = Module()
681 m1.submodules.foo = m2
682 with self.assertRaisesRegex(NameError, r"^Submodule named 'foo' already exists$"):
683 m1.submodules.foo = m2
684
685 def test_submodule_get(self):
686 m1 = Module()
687 m2 = Module()
688 m1.submodules.foo = m2
689 m3 = m1.submodules.foo
690 self.assertEqual(m2, m3)
691
692 def test_submodule_get_index(self):
693 m1 = Module()
694 m2 = Module()
695 m1.submodules["foo"] = m2
696 m3 = m1.submodules["foo"]
697 self.assertEqual(m2, m3)
698
699 def test_submodule_get_unset(self):
700 m1 = Module()
701 with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"):
702 m2 = m1.submodules.foo
703 with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"):
704 m2 = m1.submodules["foo"]
705
706 def test_domain_named_implicit(self):
707 m = Module()
708 m.domains += ClockDomain("sync")
709 self.assertEqual(len(m._domains), 1)
710
711 def test_domain_named_explicit(self):
712 m = Module()
713 m.domains.foo = ClockDomain()
714 self.assertEqual(len(m._domains), 1)
715 self.assertEqual(m._domains["foo"].name, "foo")
716
717 def test_domain_add_wrong(self):
718 m = Module()
719 with self.assertRaisesRegex(TypeError,
720 r"^Only clock domains may be added to `m\.domains`, not 1$"):
721 m.domains.foo = 1
722 with self.assertRaisesRegex(TypeError,
723 r"^Only clock domains may be added to `m\.domains`, not 1$"):
724 m.domains += 1
725
726 def test_domain_add_wrong_name(self):
727 m = Module()
728 with self.assertRaisesRegex(NameError,
729 r"^Clock domain name 'bar' must match name in `m\.domains\.foo \+= \.\.\.` syntax$"):
730 m.domains.foo = ClockDomain("bar")
731
732 def test_domain_add_wrong_duplicate(self):
733 m = Module()
734 m.domains += ClockDomain("foo")
735 with self.assertRaisesRegex(NameError,
736 r"^Clock domain named 'foo' already exists$"):
737 m.domains += ClockDomain("foo")
738
739 def test_lower(self):
740 m1 = Module()
741 m1.d.comb += self.c1.eq(self.s1)
742 m2 = Module()
743 m2.d.comb += self.c2.eq(self.s2)
744 m2.d.sync += self.c3.eq(self.s3)
745 m1.submodules.foo = m2
746
747 f1 = m1.elaborate(platform=None)
748 self.assertRepr(f1.statements, """
749 (
750 (eq (sig c1) (sig s1))
751 )
752 """)
753 self.assertEqual(f1.drivers, {
754 None: SignalSet((self.c1,))
755 })
756 self.assertEqual(len(f1.subfragments), 1)
757 (f2, f2_name), = f1.subfragments
758 self.assertEqual(f2_name, "foo")
759 self.assertRepr(f2.statements, """
760 (
761 (eq (sig c2) (sig s2))
762 (eq (sig c3) (sig s3))
763 )
764 """)
765 self.assertEqual(f2.drivers, {
766 None: SignalSet((self.c2,)),
767 "sync": SignalSet((self.c3,))
768 })
769 self.assertEqual(len(f2.subfragments), 0)