559 lines
16 KiB
Python
559 lines
16 KiB
Python
from collections import OrderedDict
|
|
|
|
from ..hdl.ast import *
|
|
from ..hdl.cd import *
|
|
from ..hdl.dsl import *
|
|
from .tools import *
|
|
|
|
|
|
class DSLTestCase(FHDLTestCase):
|
|
def setUp(self):
|
|
self.s1 = Signal()
|
|
self.s2 = Signal()
|
|
self.s3 = Signal()
|
|
self.c1 = Signal()
|
|
self.c2 = Signal()
|
|
self.c3 = Signal()
|
|
self.w1 = Signal(4)
|
|
|
|
def test_d_comb(self):
|
|
m = Module()
|
|
m.d.comb += self.c1.eq(1)
|
|
m._flush()
|
|
self.assertEqual(m._driving[self.c1], None)
|
|
self.assertRepr(m._statements, """(
|
|
(eq (sig c1) (const 1'd1))
|
|
)""")
|
|
|
|
def test_d_sync(self):
|
|
m = Module()
|
|
m.d.sync += self.c1.eq(1)
|
|
m._flush()
|
|
self.assertEqual(m._driving[self.c1], "sync")
|
|
self.assertRepr(m._statements, """(
|
|
(eq (sig c1) (const 1'd1))
|
|
)""")
|
|
|
|
def test_d_pix(self):
|
|
m = Module()
|
|
m.d.pix += self.c1.eq(1)
|
|
m._flush()
|
|
self.assertEqual(m._driving[self.c1], "pix")
|
|
self.assertRepr(m._statements, """(
|
|
(eq (sig c1) (const 1'd1))
|
|
)""")
|
|
|
|
def test_d_index(self):
|
|
m = Module()
|
|
m.d["pix"] += self.c1.eq(1)
|
|
m._flush()
|
|
self.assertEqual(m._driving[self.c1], "pix")
|
|
self.assertRepr(m._statements, """(
|
|
(eq (sig c1) (const 1'd1))
|
|
)""")
|
|
|
|
def test_d_no_conflict(self):
|
|
m = Module()
|
|
m.d.comb += self.w1[0].eq(1)
|
|
m.d.comb += self.w1[1].eq(1)
|
|
|
|
def test_d_conflict(self):
|
|
m = Module()
|
|
with self.assertRaises(SyntaxError,
|
|
msg="Driver-driver conflict: trying to drive (sig c1) from d.sync, but it "
|
|
"is already driven from d.comb"):
|
|
m.d.comb += self.c1.eq(1)
|
|
m.d.sync += self.c1.eq(1)
|
|
|
|
def test_d_wrong(self):
|
|
m = Module()
|
|
with self.assertRaises(AttributeError,
|
|
msg="Cannot assign 'd.pix' attribute; did you mean 'd.pix +='?"):
|
|
m.d.pix = None
|
|
|
|
def test_d_asgn_wrong(self):
|
|
m = Module()
|
|
with self.assertRaises(SyntaxError,
|
|
msg="Only assignments, asserts, and assumes may be appended to d.sync"):
|
|
m.d.sync += Switch(self.s1, {})
|
|
|
|
def test_comb_wrong(self):
|
|
m = Module()
|
|
with self.assertRaises(AttributeError,
|
|
msg="'Module' object has no attribute 'comb'; did you mean 'd.comb'?"):
|
|
m.comb += self.c1.eq(1)
|
|
|
|
def test_sync_wrong(self):
|
|
m = Module()
|
|
with self.assertRaises(AttributeError,
|
|
msg="'Module' object has no attribute 'sync'; did you mean 'd.sync'?"):
|
|
m.sync += self.c1.eq(1)
|
|
|
|
def test_attr_wrong(self):
|
|
m = Module()
|
|
with self.assertRaises(AttributeError,
|
|
msg="'Module' object has no attribute 'nonexistentattr'"):
|
|
m.nonexistentattr
|
|
|
|
def test_clock_signal(self):
|
|
m = Module()
|
|
m.d.comb += ClockSignal("pix").eq(ClockSignal())
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(eq (clk pix) (clk sync))
|
|
)
|
|
""")
|
|
|
|
def test_reset_signal(self):
|
|
m = Module()
|
|
m.d.comb += ResetSignal("pix").eq(1)
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(eq (rst pix) (const 1'd1))
|
|
)
|
|
""")
|
|
|
|
def test_sample_domain(self):
|
|
m = Module()
|
|
i = Signal()
|
|
o1 = Signal()
|
|
o2 = Signal()
|
|
o3 = Signal()
|
|
m.d.sync += o1.eq(Past(i))
|
|
m.d.pix += o2.eq(Past(i))
|
|
m.d.pix += o3.eq(Past(i, domain="sync"))
|
|
f = m.elaborate(platform=None)
|
|
self.assertRepr(f.statements, """
|
|
(
|
|
(eq (sig o1) (sample (sig i) @ sync[1]))
|
|
(eq (sig o2) (sample (sig i) @ pix[1]))
|
|
(eq (sig o3) (sample (sig i) @ sync[1]))
|
|
)
|
|
""")
|
|
|
|
def test_If(self):
|
|
m = Module()
|
|
with m.If(self.s1):
|
|
m.d.comb += self.c1.eq(1)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (cat (sig s1))
|
|
(case 1 (eq (sig c1) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_If_Elif(self):
|
|
m = Module()
|
|
with m.If(self.s1):
|
|
m.d.comb += self.c1.eq(1)
|
|
with m.Elif(self.s2):
|
|
m.d.sync += self.c2.eq(0)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (cat (sig s1) (sig s2))
|
|
(case -1 (eq (sig c1) (const 1'd1)))
|
|
(case 1- (eq (sig c2) (const 1'd0)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_If_Elif_Else(self):
|
|
m = Module()
|
|
with m.If(self.s1):
|
|
m.d.comb += self.c1.eq(1)
|
|
with m.Elif(self.s2):
|
|
m.d.sync += self.c2.eq(0)
|
|
with m.Else():
|
|
m.d.comb += self.c3.eq(1)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (cat (sig s1) (sig s2))
|
|
(case -1 (eq (sig c1) (const 1'd1)))
|
|
(case 1- (eq (sig c2) (const 1'd0)))
|
|
(case -- (eq (sig c3) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_If_If(self):
|
|
m = Module()
|
|
with m.If(self.s1):
|
|
m.d.comb += self.c1.eq(1)
|
|
with m.If(self.s2):
|
|
m.d.comb += self.c2.eq(1)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (cat (sig s1))
|
|
(case 1 (eq (sig c1) (const 1'd1)))
|
|
)
|
|
(switch (cat (sig s2))
|
|
(case 1 (eq (sig c2) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_If_nested_If(self):
|
|
m = Module()
|
|
with m.If(self.s1):
|
|
m.d.comb += self.c1.eq(1)
|
|
with m.If(self.s2):
|
|
m.d.comb += self.c2.eq(1)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (cat (sig s1))
|
|
(case 1 (eq (sig c1) (const 1'd1))
|
|
(switch (cat (sig s2))
|
|
(case 1 (eq (sig c2) (const 1'd1)))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_If_dangling_Else(self):
|
|
m = Module()
|
|
with m.If(self.s1):
|
|
m.d.comb += self.c1.eq(1)
|
|
with m.If(self.s2):
|
|
m.d.comb += self.c2.eq(1)
|
|
with m.Else():
|
|
m.d.comb += self.c3.eq(1)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (cat (sig s1))
|
|
(case 1
|
|
(eq (sig c1) (const 1'd1))
|
|
(switch (cat (sig s2))
|
|
(case 1 (eq (sig c2) (const 1'd1)))
|
|
)
|
|
)
|
|
(case -
|
|
(eq (sig c3) (const 1'd1))
|
|
)
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_Elif_wrong(self):
|
|
m = Module()
|
|
with self.assertRaises(SyntaxError,
|
|
msg="Elif without preceding If"):
|
|
with m.Elif(self.s2):
|
|
pass
|
|
|
|
def test_Else_wrong(self):
|
|
m = Module()
|
|
with self.assertRaises(SyntaxError,
|
|
msg="Else without preceding If/Elif"):
|
|
with m.Else():
|
|
pass
|
|
|
|
def test_If_wide(self):
|
|
m = Module()
|
|
with m.If(self.w1):
|
|
m.d.comb += self.c1.eq(1)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (cat (b (sig w1)))
|
|
(case 1 (eq (sig c1) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_Switch(self):
|
|
m = Module()
|
|
with m.Switch(self.w1):
|
|
with m.Case(3):
|
|
m.d.comb += self.c1.eq(1)
|
|
with m.Case("11--"):
|
|
m.d.comb += self.c2.eq(1)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (sig w1)
|
|
(case 0011 (eq (sig c1) (const 1'd1)))
|
|
(case 11-- (eq (sig c2) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_Switch_default(self):
|
|
m = Module()
|
|
with m.Switch(self.w1):
|
|
with m.Case(3):
|
|
m.d.comb += self.c1.eq(1)
|
|
with m.Case():
|
|
m.d.comb += self.c2.eq(1)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (sig w1)
|
|
(case 0011 (eq (sig c1) (const 1'd1)))
|
|
(case ---- (eq (sig c2) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_Switch_const_test(self):
|
|
m = Module()
|
|
with m.Switch(1):
|
|
with m.Case(1):
|
|
m.d.comb += self.c1.eq(1)
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (const 1'd1)
|
|
(case 1 (eq (sig c1) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_Case_width_wrong(self):
|
|
m = Module()
|
|
with m.Switch(self.w1):
|
|
with self.assertRaises(SyntaxError,
|
|
msg="Case value '--' must have the same width as test (which is 4)"):
|
|
with m.Case("--"):
|
|
pass
|
|
with self.assertWarns(SyntaxWarning,
|
|
msg="Case value '10110' is wider than test (which has width 4); comparison "
|
|
"will never be true"):
|
|
with m.Case(0b10110):
|
|
pass
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (sig w1) )
|
|
)
|
|
""")
|
|
|
|
def test_Case_outside_Switch_wrong(self):
|
|
m = Module()
|
|
with self.assertRaises(SyntaxError,
|
|
msg="Case is not permitted outside of Switch"):
|
|
with m.Case():
|
|
pass
|
|
|
|
def test_If_inside_Switch_wrong(self):
|
|
m = Module()
|
|
with m.Switch(self.s1):
|
|
with self.assertRaises(SyntaxError,
|
|
msg="If is not permitted inside of Switch"):
|
|
with m.If(self.s2):
|
|
pass
|
|
|
|
def test_FSM_basic(self):
|
|
a = Signal()
|
|
b = Signal()
|
|
c = Signal()
|
|
m = Module()
|
|
with m.FSM():
|
|
with m.State("FIRST"):
|
|
m.d.comb += a.eq(1)
|
|
m.next = "SECOND"
|
|
with m.State("SECOND"):
|
|
m.d.sync += b.eq(~b)
|
|
with m.If(c):
|
|
m.next = "FIRST"
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (sig fsm_state)
|
|
(case 0
|
|
(eq (sig a) (const 1'd1))
|
|
(eq (sig fsm_state) (const 1'd1))
|
|
)
|
|
(case 1
|
|
(eq (sig b) (~ (sig b)))
|
|
(switch (cat (sig c))
|
|
(case 1
|
|
(eq (sig fsm_state) (const 1'd0)))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
""")
|
|
self.assertEqual({repr(k): v for k, v in m._driving.items()}, {
|
|
"(sig a)": None,
|
|
"(sig fsm_state)": "sync",
|
|
"(sig b)": "sync",
|
|
})
|
|
|
|
frag = m.elaborate(platform=None)
|
|
fsm = frag.find_generated("fsm")
|
|
self.assertIsInstance(fsm.state, Signal)
|
|
self.assertEqual(fsm.encoding, OrderedDict({
|
|
"FIRST": 0,
|
|
"SECOND": 1,
|
|
}))
|
|
self.assertEqual(fsm.decoding, OrderedDict({
|
|
0: "FIRST",
|
|
1: "SECOND"
|
|
}))
|
|
|
|
def test_FSM_reset(self):
|
|
a = Signal()
|
|
m = Module()
|
|
with m.FSM(reset="SECOND"):
|
|
with m.State("FIRST"):
|
|
m.d.comb += a.eq(0)
|
|
m.next = "SECOND"
|
|
with m.State("SECOND"):
|
|
m.next = "FIRST"
|
|
m._flush()
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (sig fsm_state)
|
|
(case 0
|
|
(eq (sig a) (const 1'd0))
|
|
(eq (sig fsm_state) (const 1'd1))
|
|
)
|
|
(case 1
|
|
(eq (sig fsm_state) (const 1'd0))
|
|
)
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_FSM_ongoing(self):
|
|
a = Signal()
|
|
b = Signal()
|
|
m = Module()
|
|
with m.FSM() as fsm:
|
|
m.d.comb += b.eq(fsm.ongoing("SECOND"))
|
|
with m.State("FIRST"):
|
|
pass
|
|
m.d.comb += a.eq(fsm.ongoing("FIRST"))
|
|
with m.State("SECOND"):
|
|
pass
|
|
m._flush()
|
|
self.assertEqual(m._generated["fsm"].state.reset, 1)
|
|
self.maxDiff = 10000
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(eq (sig b) (== (sig fsm_state) (const 1'd0)))
|
|
(eq (sig a) (== (sig fsm_state) (const 1'd1)))
|
|
(switch (sig fsm_state)
|
|
(case 1
|
|
)
|
|
(case 0
|
|
)
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_FSM_wrong_redefined(self):
|
|
m = Module()
|
|
with m.FSM():
|
|
with m.State("FOO"):
|
|
pass
|
|
with self.assertRaises(SyntaxError,
|
|
msg="FSM state 'FOO' is already defined"):
|
|
with m.State("FOO"):
|
|
pass
|
|
|
|
def test_FSM_wrong_next(self):
|
|
m = Module()
|
|
with self.assertRaises(SyntaxError,
|
|
msg="Only assignment to `m.next` is permitted"):
|
|
m.next
|
|
with self.assertRaises(SyntaxError,
|
|
msg="`m.next = <...>` is only permitted inside an FSM state"):
|
|
m.next = "FOO"
|
|
with self.assertRaises(SyntaxError,
|
|
msg="`m.next = <...>` is only permitted inside an FSM state"):
|
|
with m.FSM():
|
|
m.next = "FOO"
|
|
|
|
def test_auto_pop_ctrl(self):
|
|
m = Module()
|
|
with m.If(self.w1):
|
|
m.d.comb += self.c1.eq(1)
|
|
m.d.comb += self.c2.eq(1)
|
|
self.assertRepr(m._statements, """
|
|
(
|
|
(switch (cat (b (sig w1)))
|
|
(case 1 (eq (sig c1) (const 1'd1)))
|
|
)
|
|
(eq (sig c2) (const 1'd1))
|
|
)
|
|
""")
|
|
|
|
def test_submodule_anon(self):
|
|
m1 = Module()
|
|
m2 = Module()
|
|
m1.submodules += m2
|
|
self.assertEqual(m1._submodules, [(m2, None)])
|
|
|
|
def test_submodule_anon_multi(self):
|
|
m1 = Module()
|
|
m2 = Module()
|
|
m3 = Module()
|
|
m1.submodules += m2, m3
|
|
self.assertEqual(m1._submodules, [(m2, None), (m3, None)])
|
|
|
|
def test_submodule_named(self):
|
|
m1 = Module()
|
|
m2 = Module()
|
|
m1.submodules.foo = m2
|
|
self.assertEqual(m1._submodules, [(m2, "foo")])
|
|
|
|
def test_submodule_wrong(self):
|
|
m = Module()
|
|
with self.assertRaises(TypeError,
|
|
msg="Trying to add '1', which does not implement .elaborate(), as a submodule"):
|
|
m.submodules.foo = 1
|
|
with self.assertRaises(TypeError,
|
|
msg="Trying to add '1', which does not implement .elaborate(), as a submodule"):
|
|
m.submodules += 1
|
|
|
|
def test_domain_named_implicit(self):
|
|
m = Module()
|
|
m.domains += ClockDomain("sync")
|
|
self.assertEqual(len(m._domains), 1)
|
|
|
|
def test_domain_named_explicit(self):
|
|
m = Module()
|
|
m.domains.foo = ClockDomain()
|
|
self.assertEqual(len(m._domains), 1)
|
|
self.assertEqual(m._domains[0].name, "foo")
|
|
|
|
def test_lower(self):
|
|
m1 = Module()
|
|
m1.d.comb += self.c1.eq(self.s1)
|
|
m2 = Module()
|
|
m2.d.comb += self.c2.eq(self.s2)
|
|
m2.d.sync += self.c3.eq(self.s3)
|
|
m1.submodules.foo = m2
|
|
|
|
f1 = m1.elaborate(platform=None)
|
|
self.assertRepr(f1.statements, """
|
|
(
|
|
(eq (sig c1) (sig s1))
|
|
)
|
|
""")
|
|
self.assertEqual(f1.drivers, {
|
|
None: SignalSet((self.c1,))
|
|
})
|
|
self.assertEqual(len(f1.subfragments), 1)
|
|
(f2, f2_name), = f1.subfragments
|
|
self.assertEqual(f2_name, "foo")
|
|
self.assertRepr(f2.statements, """
|
|
(
|
|
(eq (sig c2) (sig s2))
|
|
(eq (sig c3) (sig s3))
|
|
)
|
|
""")
|
|
self.assertEqual(f2.drivers, {
|
|
None: SignalSet((self.c2,)),
|
|
"sync": SignalSet((self.c3,))
|
|
})
|
|
self.assertEqual(len(f2.subfragments), 0)
|