test.compat: import tests from Migen as appropriate.
authorwhitequark <cz@m-labs.hk>
Tue, 18 Dec 2018 18:05:37 +0000 (18:05 +0000)
committerwhitequark <cz@m-labs.hk>
Sat, 26 Jan 2019 01:01:03 +0000 (01:01 +0000)
test_signed and test_coding are adjusted slightly to account for
differences in comb propagation between the simulators; we might want
to revert that eventually.

doc/COMPAT_SUMMARY.md
nmigen/compat/sim/__init__.py
nmigen/test/compat/__init__.py [new file with mode: 0644]
nmigen/test/compat/support.py [new file with mode: 0644]
nmigen/test/compat/test_coding.py [new file with mode: 0644]
nmigen/test/compat/test_constant.py [new file with mode: 0644]
nmigen/test/compat/test_fifo.py [new file with mode: 0644]
nmigen/test/compat/test_fsm.py [new file with mode: 0644]
nmigen/test/compat/test_passive.py [new file with mode: 0644]
nmigen/test/compat/test_signed.py [new file with mode: 0644]
nmigen/test/compat/test_size.py [new file with mode: 0644]

index ab58ae803a0dae369dbd802f2a219929f6b46296..0b2eb4a5e70ec61c99747627ecf74f79075093ae 100644 (file)
@@ -185,8 +185,8 @@ Compatibility summary
     - (⊙) `core` **brk**
     - (⊙) `vcd` **brk** → `vcd`
     - (⊙) `Simulator` **brk**
-    - (+) `run_simulation` **obs** → `.back.pysim.Simulator`
-    - (â\88\92) `passive` **obs** → `.hdl.ast.Passive`
+    - () `run_simulation` **obs** → `.back.pysim.Simulator`
+    - (â\8a\95) `passive` **obs** → `.hdl.ast.Passive`
   - (−) `build` ?
   - (+) `util` **obs**
     - (+) `misc` ⇒ `.tools`
index 8219cf9d90b09358808966526532a3faa1aa9840..23aab8bd94a852ef0f2419a50b048a2f26ba6d55 100644 (file)
@@ -1,7 +1,10 @@
+import functools
+import collections
+import inspect
 from ...back.pysim import *
 
 
-__all__ = ["run_simulation"]
+__all__ = ["run_simulation", "passive"]
 
 
 def run_simulation(fragment_or_module, generators, clocks={"sync": 10}, vcd_name=None,
@@ -19,6 +22,18 @@ def run_simulation(fragment_or_module, generators, clocks={"sync": 10}, vcd_name
     with Simulator(fragment, vcd_file=open(vcd_name, "w") if vcd_name else None) as sim:
         for domain, period in clocks.items():
             sim.add_clock(period / 1e9, domain=domain)
-        for domain, process in generators.items():
-            sim.add_sync_process(process, domain=domain)
+        for domain, processes in generators.items():
+            if isinstance(processes, collections.Iterable) and not inspect.isgenerator(processes):
+                for process in processes:
+                    sim.add_sync_process(process, domain=domain)
+            else:
+                sim.add_sync_process(processes, domain=domain)
         sim.run()
+
+
+def passive(generator):
+    @functools.wraps(generator)
+    def wrapper(*args, **kwargs):
+        yield Passive()
+        yield from generator(*args, **kwargs)
+    return wrapper
diff --git a/nmigen/test/compat/__init__.py b/nmigen/test/compat/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/nmigen/test/compat/support.py b/nmigen/test/compat/support.py
new file mode 100644 (file)
index 0000000..2a1292f
--- /dev/null
@@ -0,0 +1,13 @@
+from ...compat import *
+# from ...compat.fhdl import verilog
+
+
+class SimCase:
+    def setUp(self, *args, **kwargs):
+        self.tb = self.TestBench(*args, **kwargs)
+
+    # def test_to_verilog(self):
+    #     verilog.convert(self.tb)
+
+    def run_with(self, generator):
+        run_simulation(self.tb, generator)
diff --git a/nmigen/test/compat/test_coding.py b/nmigen/test/compat/test_coding.py
new file mode 100644 (file)
index 0000000..5d945b3
--- /dev/null
@@ -0,0 +1,114 @@
+import unittest
+
+from ...compat import *
+from ...compat.genlib.coding import *
+
+from .support import SimCase
+
+
+class EncCase(SimCase, unittest.TestCase):
+    class TestBench(Module):
+        def __init__(self):
+            self.submodules.dut = Encoder(8)
+
+    def test_sizes(self):
+        self.assertEqual(len(self.tb.dut.i), 8)
+        self.assertEqual(len(self.tb.dut.o), 3)
+        self.assertEqual(len(self.tb.dut.n), 1)
+
+    def test_run_sequence(self):
+        seq = list(range(1<<8))
+        def gen():
+            for _ in range(256):
+                if seq:
+                    yield self.tb.dut.i.eq(seq.pop(0))
+                yield
+                if (yield self.tb.dut.n):
+                    self.assertNotIn((yield self.tb.dut.i), [1<<i for i in range(8)])
+                else:
+                    self.assertEqual((yield self.tb.dut.i), 1<<(yield self.tb.dut.o))
+        self.run_with(gen())
+
+
+class PrioEncCase(SimCase, unittest.TestCase):
+    class TestBench(Module):
+        def __init__(self):
+            self.submodules.dut = PriorityEncoder(8)
+
+    def test_sizes(self):
+        self.assertEqual(len(self.tb.dut.i), 8)
+        self.assertEqual(len(self.tb.dut.o), 3)
+        self.assertEqual(len(self.tb.dut.n), 1)
+
+    def test_run_sequence(self):
+        seq = list(range(1<<8))
+        def gen():
+            for _ in range(256):
+                if seq:
+                    yield self.tb.dut.i.eq(seq.pop(0))
+                yield
+                i = yield self.tb.dut.i
+                if (yield self.tb.dut.n):
+                    self.assertEqual(i, 0)
+                else:
+                    o = yield self.tb.dut.o
+                    if o > 0:
+                        self.assertEqual(i & 1<<(o - 1), 0)
+                    self.assertGreaterEqual(i, 1<<o)
+        self.run_with(gen())
+
+
+class DecCase(SimCase, unittest.TestCase):
+    class TestBench(Module):
+        def __init__(self):
+            self.submodules.dut = Decoder(8)
+
+    def test_sizes(self):
+        self.assertEqual(len(self.tb.dut.i), 3)
+        self.assertEqual(len(self.tb.dut.o), 8)
+        self.assertEqual(len(self.tb.dut.n), 1)
+
+    def test_run_sequence(self):
+        seq = list(range(8*2))
+        def gen():
+            for _ in range(256):
+                if seq:
+                    i = seq.pop()
+                    yield self.tb.dut.i.eq(i//2)
+                    yield self.tb.dut.n.eq(i%2)
+                yield
+                i = yield self.tb.dut.i
+                o = yield self.tb.dut.o
+                if (yield self.tb.dut.n):
+                    self.assertEqual(o, 0)
+                else:
+                    self.assertEqual(o, 1<<i)
+        self.run_with(gen())
+
+
+class SmallPrioEncCase(SimCase, unittest.TestCase):
+    class TestBench(Module):
+        def __init__(self):
+            self.submodules.dut = PriorityEncoder(1)
+
+    def test_sizes(self):
+        self.assertEqual(len(self.tb.dut.i), 1)
+        self.assertEqual(len(self.tb.dut.o), 1)
+        self.assertEqual(len(self.tb.dut.n), 1)
+
+    def test_run_sequence(self):
+        seq = list(range(1))
+        def gen():
+            for _ in range(5):
+                if seq:
+                    yield self.tb.dut.i.eq(seq.pop(0))
+                yield
+                i = yield self.tb.dut.i
+                if (yield self.tb.dut.n):
+                    self.assertEqual(i, 0)
+                else:
+                    o = yield self.tb.dut.o
+                    if o > 0:
+                        self.assertEqual(i & 1<<(o - 1), 0)
+                    self.assertGreaterEqual(i, 1<<o)
+        self.run_with(gen())
diff --git a/nmigen/test/compat/test_constant.py b/nmigen/test/compat/test_constant.py
new file mode 100644 (file)
index 0000000..e18c207
--- /dev/null
@@ -0,0 +1,29 @@
+import unittest
+
+from ...compat import *
+from .support import SimCase
+
+
+class ConstantCase(SimCase, unittest.TestCase):
+    class TestBench(Module):
+        def __init__(self):
+            self.sigs = [
+                (Signal(3), Constant(0), 0),
+                (Signal(3), Constant(5), 5),
+                (Signal(3), Constant(1, 2), 1),
+                (Signal(3), Constant(-1, 7), 7),
+                (Signal(3), Constant(0b10101)[:3], 0b101),
+                (Signal(3), Constant(0b10101)[1:4], 0b10),
+                (Signal(4), Constant(0b1100)[::-1], 0b0011),
+            ]
+            self.comb += [a.eq(b) for a, b, c in self.sigs]
+
+    def test_comparisons(self):
+        def gen():
+            for s, l, v in self.tb.sigs:
+                s = yield s
+                self.assertEqual(
+                    s, int(v),
+                    "got {}, want {} from literal {}".format(
+                        s, v, l))
+        self.run_with(gen())
diff --git a/nmigen/test/compat/test_fifo.py b/nmigen/test/compat/test_fifo.py
new file mode 100644 (file)
index 0000000..af541f8
--- /dev/null
@@ -0,0 +1,56 @@
+import unittest
+from itertools import count
+
+from ...compat import *
+from ...compat.genlib.fifo import SyncFIFO
+
+from .support import SimCase
+
+
+class SyncFIFOCase(SimCase, unittest.TestCase):
+    class TestBench(Module):
+        def __init__(self):
+            self.submodules.dut = SyncFIFO(64, 2)
+
+            self.sync += [
+                If(self.dut.we & self.dut.writable,
+                    self.dut.din[:32].eq(self.dut.din[:32] + 1),
+                    self.dut.din[32:].eq(self.dut.din[32:] + 2)
+                )
+            ]
+
+    def test_run_sequence(self):
+        seq = list(range(20))
+        def gen():
+            for cycle in count():
+                # fire re and we at "random"
+                yield self.tb.dut.we.eq(cycle % 2 == 0)
+                yield self.tb.dut.re.eq(cycle % 3 == 0)
+                # the output if valid must be correct
+                if (yield self.tb.dut.readable) and (yield self.tb.dut.re):
+                    try:
+                        i = seq.pop(0)
+                    except IndexError:
+                        break
+                    self.assertEqual((yield self.tb.dut.dout[:32]), i)
+                    self.assertEqual((yield self.tb.dut.dout[32:]), i*2)
+                yield
+        self.run_with(gen())
+
+    def test_replace(self):
+        seq = [x for x in range(20) if x % 5]
+        def gen():
+            for cycle in count():
+                yield self.tb.dut.we.eq(cycle % 2 == 0)
+                yield self.tb.dut.re.eq(cycle % 7 == 0)
+                yield self.tb.dut.replace.eq(
+                    (yield self.tb.dut.din[:32]) % 5 == 1)
+                if (yield self.tb.dut.readable) and (yield self.tb.dut.re):
+                    try:
+                        i = seq.pop(0)
+                    except IndexError:
+                        break
+                    self.assertEqual((yield self.tb.dut.dout[:32]), i)
+                    self.assertEqual((yield self.tb.dut.dout[32:]), i*2)
+                yield
+        self.run_with(gen())
diff --git a/nmigen/test/compat/test_fsm.py b/nmigen/test/compat/test_fsm.py
new file mode 100644 (file)
index 0000000..58de5be
--- /dev/null
@@ -0,0 +1,87 @@
+import unittest
+from itertools import count
+
+from ...compat import *
+from ...compat.genlib.fsm import FSM
+
+from .support import SimCase
+
+
+class FSMCase(SimCase, unittest.TestCase):
+    class TestBench(Module):
+        def __init__(self):
+            self.ctrl   = Signal()
+            self.data   = Signal()
+            self.status = Signal(8)
+
+            self.submodules.dut = FSM()
+            self.dut.act("IDLE",
+                If(self.ctrl,
+                    NextState("START")
+                )
+            )
+            self.dut.act("START",
+                If(self.data,
+                    NextState("SET-STATUS-LOW")
+                ).Else(
+                    NextState("SET-STATUS")
+                )
+            )
+            self.dut.act("SET-STATUS",
+                NextValue(self.status, 0xaa),
+                NextState("IDLE")
+            )
+            self.dut.act("SET-STATUS-LOW",
+                NextValue(self.status[:4], 0xb),
+                NextState("IDLE")
+            )
+
+    def assertState(self, fsm, state):
+        self.assertEqual(fsm.decoding[(yield fsm.state)], state)
+
+    def test_next_state(self):
+        def gen():
+            yield from self.assertState(self.tb.dut, "IDLE")
+            yield
+            yield from self.assertState(self.tb.dut, "IDLE")
+            yield self.tb.ctrl.eq(1)
+            yield
+            yield from self.assertState(self.tb.dut, "IDLE")
+            yield self.tb.ctrl.eq(0)
+            yield
+            yield from self.assertState(self.tb.dut, "START")
+            yield
+            yield from self.assertState(self.tb.dut, "SET-STATUS")
+            yield self.tb.ctrl.eq(1)
+            yield
+            yield from self.assertState(self.tb.dut, "IDLE")
+            yield self.tb.ctrl.eq(0)
+            yield self.tb.data.eq(1)
+            yield
+            yield from self.assertState(self.tb.dut, "START")
+            yield self.tb.data.eq(0)
+            yield
+            yield from self.assertState(self.tb.dut, "SET-STATUS-LOW")
+        self.run_with(gen())
+
+    def test_next_value(self):
+        def gen():
+            self.assertEqual((yield self.tb.status), 0x00)
+            yield self.tb.ctrl.eq(1)
+            yield
+            yield self.tb.ctrl.eq(0)
+            yield
+            yield
+            yield from self.assertState(self.tb.dut, "SET-STATUS")
+            yield self.tb.ctrl.eq(1)
+            yield
+            self.assertEqual((yield self.tb.status), 0xaa)
+            yield self.tb.ctrl.eq(0)
+            yield self.tb.data.eq(1)
+            yield
+            yield self.tb.data.eq(0)
+            yield
+            yield from self.assertState(self.tb.dut, "SET-STATUS-LOW")
+            yield
+            self.assertEqual((yield self.tb.status), 0xab)
+        self.run_with(gen())
diff --git a/nmigen/test/compat/test_passive.py b/nmigen/test/compat/test_passive.py
new file mode 100644 (file)
index 0000000..da3483b
--- /dev/null
@@ -0,0 +1,23 @@
+import unittest
+
+from ...compat import *
+
+
+class PassiveCase(unittest.TestCase):
+    def test_terminates_correctly(self):
+        n = 5
+
+        count = 0
+        @passive
+        def counter():
+            nonlocal count
+            while True:
+                yield
+                count += 1
+
+        def terminator():
+            for i in range(n):
+                yield
+
+        run_simulation(Module(), [counter(), terminator()])
+        self.assertEqual(count, n)
diff --git a/nmigen/test/compat/test_signed.py b/nmigen/test/compat/test_signed.py
new file mode 100644 (file)
index 0000000..0eaaea0
--- /dev/null
@@ -0,0 +1,42 @@
+import unittest
+
+from ...compat import *
+from .support import SimCase
+
+
+class SignedCase(SimCase, unittest.TestCase):
+    class TestBench(Module):
+        def __init__(self):
+            self.a = Signal((3, True))
+            self.b = Signal((4, True))
+            comps = [
+                lambda p, q: p > q,
+                lambda p, q: p >= q,
+                lambda p, q: p < q,
+                lambda p, q: p <= q,
+                lambda p, q: p == q,
+                lambda p, q: p != q,
+            ]
+            self.vals = []
+            for asign in 1, -1:
+                for bsign in 1, -1:
+                    for f in comps:
+                        r = Signal()
+                        r0 = f(asign*self.a, bsign*self.b)
+                        self.comb += r.eq(r0)
+                        self.vals.append((asign, bsign, f, r, r0.op))
+
+    def test_comparisons(self):
+        def gen():
+            for i in range(-4, 4):
+                yield self.tb.a.eq(i)
+                yield self.tb.b.eq(i)
+                yield
+                a = yield self.tb.a
+                b = yield self.tb.b
+                for asign, bsign, f, r, op in self.tb.vals:
+                    r, r0 = (yield r), f(asign*a, bsign*b)
+                    self.assertEqual(r, int(r0),
+                            "got {}, want {}*{} {} {}*{} = {}".format(
+                                r, asign, a, op, bsign, b, r0))
+        self.run_with(gen())
diff --git a/nmigen/test/compat/test_size.py b/nmigen/test/compat/test_size.py
new file mode 100644 (file)
index 0000000..7c34e6c
--- /dev/null
@@ -0,0 +1,19 @@
+import unittest
+
+from ...compat import *
+
+
+def _same_slices(a, b):
+    return a.value is b.value and a.start == b.start and a.stop == b.stop
+
+
+class SignalSizeCase(unittest.TestCase):
+    def setUp(self):
+        self.i = C(0xaa)
+        self.j = C(-127)
+        self.s = Signal((13, True))
+
+    def test_len(self):
+        self.assertEqual(len(self.s), 13)
+        self.assertEqual(len(self.i), 8)
+        self.assertEqual(len(self.j), 8)