1 # nmigen: UnusedElaboratable=no
3 from collections
import OrderedDict
6 from nmigen
.hdl
.ast
import *
7 from nmigen
.hdl
.cd
import *
8 from nmigen
.hdl
.dsl
import *
13 class DSLTestCase(FHDLTestCase
):
23 def test_cant_inherit(self
):
24 with self
.assertRaisesRegex(SyntaxError,
25 (r
"^Instead of inheriting from `Module`, inherit from `Elaboratable` and "
26 r
"return a `Module` from the `elaborate\(self, platform\)` method$")):
30 def test_d_comb(self
):
32 m
.d
.comb
+= self
.c1
.eq(1)
34 self
.assertEqual(m
._driving
[self
.c1
], None)
35 self
.assertRepr(m
._statements
, """(
36 (eq (sig c1) (const 1'd1))
39 def test_d_sync(self
):
41 m
.d
.sync
+= self
.c1
.eq(1)
43 self
.assertEqual(m
._driving
[self
.c1
], "sync")
44 self
.assertRepr(m
._statements
, """(
45 (eq (sig c1) (const 1'd1))
50 m
.d
.pix
+= self
.c1
.eq(1)
52 self
.assertEqual(m
._driving
[self
.c1
], "pix")
53 self
.assertRepr(m
._statements
, """(
54 (eq (sig c1) (const 1'd1))
57 def test_d_index(self
):
59 m
.d
["pix"] += self
.c1
.eq(1)
61 self
.assertEqual(m
._driving
[self
.c1
], "pix")
62 self
.assertRepr(m
._statements
, """(
63 (eq (sig c1) (const 1'd1))
66 def test_d_no_conflict(self
):
68 m
.d
.comb
+= self
.w1
[0].eq(1)
69 m
.d
.comb
+= self
.w1
[1].eq(1)
71 def test_d_conflict(self
):
73 with self
.assertRaisesRegex(SyntaxError,
74 (r
"^Driver-driver conflict: trying to drive \(sig c1\) from d\.sync, but it "
75 r
"is already driven from d\.comb$")):
76 m
.d
.comb
+= self
.c1
.eq(1)
77 m
.d
.sync
+= self
.c1
.eq(1)
79 def test_d_wrong(self
):
81 with self
.assertRaisesRegex(AttributeError,
82 r
"^Cannot assign 'd\.pix' attribute; did you mean 'd.pix \+='\?$"):
85 def test_d_asgn_wrong(self
):
87 with self
.assertRaisesRegex(SyntaxError,
88 r
"^Only assignments and property checks may be appended to d\.sync$"):
89 m
.d
.sync
+= Switch(self
.s1
, {})
91 def test_comb_wrong(self
):
93 with self
.assertRaisesRegex(AttributeError,
94 r
"^'Module' object has no attribute 'comb'; did you mean 'd\.comb'\?$"):
95 m
.comb
+= self
.c1
.eq(1)
97 def test_sync_wrong(self
):
99 with self
.assertRaisesRegex(AttributeError,
100 r
"^'Module' object has no attribute 'sync'; did you mean 'd\.sync'\?$"):
101 m
.sync
+= self
.c1
.eq(1)
103 def test_attr_wrong(self
):
105 with self
.assertRaisesRegex(AttributeError,
106 r
"^'Module' object has no attribute 'nonexistentattr'$"):
109 def test_d_suspicious(self
):
111 with self
.assertWarnsRegex(SyntaxWarning,
112 (r
"^Using '<module>\.d\.submodules' would add statements to clock domain "
113 r
"'submodules'; did you mean <module>\.submodules instead\?$")):
116 def test_clock_signal(self
):
118 m
.d
.comb
+= ClockSignal("pix").eq(ClockSignal())
119 self
.assertRepr(m
._statements
, """
121 (eq (clk pix) (clk sync))
125 def test_reset_signal(self
):
127 m
.d
.comb
+= ResetSignal("pix").eq(1)
128 self
.assertRepr(m
._statements
, """
130 (eq (rst pix) (const 1'd1))
134 def test_sample_domain(self
):
140 m
.d
.sync
+= o1
.eq(Past(i
))
141 m
.d
.pix
+= o2
.eq(Past(i
))
142 m
.d
.pix
+= o3
.eq(Past(i
, domain
="sync"))
143 f
= m
.elaborate(platform
=None)
144 self
.assertRepr(f
.statements
, """
146 (eq (sig o1) (sample (sig i) @ sync[1]))
147 (eq (sig o2) (sample (sig i) @ pix[1]))
148 (eq (sig o3) (sample (sig i) @ sync[1]))
155 m
.d
.comb
+= self
.c1
.eq(1)
157 self
.assertRepr(m
._statements
, """
159 (switch (cat (sig s1))
160 (case 1 (eq (sig c1) (const 1'd1)))
165 def test_If_Elif(self
):
168 m
.d
.comb
+= self
.c1
.eq(1)
169 with m
.Elif(self
.s2
):
170 m
.d
.sync
+= self
.c2
.eq(0)
172 self
.assertRepr(m
._statements
, """
174 (switch (cat (sig s1) (sig s2))
175 (case -1 (eq (sig c1) (const 1'd1)))
176 (case 1- (eq (sig c2) (const 1'd0)))
181 def test_If_Elif_Else(self
):
184 m
.d
.comb
+= self
.c1
.eq(1)
185 with m
.Elif(self
.s2
):
186 m
.d
.sync
+= self
.c2
.eq(0)
188 m
.d
.comb
+= self
.c3
.eq(1)
190 self
.assertRepr(m
._statements
, """
192 (switch (cat (sig s1) (sig s2))
193 (case -1 (eq (sig c1) (const 1'd1)))
194 (case 1- (eq (sig c2) (const 1'd0)))
195 (default (eq (sig c3) (const 1'd1)))
200 def test_If_If(self
):
203 m
.d
.comb
+= self
.c1
.eq(1)
205 m
.d
.comb
+= self
.c2
.eq(1)
207 self
.assertRepr(m
._statements
, """
209 (switch (cat (sig s1))
210 (case 1 (eq (sig c1) (const 1'd1)))
212 (switch (cat (sig s2))
213 (case 1 (eq (sig c2) (const 1'd1)))
218 def test_If_nested_If(self
):
221 m
.d
.comb
+= self
.c1
.eq(1)
223 m
.d
.comb
+= self
.c2
.eq(1)
225 self
.assertRepr(m
._statements
, """
227 (switch (cat (sig s1))
228 (case 1 (eq (sig c1) (const 1'd1))
229 (switch (cat (sig s2))
230 (case 1 (eq (sig c2) (const 1'd1)))
237 def test_If_dangling_Else(self
):
240 m
.d
.comb
+= self
.c1
.eq(1)
242 m
.d
.comb
+= self
.c2
.eq(1)
244 m
.d
.comb
+= self
.c3
.eq(1)
246 self
.assertRepr(m
._statements
, """
248 (switch (cat (sig s1))
250 (eq (sig c1) (const 1'd1))
251 (switch (cat (sig s2))
252 (case 1 (eq (sig c2) (const 1'd1)))
256 (eq (sig c3) (const 1'd1))
262 def test_Elif_wrong(self
):
264 with self
.assertRaisesRegex(SyntaxError,
265 r
"^Elif without preceding If$"):
266 with m
.Elif(self
.s2
):
269 def test_Else_wrong(self
):
271 with self
.assertRaisesRegex(SyntaxError,
272 r
"^Else without preceding If\/Elif$"):
276 def test_If_wide(self
):
279 m
.d
.comb
+= self
.c1
.eq(1)
281 self
.assertRepr(m
._statements
, """
283 (switch (cat (b (sig w1)))
284 (case 1 (eq (sig c1) (const 1'd1)))
289 def test_If_signed_suspicious(self
):
291 with self
.assertWarnsRegex(SyntaxWarning,
292 (r
"^Signed values in If\/Elif conditions usually result from inverting Python "
293 r
"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
294 r
"`not flag`\. \(If this is a false positive, silence this warning with "
295 r
"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$")):
299 def test_Elif_signed_suspicious(self
):
303 with self
.assertWarnsRegex(SyntaxWarning,
304 (r
"^Signed values in If\/Elif conditions usually result from inverting Python "
305 r
"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
306 r
"`not flag`\. \(If this is a false positive, silence this warning with "
307 r
"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$")):
311 def test_if_If_Elif_Else(self
):
313 with self
.assertRaisesRegex(SyntaxError,
314 r
"^`if m\.If\(\.\.\.\):` does not work; use `with m\.If\(\.\.\.\)`$"):
319 with self
.assertRaisesRegex(SyntaxError,
320 r
"^`if m\.Elif\(\.\.\.\):` does not work; use `with m\.Elif\(\.\.\.\)`$"):
323 with self
.assertRaisesRegex(SyntaxError,
324 r
"^`if m\.Else\(\.\.\.\):` does not work; use `with m\.Else\(\.\.\.\)`$"):
328 def test_Switch(self
):
330 with m
.Switch(self
.w1
):
332 m
.d
.comb
+= self
.c1
.eq(1)
334 m
.d
.comb
+= self
.c2
.eq(1)
335 with m
.Case("1 0--"):
336 m
.d
.comb
+= self
.c2
.eq(1)
338 self
.assertRepr(m
._statements
, """
341 (case 0011 (eq (sig c1) (const 1'd1)))
342 (case 11-- (eq (sig c2) (const 1'd1)))
343 (case 10-- (eq (sig c2) (const 1'd1)))
348 def test_Switch_default_Case(self
):
350 with m
.Switch(self
.w1
):
352 m
.d
.comb
+= self
.c1
.eq(1)
354 m
.d
.comb
+= self
.c2
.eq(1)
356 self
.assertRepr(m
._statements
, """
359 (case 0011 (eq (sig c1) (const 1'd1)))
360 (default (eq (sig c2) (const 1'd1)))
365 def test_Switch_default_Default(self
):
367 with m
.Switch(self
.w1
):
369 m
.d
.comb
+= self
.c1
.eq(1)
371 m
.d
.comb
+= self
.c2
.eq(1)
373 self
.assertRepr(m
._statements
, """
376 (case 0011 (eq (sig c1) (const 1'd1)))
377 (default (eq (sig c2) (const 1'd1)))
382 def test_Switch_const_test(self
):
386 m
.d
.comb
+= self
.c1
.eq(1)
388 self
.assertRepr(m
._statements
, """
391 (case 1 (eq (sig c1) (const 1'd1)))
396 def test_Switch_enum(self
):
403 with m
.Case(Color
.RED
):
404 m
.d
.comb
+= self
.c1
.eq(1)
405 self
.assertRepr(m
._statements
, """
408 (case 01 (eq (sig c1) (const 1'd1)))
413 def test_Case_width_wrong(self
):
417 with m
.Switch(self
.w1
):
418 with self
.assertRaisesRegex(SyntaxError,
419 r
"^Case pattern '--' must have the same width as switch value \(which is 4\)$"):
422 with self
.assertWarnsRegex(SyntaxWarning,
423 (r
"^Case pattern '10110' is wider than switch value \(which has width 4\); "
424 r
"comparison will never be true$")):
425 with m
.Case(0b10110):
427 with self
.assertWarnsRegex(SyntaxWarning,
428 (r
"^Case pattern '10101010' \(Color\.RED\) is wider than switch value "
429 r
"\(which has width 4\); comparison will never be true$")):
430 with m
.Case(Color
.RED
):
432 self
.assertRepr(m
._statements
, """
438 def test_Case_bits_wrong(self
):
440 with m
.Switch(self
.w1
):
441 with self
.assertRaisesRegex(SyntaxError,
442 (r
"^Case pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
443 r
"and may include whitespace$")):
447 def test_Case_pattern_wrong(self
):
449 with m
.Switch(self
.w1
):
450 with self
.assertRaisesRegex(SyntaxError,
451 r
"^Case pattern must be an integer, a string, or an enumeration, not 1\.0$"):
455 def test_Case_outside_Switch_wrong(self
):
457 with self
.assertRaisesRegex(SyntaxError,
458 r
"^Case is not permitted outside of Switch$"):
462 def test_If_inside_Switch_wrong(self
):
464 with m
.Switch(self
.s1
):
465 with self
.assertRaisesRegex(SyntaxError,
466 (r
"^If is not permitted directly inside of Switch; "
467 r
"it is permitted inside of Switch Case$")):
471 def test_FSM_basic(self
):
477 with m
.State("FIRST"):
480 with m
.State("SECOND"):
485 self
.assertRepr(m
._statements
, """
487 (switch (sig fsm_state)
489 (eq (sig a) (const 1'd1))
490 (eq (sig fsm_state) (const 1'd1))
493 (eq (sig b) (~ (sig b)))
494 (switch (cat (sig c))
496 (eq (sig fsm_state) (const 1'd0)))
502 self
.assertEqual({repr(k
): v
for k
, v
in m
._driving
.items()}, {
504 "(sig fsm_state)": "sync",
508 frag
= m
.elaborate(platform
=None)
509 fsm
= frag
.find_generated("fsm")
510 self
.assertIsInstance(fsm
.state
, Signal
)
511 self
.assertEqual(fsm
.encoding
, OrderedDict({
515 self
.assertEqual(fsm
.decoding
, OrderedDict({
520 def test_FSM_reset(self
):
523 with m
.FSM(reset
="SECOND"):
524 with m
.State("FIRST"):
527 with m
.State("SECOND"):
530 self
.assertRepr(m
._statements
, """
532 (switch (sig fsm_state)
534 (eq (sig a) (const 1'd0))
535 (eq (sig fsm_state) (const 1'd1))
538 (eq (sig fsm_state) (const 1'd0))
544 def test_FSM_ongoing(self
):
549 m
.d
.comb
+= b
.eq(fsm
.ongoing("SECOND"))
550 with m
.State("FIRST"):
552 m
.d
.comb
+= a
.eq(fsm
.ongoing("FIRST"))
553 with m
.State("SECOND"):
556 self
.assertEqual(m
._generated
["fsm"].state
.reset
, 1)
558 self
.assertRepr(m
._statements
, """
560 (eq (sig b) (== (sig fsm_state) (const 1'd0)))
561 (eq (sig a) (== (sig fsm_state) (const 1'd1)))
562 (switch (sig fsm_state)
571 def test_FSM_empty(self
):
575 self
.assertRepr(m
._statements
, """
579 def test_FSM_wrong_domain(self
):
581 with self
.assertRaisesRegex(ValueError,
582 r
"^FSM may not be driven by the 'comb' domain$"):
583 with m
.FSM(domain
="comb"):
586 def test_FSM_wrong_undefined(self
):
588 with self
.assertRaisesRegex(NameError,
589 r
"^FSM state 'FOO' is referenced but not defined$"):
593 def test_FSM_wrong_redefined(self
):
598 with self
.assertRaisesRegex(NameError,
599 r
"^FSM state 'FOO' is already defined$"):
603 def test_FSM_wrong_next(self
):
605 with self
.assertRaisesRegex(SyntaxError,
606 r
"^Only assignment to `m\.next` is permitted$"):
608 with self
.assertRaisesRegex(SyntaxError,
609 r
"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
611 with self
.assertRaisesRegex(SyntaxError,
612 r
"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
616 def test_If_inside_FSM_wrong(self
):
621 with self
.assertRaisesRegex(SyntaxError,
622 (r
"^If is not permitted directly inside of FSM; "
623 r
"it is permitted inside of FSM State$")):
627 def test_auto_pop_ctrl(self
):
630 m
.d
.comb
+= self
.c1
.eq(1)
631 m
.d
.comb
+= self
.c2
.eq(1)
632 self
.assertRepr(m
._statements
, """
634 (switch (cat (b (sig w1)))
635 (case 1 (eq (sig c1) (const 1'd1)))
637 (eq (sig c2) (const 1'd1))
641 def test_submodule_anon(self
):
645 self
.assertEqual(m1
._anon_submodules
, [m2
])
646 self
.assertEqual(m1
._named_submodules
, {})
648 def test_submodule_anon_multi(self
):
652 m1
.submodules
+= m2
, m3
653 self
.assertEqual(m1
._anon_submodules
, [m2
, m3
])
654 self
.assertEqual(m1
._named_submodules
, {})
656 def test_submodule_named(self
):
659 m1
.submodules
.foo
= m2
660 self
.assertEqual(m1
._anon_submodules
, [])
661 self
.assertEqual(m1
._named_submodules
, {"foo": m2
})
663 def test_submodule_named_index(self
):
666 m1
.submodules
["foo"] = m2
667 self
.assertEqual(m1
._anon_submodules
, [])
668 self
.assertEqual(m1
._named_submodules
, {"foo": m2
})
670 def test_submodule_wrong(self
):
672 with self
.assertRaisesRegex(TypeError,
673 r
"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
675 with self
.assertRaisesRegex(TypeError,
676 r
"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
679 def test_submodule_named_conflict(self
):
682 m1
.submodules
.foo
= m2
683 with self
.assertRaisesRegex(NameError, r
"^Submodule named 'foo' already exists$"):
684 m1
.submodules
.foo
= m2
686 def test_submodule_get(self
):
689 m1
.submodules
.foo
= m2
690 m3
= m1
.submodules
.foo
691 self
.assertEqual(m2
, m3
)
693 def test_submodule_get_index(self
):
696 m1
.submodules
["foo"] = m2
697 m3
= m1
.submodules
["foo"]
698 self
.assertEqual(m2
, m3
)
700 def test_submodule_get_unset(self
):
702 with self
.assertRaisesRegex(AttributeError, r
"^No submodule named 'foo' exists$"):
703 m2
= m1
.submodules
.foo
704 with self
.assertRaisesRegex(AttributeError, r
"^No submodule named 'foo' exists$"):
705 m2
= m1
.submodules
["foo"]
707 def test_domain_named_implicit(self
):
709 m
.domains
+= ClockDomain("sync")
710 self
.assertEqual(len(m
._domains
), 1)
712 def test_domain_named_explicit(self
):
714 m
.domains
.foo
= ClockDomain()
715 self
.assertEqual(len(m
._domains
), 1)
716 self
.assertEqual(m
._domains
["foo"].name
, "foo")
718 def test_domain_add_wrong(self
):
720 with self
.assertRaisesRegex(TypeError,
721 r
"^Only clock domains may be added to `m\.domains`, not 1$"):
723 with self
.assertRaisesRegex(TypeError,
724 r
"^Only clock domains may be added to `m\.domains`, not 1$"):
727 def test_domain_add_wrong_name(self
):
729 with self
.assertRaisesRegex(NameError,
730 r
"^Clock domain name 'bar' must match name in `m\.domains\.foo \+= \.\.\.` syntax$"):
731 m
.domains
.foo
= ClockDomain("bar")
733 def test_domain_add_wrong_duplicate(self
):
735 m
.domains
+= ClockDomain("foo")
736 with self
.assertRaisesRegex(NameError,
737 r
"^Clock domain named 'foo' already exists$"):
738 m
.domains
+= ClockDomain("foo")
740 def test_lower(self
):
742 m1
.d
.comb
+= self
.c1
.eq(self
.s1
)
744 m2
.d
.comb
+= self
.c2
.eq(self
.s2
)
745 m2
.d
.sync
+= self
.c3
.eq(self
.s3
)
746 m1
.submodules
.foo
= m2
748 f1
= m1
.elaborate(platform
=None)
749 self
.assertRepr(f1
.statements
, """
751 (eq (sig c1) (sig s1))
754 self
.assertEqual(f1
.drivers
, {
755 None: SignalSet((self
.c1
,))
757 self
.assertEqual(len(f1
.subfragments
), 1)
758 (f2
, f2_name
), = f1
.subfragments
759 self
.assertEqual(f2_name
, "foo")
760 self
.assertRepr(f2
.statements
, """
762 (eq (sig c2) (sig s2))
763 (eq (sig c3) (sig s3))
766 self
.assertEqual(f2
.drivers
, {
767 None: SignalSet((self
.c2
,)),
768 "sync": SignalSet((self
.c3
,))
770 self
.assertEqual(len(f2
.subfragments
), 0)