1 # nmigen: UnusedElaboratable=no
3 from collections
import OrderedDict
6 from ..hdl
.ast
import *
8 from ..hdl
.dsl
import *
12 class DSLTestCase(FHDLTestCase
):
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$")):
29 def test_d_comb(self
):
31 m
.d
.comb
+= self
.c1
.eq(1)
33 self
.assertEqual(m
._driving
[self
.c1
], None)
34 self
.assertRepr(m
._statements
, """(
35 (eq (sig c1) (const 1'd1))
38 def test_d_sync(self
):
40 m
.d
.sync
+= self
.c1
.eq(1)
42 self
.assertEqual(m
._driving
[self
.c1
], "sync")
43 self
.assertRepr(m
._statements
, """(
44 (eq (sig c1) (const 1'd1))
49 m
.d
.pix
+= self
.c1
.eq(1)
51 self
.assertEqual(m
._driving
[self
.c1
], "pix")
52 self
.assertRepr(m
._statements
, """(
53 (eq (sig c1) (const 1'd1))
56 def test_d_index(self
):
58 m
.d
["pix"] += self
.c1
.eq(1)
60 self
.assertEqual(m
._driving
[self
.c1
], "pix")
61 self
.assertRepr(m
._statements
, """(
62 (eq (sig c1) (const 1'd1))
65 def test_d_no_conflict(self
):
67 m
.d
.comb
+= self
.w1
[0].eq(1)
68 m
.d
.comb
+= self
.w1
[1].eq(1)
70 def test_d_conflict(self
):
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)
78 def test_d_wrong(self
):
80 with self
.assertRaisesRegex(AttributeError,
81 r
"^Cannot assign 'd\.pix' attribute; did you mean 'd.pix \+='\?$"):
84 def test_d_asgn_wrong(self
):
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
, {})
90 def test_comb_wrong(self
):
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)
96 def test_sync_wrong(self
):
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)
102 def test_attr_wrong(self
):
104 with self
.assertRaisesRegex(AttributeError,
105 r
"^'Module' object has no attribute 'nonexistentattr'$"):
108 def test_d_suspicious(self
):
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\?$")):
115 def test_clock_signal(self
):
117 m
.d
.comb
+= ClockSignal("pix").eq(ClockSignal())
118 self
.assertRepr(m
._statements
, """
120 (eq (clk pix) (clk sync))
124 def test_reset_signal(self
):
126 m
.d
.comb
+= ResetSignal("pix").eq(1)
127 self
.assertRepr(m
._statements
, """
129 (eq (rst pix) (const 1'd1))
133 def test_sample_domain(self
):
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
, """
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]))
154 m
.d
.comb
+= self
.c1
.eq(1)
156 self
.assertRepr(m
._statements
, """
158 (switch (cat (sig s1))
159 (case 1 (eq (sig c1) (const 1'd1)))
164 def test_If_Elif(self
):
167 m
.d
.comb
+= self
.c1
.eq(1)
168 with m
.Elif(self
.s2
):
169 m
.d
.sync
+= self
.c2
.eq(0)
171 self
.assertRepr(m
._statements
, """
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)))
180 def test_If_Elif_Else(self
):
183 m
.d
.comb
+= self
.c1
.eq(1)
184 with m
.Elif(self
.s2
):
185 m
.d
.sync
+= self
.c2
.eq(0)
187 m
.d
.comb
+= self
.c3
.eq(1)
189 self
.assertRepr(m
._statements
, """
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)))
199 def test_If_If(self
):
202 m
.d
.comb
+= self
.c1
.eq(1)
204 m
.d
.comb
+= self
.c2
.eq(1)
206 self
.assertRepr(m
._statements
, """
208 (switch (cat (sig s1))
209 (case 1 (eq (sig c1) (const 1'd1)))
211 (switch (cat (sig s2))
212 (case 1 (eq (sig c2) (const 1'd1)))
217 def test_If_nested_If(self
):
220 m
.d
.comb
+= self
.c1
.eq(1)
222 m
.d
.comb
+= self
.c2
.eq(1)
224 self
.assertRepr(m
._statements
, """
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)))
236 def test_If_dangling_Else(self
):
239 m
.d
.comb
+= self
.c1
.eq(1)
241 m
.d
.comb
+= self
.c2
.eq(1)
243 m
.d
.comb
+= self
.c3
.eq(1)
245 self
.assertRepr(m
._statements
, """
247 (switch (cat (sig s1))
249 (eq (sig c1) (const 1'd1))
250 (switch (cat (sig s2))
251 (case 1 (eq (sig c2) (const 1'd1)))
255 (eq (sig c3) (const 1'd1))
261 def test_Elif_wrong(self
):
263 with self
.assertRaisesRegex(SyntaxError,
264 r
"^Elif without preceding If$"):
265 with m
.Elif(self
.s2
):
268 def test_Else_wrong(self
):
270 with self
.assertRaisesRegex(SyntaxError,
271 r
"^Else without preceding If\/Elif$"):
275 def test_If_wide(self
):
278 m
.d
.comb
+= self
.c1
.eq(1)
280 self
.assertRepr(m
._statements
, """
282 (switch (cat (b (sig w1)))
283 (case 1 (eq (sig c1) (const 1'd1)))
288 def test_If_signed_suspicious(self
):
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\(\)\)`\.\)$")):
298 def test_Elif_signed_suspicious(self
):
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\(\)\)`\.\)$")):
310 def test_if_If_Elif_Else(self
):
312 with self
.assertRaisesRegex(SyntaxError,
313 r
"^`if m\.If\(\.\.\.\):` does not work; use `with m\.If\(\.\.\.\)`$"):
318 with self
.assertRaisesRegex(SyntaxError,
319 r
"^`if m\.Elif\(\.\.\.\):` does not work; use `with m\.Elif\(\.\.\.\)`$"):
322 with self
.assertRaisesRegex(SyntaxError,
323 r
"^`if m\.Else\(\.\.\.\):` does not work; use `with m\.Else\(\.\.\.\)`$"):
327 def test_Switch(self
):
329 with m
.Switch(self
.w1
):
331 m
.d
.comb
+= self
.c1
.eq(1)
333 m
.d
.comb
+= self
.c2
.eq(1)
334 with m
.Case("1 0--"):
335 m
.d
.comb
+= self
.c2
.eq(1)
337 self
.assertRepr(m
._statements
, """
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)))
347 def test_Switch_default_Case(self
):
349 with m
.Switch(self
.w1
):
351 m
.d
.comb
+= self
.c1
.eq(1)
353 m
.d
.comb
+= self
.c2
.eq(1)
355 self
.assertRepr(m
._statements
, """
358 (case 0011 (eq (sig c1) (const 1'd1)))
359 (default (eq (sig c2) (const 1'd1)))
364 def test_Switch_default_Default(self
):
366 with m
.Switch(self
.w1
):
368 m
.d
.comb
+= self
.c1
.eq(1)
370 m
.d
.comb
+= self
.c2
.eq(1)
372 self
.assertRepr(m
._statements
, """
375 (case 0011 (eq (sig c1) (const 1'd1)))
376 (default (eq (sig c2) (const 1'd1)))
381 def test_Switch_const_test(self
):
385 m
.d
.comb
+= self
.c1
.eq(1)
387 self
.assertRepr(m
._statements
, """
390 (case 1 (eq (sig c1) (const 1'd1)))
395 def test_Switch_enum(self
):
402 with m
.Case(Color
.RED
):
403 m
.d
.comb
+= self
.c1
.eq(1)
404 self
.assertRepr(m
._statements
, """
407 (case 01 (eq (sig c1) (const 1'd1)))
412 def test_Case_width_wrong(self
):
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\)$"):
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):
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
):
431 self
.assertRepr(m
._statements
, """
437 def test_Case_bits_wrong(self
):
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$")):
446 def test_Case_pattern_wrong(self
):
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$"):
454 def test_Case_outside_Switch_wrong(self
):
456 with self
.assertRaisesRegex(SyntaxError,
457 r
"^Case is not permitted outside of Switch$"):
461 def test_If_inside_Switch_wrong(self
):
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$")):
470 def test_FSM_basic(self
):
476 with m
.State("FIRST"):
479 with m
.State("SECOND"):
484 self
.assertRepr(m
._statements
, """
486 (switch (sig fsm_state)
488 (eq (sig a) (const 1'd1))
489 (eq (sig fsm_state) (const 1'd1))
492 (eq (sig b) (~ (sig b)))
493 (switch (cat (sig c))
495 (eq (sig fsm_state) (const 1'd0)))
501 self
.assertEqual({repr(k
): v
for k
, v
in m
._driving
.items()}, {
503 "(sig fsm_state)": "sync",
507 frag
= m
.elaborate(platform
=None)
508 fsm
= frag
.find_generated("fsm")
509 self
.assertIsInstance(fsm
.state
, Signal
)
510 self
.assertEqual(fsm
.encoding
, OrderedDict({
514 self
.assertEqual(fsm
.decoding
, OrderedDict({
519 def test_FSM_reset(self
):
522 with m
.FSM(reset
="SECOND"):
523 with m
.State("FIRST"):
526 with m
.State("SECOND"):
529 self
.assertRepr(m
._statements
, """
531 (switch (sig fsm_state)
533 (eq (sig a) (const 1'd0))
534 (eq (sig fsm_state) (const 1'd1))
537 (eq (sig fsm_state) (const 1'd0))
543 def test_FSM_ongoing(self
):
548 m
.d
.comb
+= b
.eq(fsm
.ongoing("SECOND"))
549 with m
.State("FIRST"):
551 m
.d
.comb
+= a
.eq(fsm
.ongoing("FIRST"))
552 with m
.State("SECOND"):
555 self
.assertEqual(m
._generated
["fsm"].state
.reset
, 1)
557 self
.assertRepr(m
._statements
, """
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)
570 def test_FSM_empty(self
):
574 self
.assertRepr(m
._statements
, """
578 def test_FSM_wrong_domain(self
):
580 with self
.assertRaisesRegex(ValueError,
581 r
"^FSM may not be driven by the 'comb' domain$"):
582 with m
.FSM(domain
="comb"):
585 def test_FSM_wrong_undefined(self
):
587 with self
.assertRaisesRegex(NameError,
588 r
"^FSM state 'FOO' is referenced but not defined$"):
592 def test_FSM_wrong_redefined(self
):
597 with self
.assertRaisesRegex(NameError,
598 r
"^FSM state 'FOO' is already defined$"):
602 def test_FSM_wrong_next(self
):
604 with self
.assertRaisesRegex(SyntaxError,
605 r
"^Only assignment to `m\.next` is permitted$"):
607 with self
.assertRaisesRegex(SyntaxError,
608 r
"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
610 with self
.assertRaisesRegex(SyntaxError,
611 r
"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
615 def test_If_inside_FSM_wrong(self
):
620 with self
.assertRaisesRegex(SyntaxError,
621 (r
"^If is not permitted directly inside of FSM; "
622 r
"it is permitted inside of FSM State$")):
626 def test_auto_pop_ctrl(self
):
629 m
.d
.comb
+= self
.c1
.eq(1)
630 m
.d
.comb
+= self
.c2
.eq(1)
631 self
.assertRepr(m
._statements
, """
633 (switch (cat (b (sig w1)))
634 (case 1 (eq (sig c1) (const 1'd1)))
636 (eq (sig c2) (const 1'd1))
640 def test_submodule_anon(self
):
644 self
.assertEqual(m1
._anon_submodules
, [m2
])
645 self
.assertEqual(m1
._named_submodules
, {})
647 def test_submodule_anon_multi(self
):
651 m1
.submodules
+= m2
, m3
652 self
.assertEqual(m1
._anon_submodules
, [m2
, m3
])
653 self
.assertEqual(m1
._named_submodules
, {})
655 def test_submodule_named(self
):
658 m1
.submodules
.foo
= m2
659 self
.assertEqual(m1
._anon_submodules
, [])
660 self
.assertEqual(m1
._named_submodules
, {"foo": m2
})
662 def test_submodule_named_index(self
):
665 m1
.submodules
["foo"] = m2
666 self
.assertEqual(m1
._anon_submodules
, [])
667 self
.assertEqual(m1
._named_submodules
, {"foo": m2
})
669 def test_submodule_wrong(self
):
671 with self
.assertRaisesRegex(TypeError,
672 r
"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
674 with self
.assertRaisesRegex(TypeError,
675 r
"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
678 def test_submodule_named_conflict(self
):
681 m1
.submodules
.foo
= m2
682 with self
.assertRaisesRegex(NameError, r
"^Submodule named 'foo' already exists$"):
683 m1
.submodules
.foo
= m2
685 def test_submodule_get(self
):
688 m1
.submodules
.foo
= m2
689 m3
= m1
.submodules
.foo
690 self
.assertEqual(m2
, m3
)
692 def test_submodule_get_index(self
):
695 m1
.submodules
["foo"] = m2
696 m3
= m1
.submodules
["foo"]
697 self
.assertEqual(m2
, m3
)
699 def test_submodule_get_unset(self
):
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"]
706 def test_domain_named_implicit(self
):
708 m
.domains
+= ClockDomain("sync")
709 self
.assertEqual(len(m
._domains
), 1)
711 def test_domain_named_explicit(self
):
713 m
.domains
.foo
= ClockDomain()
714 self
.assertEqual(len(m
._domains
), 1)
715 self
.assertEqual(m
._domains
["foo"].name
, "foo")
717 def test_domain_add_wrong(self
):
719 with self
.assertRaisesRegex(TypeError,
720 r
"^Only clock domains may be added to `m\.domains`, not 1$"):
722 with self
.assertRaisesRegex(TypeError,
723 r
"^Only clock domains may be added to `m\.domains`, not 1$"):
726 def test_domain_add_wrong_name(self
):
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")
732 def test_domain_add_wrong_duplicate(self
):
734 m
.domains
+= ClockDomain("foo")
735 with self
.assertRaisesRegex(NameError,
736 r
"^Clock domain named 'foo' already exists$"):
737 m
.domains
+= ClockDomain("foo")
739 def test_lower(self
):
741 m1
.d
.comb
+= self
.c1
.eq(self
.s1
)
743 m2
.d
.comb
+= self
.c2
.eq(self
.s2
)
744 m2
.d
.sync
+= self
.c3
.eq(self
.s3
)
745 m1
.submodules
.foo
= m2
747 f1
= m1
.elaborate(platform
=None)
748 self
.assertRepr(f1
.statements
, """
750 (eq (sig c1) (sig s1))
753 self
.assertEqual(f1
.drivers
, {
754 None: SignalSet((self
.c1
,))
756 self
.assertEqual(len(f1
.subfragments
), 1)
757 (f2
, f2_name
), = f1
.subfragments
758 self
.assertEqual(f2_name
, "foo")
759 self
.assertRepr(f2
.statements
, """
761 (eq (sig c2) (sig s2))
762 (eq (sig c3) (sig s3))
765 self
.assertEqual(f2
.drivers
, {
766 None: SignalSet((self
.c2
,)),
767 "sync": SignalSet((self
.c3
,))
769 self
.assertEqual(len(f2
.subfragments
), 0)