tests: move out of the main package.
authorwhitequark <whitequark@whitequark.org>
Thu, 27 Aug 2020 00:33:31 +0000 (00:33 +0000)
committerwhitequark <whitequark@whitequark.org>
Thu, 27 Aug 2020 00:33:31 +0000 (00:33 +0000)
Compared to tests in the repository root, tests in the package have
many downsides:
  * Unless explicitly excluded in find_packages(), tests and their
    support code effectively become a part of public API.
    This, unfortunately, happened with FHDLTestCase, which was never
    intended for downstream use.
  * Even if explicitly excluded from the setuptools package, using
    an editable install, or setting PYTHONPATH still allows accessing
    the tests.
  * Having a sub-package that is present in the source tree but not
    exported (or, worse, exported only sometimes) is confusing.
  * The name `nmigen.test` cannot be used for anything else, such as
    testing utilities that *are* intended for downstream use.

62 files changed:
.gitignore
nmigen/test/__init__.py [deleted file]
nmigen/test/compat/__init__.py [deleted file]
nmigen/test/compat/support.py [deleted file]
nmigen/test/compat/test_coding.py [deleted file]
nmigen/test/compat/test_constant.py [deleted file]
nmigen/test/compat/test_fifo.py [deleted file]
nmigen/test/compat/test_fsm.py [deleted file]
nmigen/test/compat/test_passive.py [deleted file]
nmigen/test/compat/test_run_simulation.py [deleted file]
nmigen/test/compat/test_signed.py [deleted file]
nmigen/test/compat/test_size.py [deleted file]
nmigen/test/test_build_dsl.py [deleted file]
nmigen/test/test_build_plat.py [deleted file]
nmigen/test/test_build_res.py [deleted file]
nmigen/test/test_compat.py [deleted file]
nmigen/test/test_examples.py [deleted file]
nmigen/test/test_hdl_ast.py [deleted file]
nmigen/test/test_hdl_cd.py [deleted file]
nmigen/test/test_hdl_dsl.py [deleted file]
nmigen/test/test_hdl_ir.py [deleted file]
nmigen/test/test_hdl_mem.py [deleted file]
nmigen/test/test_hdl_rec.py [deleted file]
nmigen/test/test_hdl_xfrm.py [deleted file]
nmigen/test/test_lib_cdc.py [deleted file]
nmigen/test/test_lib_coding.py [deleted file]
nmigen/test/test_lib_fifo.py [deleted file]
nmigen/test/test_lib_io.py [deleted file]
nmigen/test/test_lib_scheduler.py [deleted file]
nmigen/test/test_sim.py [deleted file]
nmigen/test/utils.py [deleted file]
setup.py
tests/__init__.py [new file with mode: 0644]
tests/compat/__init__.py [new file with mode: 0644]
tests/compat/support.py [new file with mode: 0644]
tests/compat/test_coding.py [new file with mode: 0644]
tests/compat/test_constant.py [new file with mode: 0644]
tests/compat/test_fifo.py [new file with mode: 0644]
tests/compat/test_fsm.py [new file with mode: 0644]
tests/compat/test_passive.py [new file with mode: 0644]
tests/compat/test_run_simulation.py [new file with mode: 0644]
tests/compat/test_signed.py [new file with mode: 0644]
tests/compat/test_size.py [new file with mode: 0644]
tests/test_build_dsl.py [new file with mode: 0644]
tests/test_build_plat.py [new file with mode: 0644]
tests/test_build_res.py [new file with mode: 0644]
tests/test_compat.py [new file with mode: 0644]
tests/test_examples.py [new file with mode: 0644]
tests/test_hdl_ast.py [new file with mode: 0644]
tests/test_hdl_cd.py [new file with mode: 0644]
tests/test_hdl_dsl.py [new file with mode: 0644]
tests/test_hdl_ir.py [new file with mode: 0644]
tests/test_hdl_mem.py [new file with mode: 0644]
tests/test_hdl_rec.py [new file with mode: 0644]
tests/test_hdl_xfrm.py [new file with mode: 0644]
tests/test_lib_cdc.py [new file with mode: 0644]
tests/test_lib_coding.py [new file with mode: 0644]
tests/test_lib_fifo.py [new file with mode: 0644]
tests/test_lib_io.py [new file with mode: 0644]
tests/test_lib_scheduler.py [new file with mode: 0644]
tests/test_sim.py [new file with mode: 0644]
tests/utils.py [new file with mode: 0644]

index 7c2e9bc7698acc8219b291a1fe64ff025367ea45..d11a3eb61749de07cfec0f1a7027b9a93d308774 100644 (file)
@@ -9,7 +9,7 @@ __pycache__/
 /htmlcov
 
 # tests
-**/test/spec_*/
+/tests/spec_*/
 *.vcd
 *.gtkw
 
diff --git a/nmigen/test/__init__.py b/nmigen/test/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/nmigen/test/compat/__init__.py b/nmigen/test/compat/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/nmigen/test/compat/support.py b/nmigen/test/compat/support.py
deleted file mode 100644 (file)
index 8491652..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-from ..._utils import _ignore_deprecated
-from ...compat import *
-from ...compat.fhdl import verilog
-
-
-class SimCase:
-    def setUp(self, *args, **kwargs):
-        with _ignore_deprecated():
-            self.tb = self.TestBench(*args, **kwargs)
-
-    def test_to_verilog(self):
-        verilog.convert(self.tb)
-
-    def run_with(self, generator):
-        with _ignore_deprecated():
-            run_simulation(self.tb, generator)
diff --git a/nmigen/test/compat/test_coding.py b/nmigen/test/compat/test_coding.py
deleted file mode 100644 (file)
index 452a048..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-# nmigen: UnusedElaboratable=no
-
-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
deleted file mode 100644 (file)
index e18c207..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-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
deleted file mode 100644 (file)
index bc6b81c..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-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())
diff --git a/nmigen/test/compat/test_fsm.py b/nmigen/test/compat/test_fsm.py
deleted file mode 100644 (file)
index 58de5be..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-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
deleted file mode 100644 (file)
index da3483b..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-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_run_simulation.py b/nmigen/test/compat/test_run_simulation.py
deleted file mode 100644 (file)
index 0c33413..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-import unittest
-from ... import Signal, Module, Elaboratable
-from .support import SimCase
-
-
-class RunSimulation(SimCase, unittest.TestCase):
-    """ test for https://github.com/nmigen/nmigen/issues/344 """
-
-    class TestBench(Elaboratable):
-        def __init__(self):
-            self.a = Signal()
-
-        def elaborate(self, platform):
-            m = Module()
-            m.d.sync += self.a.eq(~self.a)
-            return m
-
-    def test_run_simulation(self):
-        def gen():
-            yield
-            for i in range(10):
-                yield
-                a = (yield self.tb.a)
-                self.assertEqual(a, i % 2)
-
-        self.run_with(gen())
diff --git a/nmigen/test/compat/test_signed.py b/nmigen/test/compat/test_signed.py
deleted file mode 100644 (file)
index 0eaaea0..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-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
deleted file mode 100644 (file)
index e3864f9..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-import unittest
-
-from ..._utils import _ignore_deprecated
-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)
-        with _ignore_deprecated():
-            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)
diff --git a/nmigen/test/test_build_dsl.py b/nmigen/test/test_build_dsl.py
deleted file mode 100644 (file)
index 87f082a..0000000
+++ /dev/null
@@ -1,328 +0,0 @@
-from collections import OrderedDict
-
-from ..build.dsl import *
-from .utils import *
-
-
-class PinsTestCase(FHDLTestCase):
-    def test_basic(self):
-        p = Pins("A0 A1 A2")
-        self.assertEqual(repr(p), "(pins io A0 A1 A2)")
-        self.assertEqual(len(p.names), 3)
-        self.assertEqual(p.dir, "io")
-        self.assertEqual(p.invert, False)
-        self.assertEqual(list(p), ["A0", "A1", "A2"])
-
-    def test_invert(self):
-        p = PinsN("A0")
-        self.assertEqual(repr(p), "(pins-n io A0)")
-        self.assertEqual(p.invert, True)
-
-    def test_invert_arg(self):
-        p = Pins("A0", invert=True)
-        self.assertEqual(p.invert, True)
-
-    def test_conn(self):
-        p = Pins("0 1 2", conn=("pmod", 0))
-        self.assertEqual(list(p), ["pmod_0:0", "pmod_0:1", "pmod_0:2"])
-        p = Pins("0 1 2", conn=("pmod", "a"))
-        self.assertEqual(list(p), ["pmod_a:0", "pmod_a:1", "pmod_a:2"])
-
-    def test_map_names(self):
-        p = Pins("0 1 2", conn=("pmod", 0))
-        mapping = {
-            "pmod_0:0": "A0",
-            "pmod_0:1": "A1",
-            "pmod_0:2": "A2",
-        }
-        self.assertEqual(p.map_names(mapping, p), ["A0", "A1", "A2"])
-
-    def test_map_names_recur(self):
-        p = Pins("0", conn=("pmod", 0))
-        mapping = {
-            "pmod_0:0": "ext_0:1",
-            "ext_0:1":  "A1",
-        }
-        self.assertEqual(p.map_names(mapping, p), ["A1"])
-
-    def test_wrong_names(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Names must be a whitespace-separated string, not \['A0', 'A1', 'A2'\]$"):
-            p = Pins(["A0", "A1", "A2"])
-
-    def test_wrong_dir(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not 'wrong'$"):
-            p = Pins("A0 A1", dir="wrong")
-
-    def test_wrong_conn(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Connector must be None or a pair of string \(connector name\) and "
-                    r"integer\/string \(connector number\), not \('foo', None\)$")):
-            p = Pins("A0 A1", conn=("foo", None))
-
-    def test_wrong_map_names(self):
-        p = Pins("0 1 2", conn=("pmod", 0))
-        mapping = {
-            "pmod_0:0": "A0",
-        }
-        with self.assertRaisesRegex(NameError,
-                (r"^Resource \(pins io pmod_0:0 pmod_0:1 pmod_0:2\) refers to nonexistent "
-                    r"connector pin pmod_0:1$")):
-            p.map_names(mapping, p)
-
-    def test_wrong_assert_width(self):
-        with self.assertRaisesRegex(AssertionError,
-                r"^3 names are specified \(0 1 2\), but 4 names are expected$"):
-            Pins("0 1 2", assert_width=4)
-
-
-class DiffPairsTestCase(FHDLTestCase):
-    def test_basic(self):
-        dp = DiffPairs(p="A0 A1", n="B0 B1")
-        self.assertEqual(repr(dp), "(diffpairs io (p A0 A1) (n B0 B1))")
-        self.assertEqual(dp.p.names, ["A0", "A1"])
-        self.assertEqual(dp.n.names, ["B0", "B1"])
-        self.assertEqual(dp.dir, "io")
-        self.assertEqual(list(dp), [("A0", "B0"), ("A1", "B1")])
-
-    def test_invert(self):
-        dp = DiffPairsN(p="A0", n="B0")
-        self.assertEqual(repr(dp), "(diffpairs-n io (p A0) (n B0))")
-        self.assertEqual(dp.p.names, ["A0"])
-        self.assertEqual(dp.n.names, ["B0"])
-        self.assertEqual(dp.invert, True)
-
-    def test_conn(self):
-        dp = DiffPairs(p="0 1 2", n="3 4 5", conn=("pmod", 0))
-        self.assertEqual(list(dp), [
-            ("pmod_0:0", "pmod_0:3"),
-            ("pmod_0:1", "pmod_0:4"),
-            ("pmod_0:2", "pmod_0:5"),
-        ])
-
-    def test_dir(self):
-        dp = DiffPairs("A0", "B0", dir="o")
-        self.assertEqual(dp.dir, "o")
-        self.assertEqual(dp.p.dir, "o")
-        self.assertEqual(dp.n.dir, "o")
-
-    def test_wrong_width(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Positive and negative pins must have the same width, but \(pins io A0\) "
-                    r"and \(pins io B0 B1\) do not$")):
-            dp = DiffPairs("A0", "B0 B1")
-
-    def test_wrong_assert_width(self):
-        with self.assertRaisesRegex(AssertionError,
-                r"^3 names are specified \(0 1 2\), but 4 names are expected$"):
-            DiffPairs("0 1 2", "3 4 5", assert_width=4)
-
-
-class AttrsTestCase(FHDLTestCase):
-    def test_basic(self):
-        a = Attrs(IO_STANDARD="LVCMOS33", PULLUP=1)
-        self.assertEqual(a["IO_STANDARD"], "LVCMOS33")
-        self.assertEqual(repr(a), "(attrs IO_STANDARD='LVCMOS33' PULLUP=1)")
-
-    def test_remove(self):
-        a = Attrs(FOO=None)
-        self.assertEqual(a["FOO"], None)
-        self.assertEqual(repr(a), "(attrs !FOO)")
-
-    def test_callable(self):
-        fn = lambda self: "FOO"
-        a = Attrs(FOO=fn)
-        self.assertEqual(a["FOO"], fn)
-        self.assertEqual(repr(a), "(attrs FOO={!r})".format(fn))
-
-    def test_wrong_value(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Value of attribute FOO must be None, int, str, or callable, not 1\.0$"):
-            a = Attrs(FOO=1.0)
-
-
-class ClockTestCase(FHDLTestCase):
-    def test_basic(self):
-        c = Clock(1_000_000)
-        self.assertEqual(c.frequency, 1e6)
-        self.assertEqual(c.period, 1e-6)
-        self.assertEqual(repr(c), "(clock 1000000.0)")
-
-
-class SubsignalTestCase(FHDLTestCase):
-    def test_basic_pins(self):
-        s = Subsignal("a", Pins("A0"), Attrs(IOSTANDARD="LVCMOS33"))
-        self.assertEqual(repr(s),
-            "(subsignal a (pins io A0) (attrs IOSTANDARD='LVCMOS33'))")
-
-    def test_basic_diffpairs(self):
-        s = Subsignal("a", DiffPairs("A0", "B0"))
-        self.assertEqual(repr(s),
-            "(subsignal a (diffpairs io (p A0) (n B0)))")
-
-    def test_basic_subsignals(self):
-        s = Subsignal("a",
-                Subsignal("b", Pins("A0")),
-                Subsignal("c", Pins("A1")))
-        self.assertEqual(repr(s),
-            "(subsignal a (subsignal b (pins io A0)) "
-                         "(subsignal c (pins io A1)))")
-
-    def test_attrs(self):
-        s = Subsignal("a",
-                Subsignal("b", Pins("A0")),
-                Subsignal("c", Pins("A0"), Attrs(SLEW="FAST")),
-                Attrs(IOSTANDARD="LVCMOS33"))
-        self.assertEqual(s.attrs, {"IOSTANDARD": "LVCMOS33"})
-        self.assertEqual(s.ios[0].attrs, {})
-        self.assertEqual(s.ios[1].attrs, {"SLEW": "FAST"})
-
-    def test_attrs_many(self):
-        s = Subsignal("a", Pins("A0"), Attrs(SLEW="FAST"), Attrs(PULLUP="1"))
-        self.assertEqual(s.attrs, {"SLEW": "FAST", "PULLUP": "1"})
-
-    def test_clock(self):
-        s = Subsignal("a", Pins("A0"), Clock(1e6))
-        self.assertEqual(s.clock.frequency, 1e6)
-
-    def test_wrong_empty_io(self):
-        with self.assertRaisesRegex(ValueError, r"^Missing I\/O constraints$"):
-            s = Subsignal("a")
-
-    def test_wrong_io(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Constraint must be one of Pins, DiffPairs, Subsignal, Attrs, or Clock, "
-                    r"not 'wrong'$")):
-            s = Subsignal("a", "wrong")
-
-    def test_wrong_pins(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Pins and DiffPairs are incompatible with other location or subsignal "
-                    r"constraints, but \(pins io A1\) appears after \(pins io A0\)$")):
-            s = Subsignal("a", Pins("A0"), Pins("A1"))
-
-    def test_wrong_diffpairs(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Pins and DiffPairs are incompatible with other location or subsignal "
-                    r"constraints, but \(pins io A1\) appears after \(diffpairs io \(p A0\) \(n B0\)\)$")):
-            s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1"))
-
-    def test_wrong_subsignals(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Pins and DiffPairs are incompatible with other location or subsignal "
-                    r"constraints, but \(pins io B0\) appears after \(subsignal b \(pins io A0\)\)$")):
-            s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0"))
-
-    def test_wrong_clock(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Clock constraint can only be applied to Pins or DiffPairs, not "
-                    r"\(subsignal b \(pins io A0\)\)$")):
-            s = Subsignal("a", Subsignal("b", Pins("A0")), Clock(1e6))
-
-    def test_wrong_clock_many(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Clock constraint can be applied only once$"):
-            s = Subsignal("a", Pins("A0"), Clock(1e6), Clock(1e7))
-
-
-class ResourceTestCase(FHDLTestCase):
-    def test_basic(self):
-        r = Resource("serial", 0,
-                Subsignal("tx", Pins("A0", dir="o")),
-                Subsignal("rx", Pins("A1", dir="i")),
-                Attrs(IOSTANDARD="LVCMOS33"))
-        self.assertEqual(repr(r), "(resource serial 0"
-                                  " (subsignal tx (pins o A0))"
-                                  " (subsignal rx (pins i A1))"
-                                  " (attrs IOSTANDARD='LVCMOS33'))")
-
-    def test_family(self):
-        ios = [Subsignal("clk", Pins("A0", dir="o"))]
-        r1  = Resource.family(0, default_name="spi", ios=ios)
-        r2  = Resource.family("spi_flash", 0, default_name="spi", ios=ios)
-        r3  = Resource.family("spi_flash", 0, default_name="spi", ios=ios, name_suffix="4x")
-        r4  = Resource.family(0, default_name="spi", ios=ios, name_suffix="2x")
-        self.assertEqual(r1.name, "spi")
-        self.assertEqual(r1.ios, ios)
-        self.assertEqual(r2.name, "spi_flash")
-        self.assertEqual(r2.ios, ios)
-        self.assertEqual(r3.name, "spi_flash_4x")
-        self.assertEqual(r3.ios, ios)
-        self.assertEqual(r4.name, "spi_2x")
-        self.assertEqual(r4.ios, ios)
-
-
-class ConnectorTestCase(FHDLTestCase):
-    def test_string(self):
-        c = Connector("pmod", 0, "A0 A1 A2 A3 - - A4 A5 A6 A7 - -")
-        self.assertEqual(c.name, "pmod")
-        self.assertEqual(c.number, 0)
-        self.assertEqual(c.mapping, OrderedDict([
-            ("1", "A0"),
-            ("2", "A1"),
-            ("3", "A2"),
-            ("4", "A3"),
-            ("7", "A4"),
-            ("8", "A5"),
-            ("9", "A6"),
-            ("10", "A7"),
-        ]))
-        self.assertEqual(list(c), [
-            ("pmod_0:1", "A0"),
-            ("pmod_0:2", "A1"),
-            ("pmod_0:3", "A2"),
-            ("pmod_0:4", "A3"),
-            ("pmod_0:7", "A4"),
-            ("pmod_0:8", "A5"),
-            ("pmod_0:9", "A6"),
-            ("pmod_0:10", "A7"),
-        ])
-        self.assertEqual(repr(c),
-            "(connector pmod 0 1=>A0 2=>A1 3=>A2 4=>A3 7=>A4 8=>A5 9=>A6 10=>A7)")
-
-    def test_dict(self):
-        c = Connector("ext", 1, {"DP0": "A0", "DP1": "A1"})
-        self.assertEqual(c.name, "ext")
-        self.assertEqual(c.number, 1)
-        self.assertEqual(c.mapping, OrderedDict([
-            ("DP0", "A0"),
-            ("DP1", "A1"),
-        ]))
-
-    def test_conn(self):
-        c = Connector("pmod", 0, "0 1 2 3 - - 4 5 6 7 - -", conn=("expansion", 0))
-        self.assertEqual(c.mapping, OrderedDict([
-            ("1", "expansion_0:0"),
-            ("2", "expansion_0:1"),
-            ("3", "expansion_0:2"),
-            ("4", "expansion_0:3"),
-            ("7", "expansion_0:4"),
-            ("8", "expansion_0:5"),
-            ("9", "expansion_0:6"),
-            ("10", "expansion_0:7"),
-        ]))
-
-    def test_str_name(self):
-        c = Connector("ext", "A", "0 1 2")
-        self.assertEqual(c.name, "ext")
-        self.assertEqual(c.number, "A")
-
-    def test_conn_wrong_name(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Connector must be None or a pair of string \(connector name\) and "
-                    r"integer\/string \(connector number\), not \('foo', None\)$")):
-            Connector("ext", "A", "0 1 2", conn=("foo", None))
-
-    def test_wrong_io(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Connector I\/Os must be a dictionary or a string, not \[\]$"):
-            Connector("pmod", 0, [])
-
-    def test_wrong_dict_key_value(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Connector pin name must be a string, not 0$"):
-            Connector("pmod", 0, {0: "A"})
-        with self.assertRaisesRegex(TypeError,
-                r"^Platform pin name must be a string, not 0$"):
-            Connector("pmod", 0, {"A": 0})
diff --git a/nmigen/test/test_build_plat.py b/nmigen/test/test_build_plat.py
deleted file mode 100644 (file)
index 1342501..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-from .. import *
-from ..build.plat import *
-from .utils import *
-
-
-class MockPlatform(Platform):
-    resources  = []
-    connectors = []
-
-    required_tools = []
-
-    def toolchain_prepare(self, fragment, name, **kwargs):
-        raise NotImplementedError
-
-
-class PlatformTestCase(FHDLTestCase):
-    def setUp(self):
-        self.platform = MockPlatform()
-
-    def test_add_file_str(self):
-        self.platform.add_file("x.txt", "foo")
-        self.assertEqual(self.platform.extra_files["x.txt"], "foo")
-
-    def test_add_file_bytes(self):
-        self.platform.add_file("x.txt", b"foo")
-        self.assertEqual(self.platform.extra_files["x.txt"], b"foo")
-
-    def test_add_file_exact_duplicate(self):
-        self.platform.add_file("x.txt", b"foo")
-        self.platform.add_file("x.txt", b"foo")
-
-    def test_add_file_io(self):
-        with open(__file__) as f:
-            self.platform.add_file("x.txt", f)
-        with open(__file__) as f:
-            self.assertEqual(self.platform.extra_files["x.txt"], f.read())
-
-    def test_add_file_wrong_filename(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^File name must be a string, not 1$"):
-            self.platform.add_file(1, "")
-
-    def test_add_file_wrong_contents(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^File contents must be str, bytes, or a file-like object, not 1$"):
-            self.platform.add_file("foo", 1)
-
-    def test_add_file_wrong_duplicate(self):
-        self.platform.add_file("foo", "")
-        with self.assertRaisesRegex(ValueError,
-                r"^File 'foo' already exists$"):
-            self.platform.add_file("foo", "bar")
diff --git a/nmigen/test/test_build_res.py b/nmigen/test/test_build_res.py
deleted file mode 100644 (file)
index 740d468..0000000
+++ /dev/null
@@ -1,313 +0,0 @@
-# nmigen: UnusedElaboratable=no
-
-from .. import *
-from ..hdl.rec import *
-from ..lib.io import *
-from ..build.dsl import *
-from ..build.res import *
-from .utils import *
-
-
-class ResourceManagerTestCase(FHDLTestCase):
-    def setUp(self):
-        self.resources = [
-            Resource("clk100", 0, DiffPairs("H1", "H2", dir="i"), Clock(100e6)),
-            Resource("clk50", 0, Pins("K1"), Clock(50e6)),
-            Resource("user_led", 0, Pins("A0", dir="o")),
-            Resource("i2c", 0,
-                Subsignal("scl", Pins("N10", dir="o")),
-                Subsignal("sda", Pins("N11"))
-            )
-        ]
-        self.connectors = [
-            Connector("pmod", 0, "B0 B1 B2 B3 - -"),
-        ]
-        self.cm = ResourceManager(self.resources, self.connectors)
-
-    def test_basic(self):
-        self.cm = ResourceManager(self.resources, self.connectors)
-        self.assertEqual(self.cm.resources, {
-            ("clk100",   0): self.resources[0],
-            ("clk50",    0): self.resources[1],
-            ("user_led", 0): self.resources[2],
-            ("i2c",      0): self.resources[3]
-        })
-        self.assertEqual(self.cm.connectors, {
-            ("pmod", 0): self.connectors[0],
-        })
-
-    def test_add_resources(self):
-        new_resources = [
-            Resource("user_led", 1, Pins("A1", dir="o"))
-        ]
-        self.cm.add_resources(new_resources)
-        self.assertEqual(self.cm.resources, {
-            ("clk100",   0): self.resources[0],
-            ("clk50",    0): self.resources[1],
-            ("user_led", 0): self.resources[2],
-            ("i2c",      0): self.resources[3],
-            ("user_led", 1): new_resources[0]
-        })
-
-    def test_lookup(self):
-        r = self.cm.lookup("user_led", 0)
-        self.assertIs(r, self.cm.resources["user_led", 0])
-
-    def test_request_basic(self):
-        r = self.cm.lookup("user_led", 0)
-        user_led = self.cm.request("user_led", 0)
-
-        self.assertIsInstance(user_led, Pin)
-        self.assertEqual(user_led.name, "user_led_0")
-        self.assertEqual(user_led.width, 1)
-        self.assertEqual(user_led.dir, "o")
-
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 1)
-
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("user_led_0__io", ["A0"], {})
-        ])
-
-    def test_request_with_dir(self):
-        i2c = self.cm.request("i2c", 0, dir={"sda": "o"})
-        self.assertIsInstance(i2c, Record)
-        self.assertIsInstance(i2c.sda, Pin)
-        self.assertEqual(i2c.sda.dir, "o")
-
-    def test_request_tristate(self):
-        i2c = self.cm.request("i2c", 0)
-        self.assertEqual(i2c.sda.dir, "io")
-
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 2)
-        scl, sda = ports
-        self.assertEqual(ports[1].name, "i2c_0__sda__io")
-        self.assertEqual(ports[1].width, 1)
-
-        scl_info, sda_info = self.cm.iter_single_ended_pins()
-        self.assertIs(scl_info[0], i2c.scl)
-        self.assertIs(scl_info[1].io, scl)
-        self.assertEqual(scl_info[2], {})
-        self.assertEqual(scl_info[3], False)
-        self.assertIs(sda_info[0], i2c.sda)
-        self.assertIs(sda_info[1].io, sda)
-
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("i2c_0__scl__io", ["N10"], {}),
-            ("i2c_0__sda__io", ["N11"], {})
-        ])
-
-    def test_request_diffpairs(self):
-        clk100 = self.cm.request("clk100", 0)
-        self.assertIsInstance(clk100, Pin)
-        self.assertEqual(clk100.dir, "i")
-        self.assertEqual(clk100.width, 1)
-
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 2)
-        p, n = ports
-        self.assertEqual(p.name, "clk100_0__p")
-        self.assertEqual(p.width, clk100.width)
-        self.assertEqual(n.name, "clk100_0__n")
-        self.assertEqual(n.width, clk100.width)
-
-        clk100_info, = self.cm.iter_differential_pins()
-        self.assertIs(clk100_info[0], clk100)
-        self.assertIs(clk100_info[1].p, p)
-        self.assertIs(clk100_info[1].n, n)
-        self.assertEqual(clk100_info[2], {})
-        self.assertEqual(clk100_info[3], False)
-
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("clk100_0__p", ["H1"], {}),
-            ("clk100_0__n", ["H2"], {}),
-        ])
-
-    def test_request_inverted(self):
-        new_resources = [
-            Resource("cs", 0, PinsN("X0")),
-            Resource("clk", 0, DiffPairsN("Y0", "Y1")),
-        ]
-        self.cm.add_resources(new_resources)
-
-        cs = self.cm.request("cs")
-        clk = self.cm.request("clk")
-        cs_io, clk_p, clk_n = self.cm.iter_ports()
-
-        cs_info, = self.cm.iter_single_ended_pins()
-        self.assertIs(cs_info[0], cs)
-        self.assertIs(cs_info[1].io, cs_io)
-        self.assertEqual(cs_info[2], {})
-        self.assertEqual(cs_info[3], True)
-
-        clk_info, = self.cm.iter_differential_pins()
-        self.assertIs(clk_info[0], clk)
-        self.assertIs(clk_info[1].p, clk_p)
-        self.assertIs(clk_info[1].n, clk_n)
-        self.assertEqual(clk_info[2], {})
-        self.assertEqual(clk_info[3], True)
-
-    def test_request_raw(self):
-        clk50 = self.cm.request("clk50", 0, dir="-")
-        self.assertIsInstance(clk50, Record)
-        self.assertIsInstance(clk50.io, Signal)
-
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 1)
-        self.assertIs(ports[0], clk50.io)
-
-    def test_request_raw_diffpairs(self):
-        clk100 = self.cm.request("clk100", 0, dir="-")
-        self.assertIsInstance(clk100, Record)
-        self.assertIsInstance(clk100.p, Signal)
-        self.assertIsInstance(clk100.n, Signal)
-
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 2)
-        self.assertIs(ports[0], clk100.p)
-        self.assertIs(ports[1], clk100.n)
-
-    def test_request_via_connector(self):
-        self.cm.add_resources([
-            Resource("spi", 0,
-                Subsignal("ss",   Pins("1", conn=("pmod", 0))),
-                Subsignal("clk",  Pins("2", conn=("pmod", 0))),
-                Subsignal("miso", Pins("3", conn=("pmod", 0))),
-                Subsignal("mosi", Pins("4", conn=("pmod", 0))),
-            )
-        ])
-        spi0 = self.cm.request("spi", 0)
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("spi_0__ss__io",   ["B0"], {}),
-            ("spi_0__clk__io",  ["B1"], {}),
-            ("spi_0__miso__io", ["B2"], {}),
-            ("spi_0__mosi__io", ["B3"], {}),
-        ])
-
-    def test_request_via_nested_connector(self):
-        new_connectors = [
-            Connector("pmod_extension", 0, "1 2 3 4 - -", conn=("pmod", 0)),
-        ]
-        self.cm.add_connectors(new_connectors)
-        self.cm.add_resources([
-            Resource("spi", 0,
-                Subsignal("ss",   Pins("1", conn=("pmod_extension", 0))),
-                Subsignal("clk",  Pins("2", conn=("pmod_extension", 0))),
-                Subsignal("miso", Pins("3", conn=("pmod_extension", 0))),
-                Subsignal("mosi", Pins("4", conn=("pmod_extension", 0))),
-            )
-        ])
-        spi0 = self.cm.request("spi", 0)
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("spi_0__ss__io",   ["B0"], {}),
-            ("spi_0__clk__io",  ["B1"], {}),
-            ("spi_0__miso__io", ["B2"], {}),
-            ("spi_0__mosi__io", ["B3"], {}),
-        ])
-
-    def test_request_clock(self):
-        clk100 = self.cm.request("clk100", 0)
-        clk50 = self.cm.request("clk50", 0, dir="i")
-        clk100_port_p, clk100_port_n, clk50_port = self.cm.iter_ports()
-        self.assertEqual(list(self.cm.iter_clock_constraints()), [
-            (clk100.i, clk100_port_p, 100e6),
-            (clk50.i, clk50_port, 50e6)
-        ])
-
-    def test_add_clock(self):
-        i2c = self.cm.request("i2c")
-        self.cm.add_clock_constraint(i2c.scl.o, 100e3)
-        self.assertEqual(list(self.cm.iter_clock_constraints()), [
-            (i2c.scl.o, None, 100e3)
-        ])
-
-    def test_wrong_resources(self):
-        with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Resource$"):
-            self.cm.add_resources(['wrong'])
-
-    def test_wrong_resources_duplicate(self):
-        with self.assertRaisesRegex(NameError,
-                (r"^Trying to add \(resource user_led 0 \(pins o A1\)\), but "
-                    r"\(resource user_led 0 \(pins o A0\)\) has the same name and number$")):
-            self.cm.add_resources([Resource("user_led", 0, Pins("A1", dir="o"))])
-
-    def test_wrong_connectors(self):
-        with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Connector$"):
-            self.cm.add_connectors(['wrong'])
-
-    def test_wrong_connectors_duplicate(self):
-        with self.assertRaisesRegex(NameError,
-                (r"^Trying to add \(connector pmod 0 1=>1 2=>2\), but "
-                    r"\(connector pmod 0 1=>B0 2=>B1 3=>B2 4=>B3\) has the same name and number$")):
-            self.cm.add_connectors([Connector("pmod", 0, "1 2")])
-
-    def test_wrong_lookup(self):
-        with self.assertRaisesRegex(ResourceError,
-                r"^Resource user_led#1 does not exist$"):
-            r = self.cm.lookup("user_led", 1)
-
-    def test_wrong_clock_signal(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Object None is not a Signal$"):
-            self.cm.add_clock_constraint(None, 10e6)
-
-    def test_wrong_clock_frequency(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Frequency must be a number, not None$"):
-            self.cm.add_clock_constraint(Signal(), None)
-
-    def test_wrong_request_duplicate(self):
-        with self.assertRaisesRegex(ResourceError,
-                r"^Resource user_led#0 has already been requested$"):
-            self.cm.request("user_led", 0)
-            self.cm.request("user_led", 0)
-
-    def test_wrong_request_duplicate_physical(self):
-        self.cm.add_resources([
-            Resource("clk20", 0, Pins("H1", dir="i")),
-        ])
-        self.cm.request("clk100", 0)
-        with self.assertRaisesRegex(ResourceError,
-                (r"^Resource component clk20_0 uses physical pin H1, but it is already "
-                    r"used by resource component clk100_0 that was requested earlier$")):
-            self.cm.request("clk20", 0)
-
-    def test_wrong_request_with_dir(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Direction must be one of \"i\", \"o\", \"oe\", \"io\", or \"-\", "
-                    r"not 'wrong'$")):
-            user_led = self.cm.request("user_led", 0, dir="wrong")
-
-    def test_wrong_request_with_dir_io(self):
-        with self.assertRaisesRegex(ValueError,
-                (r"^Direction of \(pins o A0\) cannot be changed from \"o\" to \"i\"; direction "
-                    r"can be changed from \"io\" to \"i\", \"o\", or \"oe\", or from anything "
-                    r"to \"-\"$")):
-            user_led = self.cm.request("user_led", 0, dir="i")
-
-    def test_wrong_request_with_dir_dict(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Directions must be a dict, not 'i', because \(resource i2c 0 \(subsignal scl "
-                    r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) "
-                    r"has subsignals$")):
-            i2c = self.cm.request("i2c", 0, dir="i")
-
-    def test_wrong_request_with_wrong_xdr(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Data rate of \(pins o A0\) must be a non-negative integer, not -1$"):
-            user_led = self.cm.request("user_led", 0, xdr=-1)
-
-    def test_wrong_request_with_xdr_dict(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Data rate must be a dict, not 2, because \(resource i2c 0 \(subsignal scl "
-                    r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) "
-                    r"has subsignals$"):
-            i2c = self.cm.request("i2c", 0, xdr=2)
-
-    def test_wrong_clock_constraint_twice(self):
-        clk100 = self.cm.request("clk100")
-        with self.assertRaisesRegex(ValueError,
-                (r"^Cannot add clock constraint on \(sig clk100_0__i\), which is already "
-                    r"constrained to 100000000\.0 Hz$")):
-            self.cm.add_clock_constraint(clk100.i, 1e6)
diff --git a/nmigen/test/test_compat.py b/nmigen/test/test_compat.py
deleted file mode 100644 (file)
index 62e2d92..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-from ..hdl.ir import Fragment
-from ..compat import *
-from .utils import *
-
-
-class CompatTestCase(FHDLTestCase):
-    def test_fragment_get(self):
-        m = Module()
-        f = Fragment.get(m, platform=None)
diff --git a/nmigen/test/test_examples.py b/nmigen/test/test_examples.py
deleted file mode 100644 (file)
index 44211d3..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-import sys
-import subprocess
-from pathlib import Path
-
-from .utils import *
-
-
-def example_test(name):
-    path = (Path(__file__).parent / ".." / ".." / "examples" / name).resolve()
-    def test_function(self):
-        subprocess.check_call([sys.executable, str(path), "generate", "-t", "v"],
-                              stdout=subprocess.DEVNULL)
-    return test_function
-
-
-class ExamplesTestCase(FHDLTestCase):
-    test_alu        = example_test("basic/alu.py")
-    test_alu_hier   = example_test("basic/alu_hier.py")
-    test_arst       = example_test("basic/arst.py")
-    test_cdc        = example_test("basic/cdc.py")
-    test_ctr        = example_test("basic/ctr.py")
-    test_ctr_en     = example_test("basic/ctr_en.py")
-    test_fsm        = example_test("basic/fsm.py")
-    test_gpio       = example_test("basic/gpio.py")
-    test_inst       = example_test("basic/inst.py")
-    test_mem        = example_test("basic/mem.py")
-    test_pmux       = example_test("basic/pmux.py")
-    test_por        = example_test("basic/por.py")
-
-    def test_uart(self):
-        path = (Path(__file__).parent / ".." / ".." / "examples" / "basic" / "uart.py").resolve()
-        def test_function(self):
-            subprocess.check_call([sys.executable, str(path), "generate"],
-                                  stdout=subprocess.DEVNULL)
diff --git a/nmigen/test/test_hdl_ast.py b/nmigen/test/test_hdl_ast.py
deleted file mode 100644 (file)
index af96e16..0000000
+++ /dev/null
@@ -1,1058 +0,0 @@
-import warnings
-from enum import Enum
-
-from ..hdl.ast import *
-from .utils import *
-
-
-class UnsignedEnum(Enum):
-    FOO = 1
-    BAR = 2
-    BAZ = 3
-
-
-class SignedEnum(Enum):
-    FOO = -1
-    BAR =  0
-    BAZ = +1
-
-
-class StringEnum(Enum):
-    FOO = "a"
-    BAR = "b"
-
-
-class ShapeTestCase(FHDLTestCase):
-    def test_make(self):
-        s1 = Shape()
-        self.assertEqual(s1.width, 1)
-        self.assertEqual(s1.signed, False)
-        s2 = Shape(signed=True)
-        self.assertEqual(s2.width, 1)
-        self.assertEqual(s2.signed, True)
-        s3 = Shape(3, True)
-        self.assertEqual(s3.width, 3)
-        self.assertEqual(s3.signed, True)
-
-    def test_make_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Width must be a non-negative integer, not -1$"):
-            Shape(-1)
-
-    def test_compare_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Shapes may be compared with other Shapes and \(int, bool\) tuples, not 'hi'$"):
-            Shape(1, True) == 'hi'
-
-    def test_compare_tuple_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Shapes may be compared with other Shapes and \(int, bool\) tuples, not \(2, 3\)$"):
-            Shape(1, True) == (2, 3)
-
-    def test_repr(self):
-        self.assertEqual(repr(Shape()), "unsigned(1)")
-        self.assertEqual(repr(Shape(2, True)), "signed(2)")
-
-    def test_tuple(self):
-        width, signed = Shape()
-        self.assertEqual(width, 1)
-        self.assertEqual(signed, False)
-
-    def test_unsigned(self):
-        s1 = unsigned(2)
-        self.assertIsInstance(s1, Shape)
-        self.assertEqual(s1.width, 2)
-        self.assertEqual(s1.signed, False)
-
-    def test_signed(self):
-        s1 = signed(2)
-        self.assertIsInstance(s1, Shape)
-        self.assertEqual(s1.width, 2)
-        self.assertEqual(s1.signed, True)
-
-    def test_cast_shape(self):
-        s1 = Shape.cast(unsigned(1))
-        self.assertEqual(s1.width, 1)
-        self.assertEqual(s1.signed, False)
-        s2 = Shape.cast(signed(3))
-        self.assertEqual(s2.width, 3)
-        self.assertEqual(s2.signed, True)
-
-    def test_cast_int(self):
-        s1 = Shape.cast(2)
-        self.assertEqual(s1.width, 2)
-        self.assertEqual(s1.signed, False)
-
-    def test_cast_int_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Width must be a non-negative integer, not -1$"):
-            Shape.cast(-1)
-
-    def test_cast_tuple(self):
-        with warnings.catch_warnings():
-            warnings.filterwarnings(action="ignore", category=DeprecationWarning)
-            s1 = Shape.cast((1, True))
-            self.assertEqual(s1.width, 1)
-            self.assertEqual(s1.signed, True)
-
-    def test_cast_tuple_wrong(self):
-        with warnings.catch_warnings():
-            warnings.filterwarnings(action="ignore", category=DeprecationWarning)
-            with self.assertRaisesRegex(TypeError,
-                    r"^Width must be a non-negative integer, not -1$"):
-                Shape.cast((-1, True))
-
-    def test_cast_range(self):
-        s1 = Shape.cast(range(0, 8))
-        self.assertEqual(s1.width, 3)
-        self.assertEqual(s1.signed, False)
-        s2 = Shape.cast(range(0, 9))
-        self.assertEqual(s2.width, 4)
-        self.assertEqual(s2.signed, False)
-        s3 = Shape.cast(range(-7, 8))
-        self.assertEqual(s3.width, 4)
-        self.assertEqual(s3.signed, True)
-        s4 = Shape.cast(range(0, 1))
-        self.assertEqual(s4.width, 1)
-        self.assertEqual(s4.signed, False)
-        s5 = Shape.cast(range(-1, 0))
-        self.assertEqual(s5.width, 1)
-        self.assertEqual(s5.signed, True)
-        s6 = Shape.cast(range(0, 0))
-        self.assertEqual(s6.width, 0)
-        self.assertEqual(s6.signed, False)
-        s7 = Shape.cast(range(-1, -1))
-        self.assertEqual(s7.width, 0)
-        self.assertEqual(s7.signed, True)
-
-    def test_cast_enum(self):
-        s1 = Shape.cast(UnsignedEnum)
-        self.assertEqual(s1.width, 2)
-        self.assertEqual(s1.signed, False)
-        s2 = Shape.cast(SignedEnum)
-        self.assertEqual(s2.width, 2)
-        self.assertEqual(s2.signed, True)
-
-    def test_cast_enum_bad(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Only enumerations with integer values can be used as value shapes$"):
-            Shape.cast(StringEnum)
-
-    def test_cast_bad(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Object 'foo' cannot be used as value shape$"):
-            Shape.cast("foo")
-
-
-class ValueTestCase(FHDLTestCase):
-    def test_cast(self):
-        self.assertIsInstance(Value.cast(0), Const)
-        self.assertIsInstance(Value.cast(True), Const)
-        c = Const(0)
-        self.assertIs(Value.cast(c), c)
-        with self.assertRaisesRegex(TypeError,
-                r"^Object 'str' cannot be converted to an nMigen value$"):
-            Value.cast("str")
-
-    def test_cast_enum(self):
-        e1 = Value.cast(UnsignedEnum.FOO)
-        self.assertIsInstance(e1, Const)
-        self.assertEqual(e1.shape(), unsigned(2))
-        e2 = Value.cast(SignedEnum.FOO)
-        self.assertIsInstance(e2, Const)
-        self.assertEqual(e2.shape(), signed(2))
-
-    def test_cast_enum_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Only enumerations with integer values can be used as value shapes$"):
-            Value.cast(StringEnum.FOO)
-
-    def test_bool(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Attempted to convert nMigen value to Python boolean$"):
-            if Const(0):
-                pass
-
-    def test_len(self):
-        self.assertEqual(len(Const(10)), 4)
-
-    def test_getitem_int(self):
-        s1 = Const(10)[0]
-        self.assertIsInstance(s1, Slice)
-        self.assertEqual(s1.start, 0)
-        self.assertEqual(s1.stop, 1)
-        s2 = Const(10)[-1]
-        self.assertIsInstance(s2, Slice)
-        self.assertEqual(s2.start, 3)
-        self.assertEqual(s2.stop, 4)
-        with self.assertRaisesRegex(IndexError,
-                r"^Cannot index 5 bits into 4-bit value$"):
-            Const(10)[5]
-
-    def test_getitem_slice(self):
-        s1 = Const(10)[1:3]
-        self.assertIsInstance(s1, Slice)
-        self.assertEqual(s1.start, 1)
-        self.assertEqual(s1.stop, 3)
-        s2 = Const(10)[1:-2]
-        self.assertIsInstance(s2, Slice)
-        self.assertEqual(s2.start, 1)
-        self.assertEqual(s2.stop, 2)
-        s3 = Const(31)[::2]
-        self.assertIsInstance(s3, Cat)
-        self.assertIsInstance(s3.parts[0], Slice)
-        self.assertEqual(s3.parts[0].start, 0)
-        self.assertEqual(s3.parts[0].stop, 1)
-        self.assertIsInstance(s3.parts[1], Slice)
-        self.assertEqual(s3.parts[1].start, 2)
-        self.assertEqual(s3.parts[1].stop, 3)
-        self.assertIsInstance(s3.parts[2], Slice)
-        self.assertEqual(s3.parts[2].start, 4)
-        self.assertEqual(s3.parts[2].stop, 5)
-
-    def test_getitem_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Cannot index value with 'str'$"):
-            Const(31)["str"]
-
-    def test_shift_left(self):
-        self.assertRepr(Const(256, unsigned(9)).shift_left(0),
-                        "(cat (const 0'd0) (const 9'd256))")
-
-        self.assertRepr(Const(256, unsigned(9)).shift_left(1),
-                        "(cat (const 1'd0) (const 9'd256))")
-        self.assertRepr(Const(256, unsigned(9)).shift_left(5),
-                        "(cat (const 5'd0) (const 9'd256))")
-        self.assertRepr(Const(256, signed(9)).shift_left(1),
-                        "(s (cat (const 1'd0) (const 9'sd-256)))")
-        self.assertRepr(Const(256, signed(9)).shift_left(5),
-                        "(s (cat (const 5'd0) (const 9'sd-256)))")
-
-        self.assertRepr(Const(256, unsigned(9)).shift_left(-1),
-                        "(slice (const 9'd256) 1:9)")
-        self.assertRepr(Const(256, unsigned(9)).shift_left(-5),
-                        "(slice (const 9'd256) 5:9)")
-        self.assertRepr(Const(256, signed(9)).shift_left(-1),
-                        "(s (slice (const 9'sd-256) 1:9))")
-        self.assertRepr(Const(256, signed(9)).shift_left(-5),
-                        "(s (slice (const 9'sd-256) 5:9))")
-        self.assertRepr(Const(256, signed(9)).shift_left(-15),
-                        "(s (slice (const 9'sd-256) 9:9))")
-
-    def test_shift_left_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Shift amount must be an integer, not 'str'$"):
-            Const(31).shift_left("str")
-
-    def test_shift_right(self):
-        self.assertRepr(Const(256, unsigned(9)).shift_right(0),
-                        "(slice (const 9'd256) 0:9)")
-
-        self.assertRepr(Const(256, unsigned(9)).shift_right(-1),
-                        "(cat (const 1'd0) (const 9'd256))")
-        self.assertRepr(Const(256, unsigned(9)).shift_right(-5),
-                        "(cat (const 5'd0) (const 9'd256))")
-        self.assertRepr(Const(256, signed(9)).shift_right(-1),
-                        "(s (cat (const 1'd0) (const 9'sd-256)))")
-        self.assertRepr(Const(256, signed(9)).shift_right(-5),
-                        "(s (cat (const 5'd0) (const 9'sd-256)))")
-
-        self.assertRepr(Const(256, unsigned(9)).shift_right(1),
-                        "(slice (const 9'd256) 1:9)")
-        self.assertRepr(Const(256, unsigned(9)).shift_right(5),
-                        "(slice (const 9'd256) 5:9)")
-        self.assertRepr(Const(256, signed(9)).shift_right(1),
-                        "(s (slice (const 9'sd-256) 1:9))")
-        self.assertRepr(Const(256, signed(9)).shift_right(5),
-                        "(s (slice (const 9'sd-256) 5:9))")
-        self.assertRepr(Const(256, signed(9)).shift_right(15),
-                        "(s (slice (const 9'sd-256) 9:9))")
-
-    def test_shift_right_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Shift amount must be an integer, not 'str'$"):
-            Const(31).shift_left("str")
-
-    def test_rotate_left(self):
-        self.assertRepr(Const(256).rotate_left(1),
-                        "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))")
-        self.assertRepr(Const(256).rotate_left(7),
-                        "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))")
-        self.assertRepr(Const(256).rotate_left(-1),
-                        "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))")
-        self.assertRepr(Const(256).rotate_left(-7),
-                        "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))")
-
-    def test_rotate_left_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Rotate amount must be an integer, not 'str'$"):
-            Const(31).rotate_left("str")
-
-    def test_rotate_right(self):
-        self.assertRepr(Const(256).rotate_right(1),
-                        "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))")
-        self.assertRepr(Const(256).rotate_right(7),
-                        "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))")
-        self.assertRepr(Const(256).rotate_right(-1),
-                        "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))")
-        self.assertRepr(Const(256).rotate_right(-7),
-                        "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))")
-
-    def test_rotate_right_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Rotate amount must be an integer, not 'str'$"):
-            Const(31).rotate_right("str")
-
-
-class ConstTestCase(FHDLTestCase):
-    def test_shape(self):
-        self.assertEqual(Const(0).shape(),   unsigned(1))
-        self.assertIsInstance(Const(0).shape(), Shape)
-        self.assertEqual(Const(1).shape(),   unsigned(1))
-        self.assertEqual(Const(10).shape(),  unsigned(4))
-        self.assertEqual(Const(-10).shape(), signed(5))
-
-        self.assertEqual(Const(1, 4).shape(),          unsigned(4))
-        self.assertEqual(Const(-1, 4).shape(),         signed(4))
-        self.assertEqual(Const(1, signed(4)).shape(),  signed(4))
-        self.assertEqual(Const(0, unsigned(0)).shape(), unsigned(0))
-
-    def test_shape_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Width must be a non-negative integer, not -1$"):
-            Const(1, -1)
-
-    def test_normalization(self):
-        self.assertEqual(Const(0b10110, signed(5)).value, -10)
-
-    def test_value(self):
-        self.assertEqual(Const(10).value, 10)
-
-    def test_repr(self):
-        self.assertEqual(repr(Const(10)),  "(const 4'd10)")
-        self.assertEqual(repr(Const(-10)), "(const 5'sd-10)")
-
-    def test_hash(self):
-        with self.assertRaises(TypeError):
-            hash(Const(0))
-
-
-class OperatorTestCase(FHDLTestCase):
-    def test_bool(self):
-        v = Const(0, 4).bool()
-        self.assertEqual(repr(v), "(b (const 4'd0))")
-        self.assertEqual(v.shape(), unsigned(1))
-
-    def test_invert(self):
-        v = ~Const(0, 4)
-        self.assertEqual(repr(v), "(~ (const 4'd0))")
-        self.assertEqual(v.shape(), unsigned(4))
-
-    def test_as_unsigned(self):
-        v = Const(-1, signed(4)).as_unsigned()
-        self.assertEqual(repr(v), "(u (const 4'sd-1))")
-        self.assertEqual(v.shape(), unsigned(4))
-
-    def test_as_signed(self):
-        v = Const(1, unsigned(4)).as_signed()
-        self.assertEqual(repr(v), "(s (const 4'd1))")
-        self.assertEqual(v.shape(), signed(4))
-
-    def test_neg(self):
-        v1 = -Const(0, unsigned(4))
-        self.assertEqual(repr(v1), "(- (const 4'd0))")
-        self.assertEqual(v1.shape(), signed(5))
-        v2 = -Const(0, signed(4))
-        self.assertEqual(repr(v2), "(- (const 4'sd0))")
-        self.assertEqual(v2.shape(), signed(5))
-
-    def test_add(self):
-        v1 = Const(0, unsigned(4)) + Const(0, unsigned(6))
-        self.assertEqual(repr(v1), "(+ (const 4'd0) (const 6'd0))")
-        self.assertEqual(v1.shape(), unsigned(7))
-        v2 = Const(0, signed(4)) + Const(0, signed(6))
-        self.assertEqual(v2.shape(), signed(7))
-        v3 = Const(0, signed(4)) + Const(0, unsigned(4))
-        self.assertEqual(v3.shape(), signed(6))
-        v4 = Const(0, unsigned(4)) + Const(0, signed(4))
-        self.assertEqual(v4.shape(), signed(6))
-        v5 = 10 + Const(0, 4)
-        self.assertEqual(v5.shape(), unsigned(5))
-
-    def test_sub(self):
-        v1 = Const(0, unsigned(4)) - Const(0, unsigned(6))
-        self.assertEqual(repr(v1), "(- (const 4'd0) (const 6'd0))")
-        self.assertEqual(v1.shape(), unsigned(7))
-        v2 = Const(0, signed(4)) - Const(0, signed(6))
-        self.assertEqual(v2.shape(), signed(7))
-        v3 = Const(0, signed(4)) - Const(0, unsigned(4))
-        self.assertEqual(v3.shape(), signed(6))
-        v4 = Const(0, unsigned(4)) - Const(0, signed(4))
-        self.assertEqual(v4.shape(), signed(6))
-        v5 = 10 - Const(0, 4)
-        self.assertEqual(v5.shape(), unsigned(5))
-
-    def test_mul(self):
-        v1 = Const(0, unsigned(4)) * Const(0, unsigned(6))
-        self.assertEqual(repr(v1), "(* (const 4'd0) (const 6'd0))")
-        self.assertEqual(v1.shape(), unsigned(10))
-        v2 = Const(0, signed(4)) * Const(0, signed(6))
-        self.assertEqual(v2.shape(), signed(10))
-        v3 = Const(0, signed(4)) * Const(0, unsigned(4))
-        self.assertEqual(v3.shape(), signed(8))
-        v5 = 10 * Const(0, 4)
-        self.assertEqual(v5.shape(), unsigned(8))
-
-    def test_mod(self):
-        v1 = Const(0, unsigned(4)) % Const(0, unsigned(6))
-        self.assertEqual(repr(v1), "(% (const 4'd0) (const 6'd0))")
-        self.assertEqual(v1.shape(), unsigned(4))
-        v3 = Const(0, signed(4)) % Const(0, unsigned(4))
-        self.assertEqual(v3.shape(), signed(4))
-        v5 = 10 % Const(0, 4)
-        self.assertEqual(v5.shape(), unsigned(4))
-
-    def test_mod_wrong(self):
-        with self.assertRaisesRegex(NotImplementedError,
-                r"^Division by a signed value is not supported$"):
-            Const(0, signed(4)) % Const(0, signed(6))
-
-    def test_floordiv(self):
-        v1 = Const(0, unsigned(4)) // Const(0, unsigned(6))
-        self.assertEqual(repr(v1), "(// (const 4'd0) (const 6'd0))")
-        self.assertEqual(v1.shape(), unsigned(4))
-        v3 = Const(0, signed(4)) // Const(0, unsigned(4))
-        self.assertEqual(v3.shape(), signed(4))
-        v5 = 10 // Const(0, 4)
-        self.assertEqual(v5.shape(), unsigned(4))
-
-    def test_floordiv_wrong(self):
-        with self.assertRaisesRegex(NotImplementedError,
-                r"^Division by a signed value is not supported$"):
-            Const(0, signed(4)) // Const(0, signed(6))
-
-    def test_and(self):
-        v1 = Const(0, unsigned(4)) & Const(0, unsigned(6))
-        self.assertEqual(repr(v1), "(& (const 4'd0) (const 6'd0))")
-        self.assertEqual(v1.shape(), unsigned(6))
-        v2 = Const(0, signed(4)) & Const(0, signed(6))
-        self.assertEqual(v2.shape(), signed(6))
-        v3 = Const(0, signed(4)) & Const(0, unsigned(4))
-        self.assertEqual(v3.shape(), signed(5))
-        v4 = Const(0, unsigned(4)) & Const(0, signed(4))
-        self.assertEqual(v4.shape(), signed(5))
-        v5 = 10 & Const(0, 4)
-        self.assertEqual(v5.shape(), unsigned(4))
-
-    def test_or(self):
-        v1 = Const(0, unsigned(4)) | Const(0, unsigned(6))
-        self.assertEqual(repr(v1), "(| (const 4'd0) (const 6'd0))")
-        self.assertEqual(v1.shape(), unsigned(6))
-        v2 = Const(0, signed(4)) | Const(0, signed(6))
-        self.assertEqual(v2.shape(), signed(6))
-        v3 = Const(0, signed(4)) | Const(0, unsigned(4))
-        self.assertEqual(v3.shape(), signed(5))
-        v4 = Const(0, unsigned(4)) | Const(0, signed(4))
-        self.assertEqual(v4.shape(), signed(5))
-        v5 = 10 | Const(0, 4)
-        self.assertEqual(v5.shape(), unsigned(4))
-
-    def test_xor(self):
-        v1 = Const(0, unsigned(4)) ^ Const(0, unsigned(6))
-        self.assertEqual(repr(v1), "(^ (const 4'd0) (const 6'd0))")
-        self.assertEqual(v1.shape(), unsigned(6))
-        v2 = Const(0, signed(4)) ^ Const(0, signed(6))
-        self.assertEqual(v2.shape(), signed(6))
-        v3 = Const(0, signed(4)) ^ Const(0, unsigned(4))
-        self.assertEqual(v3.shape(), signed(5))
-        v4 = Const(0, unsigned(4)) ^ Const(0, signed(4))
-        self.assertEqual(v4.shape(), signed(5))
-        v5 = 10 ^ Const(0, 4)
-        self.assertEqual(v5.shape(), unsigned(4))
-
-    def test_shl(self):
-        v1 = Const(1, 4) << Const(4)
-        self.assertEqual(repr(v1), "(<< (const 4'd1) (const 3'd4))")
-        self.assertEqual(v1.shape(), unsigned(11))
-
-    def test_shl_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Shift amount must be unsigned$"):
-            1 << Const(0, signed(6))
-        with self.assertRaisesRegex(TypeError,
-                r"^Shift amount must be unsigned$"):
-            Const(1, unsigned(4)) << -1
-
-    def test_shr(self):
-        v1 = Const(1, 4) >> Const(4)
-        self.assertEqual(repr(v1), "(>> (const 4'd1) (const 3'd4))")
-        self.assertEqual(v1.shape(), unsigned(4))
-
-    def test_shr_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Shift amount must be unsigned$"):
-            1 << Const(0, signed(6))
-        with self.assertRaisesRegex(TypeError,
-                r"^Shift amount must be unsigned$"):
-            Const(1, unsigned(4)) << -1
-
-    def test_lt(self):
-        v = Const(0, 4) < Const(0, 6)
-        self.assertEqual(repr(v), "(< (const 4'd0) (const 6'd0))")
-        self.assertEqual(v.shape(), unsigned(1))
-
-    def test_le(self):
-        v = Const(0, 4) <= Const(0, 6)
-        self.assertEqual(repr(v), "(<= (const 4'd0) (const 6'd0))")
-        self.assertEqual(v.shape(), unsigned(1))
-
-    def test_gt(self):
-        v = Const(0, 4) > Const(0, 6)
-        self.assertEqual(repr(v), "(> (const 4'd0) (const 6'd0))")
-        self.assertEqual(v.shape(), unsigned(1))
-
-    def test_ge(self):
-        v = Const(0, 4) >= Const(0, 6)
-        self.assertEqual(repr(v), "(>= (const 4'd0) (const 6'd0))")
-        self.assertEqual(v.shape(), unsigned(1))
-
-    def test_eq(self):
-        v = Const(0, 4) == Const(0, 6)
-        self.assertEqual(repr(v), "(== (const 4'd0) (const 6'd0))")
-        self.assertEqual(v.shape(), unsigned(1))
-
-    def test_ne(self):
-        v = Const(0, 4) != Const(0, 6)
-        self.assertEqual(repr(v), "(!= (const 4'd0) (const 6'd0))")
-        self.assertEqual(v.shape(), unsigned(1))
-
-    def test_mux(self):
-        s  = Const(0)
-        v1 = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6)))
-        self.assertEqual(repr(v1), "(m (const 1'd0) (const 4'd0) (const 6'd0))")
-        self.assertEqual(v1.shape(), unsigned(6))
-        v2 = Mux(s, Const(0, signed(4)), Const(0, signed(6)))
-        self.assertEqual(v2.shape(), signed(6))
-        v3 = Mux(s, Const(0, signed(4)), Const(0, unsigned(4)))
-        self.assertEqual(v3.shape(), signed(5))
-        v4 = Mux(s, Const(0, unsigned(4)), Const(0, signed(4)))
-        self.assertEqual(v4.shape(), signed(5))
-
-    def test_mux_wide(self):
-        s = Const(0b100)
-        v = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6)))
-        self.assertEqual(repr(v), "(m (b (const 3'd4)) (const 4'd0) (const 6'd0))")
-
-    def test_mux_bool(self):
-        v = Mux(True, Const(0), Const(0))
-        self.assertEqual(repr(v), "(m (const 1'd1) (const 1'd0) (const 1'd0))")
-
-    def test_bool(self):
-        v = Const(0).bool()
-        self.assertEqual(repr(v), "(b (const 1'd0))")
-        self.assertEqual(v.shape(), unsigned(1))
-
-    def test_any(self):
-        v = Const(0b101).any()
-        self.assertEqual(repr(v), "(r| (const 3'd5))")
-
-    def test_all(self):
-        v = Const(0b101).all()
-        self.assertEqual(repr(v), "(r& (const 3'd5))")
-
-    def test_xor(self):
-        v = Const(0b101).xor()
-        self.assertEqual(repr(v), "(r^ (const 3'd5))")
-
-    def test_matches(self):
-        s = Signal(4)
-        self.assertRepr(s.matches(), "(const 1'd0)")
-        self.assertRepr(s.matches(1), """
-        (== (sig s) (const 1'd1))
-        """)
-        self.assertRepr(s.matches(0, 1), """
-        (r| (cat (== (sig s) (const 1'd0)) (== (sig s) (const 1'd1))))
-        """)
-        self.assertRepr(s.matches("10--"), """
-        (== (& (sig s) (const 4'd12)) (const 4'd8))
-        """)
-        self.assertRepr(s.matches("1 0--"), """
-        (== (& (sig s) (const 4'd12)) (const 4'd8))
-        """)
-
-    def test_matches_enum(self):
-        s = Signal(SignedEnum)
-        self.assertRepr(s.matches(SignedEnum.FOO), """
-        (== (sig s) (const 1'sd-1))
-        """)
-
-    def test_matches_width_wrong(self):
-        s = Signal(4)
-        with self.assertRaisesRegex(SyntaxError,
-                r"^Match pattern '--' must have the same width as match value \(which is 4\)$"):
-            s.matches("--")
-        with self.assertWarnsRegex(SyntaxWarning,
-                (r"^Match pattern '10110' is wider than match value \(which has width 4\); "
-                    r"comparison will never be true$")):
-            s.matches(0b10110)
-
-    def test_matches_bits_wrong(self):
-        s = Signal(4)
-        with self.assertRaisesRegex(SyntaxError,
-                (r"^Match pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
-                    r"and may include whitespace$")):
-            s.matches("abc")
-
-    def test_matches_pattern_wrong(self):
-        s = Signal(4)
-        with self.assertRaisesRegex(SyntaxError,
-                r"^Match pattern must be an integer, a string, or an enumeration, not 1\.0$"):
-            s.matches(1.0)
-
-    def test_hash(self):
-        with self.assertRaises(TypeError):
-            hash(Const(0) + Const(0))
-
-
-class SliceTestCase(FHDLTestCase):
-    def test_shape(self):
-        s1 = Const(10)[2]
-        self.assertEqual(s1.shape(), unsigned(1))
-        self.assertIsInstance(s1.shape(), Shape)
-        s2 = Const(-10)[0:2]
-        self.assertEqual(s2.shape(), unsigned(2))
-
-    def test_start_end_negative(self):
-        c  = Const(0, 8)
-        s1 = Slice(c, 0, -1)
-        self.assertEqual((s1.start, s1.stop), (0, 7))
-        s1 = Slice(c, -4, -1)
-        self.assertEqual((s1.start, s1.stop), (4, 7))
-
-    def test_start_end_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Slice start must be an integer, not 'x'$"):
-            Slice(0, "x", 1)
-        with self.assertRaisesRegex(TypeError,
-                r"^Slice stop must be an integer, not 'x'$"):
-            Slice(0, 1, "x")
-
-    def test_start_end_out_of_range(self):
-        c = Const(0, 8)
-        with self.assertRaisesRegex(IndexError,
-                r"^Cannot start slice 10 bits into 8-bit value$"):
-            Slice(c, 10, 12)
-        with self.assertRaisesRegex(IndexError,
-                r"^Cannot stop slice 12 bits into 8-bit value$"):
-            Slice(c, 0, 12)
-        with self.assertRaisesRegex(IndexError,
-                r"^Slice start 4 must be less than slice stop 2$"):
-            Slice(c, 4, 2)
-
-    def test_repr(self):
-        s1 = Const(10)[2]
-        self.assertEqual(repr(s1), "(slice (const 4'd10) 2:3)")
-
-
-class BitSelectTestCase(FHDLTestCase):
-    def setUp(self):
-        self.c = Const(0, 8)
-        self.s = Signal(range(self.c.width))
-
-    def test_shape(self):
-        s1 = self.c.bit_select(self.s, 2)
-        self.assertIsInstance(s1, Part)
-        self.assertEqual(s1.shape(), unsigned(2))
-        self.assertIsInstance(s1.shape(), Shape)
-        s2 = self.c.bit_select(self.s, 0)
-        self.assertIsInstance(s2, Part)
-        self.assertEqual(s2.shape(), unsigned(0))
-
-    def test_stride(self):
-        s1 = self.c.bit_select(self.s, 2)
-        self.assertIsInstance(s1, Part)
-        self.assertEqual(s1.stride, 1)
-
-    def test_const(self):
-        s1 = self.c.bit_select(1, 2)
-        self.assertIsInstance(s1, Slice)
-        self.assertRepr(s1, """(slice (const 8'd0) 1:3)""")
-
-    def test_width_wrong(self):
-        with self.assertRaises(TypeError):
-            self.c.bit_select(self.s, -1)
-
-    def test_repr(self):
-        s = self.c.bit_select(self.s, 2)
-        self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 1)")
-
-
-class WordSelectTestCase(FHDLTestCase):
-    def setUp(self):
-        self.c = Const(0, 8)
-        self.s = Signal(range(self.c.width))
-
-    def test_shape(self):
-        s1 = self.c.word_select(self.s, 2)
-        self.assertIsInstance(s1, Part)
-        self.assertEqual(s1.shape(), unsigned(2))
-        self.assertIsInstance(s1.shape(), Shape)
-
-    def test_stride(self):
-        s1 = self.c.word_select(self.s, 2)
-        self.assertIsInstance(s1, Part)
-        self.assertEqual(s1.stride, 2)
-
-    def test_const(self):
-        s1 = self.c.word_select(1, 2)
-        self.assertIsInstance(s1, Slice)
-        self.assertRepr(s1, """(slice (const 8'd0) 2:4)""")
-
-    def test_width_wrong(self):
-        with self.assertRaises(TypeError):
-            self.c.word_select(self.s, 0)
-        with self.assertRaises(TypeError):
-            self.c.word_select(self.s, -1)
-
-    def test_repr(self):
-        s = self.c.word_select(self.s, 2)
-        self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 2)")
-
-
-class CatTestCase(FHDLTestCase):
-    def test_shape(self):
-        c0 = Cat()
-        self.assertEqual(c0.shape(), unsigned(0))
-        self.assertIsInstance(c0.shape(), Shape)
-        c1 = Cat(Const(10))
-        self.assertEqual(c1.shape(), unsigned(4))
-        c2 = Cat(Const(10), Const(1))
-        self.assertEqual(c2.shape(), unsigned(5))
-        c3 = Cat(Const(10), Const(1), Const(0))
-        self.assertEqual(c3.shape(), unsigned(6))
-
-    def test_repr(self):
-        c1 = Cat(Const(10), Const(1))
-        self.assertEqual(repr(c1), "(cat (const 4'd10) (const 1'd1))")
-
-
-class ReplTestCase(FHDLTestCase):
-    def test_shape(self):
-        s1 = Repl(Const(10), 3)
-        self.assertEqual(s1.shape(), unsigned(12))
-        self.assertIsInstance(s1.shape(), Shape)
-        s2 = Repl(Const(10), 0)
-        self.assertEqual(s2.shape(), unsigned(0))
-
-    def test_count_wrong(self):
-        with self.assertRaises(TypeError):
-            Repl(Const(10), -1)
-        with self.assertRaises(TypeError):
-            Repl(Const(10), "str")
-
-    def test_repr(self):
-        s = Repl(Const(10), 3)
-        self.assertEqual(repr(s), "(repl (const 4'd10) 3)")
-
-
-class ArrayTestCase(FHDLTestCase):
-    def test_acts_like_array(self):
-        a = Array([1,2,3])
-        self.assertSequenceEqual(a, [1,2,3])
-        self.assertEqual(a[1], 2)
-        a[1] = 4
-        self.assertSequenceEqual(a, [1,4,3])
-        del a[1]
-        self.assertSequenceEqual(a, [1,3])
-        a.insert(1, 2)
-        self.assertSequenceEqual(a, [1,2,3])
-
-    def test_becomes_immutable(self):
-        a = Array([1,2,3])
-        s1 = Signal(range(len(a)))
-        s2 = Signal(range(len(a)))
-        v1 = a[s1]
-        v2 = a[s2]
-        with self.assertRaisesRegex(ValueError,
-                r"^Array can no longer be mutated after it was indexed with a value at "):
-            a[1] = 2
-        with self.assertRaisesRegex(ValueError,
-                r"^Array can no longer be mutated after it was indexed with a value at "):
-            del a[1]
-        with self.assertRaisesRegex(ValueError,
-                r"^Array can no longer be mutated after it was indexed with a value at "):
-            a.insert(1, 2)
-
-    def test_repr(self):
-        a = Array([1,2,3])
-        self.assertEqual(repr(a), "(array mutable [1, 2, 3])")
-        s = Signal(range(len(a)))
-        v = a[s]
-        self.assertEqual(repr(a), "(array [1, 2, 3])")
-
-
-class ArrayProxyTestCase(FHDLTestCase):
-    def test_index_shape(self):
-        m = Array(Array(x * y for y in range(1, 4)) for x in range(1, 4))
-        a = Signal(range(3))
-        b = Signal(range(3))
-        v = m[a][b]
-        self.assertEqual(v.shape(), unsigned(4))
-
-    def test_attr_shape(self):
-        from collections import namedtuple
-        pair = namedtuple("pair", ("p", "n"))
-        a = Array(pair(i, -i) for i in range(10))
-        s = Signal(range(len(a)))
-        v = a[s]
-        self.assertEqual(v.p.shape(), unsigned(4))
-        self.assertEqual(v.n.shape(), signed(5))
-
-    def test_attr_shape_signed(self):
-        # [unsigned(1), unsigned(1)] â†’ unsigned(1)
-        a1 = Array([1, 1])
-        v1 = a1[Const(0)]
-        self.assertEqual(v1.shape(), unsigned(1))
-        # [signed(1), signed(1)] â†’ signed(1)
-        a2 = Array([-1, -1])
-        v2 = a2[Const(0)]
-        self.assertEqual(v2.shape(), signed(1))
-        # [unsigned(1), signed(2)] â†’ signed(2)
-        a3 = Array([1, -2])
-        v3 = a3[Const(0)]
-        self.assertEqual(v3.shape(), signed(2))
-        # [unsigned(1), signed(1)] â†’ signed(2); 1st operand padded with sign bit!
-        a4 = Array([1, -1])
-        v4 = a4[Const(0)]
-        self.assertEqual(v4.shape(), signed(2))
-        # [unsigned(2), signed(1)] â†’ signed(3); 1st operand padded with sign bit!
-        a5 = Array([1, -1])
-        v5 = a5[Const(0)]
-        self.assertEqual(v5.shape(), signed(2))
-
-    def test_repr(self):
-        a = Array([1, 2, 3])
-        s = Signal(range(3))
-        v = a[s]
-        self.assertEqual(repr(v), "(proxy (array [1, 2, 3]) (sig s))")
-
-
-class SignalTestCase(FHDLTestCase):
-    def test_shape(self):
-        s1 = Signal()
-        self.assertEqual(s1.shape(), unsigned(1))
-        self.assertIsInstance(s1.shape(), Shape)
-        s2 = Signal(2)
-        self.assertEqual(s2.shape(), unsigned(2))
-        s3 = Signal(unsigned(2))
-        self.assertEqual(s3.shape(), unsigned(2))
-        s4 = Signal(signed(2))
-        self.assertEqual(s4.shape(), signed(2))
-        s5 = Signal(0)
-        self.assertEqual(s5.shape(), unsigned(0))
-        s6 = Signal(range(16))
-        self.assertEqual(s6.shape(), unsigned(4))
-        s7 = Signal(range(4, 16))
-        self.assertEqual(s7.shape(), unsigned(4))
-        s8 = Signal(range(-4, 16))
-        self.assertEqual(s8.shape(), signed(5))
-        s9 = Signal(range(-20, 16))
-        self.assertEqual(s9.shape(), signed(6))
-        s10 = Signal(range(0))
-        self.assertEqual(s10.shape(), unsigned(0))
-        s11 = Signal(range(1))
-        self.assertEqual(s11.shape(), unsigned(1))
-
-    def test_shape_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Width must be a non-negative integer, not -10$"):
-            Signal(-10)
-
-    def test_name(self):
-        s1 = Signal()
-        self.assertEqual(s1.name, "s1")
-        s2 = Signal(name="sig")
-        self.assertEqual(s2.name, "sig")
-
-    def test_reset(self):
-        s1 = Signal(4, reset=0b111, reset_less=True)
-        self.assertEqual(s1.reset, 0b111)
-        self.assertEqual(s1.reset_less, True)
-
-    def test_reset_enum(self):
-        s1 = Signal(2, reset=UnsignedEnum.BAR)
-        self.assertEqual(s1.reset, 2)
-        with self.assertRaisesRegex(TypeError,
-                r"^Reset value has to be an int or an integral Enum$"
-        ):
-            Signal(1, reset=StringEnum.FOO)
-
-    def test_reset_narrow(self):
-        with self.assertWarnsRegex(SyntaxWarning,
-                r"^Reset value 8 requires 4 bits to represent, but the signal only has 3 bits$"):
-            Signal(3, reset=8)
-        with self.assertWarnsRegex(SyntaxWarning,
-                r"^Reset value 4 requires 4 bits to represent, but the signal only has 3 bits$"):
-            Signal(signed(3), reset=4)
-        with self.assertWarnsRegex(SyntaxWarning,
-                r"^Reset value -5 requires 4 bits to represent, but the signal only has 3 bits$"):
-            Signal(signed(3), reset=-5)
-
-    def test_attrs(self):
-        s1 = Signal()
-        self.assertEqual(s1.attrs, {})
-        s2 = Signal(attrs={"no_retiming": True})
-        self.assertEqual(s2.attrs, {"no_retiming": True})
-
-    def test_repr(self):
-        s1 = Signal()
-        self.assertEqual(repr(s1), "(sig s1)")
-
-    def test_like(self):
-        s1 = Signal.like(Signal(4))
-        self.assertEqual(s1.shape(), unsigned(4))
-        s2 = Signal.like(Signal(range(-15, 1)))
-        self.assertEqual(s2.shape(), signed(5))
-        s3 = Signal.like(Signal(4, reset=0b111, reset_less=True))
-        self.assertEqual(s3.reset, 0b111)
-        self.assertEqual(s3.reset_less, True)
-        s4 = Signal.like(Signal(attrs={"no_retiming": True}))
-        self.assertEqual(s4.attrs, {"no_retiming": True})
-        s5 = Signal.like(Signal(decoder=str))
-        self.assertEqual(s5.decoder, str)
-        s6 = Signal.like(10)
-        self.assertEqual(s6.shape(), unsigned(4))
-        s7 = [Signal.like(Signal(4))][0]
-        self.assertEqual(s7.name, "$like")
-        s8 = Signal.like(s1, name_suffix="_ff")
-        self.assertEqual(s8.name, "s1_ff")
-
-    def test_decoder(self):
-        class Color(Enum):
-            RED  = 1
-            BLUE = 2
-        s = Signal(decoder=Color)
-        self.assertEqual(s.decoder(1), "RED/1")
-        self.assertEqual(s.decoder(3), "3")
-
-    def test_enum(self):
-        s1 = Signal(UnsignedEnum)
-        self.assertEqual(s1.shape(), unsigned(2))
-        s2 = Signal(SignedEnum)
-        self.assertEqual(s2.shape(), signed(2))
-        self.assertEqual(s2.decoder(SignedEnum.FOO), "FOO/-1")
-
-
-class ClockSignalTestCase(FHDLTestCase):
-    def test_domain(self):
-        s1 = ClockSignal()
-        self.assertEqual(s1.domain, "sync")
-        s2 = ClockSignal("pix")
-        self.assertEqual(s2.domain, "pix")
-
-        with self.assertRaisesRegex(TypeError,
-                r"^Clock domain name must be a string, not 1$"):
-            ClockSignal(1)
-
-    def test_shape(self):
-        s1 = ClockSignal()
-        self.assertEqual(s1.shape(), unsigned(1))
-        self.assertIsInstance(s1.shape(), Shape)
-
-    def test_repr(self):
-        s1 = ClockSignal()
-        self.assertEqual(repr(s1), "(clk sync)")
-
-    def test_wrong_name_comb(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Domain 'comb' does not have a clock$"):
-            ClockSignal("comb")
-
-
-class ResetSignalTestCase(FHDLTestCase):
-    def test_domain(self):
-        s1 = ResetSignal()
-        self.assertEqual(s1.domain, "sync")
-        s2 = ResetSignal("pix")
-        self.assertEqual(s2.domain, "pix")
-
-        with self.assertRaisesRegex(TypeError,
-                r"^Clock domain name must be a string, not 1$"):
-            ResetSignal(1)
-
-    def test_shape(self):
-        s1 = ResetSignal()
-        self.assertEqual(s1.shape(), unsigned(1))
-        self.assertIsInstance(s1.shape(), Shape)
-
-    def test_repr(self):
-        s1 = ResetSignal()
-        self.assertEqual(repr(s1), "(rst sync)")
-
-    def test_wrong_name_comb(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Domain 'comb' does not have a reset$"):
-            ResetSignal("comb")
-
-
-class MockUserValue(UserValue):
-    def __init__(self, lowered):
-        super().__init__()
-        self.lower_count = 0
-        self.lowered     = lowered
-
-    def lower(self):
-        self.lower_count += 1
-        return self.lowered
-
-
-class UserValueTestCase(FHDLTestCase):
-    def test_shape(self):
-        uv = MockUserValue(1)
-        self.assertEqual(uv.shape(), unsigned(1))
-        self.assertIsInstance(uv.shape(), Shape)
-        uv.lowered = 2
-        self.assertEqual(uv.shape(), unsigned(1))
-        self.assertEqual(uv.lower_count, 1)
-
-    def test_lower_to_user_value(self):
-        uv = MockUserValue(MockUserValue(1))
-        self.assertEqual(uv.shape(), unsigned(1))
-        self.assertIsInstance(uv.shape(), Shape)
-        uv.lowered = MockUserValue(2)
-        self.assertEqual(uv.shape(), unsigned(1))
-        self.assertEqual(uv.lower_count, 1)
-
-
-class SampleTestCase(FHDLTestCase):
-    def test_const(self):
-        s = Sample(1, 1, "sync")
-        self.assertEqual(s.shape(), unsigned(1))
-
-    def test_signal(self):
-        s1 = Sample(Signal(2), 1, "sync")
-        self.assertEqual(s1.shape(), unsigned(2))
-        s2 = Sample(ClockSignal(), 1, "sync")
-        s3 = Sample(ResetSignal(), 1, "sync")
-
-    def test_wrong_value_operator(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Sampled value must be a signal or a constant, not "
-                r"\(\+ \(sig \$signal\) \(const 1'd1\)\)$")):
-            Sample(Signal() + 1, 1, "sync")
-
-    def test_wrong_clocks_neg(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Cannot sample a value 1 cycles in the future$"):
-            Sample(Signal(), -1, "sync")
-
-    def test_wrong_domain(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Domain name must be a string or None, not 0$"):
-            Sample(Signal(), 1, 0)
-
-
-class InitialTestCase(FHDLTestCase):
-    def test_initial(self):
-        i = Initial()
-        self.assertEqual(i.shape(), unsigned(1))
diff --git a/nmigen/test/test_hdl_cd.py b/nmigen/test/test_hdl_cd.py
deleted file mode 100644 (file)
index 8927e08..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-from ..hdl.cd import *
-from .utils import *
-
-
-class ClockDomainTestCase(FHDLTestCase):
-    def test_name(self):
-        sync = ClockDomain()
-        self.assertEqual(sync.name, "sync")
-        self.assertEqual(sync.clk.name, "clk")
-        self.assertEqual(sync.rst.name, "rst")
-        self.assertEqual(sync.local, False)
-        pix = ClockDomain()
-        self.assertEqual(pix.name, "pix")
-        self.assertEqual(pix.clk.name, "pix_clk")
-        self.assertEqual(pix.rst.name, "pix_rst")
-        cd_pix = ClockDomain()
-        self.assertEqual(pix.name, "pix")
-        dom = [ClockDomain("foo")][0]
-        self.assertEqual(dom.name, "foo")
-        with self.assertRaisesRegex(ValueError,
-                r"^Clock domain name must be specified explicitly$"):
-            ClockDomain()
-        cd_reset = ClockDomain(local=True)
-        self.assertEqual(cd_reset.local, True)
-
-    def test_edge(self):
-        sync = ClockDomain()
-        self.assertEqual(sync.clk_edge, "pos")
-        sync = ClockDomain(clk_edge="pos")
-        self.assertEqual(sync.clk_edge, "pos")
-        sync = ClockDomain(clk_edge="neg")
-        self.assertEqual(sync.clk_edge, "neg")
-
-    def test_edge_wrong(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Domain clock edge must be one of 'pos' or 'neg', not 'xxx'$"):
-            ClockDomain("sync", clk_edge="xxx")
-
-    def test_with_reset(self):
-        pix = ClockDomain()
-        self.assertIsNotNone(pix.clk)
-        self.assertIsNotNone(pix.rst)
-        self.assertFalse(pix.async_reset)
-
-    def test_without_reset(self):
-        pix = ClockDomain(reset_less=True)
-        self.assertIsNotNone(pix.clk)
-        self.assertIsNone(pix.rst)
-        self.assertFalse(pix.async_reset)
-
-    def test_async_reset(self):
-        pix = ClockDomain(async_reset=True)
-        self.assertIsNotNone(pix.clk)
-        self.assertIsNotNone(pix.rst)
-        self.assertTrue(pix.async_reset)
-
-    def test_rename(self):
-        sync = ClockDomain()
-        self.assertEqual(sync.name, "sync")
-        self.assertEqual(sync.clk.name, "clk")
-        self.assertEqual(sync.rst.name, "rst")
-        sync.rename("pix")
-        self.assertEqual(sync.name, "pix")
-        self.assertEqual(sync.clk.name, "pix_clk")
-        self.assertEqual(sync.rst.name, "pix_rst")
-
-    def test_rename_reset_less(self):
-        sync = ClockDomain(reset_less=True)
-        self.assertEqual(sync.name, "sync")
-        self.assertEqual(sync.clk.name, "clk")
-        sync.rename("pix")
-        self.assertEqual(sync.name, "pix")
-        self.assertEqual(sync.clk.name, "pix_clk")
-
-    def test_wrong_name_comb(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Domain 'comb' may not be clocked$"):
-            comb = ClockDomain()
diff --git a/nmigen/test/test_hdl_dsl.py b/nmigen/test/test_hdl_dsl.py
deleted file mode 100644 (file)
index 454415d..0000000
+++ /dev/null
@@ -1,769 +0,0 @@
-# nmigen: UnusedElaboratable=no
-
-from collections import OrderedDict
-from enum import Enum
-
-from ..hdl.ast import *
-from ..hdl.cd import *
-from ..hdl.dsl import *
-from .utils 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_cant_inherit(self):
-        with self.assertRaisesRegex(SyntaxError,
-                (r"^Instead of inheriting from `Module`, inherit from `Elaboratable` and "
-                    r"return a `Module` from the `elaborate\(self, platform\)` method$")):
-            class ORGate(Module):
-                pass
-
-    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.assertRaisesRegex(SyntaxError,
-                (r"^Driver-driver conflict: trying to drive \(sig c1\) from d\.sync, but it "
-                    r"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.assertRaisesRegex(AttributeError,
-                r"^Cannot assign 'd\.pix' attribute; did you mean 'd.pix \+='\?$"):
-            m.d.pix = None
-
-    def test_d_asgn_wrong(self):
-        m = Module()
-        with self.assertRaisesRegex(SyntaxError,
-                r"^Only assignments and property checks may be appended to d\.sync$"):
-            m.d.sync += Switch(self.s1, {})
-
-    def test_comb_wrong(self):
-        m = Module()
-        with self.assertRaisesRegex(AttributeError,
-                r"^'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.assertRaisesRegex(AttributeError,
-                r"^'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.assertRaisesRegex(AttributeError,
-                r"^'Module' object has no attribute 'nonexistentattr'$"):
-            m.nonexistentattr
-
-    def test_d_suspicious(self):
-        m = Module()
-        with self.assertWarnsRegex(SyntaxWarning,
-                (r"^Using '<module>\.d\.submodules' would add statements to clock domain "
-                    r"'submodules'; did you mean <module>\.submodules instead\?$")):
-            m.d.submodules += []
-
-    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)))
-                (default (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)))
-                    )
-                )
-                (default
-                    (eq (sig c3) (const 1'd1))
-                )
-            )
-        )
-        """)
-
-    def test_Elif_wrong(self):
-        m = Module()
-        with self.assertRaisesRegex(SyntaxError,
-                r"^Elif without preceding If$"):
-            with m.Elif(self.s2):
-                pass
-
-    def test_Else_wrong(self):
-        m = Module()
-        with self.assertRaisesRegex(SyntaxError,
-                r"^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_If_signed_suspicious(self):
-        m = Module()
-        with self.assertWarnsRegex(SyntaxWarning,
-                (r"^Signed values in If\/Elif conditions usually result from inverting Python "
-                    r"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
-                    r"`not flag`\. \(If this is a false positive, silence this warning with "
-                    r"`m\.If\(x\)` â†’ `m\.If\(x\.bool\(\)\)`\.\)$")):
-            with m.If(~True):
-                pass
-
-    def test_Elif_signed_suspicious(self):
-        m = Module()
-        with m.If(0):
-            pass
-        with self.assertWarnsRegex(SyntaxWarning,
-                (r"^Signed values in If\/Elif conditions usually result from inverting Python "
-                    r"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
-                    r"`not flag`\. \(If this is a false positive, silence this warning with "
-                    r"`m\.If\(x\)` â†’ `m\.If\(x\.bool\(\)\)`\.\)$")):
-            with m.Elif(~True):
-                pass
-
-    def test_if_If_Elif_Else(self):
-        m = Module()
-        with self.assertRaisesRegex(SyntaxError,
-                r"^`if m\.If\(\.\.\.\):` does not work; use `with m\.If\(\.\.\.\)`$"):
-            if m.If(0):
-                pass
-        with m.If(0):
-            pass
-        with self.assertRaisesRegex(SyntaxError,
-                r"^`if m\.Elif\(\.\.\.\):` does not work; use `with m\.Elif\(\.\.\.\)`$"):
-            if m.Elif(0):
-                pass
-        with self.assertRaisesRegex(SyntaxError,
-                r"^`if m\.Else\(\.\.\.\):` does not work; use `with m\.Else\(\.\.\.\)`$"):
-            if m.Else():
-                pass
-
-    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)
-            with m.Case("1 0--"):
-                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)))
-                (case 10-- (eq (sig c2) (const 1'd1)))
-            )
-        )
-        """)
-
-    def test_Switch_default_Case(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)))
-                (default (eq (sig c2) (const 1'd1)))
-            )
-        )
-        """)
-
-    def test_Switch_default_Default(self):
-        m = Module()
-        with m.Switch(self.w1):
-            with m.Case(3):
-                m.d.comb += self.c1.eq(1)
-            with m.Default():
-                m.d.comb += self.c2.eq(1)
-        m._flush()
-        self.assertRepr(m._statements, """
-        (
-            (switch (sig w1)
-                (case 0011 (eq (sig c1) (const 1'd1)))
-                (default (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_Switch_enum(self):
-        class Color(Enum):
-            RED  = 1
-            BLUE = 2
-        m = Module()
-        se = Signal(Color)
-        with m.Switch(se):
-            with m.Case(Color.RED):
-                m.d.comb += self.c1.eq(1)
-        self.assertRepr(m._statements, """
-        (
-            (switch (sig se)
-                (case 01 (eq (sig c1) (const 1'd1)))
-            )
-        )
-        """)
-
-    def test_Case_width_wrong(self):
-        class Color(Enum):
-            RED = 0b10101010
-        m = Module()
-        with m.Switch(self.w1):
-            with self.assertRaisesRegex(SyntaxError,
-                    r"^Case pattern '--' must have the same width as switch value \(which is 4\)$"):
-                with m.Case("--"):
-                    pass
-            with self.assertWarnsRegex(SyntaxWarning,
-                    (r"^Case pattern '10110' is wider than switch value \(which has width 4\); "
-                        r"comparison will never be true$")):
-                with m.Case(0b10110):
-                    pass
-            with self.assertWarnsRegex(SyntaxWarning,
-                    (r"^Case pattern '10101010' \(Color\.RED\) is wider than switch value "
-                        r"\(which has width 4\); comparison will never be true$")):
-                with m.Case(Color.RED):
-                    pass
-        self.assertRepr(m._statements, """
-        (
-            (switch (sig w1) )
-        )
-        """)
-
-    def test_Case_bits_wrong(self):
-        m = Module()
-        with m.Switch(self.w1):
-            with self.assertRaisesRegex(SyntaxError,
-                    (r"^Case pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
-                        r"and may include whitespace$")):
-                with m.Case("abc"):
-                    pass
-
-    def test_Case_pattern_wrong(self):
-        m = Module()
-        with m.Switch(self.w1):
-            with self.assertRaisesRegex(SyntaxError,
-                    r"^Case pattern must be an integer, a string, or an enumeration, not 1\.0$"):
-                with m.Case(1.0):
-                    pass
-
-    def test_Case_outside_Switch_wrong(self):
-        m = Module()
-        with self.assertRaisesRegex(SyntaxError,
-                r"^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.assertRaisesRegex(SyntaxError,
-                    (r"^If is not permitted directly inside of Switch; "
-                        r"it is permitted inside of Switch Case$")):
-                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_empty(self):
-        m = Module()
-        with m.FSM():
-            pass
-        self.assertRepr(m._statements, """
-        ()
-        """)
-
-    def test_FSM_wrong_domain(self):
-        m = Module()
-        with self.assertRaisesRegex(ValueError,
-                r"^FSM may not be driven by the 'comb' domain$"):
-            with m.FSM(domain="comb"):
-                pass
-
-    def test_FSM_wrong_undefined(self):
-        m = Module()
-        with self.assertRaisesRegex(NameError,
-                r"^FSM state 'FOO' is referenced but not defined$"):
-            with m.FSM() as fsm:
-                fsm.ongoing("FOO")
-
-    def test_FSM_wrong_redefined(self):
-        m = Module()
-        with m.FSM():
-            with m.State("FOO"):
-                pass
-            with self.assertRaisesRegex(NameError,
-                    r"^FSM state 'FOO' is already defined$"):
-                with m.State("FOO"):
-                    pass
-
-    def test_FSM_wrong_next(self):
-        m = Module()
-        with self.assertRaisesRegex(SyntaxError,
-                r"^Only assignment to `m\.next` is permitted$"):
-            m.next
-        with self.assertRaisesRegex(SyntaxError,
-                r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
-            m.next = "FOO"
-        with self.assertRaisesRegex(SyntaxError,
-                r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
-            with m.FSM():
-                m.next = "FOO"
-
-    def test_If_inside_FSM_wrong(self):
-        m = Module()
-        with m.FSM():
-            with m.State("FOO"):
-                pass
-            with self.assertRaisesRegex(SyntaxError,
-                    (r"^If is not permitted directly inside of FSM; "
-                        r"it is permitted inside of FSM State$")):
-                with m.If(self.s2):
-                    pass
-
-    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._anon_submodules, [m2])
-        self.assertEqual(m1._named_submodules, {})
-
-    def test_submodule_anon_multi(self):
-        m1 = Module()
-        m2 = Module()
-        m3 = Module()
-        m1.submodules += m2, m3
-        self.assertEqual(m1._anon_submodules, [m2, m3])
-        self.assertEqual(m1._named_submodules, {})
-
-    def test_submodule_named(self):
-        m1 = Module()
-        m2 = Module()
-        m1.submodules.foo = m2
-        self.assertEqual(m1._anon_submodules, [])
-        self.assertEqual(m1._named_submodules, {"foo": m2})
-
-    def test_submodule_named_index(self):
-        m1 = Module()
-        m2 = Module()
-        m1.submodules["foo"] = m2
-        self.assertEqual(m1._anon_submodules, [])
-        self.assertEqual(m1._named_submodules, {"foo": m2})
-
-    def test_submodule_wrong(self):
-        m = Module()
-        with self.assertRaisesRegex(TypeError,
-                r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
-            m.submodules.foo = 1
-        with self.assertRaisesRegex(TypeError,
-                r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
-            m.submodules += 1
-
-    def test_submodule_named_conflict(self):
-        m1 = Module()
-        m2 = Module()
-        m1.submodules.foo = m2
-        with self.assertRaisesRegex(NameError, r"^Submodule named 'foo' already exists$"):
-            m1.submodules.foo = m2
-
-    def test_submodule_get(self):
-        m1 = Module()
-        m2 = Module()
-        m1.submodules.foo = m2
-        m3 = m1.submodules.foo
-        self.assertEqual(m2, m3)
-
-    def test_submodule_get_index(self):
-        m1 = Module()
-        m2 = Module()
-        m1.submodules["foo"] = m2
-        m3 = m1.submodules["foo"]
-        self.assertEqual(m2, m3)
-
-    def test_submodule_get_unset(self):
-        m1 = Module()
-        with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"):
-            m2 = m1.submodules.foo
-        with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"):
-            m2 = m1.submodules["foo"]
-
-    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["foo"].name, "foo")
-
-    def test_domain_add_wrong(self):
-        m = Module()
-        with self.assertRaisesRegex(TypeError,
-                r"^Only clock domains may be added to `m\.domains`, not 1$"):
-            m.domains.foo = 1
-        with self.assertRaisesRegex(TypeError,
-                r"^Only clock domains may be added to `m\.domains`, not 1$"):
-            m.domains += 1
-
-    def test_domain_add_wrong_name(self):
-        m = Module()
-        with self.assertRaisesRegex(NameError,
-                r"^Clock domain name 'bar' must match name in `m\.domains\.foo \+= \.\.\.` syntax$"):
-            m.domains.foo = ClockDomain("bar")
-
-    def test_domain_add_wrong_duplicate(self):
-        m = Module()
-        m.domains += ClockDomain("foo")
-        with self.assertRaisesRegex(NameError,
-                r"^Clock domain named 'foo' already exists$"):
-            m.domains += ClockDomain("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)
diff --git a/nmigen/test/test_hdl_ir.py b/nmigen/test/test_hdl_ir.py
deleted file mode 100644 (file)
index ab8bdd8..0000000
+++ /dev/null
@@ -1,862 +0,0 @@
-# nmigen: UnusedElaboratable=no
-
-from collections import OrderedDict
-
-from ..hdl.ast import *
-from ..hdl.cd import *
-from ..hdl.ir import *
-from ..hdl.mem import *
-from .utils import *
-
-
-class BadElaboratable(Elaboratable):
-    def elaborate(self, platform):
-        return
-
-
-class FragmentGetTestCase(FHDLTestCase):
-    def test_get_wrong(self):
-        with self.assertRaisesRegex(AttributeError,
-                r"^Object None cannot be elaborated$"):
-            Fragment.get(None, platform=None)
-
-        with self.assertWarnsRegex(UserWarning,
-                r"^\.elaborate\(\) returned None; missing return statement\?$"):
-            with self.assertRaisesRegex(AttributeError,
-                    r"^Object None cannot be elaborated$"):
-                Fragment.get(BadElaboratable(), platform=None)
-
-
-class FragmentGeneratedTestCase(FHDLTestCase):
-    def test_find_subfragment(self):
-        f1 = Fragment()
-        f2 = Fragment()
-        f1.add_subfragment(f2, "f2")
-
-        self.assertEqual(f1.find_subfragment(0), f2)
-        self.assertEqual(f1.find_subfragment("f2"), f2)
-
-    def test_find_subfragment_wrong(self):
-        f1 = Fragment()
-        f2 = Fragment()
-        f1.add_subfragment(f2, "f2")
-
-        with self.assertRaisesRegex(NameError,
-                r"^No subfragment at index #1$"):
-            f1.find_subfragment(1)
-        with self.assertRaisesRegex(NameError,
-                r"^No subfragment with name 'fx'$"):
-            f1.find_subfragment("fx")
-
-    def test_find_generated(self):
-        f1 = Fragment()
-        f2 = Fragment()
-        f2.generated["sig"] = sig = Signal()
-        f1.add_subfragment(f2, "f2")
-
-        self.assertEqual(SignalKey(f1.find_generated("f2", "sig")),
-                         SignalKey(sig))
-
-
-class FragmentDriversTestCase(FHDLTestCase):
-    def test_empty(self):
-        f = Fragment()
-        self.assertEqual(list(f.iter_comb()), [])
-        self.assertEqual(list(f.iter_sync()), [])
-
-
-class FragmentPortsTestCase(FHDLTestCase):
-    def setUp(self):
-        self.s1 = Signal()
-        self.s2 = Signal()
-        self.s3 = Signal()
-        self.c1 = Signal()
-        self.c2 = Signal()
-        self.c3 = Signal()
-
-    def test_empty(self):
-        f = Fragment()
-        self.assertEqual(list(f.iter_ports()), [])
-
-        f._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f.ports, SignalDict([]))
-
-    def test_iter_signals(self):
-        f = Fragment()
-        f.add_ports(self.s1, self.s2, dir="io")
-        self.assertEqual(SignalSet((self.s1, self.s2)), f.iter_signals())
-
-    def test_self_contained(self):
-        f = Fragment()
-        f.add_statements(
-            self.c1.eq(self.s1),
-            self.s1.eq(self.c1)
-        )
-
-        f._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f.ports, SignalDict([]))
-
-    def test_infer_input(self):
-        f = Fragment()
-        f.add_statements(
-            self.c1.eq(self.s1)
-        )
-
-        f._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f.ports, SignalDict([
-            (self.s1, "i")
-        ]))
-
-    def test_request_output(self):
-        f = Fragment()
-        f.add_statements(
-            self.c1.eq(self.s1)
-        )
-
-        f._propagate_ports(ports=(self.c1,), all_undef_as_ports=True)
-        self.assertEqual(f.ports, SignalDict([
-            (self.s1, "i"),
-            (self.c1, "o")
-        ]))
-
-    def test_input_in_subfragment(self):
-        f1 = Fragment()
-        f1.add_statements(
-            self.c1.eq(self.s1)
-        )
-        f2 = Fragment()
-        f2.add_statements(
-            self.s1.eq(0)
-        )
-        f1.add_subfragment(f2)
-        f1._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f1.ports, SignalDict())
-        self.assertEqual(f2.ports, SignalDict([
-            (self.s1, "o"),
-        ]))
-
-    def test_input_only_in_subfragment(self):
-        f1 = Fragment()
-        f2 = Fragment()
-        f2.add_statements(
-            self.c1.eq(self.s1)
-        )
-        f1.add_subfragment(f2)
-        f1._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f1.ports, SignalDict([
-            (self.s1, "i"),
-        ]))
-        self.assertEqual(f2.ports, SignalDict([
-            (self.s1, "i"),
-        ]))
-
-    def test_output_from_subfragment(self):
-        f1 = Fragment()
-        f1.add_statements(
-            self.c1.eq(0)
-        )
-        f2 = Fragment()
-        f2.add_statements(
-            self.c2.eq(1)
-        )
-        f1.add_subfragment(f2)
-
-        f1._propagate_ports(ports=(self.c2,), all_undef_as_ports=True)
-        self.assertEqual(f1.ports, SignalDict([
-            (self.c2, "o"),
-        ]))
-        self.assertEqual(f2.ports, SignalDict([
-            (self.c2, "o"),
-        ]))
-
-    def test_output_from_subfragment_2(self):
-        f1 = Fragment()
-        f1.add_statements(
-            self.c1.eq(self.s1)
-        )
-        f2 = Fragment()
-        f2.add_statements(
-            self.c2.eq(self.s1)
-        )
-        f1.add_subfragment(f2)
-        f3 = Fragment()
-        f3.add_statements(
-            self.s1.eq(0)
-        )
-        f2.add_subfragment(f3)
-
-        f1._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f2.ports, SignalDict([
-            (self.s1, "o"),
-        ]))
-
-    def test_input_output_sibling(self):
-        f1 = Fragment()
-        f2 = Fragment()
-        f2.add_statements(
-            self.c1.eq(self.c2)
-        )
-        f1.add_subfragment(f2)
-        f3 = Fragment()
-        f3.add_statements(
-            self.c2.eq(0)
-        )
-        f3.add_driver(self.c2)
-        f1.add_subfragment(f3)
-
-        f1._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f1.ports, SignalDict())
-
-    def test_output_input_sibling(self):
-        f1 = Fragment()
-        f2 = Fragment()
-        f2.add_statements(
-            self.c2.eq(0)
-        )
-        f2.add_driver(self.c2)
-        f1.add_subfragment(f2)
-        f3 = Fragment()
-        f3.add_statements(
-            self.c1.eq(self.c2)
-        )
-        f1.add_subfragment(f3)
-
-        f1._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f1.ports, SignalDict())
-
-    def test_input_cd(self):
-        sync = ClockDomain()
-        f = Fragment()
-        f.add_statements(
-            self.c1.eq(self.s1)
-        )
-        f.add_domains(sync)
-        f.add_driver(self.c1, "sync")
-
-        f._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f.ports, SignalDict([
-            (self.s1,  "i"),
-            (sync.clk, "i"),
-            (sync.rst, "i"),
-        ]))
-
-    def test_input_cd_reset_less(self):
-        sync = ClockDomain(reset_less=True)
-        f = Fragment()
-        f.add_statements(
-            self.c1.eq(self.s1)
-        )
-        f.add_domains(sync)
-        f.add_driver(self.c1, "sync")
-
-        f._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f.ports, SignalDict([
-            (self.s1,  "i"),
-            (sync.clk, "i"),
-        ]))
-
-    def test_inout(self):
-        s = Signal()
-        f1 = Fragment()
-        f2 = Instance("foo", io_x=s)
-        f1.add_subfragment(f2)
-
-        f1._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f1.ports, SignalDict([
-            (s, "io")
-        ]))
-
-    def test_in_out_same_signal(self):
-        s = Signal()
-
-        f1 = Instance("foo", i_x=s, o_y=s)
-        f2 = Fragment()
-        f2.add_subfragment(f1)
-
-        f2._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f1.ports, SignalDict([
-            (s, "o")
-        ]))
-
-        f3 = Instance("foo", o_y=s, i_x=s)
-        f4 = Fragment()
-        f4.add_subfragment(f3)
-
-        f4._propagate_ports(ports=(), all_undef_as_ports=True)
-        self.assertEqual(f3.ports, SignalDict([
-            (s, "o")
-        ]))
-
-    def test_clk_rst(self):
-        sync = ClockDomain()
-        f = Fragment()
-        f.add_domains(sync)
-
-        f = f.prepare(ports=(ClockSignal("sync"), ResetSignal("sync")))
-        self.assertEqual(f.ports, SignalDict([
-            (sync.clk, "i"),
-            (sync.rst, "i"),
-        ]))
-
-    def test_port_wrong(self):
-        f = Fragment()
-        with self.assertRaisesRegex(TypeError,
-                r"^Only signals may be added as ports, not \(const 1'd1\)$"):
-            f.prepare(ports=(Const(1),))
-
-    def test_port_not_iterable(self):
-        f = Fragment()
-        with self.assertRaisesRegex(TypeError,
-                r"^`ports` must be either a list or a tuple, not 1$"):
-            f.prepare(ports=1)
-        with self.assertRaisesRegex(TypeError,
-                (r"^`ports` must be either a list or a tuple, not \(const 1'd1\)"
-                    r" \(did you mean `ports=\(<signal>,\)`, rather than `ports=<signal>`\?\)$")):
-            f.prepare(ports=Const(1))
-
-class FragmentDomainsTestCase(FHDLTestCase):
-    def test_iter_signals(self):
-        cd1 = ClockDomain()
-        cd2 = ClockDomain(reset_less=True)
-        s1 = Signal()
-        s2 = Signal()
-
-        f = Fragment()
-        f.add_domains(cd1, cd2)
-        f.add_driver(s1, "cd1")
-        self.assertEqual(SignalSet((cd1.clk, cd1.rst, s1)), f.iter_signals())
-        f.add_driver(s2, "cd2")
-        self.assertEqual(SignalSet((cd1.clk, cd1.rst, cd2.clk, s1, s2)), f.iter_signals())
-
-    def test_propagate_up(self):
-        cd = ClockDomain()
-
-        f1 = Fragment()
-        f2 = Fragment()
-        f1.add_subfragment(f2)
-        f2.add_domains(cd)
-
-        f1._propagate_domains_up()
-        self.assertEqual(f1.domains, {"cd": cd})
-
-    def test_propagate_up_local(self):
-        cd = ClockDomain(local=True)
-
-        f1 = Fragment()
-        f2 = Fragment()
-        f1.add_subfragment(f2)
-        f2.add_domains(cd)
-
-        f1._propagate_domains_up()
-        self.assertEqual(f1.domains, {})
-
-    def test_domain_conflict(self):
-        cda = ClockDomain("sync")
-        cdb = ClockDomain("sync")
-
-        fa = Fragment()
-        fa.add_domains(cda)
-        fb = Fragment()
-        fb.add_domains(cdb)
-        f = Fragment()
-        f.add_subfragment(fa, "a")
-        f.add_subfragment(fb, "b")
-
-        f._propagate_domains_up()
-        self.assertEqual(f.domains, {"a_sync": cda, "b_sync": cdb})
-        (fa, _), (fb, _) = f.subfragments
-        self.assertEqual(fa.domains, {"a_sync": cda})
-        self.assertEqual(fb.domains, {"b_sync": cdb})
-
-    def test_domain_conflict_anon(self):
-        cda = ClockDomain("sync")
-        cdb = ClockDomain("sync")
-
-        fa = Fragment()
-        fa.add_domains(cda)
-        fb = Fragment()
-        fb.add_domains(cdb)
-        f = Fragment()
-        f.add_subfragment(fa, "a")
-        f.add_subfragment(fb)
-
-        with self.assertRaisesRegex(DomainError,
-                (r"^Domain 'sync' is defined by subfragments 'a', <unnamed #1> of fragment "
-                    r"'top'; it is necessary to either rename subfragment domains explicitly, "
-                    r"or give names to subfragments$")):
-            f._propagate_domains_up()
-
-    def test_domain_conflict_name(self):
-        cda = ClockDomain("sync")
-        cdb = ClockDomain("sync")
-
-        fa = Fragment()
-        fa.add_domains(cda)
-        fb = Fragment()
-        fb.add_domains(cdb)
-        f = Fragment()
-        f.add_subfragment(fa, "x")
-        f.add_subfragment(fb, "x")
-
-        with self.assertRaisesRegex(DomainError,
-                (r"^Domain 'sync' is defined by subfragments #0, #1 of fragment 'top', some "
-                    r"of which have identical names; it is necessary to either rename subfragment "
-                    r"domains explicitly, or give distinct names to subfragments$")):
-            f._propagate_domains_up()
-
-    def test_domain_conflict_rename_drivers(self):
-        cda = ClockDomain("sync")
-        cdb = ClockDomain("sync")
-
-        fa = Fragment()
-        fa.add_domains(cda)
-        fb = Fragment()
-        fb.add_domains(cdb)
-        fb.add_driver(ResetSignal("sync"), None)
-        f = Fragment()
-        f.add_subfragment(fa, "a")
-        f.add_subfragment(fb, "b")
-
-        f._propagate_domains_up()
-        fb_new, _ = f.subfragments[1]
-        self.assertEqual(fb_new.drivers, OrderedDict({
-            None: SignalSet((ResetSignal("b_sync"),))
-        }))
-
-    def test_domain_conflict_rename_drivers(self):
-        cda = ClockDomain("sync")
-        cdb = ClockDomain("sync")
-        s = Signal()
-
-        fa = Fragment()
-        fa.add_domains(cda)
-        fb = Fragment()
-        fb.add_domains(cdb)
-        f = Fragment()
-        f.add_subfragment(fa, "a")
-        f.add_subfragment(fb, "b")
-        f.add_driver(s, "b_sync")
-
-        f._propagate_domains(lambda name: ClockDomain(name))
-
-    def test_propagate_down(self):
-        cd = ClockDomain()
-
-        f1 = Fragment()
-        f2 = Fragment()
-        f1.add_domains(cd)
-        f1.add_subfragment(f2)
-
-        f1._propagate_domains_down()
-        self.assertEqual(f2.domains, {"cd": cd})
-
-    def test_propagate_down_idempotent(self):
-        cd = ClockDomain()
-
-        f1 = Fragment()
-        f1.add_domains(cd)
-        f2 = Fragment()
-        f2.add_domains(cd)
-        f1.add_subfragment(f2)
-
-        f1._propagate_domains_down()
-        self.assertEqual(f1.domains, {"cd": cd})
-        self.assertEqual(f2.domains, {"cd": cd})
-
-    def test_propagate(self):
-        cd = ClockDomain()
-
-        f1 = Fragment()
-        f2 = Fragment()
-        f1.add_domains(cd)
-        f1.add_subfragment(f2)
-
-        new_domains = f1._propagate_domains(missing_domain=lambda name: None)
-        self.assertEqual(f1.domains, {"cd": cd})
-        self.assertEqual(f2.domains, {"cd": cd})
-        self.assertEqual(new_domains, [])
-
-    def test_propagate_missing(self):
-        s1 = Signal()
-        f1 = Fragment()
-        f1.add_driver(s1, "sync")
-
-        with self.assertRaisesRegex(DomainError,
-                r"^Domain 'sync' is used but not defined$"):
-            f1._propagate_domains(missing_domain=lambda name: None)
-
-    def test_propagate_create_missing(self):
-        s1 = Signal()
-        f1 = Fragment()
-        f1.add_driver(s1, "sync")
-        f2 = Fragment()
-        f1.add_subfragment(f2)
-
-        new_domains = f1._propagate_domains(missing_domain=lambda name: ClockDomain(name))
-        self.assertEqual(f1.domains.keys(), {"sync"})
-        self.assertEqual(f2.domains.keys(), {"sync"})
-        self.assertEqual(f1.domains["sync"], f2.domains["sync"])
-        self.assertEqual(new_domains, [f1.domains["sync"]])
-
-    def test_propagate_create_missing_fragment(self):
-        s1 = Signal()
-        f1 = Fragment()
-        f1.add_driver(s1, "sync")
-
-        cd = ClockDomain("sync")
-        f2 = Fragment()
-        f2.add_domains(cd)
-
-        new_domains = f1._propagate_domains(missing_domain=lambda name: f2)
-        self.assertEqual(f1.domains.keys(), {"sync"})
-        self.assertEqual(f1.domains["sync"], f2.domains["sync"])
-        self.assertEqual(new_domains, [])
-        self.assertEqual(f1.subfragments, [
-            (f2, "cd_sync")
-        ])
-
-    def test_propagate_create_missing_fragment_many_domains(self):
-        s1 = Signal()
-        f1 = Fragment()
-        f1.add_driver(s1, "sync")
-
-        cd_por  = ClockDomain("por")
-        cd_sync = ClockDomain("sync")
-        f2 = Fragment()
-        f2.add_domains(cd_por, cd_sync)
-
-        new_domains = f1._propagate_domains(missing_domain=lambda name: f2)
-        self.assertEqual(f1.domains.keys(), {"sync", "por"})
-        self.assertEqual(f2.domains.keys(), {"sync", "por"})
-        self.assertEqual(f1.domains["sync"], f2.domains["sync"])
-        self.assertEqual(new_domains, [])
-        self.assertEqual(f1.subfragments, [
-            (f2, "cd_sync")
-        ])
-
-    def test_propagate_create_missing_fragment_wrong(self):
-        s1 = Signal()
-        f1 = Fragment()
-        f1.add_driver(s1, "sync")
-
-        f2 = Fragment()
-        f2.add_domains(ClockDomain("foo"))
-
-        with self.assertRaisesRegex(DomainError,
-                (r"^Fragment returned by missing domain callback does not define requested "
-                    r"domain 'sync' \(defines 'foo'\)\.$")):
-            f1._propagate_domains(missing_domain=lambda name: f2)
-
-
-class FragmentHierarchyConflictTestCase(FHDLTestCase):
-    def setUp_self_sub(self):
-        self.s1 = Signal()
-        self.c1 = Signal()
-        self.c2 = Signal()
-
-        self.f1 = Fragment()
-        self.f1.add_statements(self.c1.eq(0))
-        self.f1.add_driver(self.s1)
-        self.f1.add_driver(self.c1, "sync")
-
-        self.f1a = Fragment()
-        self.f1.add_subfragment(self.f1a, "f1a")
-
-        self.f2 = Fragment()
-        self.f2.add_statements(self.c2.eq(1))
-        self.f2.add_driver(self.s1)
-        self.f2.add_driver(self.c2, "sync")
-        self.f1.add_subfragment(self.f2)
-
-        self.f1b = Fragment()
-        self.f1.add_subfragment(self.f1b, "f1b")
-
-        self.f2a = Fragment()
-        self.f2.add_subfragment(self.f2a, "f2a")
-
-    def test_conflict_self_sub(self):
-        self.setUp_self_sub()
-
-        self.f1._resolve_hierarchy_conflicts(mode="silent")
-        self.assertEqual(self.f1.subfragments, [
-            (self.f1a, "f1a"),
-            (self.f1b, "f1b"),
-            (self.f2a, "f2a"),
-        ])
-        self.assertRepr(self.f1.statements, """
-        (
-            (eq (sig c1) (const 1'd0))
-            (eq (sig c2) (const 1'd1))
-        )
-        """)
-        self.assertEqual(self.f1.drivers, {
-            None:   SignalSet((self.s1,)),
-            "sync": SignalSet((self.c1, self.c2)),
-        })
-
-    def test_conflict_self_sub_error(self):
-        self.setUp_self_sub()
-
-        with self.assertRaisesRegex(DriverConflict,
-               r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.<unnamed #1>$"):
-            self.f1._resolve_hierarchy_conflicts(mode="error")
-
-    def test_conflict_self_sub_warning(self):
-        self.setUp_self_sub()
-
-        with self.assertWarnsRegex(DriverConflict,
-                (r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.<unnamed #1>; "
-                    r"hierarchy will be flattened$")):
-            self.f1._resolve_hierarchy_conflicts(mode="warn")
-
-    def setUp_sub_sub(self):
-        self.s1 = Signal()
-        self.c1 = Signal()
-        self.c2 = Signal()
-
-        self.f1 = Fragment()
-
-        self.f2 = Fragment()
-        self.f2.add_driver(self.s1)
-        self.f2.add_statements(self.c1.eq(0))
-        self.f1.add_subfragment(self.f2)
-
-        self.f3 = Fragment()
-        self.f3.add_driver(self.s1)
-        self.f3.add_statements(self.c2.eq(1))
-        self.f1.add_subfragment(self.f3)
-
-    def test_conflict_sub_sub(self):
-        self.setUp_sub_sub()
-
-        self.f1._resolve_hierarchy_conflicts(mode="silent")
-        self.assertEqual(self.f1.subfragments, [])
-        self.assertRepr(self.f1.statements, """
-        (
-            (eq (sig c1) (const 1'd0))
-            (eq (sig c2) (const 1'd1))
-        )
-        """)
-
-    def setUp_self_subsub(self):
-        self.s1 = Signal()
-        self.c1 = Signal()
-        self.c2 = Signal()
-
-        self.f1 = Fragment()
-        self.f1.add_driver(self.s1)
-
-        self.f2 = Fragment()
-        self.f2.add_statements(self.c1.eq(0))
-        self.f1.add_subfragment(self.f2)
-
-        self.f3 = Fragment()
-        self.f3.add_driver(self.s1)
-        self.f3.add_statements(self.c2.eq(1))
-        self.f2.add_subfragment(self.f3)
-
-    def test_conflict_self_subsub(self):
-        self.setUp_self_subsub()
-
-        self.f1._resolve_hierarchy_conflicts(mode="silent")
-        self.assertEqual(self.f1.subfragments, [])
-        self.assertRepr(self.f1.statements, """
-        (
-            (eq (sig c1) (const 1'd0))
-            (eq (sig c2) (const 1'd1))
-        )
-        """)
-
-    def setUp_memory(self):
-        self.m = Memory(width=8, depth=4)
-        self.fr = self.m.read_port().elaborate(platform=None)
-        self.fw = self.m.write_port().elaborate(platform=None)
-        self.f1 = Fragment()
-        self.f2 = Fragment()
-        self.f2.add_subfragment(self.fr)
-        self.f1.add_subfragment(self.f2)
-        self.f3 = Fragment()
-        self.f3.add_subfragment(self.fw)
-        self.f1.add_subfragment(self.f3)
-
-    def test_conflict_memory(self):
-        self.setUp_memory()
-
-        self.f1._resolve_hierarchy_conflicts(mode="silent")
-        self.assertEqual(self.f1.subfragments, [
-            (self.fr, None),
-            (self.fw, None),
-        ])
-
-    def test_conflict_memory_error(self):
-        self.setUp_memory()
-
-        with self.assertRaisesRegex(DriverConflict,
-                r"^Memory 'm' is accessed from multiple fragments: top\.<unnamed #0>, "
-                    r"top\.<unnamed #1>$"):
-            self.f1._resolve_hierarchy_conflicts(mode="error")
-
-    def test_conflict_memory_warning(self):
-        self.setUp_memory()
-
-        with self.assertWarnsRegex(DriverConflict,
-                (r"^Memory 'm' is accessed from multiple fragments: top.<unnamed #0>, "
-                    r"top.<unnamed #1>; hierarchy will be flattened$")):
-            self.f1._resolve_hierarchy_conflicts(mode="warn")
-
-    def test_explicit_flatten(self):
-        self.f1 = Fragment()
-        self.f2 = Fragment()
-        self.f2.flatten = True
-        self.f1.add_subfragment(self.f2)
-
-        self.f1._resolve_hierarchy_conflicts(mode="silent")
-        self.assertEqual(self.f1.subfragments, [])
-
-    def test_no_conflict_local_domains(self):
-        f1 = Fragment()
-        cd1 = ClockDomain("d", local=True)
-        f1.add_domains(cd1)
-        f1.add_driver(ClockSignal("d"))
-        f2 = Fragment()
-        cd2 = ClockDomain("d", local=True)
-        f2.add_domains(cd2)
-        f2.add_driver(ClockSignal("d"))
-        f3 = Fragment()
-        f3.add_subfragment(f1)
-        f3.add_subfragment(f2)
-        f3.prepare()
-
-
-class InstanceTestCase(FHDLTestCase):
-    def test_construct(self):
-        s1 = Signal()
-        s2 = Signal()
-        s3 = Signal()
-        s4 = Signal()
-        s5 = Signal()
-        s6 = Signal()
-        inst = Instance("foo",
-            ("a", "ATTR1", 1),
-            ("p", "PARAM1", 0x1234),
-            ("i", "s1", s1),
-            ("o", "s2", s2),
-            ("io", "s3", s3),
-            a_ATTR2=2,
-            p_PARAM2=0x5678,
-            i_s4=s4,
-            o_s5=s5,
-            io_s6=s6,
-        )
-        self.assertEqual(inst.attrs, OrderedDict([
-            ("ATTR1", 1),
-            ("ATTR2", 2),
-        ]))
-        self.assertEqual(inst.parameters, OrderedDict([
-            ("PARAM1", 0x1234),
-            ("PARAM2", 0x5678),
-        ]))
-        self.assertEqual(inst.named_ports, OrderedDict([
-            ("s1", (s1, "i")),
-            ("s2", (s2, "o")),
-            ("s3", (s3, "io")),
-            ("s4", (s4, "i")),
-            ("s5", (s5, "o")),
-            ("s6", (s6, "io")),
-        ]))
-
-    def test_cast_ports(self):
-        inst = Instance("foo",
-            ("i", "s1", 1),
-            ("o", "s2", 2),
-            ("io", "s3", 3),
-            i_s4=4,
-            o_s5=5,
-            io_s6=6,
-        )
-        self.assertRepr(inst.named_ports["s1"][0], "(const 1'd1)")
-        self.assertRepr(inst.named_ports["s2"][0], "(const 2'd2)")
-        self.assertRepr(inst.named_ports["s3"][0], "(const 2'd3)")
-        self.assertRepr(inst.named_ports["s4"][0], "(const 3'd4)")
-        self.assertRepr(inst.named_ports["s5"][0], "(const 3'd5)")
-        self.assertRepr(inst.named_ports["s6"][0], "(const 3'd6)")
-
-    def test_wrong_construct_arg(self):
-        s = Signal()
-        with self.assertRaisesRegex(NameError,
-                (r"^Instance argument \('', 's1', \(sig s\)\) should be a tuple "
-                    r"\(kind, name, value\) where kind is one of \"p\", \"i\", \"o\", or \"io\"$")):
-            Instance("foo", ("", "s1", s))
-
-    def test_wrong_construct_kwarg(self):
-        s = Signal()
-        with self.assertRaisesRegex(NameError,
-                (r"^Instance keyword argument x_s1=\(sig s\) does not start with one of "
-                    r"\"p_\", \"i_\", \"o_\", or \"io_\"$")):
-            Instance("foo", x_s1=s)
-
-    def setUp_cpu(self):
-        self.rst = Signal()
-        self.stb = Signal()
-        self.pins = Signal(8)
-        self.datal = Signal(4)
-        self.datah = Signal(4)
-        self.inst = Instance("cpu",
-            p_RESET=0x1234,
-            i_clk=ClockSignal(),
-            i_rst=self.rst,
-            o_stb=self.stb,
-            o_data=Cat(self.datal, self.datah),
-            io_pins=self.pins[:]
-        )
-        self.wrap = Fragment()
-        self.wrap.add_subfragment(self.inst)
-
-    def test_init(self):
-        self.setUp_cpu()
-        f = self.inst
-        self.assertEqual(f.type, "cpu")
-        self.assertEqual(f.parameters, OrderedDict([("RESET", 0x1234)]))
-        self.assertEqual(list(f.named_ports.keys()), ["clk", "rst", "stb", "data", "pins"])
-        self.assertEqual(f.ports, SignalDict([]))
-
-    def test_prepare(self):
-        self.setUp_cpu()
-        f = self.wrap.prepare()
-        sync_clk = f.domains["sync"].clk
-        self.assertEqual(f.ports, SignalDict([
-            (sync_clk, "i"),
-            (self.rst, "i"),
-            (self.pins, "io"),
-        ]))
-
-    def test_prepare_explicit_ports(self):
-        self.setUp_cpu()
-        f = self.wrap.prepare(ports=[self.rst, self.stb])
-        sync_clk = f.domains["sync"].clk
-        sync_rst = f.domains["sync"].rst
-        self.assertEqual(f.ports, SignalDict([
-            (sync_clk, "i"),
-            (sync_rst, "i"),
-            (self.rst, "i"),
-            (self.stb, "o"),
-            (self.pins, "io"),
-        ]))
-
-    def test_prepare_slice_in_port(self):
-        s = Signal(2)
-        f = Fragment()
-        f.add_subfragment(Instance("foo", o_O=s[0]))
-        f.add_subfragment(Instance("foo", o_O=s[1]))
-        fp = f.prepare(ports=[s], missing_domain=lambda name: None)
-        self.assertEqual(fp.ports, SignalDict([
-            (s, "o"),
-        ]))
-
-    def test_prepare_attrs(self):
-        self.setUp_cpu()
-        self.inst.attrs["ATTR"] = 1
-        f = self.inst.prepare()
-        self.assertEqual(f.attrs, OrderedDict([
-            ("ATTR", 1),
-        ]))
diff --git a/nmigen/test/test_hdl_mem.py b/nmigen/test/test_hdl_mem.py
deleted file mode 100644 (file)
index ed87aec..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-# nmigen: UnusedElaboratable=no
-
-from ..hdl.ast import *
-from ..hdl.mem import *
-from .utils import *
-
-
-class MemoryTestCase(FHDLTestCase):
-    def test_name(self):
-        m1 = Memory(width=8, depth=4)
-        self.assertEqual(m1.name, "m1")
-        m2 = [Memory(width=8, depth=4)][0]
-        self.assertEqual(m2.name, "$memory")
-        m3 = Memory(width=8, depth=4, name="foo")
-        self.assertEqual(m3.name, "foo")
-
-    def test_geometry(self):
-        m = Memory(width=8, depth=4)
-        self.assertEqual(m.width, 8)
-        self.assertEqual(m.depth, 4)
-
-    def test_geometry_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Memory width must be a non-negative integer, not -1$"):
-            m = Memory(width=-1, depth=4)
-        with self.assertRaisesRegex(TypeError,
-                r"^Memory depth must be a non-negative integer, not -1$"):
-            m = Memory(width=8, depth=-1)
-
-    def test_init(self):
-        m = Memory(width=8, depth=4, init=range(4))
-        self.assertEqual(m.init, [0, 1, 2, 3])
-
-    def test_init_wrong_count(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Memory initialization value count exceed memory depth \(8 > 4\)$"):
-            m = Memory(width=8, depth=4, init=range(8))
-
-    def test_init_wrong_type(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Memory initialization value at address 1: "
-                    r"'str' object cannot be interpreted as an integer$")):
-            m = Memory(width=8, depth=4, init=[1, "0"])
-
-    def test_attrs(self):
-        m1 = Memory(width=8, depth=4)
-        self.assertEqual(m1.attrs, {})
-        m2 = Memory(width=8, depth=4, attrs={"ram_block": True})
-        self.assertEqual(m2.attrs, {"ram_block": True})
-
-    def test_read_port_transparent(self):
-        mem    = Memory(width=8, depth=4)
-        rdport = mem.read_port()
-        self.assertEqual(rdport.memory, mem)
-        self.assertEqual(rdport.domain, "sync")
-        self.assertEqual(rdport.transparent, True)
-        self.assertEqual(len(rdport.addr), 2)
-        self.assertEqual(len(rdport.data), 8)
-        self.assertEqual(len(rdport.en), 1)
-        self.assertIsInstance(rdport.en, Const)
-        self.assertEqual(rdport.en.value, 1)
-
-    def test_read_port_non_transparent(self):
-        mem    = Memory(width=8, depth=4)
-        rdport = mem.read_port(transparent=False)
-        self.assertEqual(rdport.memory, mem)
-        self.assertEqual(rdport.domain, "sync")
-        self.assertEqual(rdport.transparent, False)
-        self.assertEqual(len(rdport.en), 1)
-        self.assertIsInstance(rdport.en, Signal)
-        self.assertEqual(rdport.en.reset, 1)
-
-    def test_read_port_asynchronous(self):
-        mem    = Memory(width=8, depth=4)
-        rdport = mem.read_port(domain="comb")
-        self.assertEqual(rdport.memory, mem)
-        self.assertEqual(rdport.domain, "comb")
-        self.assertEqual(rdport.transparent, True)
-        self.assertEqual(len(rdport.en), 1)
-        self.assertIsInstance(rdport.en, Const)
-        self.assertEqual(rdport.en.value, 1)
-
-    def test_read_port_wrong(self):
-        mem = Memory(width=8, depth=4)
-        with self.assertRaisesRegex(ValueError,
-                r"^Read port cannot be simultaneously asynchronous and non-transparent$"):
-            mem.read_port(domain="comb", transparent=False)
-
-    def test_write_port(self):
-        mem    = Memory(width=8, depth=4)
-        wrport = mem.write_port()
-        self.assertEqual(wrport.memory, mem)
-        self.assertEqual(wrport.domain, "sync")
-        self.assertEqual(wrport.granularity, 8)
-        self.assertEqual(len(wrport.addr), 2)
-        self.assertEqual(len(wrport.data), 8)
-        self.assertEqual(len(wrport.en), 1)
-
-    def test_write_port_granularity(self):
-        mem    = Memory(width=8, depth=4)
-        wrport = mem.write_port(granularity=2)
-        self.assertEqual(wrport.memory, mem)
-        self.assertEqual(wrport.domain, "sync")
-        self.assertEqual(wrport.granularity, 2)
-        self.assertEqual(len(wrport.addr), 2)
-        self.assertEqual(len(wrport.data), 8)
-        self.assertEqual(len(wrport.en), 4)
-
-    def test_write_port_granularity_wrong(self):
-        mem = Memory(width=8, depth=4)
-        with self.assertRaisesRegex(TypeError,
-                r"^Write port granularity must be a non-negative integer, not -1$"):
-            mem.write_port(granularity=-1)
-        with self.assertRaisesRegex(ValueError,
-                r"^Write port granularity must not be greater than memory width \(10 > 8\)$"):
-            mem.write_port(granularity=10)
-        with self.assertRaisesRegex(ValueError,
-                r"^Write port granularity must divide memory width evenly$"):
-            mem.write_port(granularity=3)
-
-
-class DummyPortTestCase(FHDLTestCase):
-    def test_name(self):
-        p1 = DummyPort(data_width=8, addr_width=2)
-        self.assertEqual(p1.addr.name, "p1_addr")
-        p2 = [DummyPort(data_width=8, addr_width=2)][0]
-        self.assertEqual(p2.addr.name, "dummy_addr")
-        p3 = DummyPort(data_width=8, addr_width=2, name="foo")
-        self.assertEqual(p3.addr.name, "foo_addr")
-
-    def test_sizes(self):
-        p1 = DummyPort(data_width=8, addr_width=2)
-        self.assertEqual(p1.addr.width, 2)
-        self.assertEqual(p1.data.width, 8)
-        self.assertEqual(p1.en.width, 1)
-        p2 = DummyPort(data_width=8, addr_width=2, granularity=2)
-        self.assertEqual(p2.en.width, 4)
diff --git a/nmigen/test/test_hdl_rec.py b/nmigen/test/test_hdl_rec.py
deleted file mode 100644 (file)
index dfcadf7..0000000
+++ /dev/null
@@ -1,329 +0,0 @@
-from enum import Enum
-
-from ..hdl.ast import *
-from ..hdl.rec import *
-from .utils import *
-
-
-class UnsignedEnum(Enum):
-    FOO = 1
-    BAR = 2
-    BAZ = 3
-
-
-class LayoutTestCase(FHDLTestCase):
-    def assertFieldEqual(self, field, expected):
-        (shape, dir) = field
-        shape = Shape.cast(shape)
-        self.assertEqual((shape, dir), expected)
-
-    def test_fields(self):
-        layout = Layout.cast([
-            ("cyc",  1),
-            ("data", signed(32)),
-            ("stb",  1, DIR_FANOUT),
-            ("ack",  1, DIR_FANIN),
-            ("info", [
-                ("a", 1),
-                ("b", 1),
-            ])
-        ])
-
-        self.assertFieldEqual(layout["cyc"], ((1, False), DIR_NONE))
-        self.assertFieldEqual(layout["data"], ((32, True), DIR_NONE))
-        self.assertFieldEqual(layout["stb"], ((1, False), DIR_FANOUT))
-        self.assertFieldEqual(layout["ack"], ((1, False), DIR_FANIN))
-        sublayout = layout["info"][0]
-        self.assertEqual(layout["info"][1], DIR_NONE)
-        self.assertFieldEqual(sublayout["a"], ((1, False), DIR_NONE))
-        self.assertFieldEqual(sublayout["b"], ((1, False), DIR_NONE))
-
-    def test_enum_field(self):
-        layout = Layout.cast([
-            ("enum", UnsignedEnum),
-            ("enum_dir", UnsignedEnum, DIR_FANOUT),
-        ])
-        self.assertFieldEqual(layout["enum"], ((2, False), DIR_NONE))
-        self.assertFieldEqual(layout["enum_dir"], ((2, False), DIR_FANOUT))
-
-    def test_range_field(self):
-        layout = Layout.cast([
-            ("range", range(0, 7)),
-        ])
-        self.assertFieldEqual(layout["range"], ((3, False), DIR_NONE))
-
-    def test_slice_tuple(self):
-        layout = Layout.cast([
-            ("a", 1),
-            ("b", 2),
-            ("c", 3)
-        ])
-        expect = Layout.cast([
-            ("a", 1),
-            ("c", 3)
-        ])
-        self.assertEqual(layout["a", "c"], expect)
-
-    def test_repr(self):
-        self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", signed(2))])),
-                         "Layout([('a', unsigned(1)), ('b', signed(2))])")
-        self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", [("c", signed(3))])])),
-                         "Layout([('a', unsigned(1)), "
-                            "('b', Layout([('c', signed(3))]))])")
-
-    def test_wrong_field(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Field \(1,\) has invalid layout: should be either \(name, shape\) or "
-                    r"\(name, shape, direction\)$")):
-            Layout.cast([(1,)])
-
-    def test_wrong_name(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Field \(1, 1\) has invalid name: should be a string$"):
-            Layout.cast([(1, 1)])
-
-    def test_wrong_name_duplicate(self):
-        with self.assertRaisesRegex(NameError,
-                r"^Field \('a', 2\) has a name that is already present in the layout$"):
-            Layout.cast([("a", 1), ("a", 2)])
-
-    def test_wrong_direction(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Field \('a', 1, 0\) has invalid direction: should be a Direction "
-                    r"instance like DIR_FANIN$")):
-            Layout.cast([("a", 1, 0)])
-
-    def test_wrong_shape(self):
-        with self.assertRaisesRegex(TypeError,
-                (r"^Field \('a', 'x'\) has invalid shape: should be castable to Shape or "
-                    r"a list of fields of a nested record$")):
-            Layout.cast([("a", "x")])
-
-
-class RecordTestCase(FHDLTestCase):
-    def test_basic(self):
-        r = Record([
-            ("stb",  1),
-            ("data", 32),
-            ("info", [
-                ("a", 1),
-                ("b", 1),
-            ])
-        ])
-
-        self.assertEqual(repr(r), "(rec r stb data (rec r__info a b))")
-        self.assertEqual(len(r),  35)
-        self.assertIsInstance(r.stb, Signal)
-        self.assertEqual(r.stb.name, "r__stb")
-        self.assertEqual(r["stb"].name, "r__stb")
-
-        self.assertTrue(hasattr(r, "stb"))
-        self.assertFalse(hasattr(r, "xxx"))
-
-    def test_unnamed(self):
-        r = [Record([
-            ("stb", 1)
-        ])][0]
-
-        self.assertEqual(repr(r), "(rec <unnamed> stb)")
-        self.assertEqual(r.stb.name, "stb")
-
-    def test_iter(self):
-        r = Record([
-            ("data", 4),
-            ("stb",  1),
-        ])
-
-        self.assertEqual(repr(r[0]),   "(slice (rec r data stb) 0:1)")
-        self.assertEqual(repr(r[0:3]), "(slice (rec r data stb) 0:3)")
-
-    def test_wrong_field(self):
-        r = Record([
-            ("stb", 1),
-            ("ack", 1),
-        ])
-        with self.assertRaisesRegex(AttributeError,
-                r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
-            r["en"]
-        with self.assertRaisesRegex(AttributeError,
-                r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
-            r.en
-
-    def test_wrong_field_unnamed(self):
-        r = [Record([
-            ("stb", 1),
-            ("ack", 1),
-        ])][0]
-        with self.assertRaisesRegex(AttributeError,
-                r"^Unnamed record does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
-            r.en
-
-    def test_construct_with_fields(self):
-        ns = Signal(1)
-        nr = Record([
-            ("burst", 1)
-        ])
-        r = Record([
-            ("stb", 1),
-            ("info", [
-                ("burst", 1)
-            ])
-        ], fields={
-            "stb":  ns,
-            "info": nr
-        })
-        self.assertIs(r.stb, ns)
-        self.assertIs(r.info, nr)
-
-    def test_like(self):
-        r1 = Record([("a", 1), ("b", 2)])
-        r2 = Record.like(r1)
-        self.assertEqual(r1.layout, r2.layout)
-        self.assertEqual(r2.name, "r2")
-        r3 = Record.like(r1, name="foo")
-        self.assertEqual(r3.name, "foo")
-        r4 = Record.like(r1, name_suffix="foo")
-        self.assertEqual(r4.name, "r1foo")
-
-    def test_like_modifications(self):
-        r1 = Record([("a", 1), ("b", [("s", 1)])])
-        self.assertEqual(r1.a.name, "r1__a")
-        self.assertEqual(r1.b.name, "r1__b")
-        self.assertEqual(r1.b.s.name, "r1__b__s")
-        r1.a.reset = 1
-        r1.b.s.reset = 1
-        r2 = Record.like(r1)
-        self.assertEqual(r2.a.reset, 1)
-        self.assertEqual(r2.b.s.reset, 1)
-        self.assertEqual(r2.a.name, "r2__a")
-        self.assertEqual(r2.b.name, "r2__b")
-        self.assertEqual(r2.b.s.name, "r2__b__s")
-
-    def test_slice_tuple(self):
-        r1 = Record([("a", 1), ("b", 2), ("c", 3)])
-        r2 = r1["a", "c"]
-        self.assertEqual(r2.layout, Layout([("a", 1), ("c", 3)]))
-        self.assertIs(r2.a, r1.a)
-        self.assertIs(r2.c, r1.c)
-
-    def test_enum_decoder(self):
-        r1 = Record([("a", UnsignedEnum)])
-        self.assertEqual(r1.a.decoder(UnsignedEnum.FOO), "FOO/1")
-
-
-class ConnectTestCase(FHDLTestCase):
-    def setUp_flat(self):
-        self.core_layout = [
-            ("addr",   32, DIR_FANOUT),
-            ("data_r", 32, DIR_FANIN),
-            ("data_w", 32, DIR_FANIN),
-        ]
-        self.periph_layout = [
-            ("addr",   32, DIR_FANOUT),
-            ("data_r", 32, DIR_FANIN),
-            ("data_w", 32, DIR_FANIN),
-        ]
-
-    def setUp_nested(self):
-        self.core_layout = [
-            ("addr",   32, DIR_FANOUT),
-            ("data", [
-                ("r",  32, DIR_FANIN),
-                ("w",  32, DIR_FANIN),
-            ]),
-        ]
-        self.periph_layout = [
-            ("addr",   32, DIR_FANOUT),
-            ("data", [
-                ("r",  32, DIR_FANIN),
-                ("w",  32, DIR_FANIN),
-            ]),
-        ]
-
-    def test_flat(self):
-        self.setUp_flat()
-
-        core    = Record(self.core_layout)
-        periph1 = Record(self.periph_layout)
-        periph2 = Record(self.periph_layout)
-
-        stmts = core.connect(periph1, periph2)
-        self.assertRepr(stmts, """(
-            (eq (sig periph1__addr) (sig core__addr))
-            (eq (sig periph2__addr) (sig core__addr))
-            (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r)))
-            (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w)))
-        )""")
-
-    def test_flat_include(self):
-        self.setUp_flat()
-
-        core    = Record(self.core_layout)
-        periph1 = Record(self.periph_layout)
-        periph2 = Record(self.periph_layout)
-
-        stmts = core.connect(periph1, periph2, include={"addr": True})
-        self.assertRepr(stmts, """(
-            (eq (sig periph1__addr) (sig core__addr))
-            (eq (sig periph2__addr) (sig core__addr))
-        )""")
-
-    def test_flat_exclude(self):
-        self.setUp_flat()
-
-        core    = Record(self.core_layout)
-        periph1 = Record(self.periph_layout)
-        periph2 = Record(self.periph_layout)
-
-        stmts = core.connect(periph1, periph2, exclude={"addr": True})
-        self.assertRepr(stmts, """(
-            (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r)))
-            (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w)))
-        )""")
-
-    def test_nested(self):
-        self.setUp_nested()
-
-        core    = Record(self.core_layout)
-        periph1 = Record(self.periph_layout)
-        periph2 = Record(self.periph_layout)
-
-        stmts = core.connect(periph1, periph2)
-        self.maxDiff = None
-        self.assertRepr(stmts, """(
-            (eq (sig periph1__addr) (sig core__addr))
-            (eq (sig periph2__addr) (sig core__addr))
-            (eq (sig core__data__r) (| (sig periph1__data__r) (sig periph2__data__r)))
-            (eq (sig core__data__w) (| (sig periph1__data__w) (sig periph2__data__w)))
-        )""")
-
-    def test_wrong_include_exclude(self):
-        self.setUp_flat()
-
-        core   = Record(self.core_layout)
-        periph = Record(self.periph_layout)
-
-        with self.assertRaisesRegex(AttributeError,
-                r"^Cannot include field 'foo' because it is not present in record 'core'$"):
-            core.connect(periph, include={"foo": True})
-
-        with self.assertRaisesRegex(AttributeError,
-                r"^Cannot exclude field 'foo' because it is not present in record 'core'$"):
-            core.connect(periph, exclude={"foo": True})
-
-    def test_wrong_direction(self):
-        recs = [Record([("x", 1)]) for _ in range(2)]
-
-        with self.assertRaisesRegex(TypeError,
-                (r"^Cannot connect field 'x' of unnamed record because it does not have "
-                    r"a direction$")):
-            recs[0].connect(recs[1])
-
-    def test_wrong_missing_field(self):
-        core   = Record([("addr", 32, DIR_FANOUT)])
-        periph = Record([])
-
-        with self.assertRaisesRegex(AttributeError,
-                (r"^Cannot connect field 'addr' of record 'core' to subordinate record 'periph' "
-                    r"because the subordinate record does not have this field$")):
-            core.connect(periph)
diff --git a/nmigen/test/test_hdl_xfrm.py b/nmigen/test/test_hdl_xfrm.py
deleted file mode 100644 (file)
index 9ec6e1b..0000000
+++ /dev/null
@@ -1,649 +0,0 @@
-# nmigen: UnusedElaboratable=no
-
-from ..hdl.ast import *
-from ..hdl.cd import *
-from ..hdl.ir import *
-from ..hdl.xfrm import *
-from ..hdl.mem import *
-from .utils import *
-
-
-class DomainRenamerTestCase(FHDLTestCase):
-    def setUp(self):
-        self.s1 = Signal()
-        self.s2 = Signal()
-        self.s3 = Signal()
-        self.s4 = Signal()
-        self.s5 = Signal()
-        self.c1 = Signal()
-
-    def test_rename_signals(self):
-        f = Fragment()
-        f.add_statements(
-            self.s1.eq(ClockSignal()),
-            ResetSignal().eq(self.s2),
-            self.s3.eq(0),
-            self.s4.eq(ClockSignal("other")),
-            self.s5.eq(ResetSignal("other")),
-        )
-        f.add_driver(self.s1, None)
-        f.add_driver(self.s2, None)
-        f.add_driver(self.s3, "sync")
-
-        f = DomainRenamer("pix")(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s1) (clk pix))
-            (eq (rst pix) (sig s2))
-            (eq (sig s3) (const 1'd0))
-            (eq (sig s4) (clk other))
-            (eq (sig s5) (rst other))
-        )
-        """)
-        self.assertEqual(f.drivers, {
-            None: SignalSet((self.s1, self.s2)),
-            "pix": SignalSet((self.s3,)),
-        })
-
-    def test_rename_multi(self):
-        f = Fragment()
-        f.add_statements(
-            self.s1.eq(ClockSignal()),
-            self.s2.eq(ResetSignal("other")),
-        )
-
-        f = DomainRenamer({"sync": "pix", "other": "pix2"})(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s1) (clk pix))
-            (eq (sig s2) (rst pix2))
-        )
-        """)
-
-    def test_rename_cd(self):
-        cd_sync = ClockDomain()
-        cd_pix  = ClockDomain()
-
-        f = Fragment()
-        f.add_domains(cd_sync, cd_pix)
-
-        f = DomainRenamer("ext")(f)
-        self.assertEqual(cd_sync.name, "ext")
-        self.assertEqual(f.domains, {
-            "ext": cd_sync,
-            "pix": cd_pix,
-        })
-
-    def test_rename_cd_preserves_allow_reset_less(self):
-        cd_pix  = ClockDomain(reset_less=True)
-
-        f = Fragment()
-        f.add_domains(cd_pix)
-        f.add_statements(
-            self.s1.eq(ResetSignal(allow_reset_less=True)),
-        )
-
-        f = DomainRenamer("pix")(f)
-        f = DomainLowerer()(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s1) (const 1'd0))
-        )
-        """)
-
-
-    def test_rename_cd_subfragment(self):
-        cd_sync = ClockDomain()
-        cd_pix  = ClockDomain()
-
-        f1 = Fragment()
-        f1.add_domains(cd_sync, cd_pix)
-        f2 = Fragment()
-        f2.add_domains(cd_sync)
-        f1.add_subfragment(f2)
-
-        f1 = DomainRenamer("ext")(f1)
-        self.assertEqual(cd_sync.name, "ext")
-        self.assertEqual(f1.domains, {
-            "ext": cd_sync,
-            "pix": cd_pix,
-        })
-
-    def test_rename_wrong_to_comb(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Domain 'sync' may not be renamed to 'comb'$"):
-            DomainRenamer("comb")
-
-    def test_rename_wrong_from_comb(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^Domain 'comb' may not be renamed$"):
-            DomainRenamer({"comb": "sync"})
-
-
-class DomainLowererTestCase(FHDLTestCase):
-    def setUp(self):
-        self.s = Signal()
-
-    def test_lower_clk(self):
-        sync = ClockDomain()
-        f = Fragment()
-        f.add_domains(sync)
-        f.add_statements(
-            self.s.eq(ClockSignal("sync"))
-        )
-
-        f = DomainLowerer()(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s) (sig clk))
-        )
-        """)
-
-    def test_lower_rst(self):
-        sync = ClockDomain()
-        f = Fragment()
-        f.add_domains(sync)
-        f.add_statements(
-            self.s.eq(ResetSignal("sync"))
-        )
-
-        f = DomainLowerer()(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s) (sig rst))
-        )
-        """)
-
-    def test_lower_rst_reset_less(self):
-        sync = ClockDomain(reset_less=True)
-        f = Fragment()
-        f.add_domains(sync)
-        f.add_statements(
-            self.s.eq(ResetSignal("sync", allow_reset_less=True))
-        )
-
-        f = DomainLowerer()(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s) (const 1'd0))
-        )
-        """)
-
-    def test_lower_drivers(self):
-        sync = ClockDomain()
-        pix = ClockDomain()
-        f = Fragment()
-        f.add_domains(sync, pix)
-        f.add_driver(ClockSignal("pix"), None)
-        f.add_driver(ResetSignal("pix"), "sync")
-
-        f = DomainLowerer()(f)
-        self.assertEqual(f.drivers, {
-            None: SignalSet((pix.clk,)),
-            "sync": SignalSet((pix.rst,))
-        })
-
-    def test_lower_wrong_domain(self):
-        f = Fragment()
-        f.add_statements(
-            self.s.eq(ClockSignal("xxx"))
-        )
-
-        with self.assertRaisesRegex(DomainError,
-                r"^Signal \(clk xxx\) refers to nonexistent domain 'xxx'$"):
-            DomainLowerer()(f)
-
-    def test_lower_wrong_reset_less_domain(self):
-        sync = ClockDomain(reset_less=True)
-        f = Fragment()
-        f.add_domains(sync)
-        f.add_statements(
-            self.s.eq(ResetSignal("sync"))
-        )
-
-        with self.assertRaisesRegex(DomainError,
-                r"^Signal \(rst sync\) refers to reset of reset-less domain 'sync'$"):
-            DomainLowerer()(f)
-
-
-class SampleLowererTestCase(FHDLTestCase):
-    def setUp(self):
-        self.i = Signal()
-        self.o1 = Signal()
-        self.o2 = Signal()
-        self.o3 = Signal()
-
-    def test_lower_signal(self):
-        f = Fragment()
-        f.add_statements(
-            self.o1.eq(Sample(self.i, 2, "sync")),
-            self.o2.eq(Sample(self.i, 1, "sync")),
-            self.o3.eq(Sample(self.i, 1, "pix")),
-        )
-
-        f = SampleLowerer()(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig o1) (sig $sample$s$i$sync$2))
-            (eq (sig o2) (sig $sample$s$i$sync$1))
-            (eq (sig o3) (sig $sample$s$i$pix$1))
-            (eq (sig $sample$s$i$sync$1) (sig i))
-            (eq (sig $sample$s$i$sync$2) (sig $sample$s$i$sync$1))
-            (eq (sig $sample$s$i$pix$1) (sig i))
-        )
-        """)
-        self.assertEqual(len(f.drivers["sync"]), 2)
-        self.assertEqual(len(f.drivers["pix"]), 1)
-
-    def test_lower_const(self):
-        f = Fragment()
-        f.add_statements(
-            self.o1.eq(Sample(1, 2, "sync")),
-        )
-
-        f = SampleLowerer()(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig o1) (sig $sample$c$1$sync$2))
-            (eq (sig $sample$c$1$sync$1) (const 1'd1))
-            (eq (sig $sample$c$1$sync$2) (sig $sample$c$1$sync$1))
-        )
-        """)
-        self.assertEqual(len(f.drivers["sync"]), 2)
-
-
-class SwitchCleanerTestCase(FHDLTestCase):
-    def test_clean(self):
-        a = Signal()
-        b = Signal()
-        c = Signal()
-        stmts = [
-            Switch(a, {
-                1: a.eq(0),
-                0: [
-                    b.eq(1),
-                    Switch(b, {1: [
-                        Switch(a|b, {})
-                    ]})
-                ]
-            })
-        ]
-
-        self.assertRepr(SwitchCleaner()(stmts), """
-        (
-            (switch (sig a)
-                (case 1
-                    (eq (sig a) (const 1'd0)))
-                (case 0
-                    (eq (sig b) (const 1'd1)))
-            )
-        )
-        """)
-
-
-class LHSGroupAnalyzerTestCase(FHDLTestCase):
-    def test_no_group_unrelated(self):
-        a = Signal()
-        b = Signal()
-        stmts = [
-            a.eq(0),
-            b.eq(0),
-        ]
-
-        groups = LHSGroupAnalyzer()(stmts)
-        self.assertEqual(list(groups.values()), [
-            SignalSet((a,)),
-            SignalSet((b,)),
-        ])
-
-    def test_group_related(self):
-        a = Signal()
-        b = Signal()
-        stmts = [
-            a.eq(0),
-            Cat(a, b).eq(0),
-        ]
-
-        groups = LHSGroupAnalyzer()(stmts)
-        self.assertEqual(list(groups.values()), [
-            SignalSet((a, b)),
-        ])
-
-    def test_no_loops(self):
-        a = Signal()
-        b = Signal()
-        stmts = [
-            a.eq(0),
-            Cat(a, b).eq(0),
-            Cat(a, b).eq(0),
-        ]
-
-        groups = LHSGroupAnalyzer()(stmts)
-        self.assertEqual(list(groups.values()), [
-            SignalSet((a, b)),
-        ])
-
-    def test_switch(self):
-        a = Signal()
-        b = Signal()
-        stmts = [
-            a.eq(0),
-            Switch(a, {
-                1: b.eq(0),
-            })
-        ]
-
-        groups = LHSGroupAnalyzer()(stmts)
-        self.assertEqual(list(groups.values()), [
-            SignalSet((a,)),
-            SignalSet((b,)),
-        ])
-
-    def test_lhs_empty(self):
-        stmts = [
-            Cat().eq(0)
-        ]
-
-        groups = LHSGroupAnalyzer()(stmts)
-        self.assertEqual(list(groups.values()), [
-        ])
-
-
-class LHSGroupFilterTestCase(FHDLTestCase):
-    def test_filter(self):
-        a = Signal()
-        b = Signal()
-        c = Signal()
-        stmts = [
-            Switch(a, {
-                1: a.eq(0),
-                0: [
-                    b.eq(1),
-                    Switch(b, {1: []})
-                ]
-            })
-        ]
-
-        self.assertRepr(LHSGroupFilter(SignalSet((a,)))(stmts), """
-        (
-            (switch (sig a)
-                (case 1
-                    (eq (sig a) (const 1'd0)))
-                (case 0 )
-            )
-        )
-        """)
-
-    def test_lhs_empty(self):
-        stmts = [
-            Cat().eq(0)
-        ]
-
-        self.assertRepr(LHSGroupFilter(SignalSet())(stmts), "()")
-
-
-class ResetInserterTestCase(FHDLTestCase):
-    def setUp(self):
-        self.s1 = Signal()
-        self.s2 = Signal(reset=1)
-        self.s3 = Signal(reset=1, reset_less=True)
-        self.c1 = Signal()
-
-    def test_reset_default(self):
-        f = Fragment()
-        f.add_statements(
-            self.s1.eq(1)
-        )
-        f.add_driver(self.s1, "sync")
-
-        f = ResetInserter(self.c1)(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s1) (const 1'd1))
-            (switch (sig c1)
-                (case 1 (eq (sig s1) (const 1'd0)))
-            )
-        )
-        """)
-
-    def test_reset_cd(self):
-        f = Fragment()
-        f.add_statements(
-            self.s1.eq(1),
-            self.s2.eq(0),
-        )
-        f.add_domains(ClockDomain("sync"))
-        f.add_driver(self.s1, "sync")
-        f.add_driver(self.s2, "pix")
-
-        f = ResetInserter({"pix": self.c1})(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s1) (const 1'd1))
-            (eq (sig s2) (const 1'd0))
-            (switch (sig c1)
-                (case 1 (eq (sig s2) (const 1'd1)))
-            )
-        )
-        """)
-
-    def test_reset_value(self):
-        f = Fragment()
-        f.add_statements(
-            self.s2.eq(0)
-        )
-        f.add_driver(self.s2, "sync")
-
-        f = ResetInserter(self.c1)(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s2) (const 1'd0))
-            (switch (sig c1)
-                (case 1 (eq (sig s2) (const 1'd1)))
-            )
-        )
-        """)
-
-    def test_reset_less(self):
-        f = Fragment()
-        f.add_statements(
-            self.s3.eq(0)
-        )
-        f.add_driver(self.s3, "sync")
-
-        f = ResetInserter(self.c1)(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s3) (const 1'd0))
-            (switch (sig c1)
-                (case 1 )
-            )
-        )
-        """)
-
-
-class EnableInserterTestCase(FHDLTestCase):
-    def setUp(self):
-        self.s1 = Signal()
-        self.s2 = Signal()
-        self.s3 = Signal()
-        self.c1 = Signal()
-
-    def test_enable_default(self):
-        f = Fragment()
-        f.add_statements(
-            self.s1.eq(1)
-        )
-        f.add_driver(self.s1, "sync")
-
-        f = EnableInserter(self.c1)(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s1) (const 1'd1))
-            (switch (sig c1)
-                (case 0 (eq (sig s1) (sig s1)))
-            )
-        )
-        """)
-
-    def test_enable_cd(self):
-        f = Fragment()
-        f.add_statements(
-            self.s1.eq(1),
-            self.s2.eq(0),
-        )
-        f.add_driver(self.s1, "sync")
-        f.add_driver(self.s2, "pix")
-
-        f = EnableInserter({"pix": self.c1})(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s1) (const 1'd1))
-            (eq (sig s2) (const 1'd0))
-            (switch (sig c1)
-                (case 0 (eq (sig s2) (sig s2)))
-            )
-        )
-        """)
-
-    def test_enable_subfragment(self):
-        f1 = Fragment()
-        f1.add_statements(
-            self.s1.eq(1)
-        )
-        f1.add_driver(self.s1, "sync")
-
-        f2 = Fragment()
-        f2.add_statements(
-            self.s2.eq(1)
-        )
-        f2.add_driver(self.s2, "sync")
-        f1.add_subfragment(f2)
-
-        f1 = EnableInserter(self.c1)(f1)
-        (f2, _), = f1.subfragments
-        self.assertRepr(f1.statements, """
-        (
-            (eq (sig s1) (const 1'd1))
-            (switch (sig c1)
-                (case 0 (eq (sig s1) (sig s1)))
-            )
-        )
-        """)
-        self.assertRepr(f2.statements, """
-        (
-            (eq (sig s2) (const 1'd1))
-            (switch (sig c1)
-                (case 0 (eq (sig s2) (sig s2)))
-            )
-        )
-        """)
-
-    def test_enable_read_port(self):
-        mem = Memory(width=8, depth=4)
-        f = EnableInserter(self.c1)(mem.read_port(transparent=False)).elaborate(platform=None)
-        self.assertRepr(f.named_ports["EN"][0], """
-        (m (sig c1) (sig mem_r_en) (const 1'd0))
-        """)
-
-    def test_enable_write_port(self):
-        mem = Memory(width=8, depth=4)
-        f = EnableInserter(self.c1)(mem.write_port()).elaborate(platform=None)
-        self.assertRepr(f.named_ports["EN"][0], """
-        (m (sig c1) (cat (repl (slice (sig mem_w_en) 0:1) 8)) (const 8'd0))
-        """)
-
-
-class _MockElaboratable(Elaboratable):
-    def __init__(self):
-        self.s1 = Signal()
-
-    def elaborate(self, platform):
-        f = Fragment()
-        f.add_statements(
-            self.s1.eq(1)
-        )
-        f.add_driver(self.s1, "sync")
-        return f
-
-
-class TransformedElaboratableTestCase(FHDLTestCase):
-    def setUp(self):
-        self.c1 = Signal()
-        self.c2 = Signal()
-
-    def test_getattr(self):
-        e = _MockElaboratable()
-        te = EnableInserter(self.c1)(e)
-
-        self.assertIs(te.s1, e.s1)
-
-    def test_composition(self):
-        e = _MockElaboratable()
-        te1 = EnableInserter(self.c1)(e)
-        te2 = ResetInserter(self.c2)(te1)
-
-        self.assertIsInstance(te1, TransformedElaboratable)
-        self.assertIs(te1, te2)
-
-        f = Fragment.get(te2, None)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s1) (const 1'd1))
-            (switch (sig c1)
-                (case 0 (eq (sig s1) (sig s1)))
-            )
-            (switch (sig c2)
-                (case 1 (eq (sig s1) (const 1'd0)))
-            )
-        )
-        """)
-
-
-class MockUserValue(UserValue):
-    def __init__(self, lowered):
-        super().__init__()
-        self.lowered = lowered
-
-    def lower(self):
-        return self.lowered
-
-
-class UserValueTestCase(FHDLTestCase):
-    def setUp(self):
-        self.s  = Signal()
-        self.c  = Signal()
-        self.uv = MockUserValue(self.s)
-
-    def test_lower(self):
-        sync = ClockDomain()
-        f = Fragment()
-        f.add_domains(sync)
-        f.add_statements(
-            self.uv.eq(1)
-        )
-        for signal in self.uv._lhs_signals():
-            f.add_driver(signal, "sync")
-
-        f = ResetInserter(self.c)(f)
-        f = DomainLowerer()(f)
-        self.assertRepr(f.statements, """
-        (
-            (eq (sig s) (const 1'd1))
-            (switch (sig c)
-                (case 1 (eq (sig s) (const 1'd0)))
-            )
-            (switch (sig rst)
-                (case 1 (eq (sig s) (const 1'd0)))
-            )
-        )
-        """)
-
-
-class UserValueRecursiveTestCase(UserValueTestCase):
-    def setUp(self):
-        self.s = Signal()
-        self.c = Signal()
-        self.uv = MockUserValue(MockUserValue(self.s))
-
-    # inherit the test_lower method from UserValueTestCase because the checks are the same
diff --git a/nmigen/test/test_lib_cdc.py b/nmigen/test/test_lib_cdc.py
deleted file mode 100644 (file)
index 3647bfa..0000000
+++ /dev/null
@@ -1,230 +0,0 @@
-# nmigen: UnusedElaboratable=no
-
-from .utils import *
-from ..hdl import *
-from ..back.pysim import *
-from ..lib.cdc import *
-
-
-class FFSynchronizerTestCase(FHDLTestCase):
-    def test_stages_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Synchronization stage count must be a positive integer, not 0$"):
-            FFSynchronizer(Signal(), Signal(), stages=0)
-        with self.assertRaisesRegex(ValueError,
-                r"^Synchronization stage count may not safely be less than 2$"):
-            FFSynchronizer(Signal(), Signal(), stages=1)
-
-    def test_basic(self):
-        i = Signal()
-        o = Signal()
-        frag = FFSynchronizer(i, o)
-
-        sim = Simulator(frag)
-        sim.add_clock(1e-6)
-        def process():
-            self.assertEqual((yield o), 0)
-            yield i.eq(1)
-            yield Tick()
-            self.assertEqual((yield o), 0)
-            yield Tick()
-            self.assertEqual((yield o), 0)
-            yield Tick()
-            self.assertEqual((yield o), 1)
-        sim.add_process(process)
-        sim.run()
-
-    def test_reset_value(self):
-        i = Signal(reset=1)
-        o = Signal()
-        frag = FFSynchronizer(i, o, reset=1)
-
-        sim = Simulator(frag)
-        sim.add_clock(1e-6)
-        def process():
-            self.assertEqual((yield o), 1)
-            yield i.eq(0)
-            yield Tick()
-            self.assertEqual((yield o), 1)
-            yield Tick()
-            self.assertEqual((yield o), 1)
-            yield Tick()
-            self.assertEqual((yield o), 0)
-        sim.add_process(process)
-        sim.run()
-
-
-class AsyncFFSynchronizerTestCase(FHDLTestCase):
-    def test_stages_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Synchronization stage count must be a positive integer, not 0$"):
-            ResetSynchronizer(Signal(), stages=0)
-        with self.assertRaisesRegex(ValueError,
-                r"^Synchronization stage count may not safely be less than 2$"):
-            ResetSynchronizer(Signal(), stages=1)
-
-    def test_edge_wrong(self):
-        with self.assertRaisesRegex(ValueError,
-                r"^AsyncFFSynchronizer async edge must be one of 'pos' or 'neg', not 'xxx'$"):
-            AsyncFFSynchronizer(Signal(), Signal(), o_domain="sync", async_edge="xxx")
-
-    def test_pos_edge(self):
-        i = Signal()
-        o = Signal()
-        m = Module()
-        m.domains += ClockDomain("sync")
-        m.submodules += AsyncFFSynchronizer(i, o)
-
-        sim = Simulator(m)
-        sim.add_clock(1e-6)
-        def process():
-            # initial reset
-            self.assertEqual((yield i), 0)
-            self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-
-            yield i.eq(1)
-            yield Delay(1e-8)
-            self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 1)
-            yield i.eq(0)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-        sim.add_process(process)
-        with sim.write_vcd("test.vcd"):
-            sim.run()
-
-    def test_neg_edge(self):
-        i = Signal(reset=1)
-        o = Signal()
-        m = Module()
-        m.domains += ClockDomain("sync")
-        m.submodules += AsyncFFSynchronizer(i, o, async_edge="neg")
-
-        sim = Simulator(m)
-        sim.add_clock(1e-6)
-        def process():
-            # initial reset
-            self.assertEqual((yield i), 1)
-            self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-
-            yield i.eq(0)
-            yield Delay(1e-8)
-            self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 1)
-            yield i.eq(1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield o), 0)
-            yield Tick(); yield Delay(1e-8)
-        sim.add_process(process)
-        with sim.write_vcd("test.vcd"):
-            sim.run()
-
-
-class ResetSynchronizerTestCase(FHDLTestCase):
-    def test_stages_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Synchronization stage count must be a positive integer, not 0$"):
-            ResetSynchronizer(Signal(), stages=0)
-        with self.assertRaisesRegex(ValueError,
-                r"^Synchronization stage count may not safely be less than 2$"):
-            ResetSynchronizer(Signal(), stages=1)
-
-    def test_basic(self):
-        arst = Signal()
-        m = Module()
-        m.domains += ClockDomain("sync")
-        m.submodules += ResetSynchronizer(arst)
-        s = Signal(reset=1)
-        m.d.sync += s.eq(0)
-
-        sim = Simulator(m)
-        sim.add_clock(1e-6)
-        def process():
-            # initial reset
-            self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield s), 0)
-            yield Tick(); yield Delay(1e-8)
-
-            yield arst.eq(1)
-            yield Delay(1e-8)
-            self.assertEqual((yield s), 0)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield s), 1)
-            yield arst.eq(0)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield s), 1)
-            yield Tick(); yield Delay(1e-8)
-            self.assertEqual((yield s), 0)
-            yield Tick(); yield Delay(1e-8)
-        sim.add_process(process)
-        with sim.write_vcd("test.vcd"):
-            sim.run()
-
-
-# TODO: test with distinct clocks
-class PulseSynchronizerTestCase(FHDLTestCase):
-    def test_stages_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^Synchronization stage count must be a positive integer, not 0$"):
-            PulseSynchronizer("w", "r", stages=0)
-        with self.assertRaisesRegex(ValueError,
-                r"^Synchronization stage count may not safely be less than 2$"):
-            PulseSynchronizer("w", "r", stages=1)
-
-    def test_smoke(self):
-        m = Module()
-        m.domains += ClockDomain("sync")
-        ps = m.submodules.dut = PulseSynchronizer("sync", "sync")
-
-        sim = Simulator(m)
-        sim.add_clock(1e-6)
-        def process():
-            yield ps.i.eq(0)
-            # TODO: think about reset
-            for n in range(5):
-                yield Tick()
-            # Make sure no pulses are generated in quiescent state
-            for n in range(3):
-                yield Tick()
-                self.assertEqual((yield ps.o), 0)
-            # Check conservation of pulses
-            accum = 0
-            for n in range(10):
-                yield ps.i.eq(1 if n < 4 else 0)
-                yield Tick()
-                accum += yield ps.o
-            self.assertEqual(accum, 4)
-        sim.add_process(process)
-        sim.run()
diff --git a/nmigen/test/test_lib_coding.py b/nmigen/test/test_lib_coding.py
deleted file mode 100644 (file)
index cf582e3..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-from .utils import *
-from ..hdl import *
-from ..asserts import *
-from ..back.pysim import *
-from ..lib.coding import *
-
-
-class EncoderTestCase(FHDLTestCase):
-    def test_basic(self):
-        enc = Encoder(4)
-        def process():
-            self.assertEqual((yield enc.n), 1)
-            self.assertEqual((yield enc.o), 0)
-
-            yield enc.i.eq(0b0001)
-            yield Settle()
-            self.assertEqual((yield enc.n), 0)
-            self.assertEqual((yield enc.o), 0)
-
-            yield enc.i.eq(0b0100)
-            yield Settle()
-            self.assertEqual((yield enc.n), 0)
-            self.assertEqual((yield enc.o), 2)
-
-            yield enc.i.eq(0b0110)
-            yield Settle()
-            self.assertEqual((yield enc.n), 1)
-            self.assertEqual((yield enc.o), 0)
-
-        sim = Simulator(enc)
-        sim.add_process(process)
-        sim.run()
-
-
-class PriorityEncoderTestCase(FHDLTestCase):
-    def test_basic(self):
-        enc = PriorityEncoder(4)
-        def process():
-            self.assertEqual((yield enc.n), 1)
-            self.assertEqual((yield enc.o), 0)
-
-            yield enc.i.eq(0b0001)
-            yield Settle()
-            self.assertEqual((yield enc.n), 0)
-            self.assertEqual((yield enc.o), 0)
-
-            yield enc.i.eq(0b0100)
-            yield Settle()
-            self.assertEqual((yield enc.n), 0)
-            self.assertEqual((yield enc.o), 2)
-
-            yield enc.i.eq(0b0110)
-            yield Settle()
-            self.assertEqual((yield enc.n), 0)
-            self.assertEqual((yield enc.o), 1)
-
-        sim = Simulator(enc)
-        sim.add_process(process)
-        sim.run()
-
-
-class DecoderTestCase(FHDLTestCase):
-    def test_basic(self):
-        dec = Decoder(4)
-        def process():
-            self.assertEqual((yield dec.o), 0b0001)
-
-            yield dec.i.eq(1)
-            yield Settle()
-            self.assertEqual((yield dec.o), 0b0010)
-
-            yield dec.i.eq(3)
-            yield Settle()
-            self.assertEqual((yield dec.o), 0b1000)
-
-            yield dec.n.eq(1)
-            yield Settle()
-            self.assertEqual((yield dec.o), 0b0000)
-
-        sim = Simulator(dec)
-        sim.add_process(process)
-        sim.run()
-
-
-class ReversibleSpec(Elaboratable):
-    def __init__(self, encoder_cls, decoder_cls, args):
-        self.encoder_cls = encoder_cls
-        self.decoder_cls = decoder_cls
-        self.coder_args  = args
-
-    def elaborate(self, platform):
-        m = Module()
-        enc, dec = self.encoder_cls(*self.coder_args), self.decoder_cls(*self.coder_args)
-        m.submodules += enc, dec
-        m.d.comb += [
-            dec.i.eq(enc.o),
-            Assert(enc.i == dec.o)
-        ]
-        return m
-
-
-class HammingDistanceSpec(Elaboratable):
-    def __init__(self, distance, encoder_cls, args):
-        self.distance    = distance
-        self.encoder_cls = encoder_cls
-        self.coder_args  = args
-
-    def elaborate(self, platform):
-        m = Module()
-        enc1, enc2 = self.encoder_cls(*self.coder_args), self.encoder_cls(*self.coder_args)
-        m.submodules += enc1, enc2
-        m.d.comb += [
-            Assume(enc1.i + 1 == enc2.i),
-            Assert(sum(enc1.o ^ enc2.o) == self.distance)
-        ]
-        return m
-
-
-class GrayCoderTestCase(FHDLTestCase):
-    def test_reversible(self):
-        spec = ReversibleSpec(encoder_cls=GrayEncoder, decoder_cls=GrayDecoder, args=(16,))
-        self.assertFormal(spec, mode="prove")
-
-    def test_distance(self):
-        spec = HammingDistanceSpec(distance=1, encoder_cls=GrayEncoder, args=(16,))
-        self.assertFormal(spec, mode="prove")
diff --git a/nmigen/test/test_lib_fifo.py b/nmigen/test/test_lib_fifo.py
deleted file mode 100644 (file)
index e76c0b0..0000000
+++ /dev/null
@@ -1,281 +0,0 @@
-# nmigen: UnusedElaboratable=no
-
-from .utils import *
-from ..hdl import *
-from ..asserts import *
-from ..back.pysim import *
-from ..lib.fifo import *
-
-
-class FIFOTestCase(FHDLTestCase):
-    def test_depth_wrong(self):
-        with self.assertRaisesRegex(TypeError,
-                r"^FIFO width must be a non-negative integer, not -1$"):
-            FIFOInterface(width=-1, depth=8, fwft=True)
-        with self.assertRaisesRegex(TypeError,
-                r"^FIFO depth must be a non-negative integer, not -1$"):
-            FIFOInterface(width=8, depth=-1, fwft=True)
-
-    def test_sync_depth(self):
-        self.assertEqual(SyncFIFO(width=8, depth=0).depth, 0)
-        self.assertEqual(SyncFIFO(width=8, depth=1).depth, 1)
-        self.assertEqual(SyncFIFO(width=8, depth=2).depth, 2)
-
-    def test_sync_buffered_depth(self):
-        self.assertEqual(SyncFIFOBuffered(width=8, depth=0).depth, 0)
-        self.assertEqual(SyncFIFOBuffered(width=8, depth=1).depth, 1)
-        self.assertEqual(SyncFIFOBuffered(width=8, depth=2).depth, 2)
-
-    def test_async_depth(self):
-        self.assertEqual(AsyncFIFO(width=8, depth=0 ).depth, 0)
-        self.assertEqual(AsyncFIFO(width=8, depth=1 ).depth, 1)
-        self.assertEqual(AsyncFIFO(width=8, depth=2 ).depth, 2)
-        self.assertEqual(AsyncFIFO(width=8, depth=3 ).depth, 4)
-        self.assertEqual(AsyncFIFO(width=8, depth=4 ).depth, 4)
-        self.assertEqual(AsyncFIFO(width=8, depth=15).depth, 16)
-        self.assertEqual(AsyncFIFO(width=8, depth=16).depth, 16)
-        self.assertEqual(AsyncFIFO(width=8, depth=17).depth, 32)
-
-    def test_async_depth_wrong(self):
-        with self.assertRaisesRegex(ValueError,
-                (r"^AsyncFIFO only supports depths that are powers of 2; "
-                    r"requested exact depth 15 is not$")):
-            AsyncFIFO(width=8, depth=15, exact_depth=True)
-
-    def test_async_buffered_depth(self):
-        self.assertEqual(AsyncFIFOBuffered(width=8, depth=0 ).depth, 0)
-        self.assertEqual(AsyncFIFOBuffered(width=8, depth=1 ).depth, 2)
-        self.assertEqual(AsyncFIFOBuffered(width=8, depth=2 ).depth, 2)
-        self.assertEqual(AsyncFIFOBuffered(width=8, depth=3 ).depth, 3)
-        self.assertEqual(AsyncFIFOBuffered(width=8, depth=4 ).depth, 5)
-        self.assertEqual(AsyncFIFOBuffered(width=8, depth=15).depth, 17)
-        self.assertEqual(AsyncFIFOBuffered(width=8, depth=16).depth, 17)
-        self.assertEqual(AsyncFIFOBuffered(width=8, depth=17).depth, 17)
-        self.assertEqual(AsyncFIFOBuffered(width=8, depth=18).depth, 33)
-
-    def test_async_buffered_depth_wrong(self):
-        with self.assertRaisesRegex(ValueError,
-                (r"^AsyncFIFOBuffered only supports depths that are one higher than powers of 2; "
-                    r"requested exact depth 16 is not$")):
-            AsyncFIFOBuffered(width=8, depth=16, exact_depth=True)
-
-class FIFOModel(Elaboratable, FIFOInterface):
-    """
-    Non-synthesizable first-in first-out queue, implemented naively as a chain of registers.
-    """
-    def __init__(self, *, width, depth, fwft, r_domain, w_domain):
-        super().__init__(width=width, depth=depth, fwft=fwft)
-
-        self.r_domain = r_domain
-        self.w_domain = w_domain
-
-        self.level = Signal(range(self.depth + 1))
-        self.r_level = Signal(range(self.depth + 1))
-        self.w_level = Signal(range(self.depth + 1))
-
-    def elaborate(self, platform):
-        m = Module()
-
-        storage = Memory(width=self.width, depth=self.depth)
-        w_port  = m.submodules.w_port = storage.write_port(domain=self.w_domain)
-        r_port  = m.submodules.r_port = storage.read_port (domain="comb")
-
-        produce = Signal(range(self.depth))
-        consume = Signal(range(self.depth))
-
-        m.d.comb += self.r_rdy.eq(self.level > 0)
-        m.d.comb += r_port.addr.eq((consume + 1) % self.depth)
-        if self.fwft:
-            m.d.comb += self.r_data.eq(r_port.data)
-        with m.If(self.r_en & self.r_rdy):
-            if not self.fwft:
-                m.d[self.r_domain] += self.r_data.eq(r_port.data)
-            m.d[self.r_domain] += consume.eq(r_port.addr)
-
-        m.d.comb += self.w_rdy.eq(self.level < self.depth)
-        m.d.comb += w_port.data.eq(self.w_data)
-        with m.If(self.w_en & self.w_rdy):
-            m.d.comb += w_port.addr.eq((produce + 1) % self.depth)
-            m.d.comb += w_port.en.eq(1)
-            m.d[self.w_domain] += produce.eq(w_port.addr)
-
-        with m.If(ResetSignal(self.r_domain) | ResetSignal(self.w_domain)):
-            m.d.sync += self.level.eq(0)
-        with m.Else():
-            m.d.sync += self.level.eq(self.level
-                + (self.w_rdy & self.w_en)
-                - (self.r_rdy & self.r_en))
-
-        m.d.comb += [
-            self.r_level.eq(self.level),
-            self.w_level.eq(self.level),
-        ]
-        m.d.comb += Assert(ResetSignal(self.r_domain) == ResetSignal(self.w_domain))
-
-        return m
-
-
-class FIFOModelEquivalenceSpec(Elaboratable):
-    """
-    The first-in first-out queue model equivalence specification: for any inputs and control
-    signals, the behavior of the implementation under test exactly matches the ideal model,
-    except for behavior not defined by the model.
-    """
-    def __init__(self, fifo, r_domain, w_domain):
-        self.fifo = fifo
-
-        self.r_domain = r_domain
-        self.w_domain = w_domain
-
-    def elaborate(self, platform):
-        m = Module()
-        m.submodules.dut  = dut  = self.fifo
-        m.submodules.gold = gold = FIFOModel(width=dut.width, depth=dut.depth, fwft=dut.fwft,
-                                             r_domain=self.r_domain, w_domain=self.w_domain)
-
-        m.d.comb += [
-            gold.r_en.eq(dut.r_rdy & dut.r_en),
-            gold.w_en.eq(dut.w_en),
-            gold.w_data.eq(dut.w_data),
-        ]
-
-        m.d.comb += Assert(dut.r_rdy.implies(gold.r_rdy))
-        m.d.comb += Assert(dut.w_rdy.implies(gold.w_rdy))
-        m.d.comb += Assert(dut.r_level == gold.r_level)
-        m.d.comb += Assert(dut.w_level == gold.w_level)
-
-        if dut.fwft:
-            m.d.comb += Assert(dut.r_rdy
-                               .implies(dut.r_data == gold.r_data))
-        else:
-            m.d.comb += Assert((Past(dut.r_rdy, domain=self.r_domain) &
-                                Past(dut.r_en, domain=self.r_domain))
-                               .implies(dut.r_data == gold.r_data))
-
-        return m
-
-
-class FIFOContractSpec(Elaboratable):
-    """
-    The first-in first-out queue contract specification: if two elements are written to the queue
-    consecutively, they must be read out consecutively at some later point, no matter all other
-    circumstances, with the exception of reset.
-    """
-    def __init__(self, fifo, *, r_domain, w_domain, bound):
-        self.fifo     = fifo
-        self.r_domain = r_domain
-        self.w_domain = w_domain
-        self.bound    = bound
-
-    def elaborate(self, platform):
-        m = Module()
-        m.submodules.dut = fifo = self.fifo
-
-        m.domains += ClockDomain("sync")
-        m.d.comb += ResetSignal().eq(0)
-        if self.w_domain != "sync":
-            m.domains += ClockDomain(self.w_domain)
-            m.d.comb += ResetSignal(self.w_domain).eq(0)
-        if self.r_domain != "sync":
-            m.domains += ClockDomain(self.r_domain)
-            m.d.comb += ResetSignal(self.r_domain).eq(0)
-
-        entry_1 = AnyConst(fifo.width)
-        entry_2 = AnyConst(fifo.width)
-
-        with m.FSM(domain=self.w_domain) as write_fsm:
-            with m.State("WRITE-1"):
-                with m.If(fifo.w_rdy):
-                    m.d.comb += [
-                        fifo.w_data.eq(entry_1),
-                        fifo.w_en.eq(1)
-                    ]
-                    m.next = "WRITE-2"
-            with m.State("WRITE-2"):
-                with m.If(fifo.w_rdy):
-                    m.d.comb += [
-                        fifo.w_data.eq(entry_2),
-                        fifo.w_en.eq(1)
-                    ]
-                    m.next = "DONE"
-            with m.State("DONE"):
-                pass
-
-        with m.FSM(domain=self.r_domain) as read_fsm:
-            read_1 = Signal(fifo.width)
-            read_2 = Signal(fifo.width)
-            with m.State("READ"):
-                m.d.comb += fifo.r_en.eq(1)
-                if fifo.fwft:
-                    r_rdy = fifo.r_rdy
-                else:
-                    r_rdy = Past(fifo.r_rdy, domain=self.r_domain)
-                with m.If(r_rdy):
-                    m.d.sync += [
-                        read_1.eq(read_2),
-                        read_2.eq(fifo.r_data),
-                    ]
-                with m.If((read_1 == entry_1) & (read_2 == entry_2)):
-                    m.next = "DONE"
-            with m.State("DONE"):
-                pass
-
-        with m.If(Initial()):
-            m.d.comb += Assume(write_fsm.ongoing("WRITE-1"))
-            m.d.comb += Assume(read_fsm.ongoing("READ"))
-        with m.If(Past(Initial(), self.bound - 1)):
-            m.d.comb += Assert(read_fsm.ongoing("DONE"))
-
-        with m.If(ResetSignal(domain=self.w_domain)):
-            m.d.comb += Assert(~fifo.r_rdy)
-
-        if self.w_domain != "sync" or self.r_domain != "sync":
-            m.d.comb += Assume(Rose(ClockSignal(self.w_domain)) |
-                               Rose(ClockSignal(self.r_domain)))
-
-        return m
-
-
-class FIFOFormalCase(FHDLTestCase):
-    def check_sync_fifo(self, fifo):
-        self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="sync", w_domain="sync"),
-                          mode="bmc", depth=fifo.depth + 1)
-        self.assertFormal(FIFOContractSpec(fifo, r_domain="sync", w_domain="sync",
-                                           bound=fifo.depth * 2 + 1),
-                          mode="hybrid", depth=fifo.depth * 2 + 1)
-
-    def test_sync_fwft_pot(self):
-        self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=True))
-
-    def test_sync_fwft_npot(self):
-        self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=True))
-
-    def test_sync_not_fwft_pot(self):
-        self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=False))
-
-    def test_sync_not_fwft_npot(self):
-        self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=False))
-
-    def test_sync_buffered_pot(self):
-        self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=4))
-
-    def test_sync_buffered_potp1(self):
-        self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=5))
-
-    def test_sync_buffered_potm1(self):
-        self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=3))
-
-    def check_async_fifo(self, fifo):
-        # TODO: properly doing model equivalence checking on this likely requires multiclock,
-        # which is not really documented nor is it clear how to use it.
-        # self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="read", w_domain="write"),
-        #                   mode="bmc", depth=fifo.depth * 3 + 1)
-        self.assertFormal(FIFOContractSpec(fifo, r_domain="read", w_domain="write",
-                                           bound=fifo.depth * 4 + 1),
-                          mode="hybrid", depth=fifo.depth * 4 + 1)
-
-    def test_async(self):
-        self.check_async_fifo(AsyncFIFO(width=8, depth=4))
-
-    def test_async_buffered(self):
-        self.check_async_fifo(AsyncFIFOBuffered(width=8, depth=4))
diff --git a/nmigen/test/test_lib_io.py b/nmigen/test/test_lib_io.py
deleted file mode 100644 (file)
index 156150e..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-from .utils import *
-from ..hdl import *
-from ..hdl.rec import *
-from ..back.pysim import *
-from ..lib.io import *
-
-
-class PinLayoutTestCase(FHDLTestCase):
-    def assertLayoutEqual(self, layout, expected):
-        casted_layout = {}
-        for name, (shape, dir) in layout.items():
-            casted_layout[name] = (Shape.cast(shape), dir)
-
-        self.assertEqual(casted_layout, expected)
-
-
-class PinLayoutCombTestCase(PinLayoutTestCase):
-    def test_pin_layout_i(self):
-        layout_1 = pin_layout(1, dir="i")
-        self.assertLayoutEqual(layout_1.fields, {
-            "i": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="i")
-        self.assertLayoutEqual(layout_2.fields, {
-            "i": ((2, False), DIR_NONE),
-        })
-
-    def test_pin_layout_o(self):
-        layout_1 = pin_layout(1, dir="o")
-        self.assertLayoutEqual(layout_1.fields, {
-            "o": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="o")
-        self.assertLayoutEqual(layout_2.fields, {
-            "o": ((2, False), DIR_NONE),
-        })
-
-    def test_pin_layout_oe(self):
-        layout_1 = pin_layout(1, dir="oe")
-        self.assertLayoutEqual(layout_1.fields, {
-            "o":  ((1, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="oe")
-        self.assertLayoutEqual(layout_2.fields, {
-            "o":  ((2, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-    def test_pin_layout_io(self):
-        layout_1 = pin_layout(1, dir="io")
-        self.assertLayoutEqual(layout_1.fields, {
-            "i":  ((1, False), DIR_NONE),
-            "o":  ((1, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="io")
-        self.assertLayoutEqual(layout_2.fields, {
-            "i":  ((2, False), DIR_NONE),
-            "o":  ((2, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-
-class PinLayoutSDRTestCase(PinLayoutTestCase):
-    def test_pin_layout_i(self):
-        layout_1 = pin_layout(1, dir="i", xdr=1)
-        self.assertLayoutEqual(layout_1.fields, {
-            "i_clk": ((1, False), DIR_NONE),
-            "i": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="i", xdr=1)
-        self.assertLayoutEqual(layout_2.fields, {
-            "i_clk": ((1, False), DIR_NONE),
-            "i": ((2, False), DIR_NONE),
-        })
-
-    def test_pin_layout_o(self):
-        layout_1 = pin_layout(1, dir="o", xdr=1)
-        self.assertLayoutEqual(layout_1.fields, {
-            "o_clk": ((1, False), DIR_NONE),
-            "o": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="o", xdr=1)
-        self.assertLayoutEqual(layout_2.fields, {
-            "o_clk": ((1, False), DIR_NONE),
-            "o": ((2, False), DIR_NONE),
-        })
-
-    def test_pin_layout_oe(self):
-        layout_1 = pin_layout(1, dir="oe", xdr=1)
-        self.assertLayoutEqual(layout_1.fields, {
-            "o_clk": ((1, False), DIR_NONE),
-            "o":  ((1, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="oe", xdr=1)
-        self.assertLayoutEqual(layout_2.fields, {
-            "o_clk": ((1, False), DIR_NONE),
-            "o":  ((2, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-    def test_pin_layout_io(self):
-        layout_1 = pin_layout(1, dir="io", xdr=1)
-        self.assertLayoutEqual(layout_1.fields, {
-            "i_clk": ((1, False), DIR_NONE),
-            "i":  ((1, False), DIR_NONE),
-            "o_clk": ((1, False), DIR_NONE),
-            "o":  ((1, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="io", xdr=1)
-        self.assertLayoutEqual(layout_2.fields, {
-            "i_clk": ((1, False), DIR_NONE),
-            "i":  ((2, False), DIR_NONE),
-            "o_clk": ((1, False), DIR_NONE),
-            "o":  ((2, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-
-class PinLayoutDDRTestCase(PinLayoutTestCase):
-    def test_pin_layout_i(self):
-        layout_1 = pin_layout(1, dir="i", xdr=2)
-        self.assertLayoutEqual(layout_1.fields, {
-            "i_clk": ((1, False), DIR_NONE),
-            "i0": ((1, False), DIR_NONE),
-            "i1": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="i", xdr=2)
-        self.assertLayoutEqual(layout_2.fields, {
-            "i_clk": ((1, False), DIR_NONE),
-            "i0": ((2, False), DIR_NONE),
-            "i1": ((2, False), DIR_NONE),
-        })
-
-    def test_pin_layout_o(self):
-        layout_1 = pin_layout(1, dir="o", xdr=2)
-        self.assertLayoutEqual(layout_1.fields, {
-            "o_clk": ((1, False), DIR_NONE),
-            "o0": ((1, False), DIR_NONE),
-            "o1": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="o", xdr=2)
-        self.assertLayoutEqual(layout_2.fields, {
-            "o_clk": ((1, False), DIR_NONE),
-            "o0": ((2, False), DIR_NONE),
-            "o1": ((2, False), DIR_NONE),
-        })
-
-    def test_pin_layout_oe(self):
-        layout_1 = pin_layout(1, dir="oe", xdr=2)
-        self.assertLayoutEqual(layout_1.fields, {
-            "o_clk": ((1, False), DIR_NONE),
-            "o0": ((1, False), DIR_NONE),
-            "o1": ((1, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="oe", xdr=2)
-        self.assertLayoutEqual(layout_2.fields, {
-            "o_clk": ((1, False), DIR_NONE),
-            "o0": ((2, False), DIR_NONE),
-            "o1": ((2, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-    def test_pin_layout_io(self):
-        layout_1 = pin_layout(1, dir="io", xdr=2)
-        self.assertLayoutEqual(layout_1.fields, {
-            "i_clk": ((1, False), DIR_NONE),
-            "i0": ((1, False), DIR_NONE),
-            "i1": ((1, False), DIR_NONE),
-            "o_clk": ((1, False), DIR_NONE),
-            "o0": ((1, False), DIR_NONE),
-            "o1": ((1, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-        layout_2 = pin_layout(2, dir="io", xdr=2)
-        self.assertLayoutEqual(layout_2.fields, {
-            "i_clk": ((1, False), DIR_NONE),
-            "i0": ((2, False), DIR_NONE),
-            "i1": ((2, False), DIR_NONE),
-            "o_clk": ((1, False), DIR_NONE),
-            "o0": ((2, False), DIR_NONE),
-            "o1": ((2, False), DIR_NONE),
-            "oe": ((1, False), DIR_NONE),
-        })
-
-
-class PinTestCase(FHDLTestCase):
-    def test_attributes(self):
-        pin = Pin(2, dir="io", xdr=2)
-        self.assertEqual(pin.width, 2)
-        self.assertEqual(pin.dir,   "io")
-        self.assertEqual(pin.xdr,   2)
diff --git a/nmigen/test/test_lib_scheduler.py b/nmigen/test/test_lib_scheduler.py
deleted file mode 100644 (file)
index 9ae4cbb..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-# nmigen: UnusedElaboratable=no
-import unittest
-from .utils import *
-from ..hdl import *
-from ..asserts import *
-from ..sim.pysim import *
-from ..lib.scheduler import *
-
-
-class RoundRobinTestCase(unittest.TestCase):
-    def test_count(self):
-        dut = RoundRobin(count=32)
-        self.assertEqual(dut.count, 32)
-        self.assertEqual(len(dut.requests), 32)
-        self.assertEqual(len(dut.grant), 5)
-
-    def test_wrong_count(self):
-        with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not 'foo'"):
-            dut = RoundRobin(count="foo")
-        with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not -1"):
-            dut = RoundRobin(count=-1)
-
-
-class RoundRobinSimulationTestCase(unittest.TestCase):
-    def test_count_one(self):
-        dut = RoundRobin(count=1)
-        sim = Simulator(dut)
-        def process():
-            yield dut.requests.eq(0)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 0)
-            self.assertFalse((yield dut.valid))
-
-            yield dut.requests.eq(1)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 0)
-            self.assertTrue((yield dut.valid))
-        sim.add_sync_process(process)
-        sim.add_clock(1e-6)
-        with sim.write_vcd("test.vcd"):
-            sim.run()
-
-    def test_transitions(self):
-        dut = RoundRobin(count=3)
-        sim = Simulator(dut)
-        def process():
-            yield dut.requests.eq(0b111)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 1)
-            self.assertTrue((yield dut.valid))
-
-            yield dut.requests.eq(0b110)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 2)
-            self.assertTrue((yield dut.valid))
-
-            yield dut.requests.eq(0b010)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 1)
-            self.assertTrue((yield dut.valid))
-
-            yield dut.requests.eq(0b011)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 0)
-            self.assertTrue((yield dut.valid))
-
-            yield dut.requests.eq(0b001)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 0)
-            self.assertTrue((yield dut.valid))
-
-            yield dut.requests.eq(0b101)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 2)
-            self.assertTrue((yield dut.valid))
-
-            yield dut.requests.eq(0b100)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 2)
-            self.assertTrue((yield dut.valid))
-
-            yield dut.requests.eq(0b000)
-            yield; yield Delay(1e-8)
-            self.assertFalse((yield dut.valid))
-
-            yield dut.requests.eq(0b001)
-            yield; yield Delay(1e-8)
-            self.assertEqual((yield dut.grant), 0)
-            self.assertTrue((yield dut.valid))
-        sim.add_sync_process(process)
-        sim.add_clock(1e-6)
-        with sim.write_vcd("test.vcd"):
-            sim.run()
diff --git a/nmigen/test/test_sim.py b/nmigen/test/test_sim.py
deleted file mode 100644 (file)
index 94424f7..0000000
+++ /dev/null
@@ -1,798 +0,0 @@
-import os
-from contextlib import contextmanager
-
-from .utils import *
-from .._utils import flatten, union
-from ..hdl.ast import *
-from ..hdl.cd import  *
-from ..hdl.mem import *
-from ..hdl.rec import *
-from ..hdl.dsl import  *
-from ..hdl.ir import *
-from ..back.pysim import *
-
-
-class SimulatorUnitTestCase(FHDLTestCase):
-    def assertStatement(self, stmt, inputs, output, reset=0):
-        inputs = [Value.cast(i) for i in inputs]
-        output = Value.cast(output)
-
-        isigs = [Signal(i.shape(), name=n) for i, n in zip(inputs, "abcd")]
-        osig  = Signal(output.shape(), name="y", reset=reset)
-
-        stmt = stmt(osig, *isigs)
-        frag = Fragment()
-        frag.add_statements(stmt)
-        for signal in flatten(s._lhs_signals() for s in Statement.cast(stmt)):
-            frag.add_driver(signal)
-
-        sim = Simulator(frag)
-        def process():
-            for isig, input in zip(isigs, inputs):
-                yield isig.eq(input)
-            yield Settle()
-            self.assertEqual((yield osig), output.value)
-        sim.add_process(process)
-        with sim.write_vcd("test.vcd", "test.gtkw", traces=[*isigs, osig]):
-            sim.run()
-
-    def test_invert(self):
-        stmt = lambda y, a: y.eq(~a)
-        self.assertStatement(stmt, [C(0b0000, 4)], C(0b1111, 4))
-        self.assertStatement(stmt, [C(0b1010, 4)], C(0b0101, 4))
-        self.assertStatement(stmt, [C(0,      4)], C(-1,     4))
-
-    def test_neg(self):
-        stmt = lambda y, a: y.eq(-a)
-        self.assertStatement(stmt, [C(0b0000, 4)], C(0b0000, 4))
-        self.assertStatement(stmt, [C(0b0001, 4)], C(0b1111, 4))
-        self.assertStatement(stmt, [C(0b1010, 4)], C(0b0110, 4))
-        self.assertStatement(stmt, [C(1,      4)], C(-1,     4))
-        self.assertStatement(stmt, [C(5,      4)], C(-5,     4))
-
-    def test_bool(self):
-        stmt = lambda y, a: y.eq(a.bool())
-        self.assertStatement(stmt, [C(0, 4)], C(0))
-        self.assertStatement(stmt, [C(1, 4)], C(1))
-        self.assertStatement(stmt, [C(2, 4)], C(1))
-
-    def test_as_unsigned(self):
-        stmt = lambda y, a, b: y.eq(a.as_unsigned() == b)
-        self.assertStatement(stmt, [C(0b01, signed(2)), C(0b0001, unsigned(4))], C(1))
-        self.assertStatement(stmt, [C(0b11, signed(2)), C(0b0011, unsigned(4))], C(1))
-
-    def test_as_signed(self):
-        stmt = lambda y, a, b: y.eq(a.as_signed() == b)
-        self.assertStatement(stmt, [C(0b01, unsigned(2)), C(0b0001, signed(4))], C(1))
-        self.assertStatement(stmt, [C(0b11, unsigned(2)), C(0b1111, signed(4))], C(1))
-
-    def test_any(self):
-        stmt = lambda y, a: y.eq(a.any())
-        self.assertStatement(stmt, [C(0b00, 2)], C(0))
-        self.assertStatement(stmt, [C(0b01, 2)], C(1))
-        self.assertStatement(stmt, [C(0b10, 2)], C(1))
-        self.assertStatement(stmt, [C(0b11, 2)], C(1))
-
-    def test_all(self):
-        stmt = lambda y, a: y.eq(a.all())
-        self.assertStatement(stmt, [C(0b00, 2)], C(0))
-        self.assertStatement(stmt, [C(0b01, 2)], C(0))
-        self.assertStatement(stmt, [C(0b10, 2)], C(0))
-        self.assertStatement(stmt, [C(0b11, 2)], C(1))
-
-    def test_xor_unary(self):
-        stmt = lambda y, a: y.eq(a.xor())
-        self.assertStatement(stmt, [C(0b00, 2)], C(0))
-        self.assertStatement(stmt, [C(0b01, 2)], C(1))
-        self.assertStatement(stmt, [C(0b10, 2)], C(1))
-        self.assertStatement(stmt, [C(0b11, 2)], C(0))
-
-    def test_add(self):
-        stmt = lambda y, a, b: y.eq(a + b)
-        self.assertStatement(stmt, [C(0,  4), C(1,  4)], C(1,   4))
-        self.assertStatement(stmt, [C(-5, 4), C(-5, 4)], C(-10, 5))
-
-    def test_sub(self):
-        stmt = lambda y, a, b: y.eq(a - b)
-        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(1,   4))
-        self.assertStatement(stmt, [C(0,  4), C(1,  4)], C(-1,  4))
-        self.assertStatement(stmt, [C(0,  4), C(10, 4)], C(-10, 5))
-
-    def test_mul(self):
-        stmt = lambda y, a, b: y.eq(a * b)
-        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(2,   8))
-        self.assertStatement(stmt, [C(2,  4), C(2,  4)], C(4,   8))
-        self.assertStatement(stmt, [C(7,  4), C(7,  4)], C(49,  8))
-
-    def test_floordiv(self):
-        stmt = lambda y, a, b: y.eq(a // b)
-        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(2,   8))
-        self.assertStatement(stmt, [C(2,  4), C(2,  4)], C(1,   8))
-        self.assertStatement(stmt, [C(7,  4), C(2,  4)], C(3,   8))
-
-    def test_mod(self):
-        stmt = lambda y, a, b: y.eq(a % b)
-        self.assertStatement(stmt, [C(2,  4), C(0,  4)], C(0,   8))
-        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(0,   8))
-        self.assertStatement(stmt, [C(2,  4), C(2,  4)], C(0,   8))
-        self.assertStatement(stmt, [C(7,  4), C(2,  4)], C(1,   8))
-
-    def test_and(self):
-        stmt = lambda y, a, b: y.eq(a & b)
-        self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1000, 4))
-
-    def test_or(self):
-        stmt = lambda y, a, b: y.eq(a | b)
-        self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1110, 4))
-
-    def test_xor_binary(self):
-        stmt = lambda y, a, b: y.eq(a ^ b)
-        self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b0110, 4))
-
-    def test_shl(self):
-        stmt = lambda y, a, b: y.eq(a << b)
-        self.assertStatement(stmt, [C(0b1001, 4), C(0)],  C(0b1001,    5))
-        self.assertStatement(stmt, [C(0b1001, 4), C(3)],  C(0b1001000, 7))
-
-    def test_shr(self):
-        stmt = lambda y, a, b: y.eq(a >> b)
-        self.assertStatement(stmt, [C(0b1001, 4), C(0)],  C(0b1001,    4))
-        self.assertStatement(stmt, [C(0b1001, 4), C(2)],  C(0b10,      4))
-
-    def test_eq(self):
-        stmt = lambda y, a, b: y.eq(a == b)
-        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1))
-        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0))
-        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0))
-
-    def test_ne(self):
-        stmt = lambda y, a, b: y.eq(a != b)
-        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0))
-        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1))
-        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1))
-
-    def test_lt(self):
-        stmt = lambda y, a, b: y.eq(a < b)
-        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0))
-        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1))
-        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0))
-
-    def test_ge(self):
-        stmt = lambda y, a, b: y.eq(a >= b)
-        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1))
-        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0))
-        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1))
-
-    def test_gt(self):
-        stmt = lambda y, a, b: y.eq(a > b)
-        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0))
-        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0))
-        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1))
-
-    def test_le(self):
-        stmt = lambda y, a, b: y.eq(a <= b)
-        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1))
-        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1))
-        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0))
-
-    def test_mux(self):
-        stmt = lambda y, a, b, c: y.eq(Mux(c, a, b))
-        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(0)], C(3, 4))
-        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(1)], C(2, 4))
-
-    def test_abs(self):
-        stmt = lambda y, a: y.eq(abs(a))
-        self.assertStatement(stmt, [C(3,  unsigned(8))], C(3,  unsigned(8)))
-        self.assertStatement(stmt, [C(-3, unsigned(8))], C(-3, unsigned(8)))
-        self.assertStatement(stmt, [C(3,  signed(8))],   C(3,  signed(8)))
-        self.assertStatement(stmt, [C(-3, signed(8))],   C(3,  signed(8)))
-
-    def test_slice(self):
-        stmt1 = lambda y, a: y.eq(a[2])
-        self.assertStatement(stmt1, [C(0b10110100, 8)], C(0b1,  1))
-        stmt2 = lambda y, a: y.eq(a[2:4])
-        self.assertStatement(stmt2, [C(0b10110100, 8)], C(0b01, 2))
-
-    def test_slice_lhs(self):
-        stmt1 = lambda y, a: y[2].eq(a)
-        self.assertStatement(stmt1, [C(0b0,  1)], C(0b11111011, 8), reset=0b11111111)
-        stmt2 = lambda y, a: y[2:4].eq(a)
-        self.assertStatement(stmt2, [C(0b01, 2)], C(0b11110111, 8), reset=0b11111011)
-
-    def test_bit_select(self):
-        stmt = lambda y, a, b: y.eq(a.bit_select(b, 3))
-        self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3))
-        self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b101, 3))
-        self.assertStatement(stmt, [C(0b10110100, 8), C(3)], C(0b110, 3))
-
-    def test_bit_select_lhs(self):
-        stmt = lambda y, a, b: y.bit_select(a, 3).eq(b)
-        self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111)
-        self.assertStatement(stmt, [C(2), C(0b101, 3)], C(0b11110111, 8), reset=0b11111111)
-        self.assertStatement(stmt, [C(3), C(0b110, 3)], C(0b11110111, 8), reset=0b11111111)
-
-    def test_word_select(self):
-        stmt = lambda y, a, b: y.eq(a.word_select(b, 3))
-        self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3))
-        self.assertStatement(stmt, [C(0b10110100, 8), C(1)], C(0b110, 3))
-        self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b010, 3))
-
-    def test_word_select_lhs(self):
-        stmt = lambda y, a, b: y.word_select(a, 3).eq(b)
-        self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111)
-        self.assertStatement(stmt, [C(1), C(0b101, 3)], C(0b11101111, 8), reset=0b11111111)
-        self.assertStatement(stmt, [C(2), C(0b110, 3)], C(0b10111111, 8), reset=0b11111111)
-
-    def test_cat(self):
-        stmt = lambda y, *xs: y.eq(Cat(*xs))
-        self.assertStatement(stmt, [C(0b10, 2), C(0b01, 2)], C(0b0110, 4))
-
-    def test_cat_lhs(self):
-        l = Signal(3)
-        m = Signal(3)
-        n = Signal(3)
-        stmt = lambda y, a: [Cat(l, m, n).eq(a), y.eq(Cat(n, m, l))]
-        self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9))
-
-    def test_nested_cat_lhs(self):
-        l = Signal(3)
-        m = Signal(3)
-        n = Signal(3)
-        stmt = lambda y, a: [Cat(Cat(l, Cat(m)), n).eq(a), y.eq(Cat(n, m, l))]
-        self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9))
-
-    def test_record(self):
-        rec = Record([
-            ("l", 1),
-            ("m", 2),
-        ])
-        stmt = lambda y, a: [rec.eq(a), y.eq(rec)]
-        self.assertStatement(stmt, [C(0b101, 3)], C(0b101, 3))
-
-    def test_repl(self):
-        stmt = lambda y, a: y.eq(Repl(a, 3))
-        self.assertStatement(stmt, [C(0b10, 2)], C(0b101010, 6))
-
-    def test_array(self):
-        array = Array([1, 4, 10])
-        stmt = lambda y, a: y.eq(array[a])
-        self.assertStatement(stmt, [C(0)], C(1))
-        self.assertStatement(stmt, [C(1)], C(4))
-        self.assertStatement(stmt, [C(2)], C(10))
-
-    def test_array_oob(self):
-        array = Array([1, 4, 10])
-        stmt = lambda y, a: y.eq(array[a])
-        self.assertStatement(stmt, [C(3)], C(10))
-        self.assertStatement(stmt, [C(4)], C(10))
-
-    def test_array_lhs(self):
-        l = Signal(3, reset=1)
-        m = Signal(3, reset=4)
-        n = Signal(3, reset=7)
-        array = Array([l, m, n])
-        stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))]
-        self.assertStatement(stmt, [C(0), C(0b000)], C(0b111100000))
-        self.assertStatement(stmt, [C(1), C(0b010)], C(0b111010001))
-        self.assertStatement(stmt, [C(2), C(0b100)], C(0b100100001))
-
-    def test_array_lhs_oob(self):
-        l = Signal(3)
-        m = Signal(3)
-        n = Signal(3)
-        array = Array([l, m, n])
-        stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))]
-        self.assertStatement(stmt, [C(3), C(0b001)], C(0b001000000))
-        self.assertStatement(stmt, [C(4), C(0b010)], C(0b010000000))
-
-    def test_array_index(self):
-        array = Array(Array(x * y for y in range(10)) for x in range(10))
-        stmt = lambda y, a, b: y.eq(array[a][b])
-        for x in range(10):
-            for y in range(10):
-                self.assertStatement(stmt, [C(x), C(y)], C(x * y))
-
-    def test_array_attr(self):
-        from collections import namedtuple
-        pair = namedtuple("pair", ("p", "n"))
-
-        array = Array(pair(x, -x) for x in range(10))
-        stmt = lambda y, a: y.eq(array[a].p + array[a].n)
-        for i in range(10):
-            self.assertStatement(stmt, [C(i)], C(0))
-
-    def test_shift_left(self):
-        stmt1 = lambda y, a: y.eq(a.shift_left(1))
-        self.assertStatement(stmt1, [C(0b10100010, 8)], C(   0b101000100, 9))
-        stmt2 = lambda y, a: y.eq(a.shift_left(4))
-        self.assertStatement(stmt2, [C(0b10100010, 8)], C(0b101000100000, 12))
-
-    def test_shift_right(self):
-        stmt1 = lambda y, a: y.eq(a.shift_right(1))
-        self.assertStatement(stmt1, [C(0b10100010, 8)], C(0b1010001, 7))
-        stmt2 = lambda y, a: y.eq(a.shift_right(4))
-        self.assertStatement(stmt2, [C(0b10100010, 8)], C(   0b1010, 4))
-
-    def test_rotate_left(self):
-        stmt = lambda y, a: y.eq(a.rotate_left(1))
-        self.assertStatement(stmt, [C(0b1)], C(0b1))
-        self.assertStatement(stmt, [C(0b1001000)], C(0b0010001))
-        stmt = lambda y, a: y.eq(a.rotate_left(5))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
-        stmt = lambda y, a: y.eq(a.rotate_left(7))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
-        stmt = lambda y, a: y.eq(a.rotate_left(9))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
-        stmt = lambda y, a: y.eq(a.rotate_left(-1))
-        self.assertStatement(stmt, [C(0b1)], C(0b1))
-        self.assertStatement(stmt, [C(0b1001000)], C(0b0100100))
-        stmt = lambda y, a: y.eq(a.rotate_left(-5))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
-        stmt = lambda y, a: y.eq(a.rotate_left(-7))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
-        stmt = lambda y, a: y.eq(a.rotate_left(-9))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
-
-    def test_rotate_right(self):
-        stmt = lambda y, a: y.eq(a.rotate_right(1))
-        self.assertStatement(stmt, [C(0b1)], C(0b1))
-        self.assertStatement(stmt, [C(0b1001000)], C(0b0100100))
-        stmt = lambda y, a: y.eq(a.rotate_right(5))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
-        stmt = lambda y, a: y.eq(a.rotate_right(7))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
-        stmt = lambda y, a: y.eq(a.rotate_right(9))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
-        stmt = lambda y, a: y.eq(a.rotate_right(-1))
-        self.assertStatement(stmt, [C(0b1)], C(0b1))
-        self.assertStatement(stmt, [C(0b1001000)], C(0b0010001))
-        stmt = lambda y, a: y.eq(a.rotate_right(-5))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
-        stmt = lambda y, a: y.eq(a.rotate_right(-7))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
-        stmt = lambda y, a: y.eq(a.rotate_right(-9))
-        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
-        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
-
-
-class SimulatorIntegrationTestCase(FHDLTestCase):
-    @contextmanager
-    def assertSimulation(self, module, deadline=None):
-        sim = Simulator(module)
-        yield sim
-        with sim.write_vcd("test.vcd", "test.gtkw"):
-            if deadline is None:
-                sim.run()
-            else:
-                sim.run_until(deadline)
-
-    def setUp_counter(self):
-        self.count = Signal(3, reset=4)
-        self.sync  = ClockDomain()
-
-        self.m = Module()
-        self.m.d.sync  += self.count.eq(self.count + 1)
-        self.m.domains += self.sync
-
-    def test_counter_process(self):
-        self.setUp_counter()
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                self.assertEqual((yield self.count), 4)
-                yield Delay(1e-6)
-                self.assertEqual((yield self.count), 4)
-                yield self.sync.clk.eq(1)
-                self.assertEqual((yield self.count), 4)
-                yield Settle()
-                self.assertEqual((yield self.count), 5)
-                yield Delay(1e-6)
-                self.assertEqual((yield self.count), 5)
-                yield self.sync.clk.eq(0)
-                self.assertEqual((yield self.count), 5)
-                yield Settle()
-                self.assertEqual((yield self.count), 5)
-                for _ in range(3):
-                    yield Delay(1e-6)
-                    yield self.sync.clk.eq(1)
-                    yield Delay(1e-6)
-                    yield self.sync.clk.eq(0)
-                self.assertEqual((yield self.count), 0)
-            sim.add_process(process)
-
-    def test_counter_clock_and_sync_process(self):
-        self.setUp_counter()
-        with self.assertSimulation(self.m) as sim:
-            sim.add_clock(1e-6, domain="sync")
-            def process():
-                self.assertEqual((yield self.count), 4)
-                self.assertEqual((yield self.sync.clk), 1)
-                yield
-                self.assertEqual((yield self.count), 5)
-                self.assertEqual((yield self.sync.clk), 1)
-                for _ in range(3):
-                    yield
-                self.assertEqual((yield self.count), 0)
-            sim.add_sync_process(process)
-
-    def test_reset(self):
-        self.setUp_counter()
-        sim = Simulator(self.m)
-        sim.add_clock(1e-6)
-        times = 0
-        def process():
-            nonlocal times
-            self.assertEqual((yield self.count), 4)
-            yield
-            self.assertEqual((yield self.count), 5)
-            yield
-            self.assertEqual((yield self.count), 6)
-            yield
-            times += 1
-        sim.add_sync_process(process)
-        sim.run()
-        sim.reset()
-        sim.run()
-        self.assertEqual(times, 2)
-
-    def setUp_alu(self):
-        self.a = Signal(8)
-        self.b = Signal(8)
-        self.o = Signal(8)
-        self.x = Signal(8)
-        self.s = Signal(2)
-        self.sync = ClockDomain(reset_less=True)
-
-        self.m = Module()
-        self.m.d.comb += self.x.eq(self.a ^ self.b)
-        with self.m.Switch(self.s):
-            with self.m.Case(0):
-                self.m.d.sync += self.o.eq(self.a + self.b)
-            with self.m.Case(1):
-                self.m.d.sync += self.o.eq(self.a - self.b)
-            with self.m.Case():
-                self.m.d.sync += self.o.eq(0)
-        self.m.domains += self.sync
-
-    def test_alu(self):
-        self.setUp_alu()
-        with self.assertSimulation(self.m) as sim:
-            sim.add_clock(1e-6)
-            def process():
-                yield self.a.eq(5)
-                yield self.b.eq(1)
-                yield
-                self.assertEqual((yield self.x), 4)
-                yield
-                self.assertEqual((yield self.o), 6)
-                yield self.s.eq(1)
-                yield
-                yield
-                self.assertEqual((yield self.o), 4)
-                yield self.s.eq(2)
-                yield
-                yield
-                self.assertEqual((yield self.o), 0)
-            sim.add_sync_process(process)
-
-    def setUp_multiclock(self):
-        self.sys = ClockDomain()
-        self.pix = ClockDomain()
-
-        self.m = Module()
-        self.m.domains += self.sys, self.pix
-
-    def test_multiclock(self):
-        self.setUp_multiclock()
-        with self.assertSimulation(self.m) as sim:
-            sim.add_clock(1e-6, domain="sys")
-            sim.add_clock(0.3e-6, domain="pix")
-
-            def sys_process():
-                yield Passive()
-                yield
-                yield
-                self.fail()
-            def pix_process():
-                yield
-                yield
-                yield
-            sim.add_sync_process(sys_process, domain="sys")
-            sim.add_sync_process(pix_process, domain="pix")
-
-    def setUp_lhs_rhs(self):
-        self.i = Signal(8)
-        self.o = Signal(8)
-
-        self.m = Module()
-        self.m.d.comb += self.o.eq(self.i)
-
-    def test_complex_lhs_rhs(self):
-        self.setUp_lhs_rhs()
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                yield self.i.eq(0b10101010)
-                yield self.i[:4].eq(-1)
-                yield Settle()
-                self.assertEqual((yield self.i[:4]), 0b1111)
-                self.assertEqual((yield self.i), 0b10101111)
-            sim.add_process(process)
-
-    def test_run_until(self):
-        m = Module()
-        s = Signal()
-        m.d.sync += s.eq(0)
-        with self.assertSimulation(m, deadline=100e-6) as sim:
-            sim.add_clock(1e-6)
-            def process():
-                for _ in range(101):
-                    yield Delay(1e-6)
-                self.fail()
-            sim.add_process(process)
-
-    def test_add_process_wrong(self):
-        with self.assertSimulation(Module()) as sim:
-            with self.assertRaisesRegex(TypeError,
-                    r"^Cannot add a process 1 because it is not a generator function$"):
-                sim.add_process(1)
-
-    def test_add_process_wrong_generator(self):
-        with self.assertSimulation(Module()) as sim:
-            with self.assertRaisesRegex(TypeError,
-                    r"^Cannot add a process <.+?> because it is not a generator function$"):
-                def process():
-                    yield Delay()
-                sim.add_process(process())
-
-    def test_add_clock_wrong_twice(self):
-        m = Module()
-        s = Signal()
-        m.d.sync += s.eq(0)
-        with self.assertSimulation(m) as sim:
-            sim.add_clock(1)
-            with self.assertRaisesRegex(ValueError,
-                    r"^Domain 'sync' already has a clock driving it$"):
-                sim.add_clock(1)
-
-    def test_add_clock_wrong_missing(self):
-        m = Module()
-        with self.assertSimulation(m) as sim:
-            with self.assertRaisesRegex(ValueError,
-                    r"^Domain 'sync' is not present in simulation$"):
-                sim.add_clock(1)
-
-    def test_add_clock_if_exists(self):
-        m = Module()
-        with self.assertSimulation(m) as sim:
-            sim.add_clock(1, if_exists=True)
-
-    def test_command_wrong(self):
-        survived = False
-        with self.assertSimulation(Module()) as sim:
-            def process():
-                nonlocal survived
-                with self.assertRaisesRegex(TypeError,
-                        r"Received unsupported command 1 from process .+?"):
-                    yield 1
-                yield Settle()
-                survived = True
-            sim.add_process(process)
-        self.assertTrue(survived)
-
-    def setUp_memory(self, rd_synchronous=True, rd_transparent=True, wr_granularity=None):
-        self.m = Module()
-        self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55])
-        self.m.submodules.rdport = self.rdport = \
-            self.memory.read_port(domain="sync" if rd_synchronous else "comb",
-                                  transparent=rd_transparent)
-        self.m.submodules.wrport = self.wrport = \
-            self.memory.write_port(granularity=wr_granularity)
-
-    def test_memory_init(self):
-        self.setUp_memory()
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                self.assertEqual((yield self.rdport.data), 0xaa)
-                yield self.rdport.addr.eq(1)
-                yield
-                yield
-                self.assertEqual((yield self.rdport.data), 0x55)
-                yield self.rdport.addr.eq(2)
-                yield
-                yield
-                self.assertEqual((yield self.rdport.data), 0x00)
-            sim.add_clock(1e-6)
-            sim.add_sync_process(process)
-
-    def test_memory_write(self):
-        self.setUp_memory()
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                yield self.wrport.addr.eq(4)
-                yield self.wrport.data.eq(0x33)
-                yield self.wrport.en.eq(1)
-                yield
-                yield self.wrport.en.eq(0)
-                yield self.rdport.addr.eq(4)
-                yield
-                self.assertEqual((yield self.rdport.data), 0x33)
-            sim.add_clock(1e-6)
-            sim.add_sync_process(process)
-
-    def test_memory_write_granularity(self):
-        self.setUp_memory(wr_granularity=4)
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                yield self.wrport.data.eq(0x50)
-                yield self.wrport.en.eq(0b00)
-                yield
-                yield self.wrport.en.eq(0)
-                yield
-                self.assertEqual((yield self.rdport.data), 0xaa)
-                yield self.wrport.en.eq(0b10)
-                yield
-                yield self.wrport.en.eq(0)
-                yield
-                self.assertEqual((yield self.rdport.data), 0x5a)
-                yield self.wrport.data.eq(0x33)
-                yield self.wrport.en.eq(0b01)
-                yield
-                yield self.wrport.en.eq(0)
-                yield
-                self.assertEqual((yield self.rdport.data), 0x53)
-            sim.add_clock(1e-6)
-            sim.add_sync_process(process)
-
-    def test_memory_read_before_write(self):
-        self.setUp_memory(rd_transparent=False)
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                yield self.wrport.data.eq(0x33)
-                yield self.wrport.en.eq(1)
-                yield
-                self.assertEqual((yield self.rdport.data), 0xaa)
-                yield
-                self.assertEqual((yield self.rdport.data), 0xaa)
-                yield Settle()
-                self.assertEqual((yield self.rdport.data), 0x33)
-            sim.add_clock(1e-6)
-            sim.add_sync_process(process)
-
-    def test_memory_write_through(self):
-        self.setUp_memory(rd_transparent=True)
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                yield self.wrport.data.eq(0x33)
-                yield self.wrport.en.eq(1)
-                yield
-                self.assertEqual((yield self.rdport.data), 0xaa)
-                yield Settle()
-                self.assertEqual((yield self.rdport.data), 0x33)
-                yield
-                yield self.rdport.addr.eq(1)
-                yield Settle()
-                self.assertEqual((yield self.rdport.data), 0x33)
-            sim.add_clock(1e-6)
-            sim.add_sync_process(process)
-
-    def test_memory_async_read_write(self):
-        self.setUp_memory(rd_synchronous=False)
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                yield self.rdport.addr.eq(0)
-                yield Settle()
-                self.assertEqual((yield self.rdport.data), 0xaa)
-                yield self.rdport.addr.eq(1)
-                yield Settle()
-                self.assertEqual((yield self.rdport.data), 0x55)
-                yield self.rdport.addr.eq(0)
-                yield self.wrport.addr.eq(0)
-                yield self.wrport.data.eq(0x33)
-                yield self.wrport.en.eq(1)
-                yield Tick("sync")
-                self.assertEqual((yield self.rdport.data), 0xaa)
-                yield Settle()
-                self.assertEqual((yield self.rdport.data), 0x33)
-            sim.add_clock(1e-6)
-            sim.add_process(process)
-
-    def test_memory_read_only(self):
-        self.m = Module()
-        self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55])
-        self.m.submodules.rdport = self.rdport = self.memory.read_port()
-        with self.assertSimulation(self.m) as sim:
-            def process():
-                self.assertEqual((yield self.rdport.data), 0xaa)
-                yield self.rdport.addr.eq(1)
-                yield
-                yield
-                self.assertEqual((yield self.rdport.data), 0x55)
-            sim.add_clock(1e-6)
-            sim.add_sync_process(process)
-
-    def test_sample_helpers(self):
-        m = Module()
-        s = Signal(2)
-        def mk(x):
-            y = Signal.like(x)
-            m.d.comb += y.eq(x)
-            return y
-        p0, r0, f0, s0 = mk(Past(s, 0)), mk(Rose(s)),    mk(Fell(s)),    mk(Stable(s))
-        p1, r1, f1, s1 = mk(Past(s)),    mk(Rose(s, 1)), mk(Fell(s, 1)), mk(Stable(s, 1))
-        p2, r2, f2, s2 = mk(Past(s, 2)), mk(Rose(s, 2)), mk(Fell(s, 2)), mk(Stable(s, 2))
-        p3, r3, f3, s3 = mk(Past(s, 3)), mk(Rose(s, 3)), mk(Fell(s, 3)), mk(Stable(s, 3))
-        with self.assertSimulation(m) as sim:
-            def process_gen():
-                yield s.eq(0b10)
-                yield
-                yield
-                yield s.eq(0b01)
-                yield
-            def process_check():
-                yield
-                yield
-                yield
-
-                self.assertEqual((yield p0), 0b01)
-                self.assertEqual((yield p1), 0b10)
-                self.assertEqual((yield p2), 0b10)
-                self.assertEqual((yield p3), 0b00)
-
-                self.assertEqual((yield s0), 0b0)
-                self.assertEqual((yield s1), 0b1)
-                self.assertEqual((yield s2), 0b0)
-                self.assertEqual((yield s3), 0b1)
-
-                self.assertEqual((yield r0), 0b01)
-                self.assertEqual((yield r1), 0b00)
-                self.assertEqual((yield r2), 0b10)
-                self.assertEqual((yield r3), 0b00)
-
-                self.assertEqual((yield f0), 0b10)
-                self.assertEqual((yield f1), 0b00)
-                self.assertEqual((yield f2), 0b00)
-                self.assertEqual((yield f3), 0b00)
-            sim.add_clock(1e-6)
-            sim.add_sync_process(process_gen)
-            sim.add_sync_process(process_check)
-
-    def test_vcd_wrong_nonzero_time(self):
-        s = Signal()
-        m = Module()
-        m.d.sync += s.eq(s)
-        sim = Simulator(m)
-        sim.add_clock(1e-6)
-        sim.run_until(1e-5)
-        with self.assertRaisesRegex(ValueError,
-                r"^Cannot start writing waveforms after advancing simulation time$"):
-            with sim.write_vcd(open(os.path.devnull, "wt")):
-                pass
-
-
-class SimulatorRegressionTestCase(FHDLTestCase):
-    def test_bug_325(self):
-        dut = Module()
-        dut.d.comb += Signal().eq(Cat())
-        Simulator(dut).run()
-
-    def test_bug_325_bis(self):
-        dut = Module()
-        dut.d.comb += Signal().eq(Repl(Const(1), 0))
-        Simulator(dut).run()
-
-    def test_bug_473(self):
-        sim = Simulator(Module())
-        def process():
-            self.assertEqual((yield -(Const(0b11, 2).as_signed())), 1)
-        sim.add_process(process)
-        sim.run()
diff --git a/nmigen/test/utils.py b/nmigen/test/utils.py
deleted file mode 100644 (file)
index 3c88403..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-import os
-import re
-import shutil
-import subprocess
-import textwrap
-import traceback
-import unittest
-import warnings
-from contextlib import contextmanager
-
-from ..hdl.ast import *
-from ..hdl.ir import *
-from ..back import rtlil
-from .._toolchain import require_tool
-
-
-__all__ = ["FHDLTestCase"]
-
-
-class FHDLTestCase(unittest.TestCase):
-    def assertRepr(self, obj, repr_str):
-        if isinstance(obj, list):
-            obj = Statement.cast(obj)
-        def prepare_repr(repr_str):
-            repr_str = re.sub(r"\s+",   " ",  repr_str)
-            repr_str = re.sub(r"\( (?=\()", "(", repr_str)
-            repr_str = re.sub(r"\) (?=\))", ")", repr_str)
-            return repr_str.strip()
-        self.assertEqual(prepare_repr(repr(obj)), prepare_repr(repr_str))
-
-    def assertFormal(self, spec, mode="bmc", depth=1):
-        caller, *_ = traceback.extract_stack(limit=2)
-        spec_root, _ = os.path.splitext(caller.filename)
-        spec_dir = os.path.dirname(spec_root)
-        spec_name = "{}_{}".format(
-            os.path.basename(spec_root).replace("test_", "spec_"),
-            caller.name.replace("test_", "")
-        )
-
-        # The sby -f switch seems not fully functional when sby is reading from stdin.
-        if os.path.exists(os.path.join(spec_dir, spec_name)):
-            shutil.rmtree(os.path.join(spec_dir, spec_name))
-
-        if mode == "hybrid":
-            # A mix of BMC and k-induction, as per personal communication with Clifford Wolf.
-            script = "setattr -unset init w:* a:nmigen.sample_reg %d"
-            mode   = "bmc"
-        else:
-            script = ""
-
-        config = textwrap.dedent("""\
-        [options]
-        mode {mode}
-        depth {depth}
-        wait on
-
-        [engines]
-        smtbmc
-
-        [script]
-        read_ilang top.il
-        prep
-        {script}
-
-        [file top.il]
-        {rtlil}
-        """).format(
-            mode=mode,
-            depth=depth,
-            script=script,
-            rtlil=rtlil.convert(Fragment.get(spec, platform="formal"))
-        )
-        with subprocess.Popen([require_tool("sby"), "-f", "-d", spec_name], cwd=spec_dir,
-                              universal_newlines=True,
-                              stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
-            stdout, stderr = proc.communicate(config)
-            if proc.returncode != 0:
-                self.fail("Formal verification failed:\n" + stdout)
index d08cddd6c953f9b6751e5403b54187fa341eb4d0..69b3d04d39f71bfa8a5f42386ca8583192c23749 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -48,7 +48,7 @@ setup(
         "builtin-yosys": ["nmigen-yosys>=0.9.*"],
         "remote-build": ["paramiko~=2.7"],
     },
-    packages=find_packages(exclude=["*.test*"]),
+    packages=find_packages(),
     entry_points={
         "console_scripts": [
             "nmigen-rpc = nmigen.rpc:main",
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/compat/__init__.py b/tests/compat/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/compat/support.py b/tests/compat/support.py
new file mode 100644 (file)
index 0000000..05923f8
--- /dev/null
@@ -0,0 +1,16 @@
+from nmigen.compat import *
+from nmigen.compat.fhdl import verilog
+from nmigen._utils import _ignore_deprecated
+
+
+class SimCase:
+    def setUp(self, *args, **kwargs):
+        with _ignore_deprecated():
+            self.tb = self.TestBench(*args, **kwargs)
+
+    def test_to_verilog(self):
+        verilog.convert(self.tb)
+
+    def run_with(self, generator):
+        with _ignore_deprecated():
+            run_simulation(self.tb, generator)
diff --git a/tests/compat/test_coding.py b/tests/compat/test_coding.py
new file mode 100644 (file)
index 0000000..a65dea6
--- /dev/null
@@ -0,0 +1,116 @@
+# nmigen: UnusedElaboratable=no
+
+import unittest
+
+from nmigen.compat import *
+from nmigen.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/tests/compat/test_constant.py b/tests/compat/test_constant.py
new file mode 100644 (file)
index 0000000..238c723
--- /dev/null
@@ -0,0 +1,30 @@
+import unittest
+
+from nmigen.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/tests/compat/test_fifo.py b/tests/compat/test_fifo.py
new file mode 100644 (file)
index 0000000..6cff7dc
--- /dev/null
@@ -0,0 +1,38 @@
+import unittest
+from itertools import count
+
+from nmigen.compat import *
+from nmigen.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())
diff --git a/tests/compat/test_fsm.py b/tests/compat/test_fsm.py
new file mode 100644 (file)
index 0000000..8fbf47b
--- /dev/null
@@ -0,0 +1,87 @@
+import unittest
+from itertools import count
+
+from nmigen.compat import *
+from nmigen.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/tests/compat/test_passive.py b/tests/compat/test_passive.py
new file mode 100644 (file)
index 0000000..c030f70
--- /dev/null
@@ -0,0 +1,23 @@
+import unittest
+
+from nmigen.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/tests/compat/test_run_simulation.py b/tests/compat/test_run_simulation.py
new file mode 100644 (file)
index 0000000..0857e75
--- /dev/null
@@ -0,0 +1,28 @@
+import unittest
+
+from nmigen import Signal, Module, Elaboratable
+
+from .support import SimCase
+
+
+class RunSimulation(SimCase, unittest.TestCase):
+    """ test for https://github.com/nmigen/nmigen/issues/344 """
+
+    class TestBench(Elaboratable):
+        def __init__(self):
+            self.a = Signal()
+
+        def elaborate(self, platform):
+            m = Module()
+            m.d.sync += self.a.eq(~self.a)
+            return m
+
+    def test_run_simulation(self):
+        def gen():
+            yield
+            for i in range(10):
+                yield
+                a = (yield self.tb.a)
+                self.assertEqual(a, i % 2)
+
+        self.run_with(gen())
diff --git a/tests/compat/test_signed.py b/tests/compat/test_signed.py
new file mode 100644 (file)
index 0000000..f0a8c61
--- /dev/null
@@ -0,0 +1,43 @@
+import unittest
+
+from nmigen.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/tests/compat/test_size.py b/tests/compat/test_size.py
new file mode 100644 (file)
index 0000000..8211aa9
--- /dev/null
@@ -0,0 +1,21 @@
+import unittest
+
+from nmigen._utils import _ignore_deprecated
+from nmigen.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)
+        with _ignore_deprecated():
+            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)
diff --git a/tests/test_build_dsl.py b/tests/test_build_dsl.py
new file mode 100644 (file)
index 0000000..76cd2e9
--- /dev/null
@@ -0,0 +1,329 @@
+from collections import OrderedDict
+
+from nmigen.build.dsl import *
+
+from .utils import *
+
+
+class PinsTestCase(FHDLTestCase):
+    def test_basic(self):
+        p = Pins("A0 A1 A2")
+        self.assertEqual(repr(p), "(pins io A0 A1 A2)")
+        self.assertEqual(len(p.names), 3)
+        self.assertEqual(p.dir, "io")
+        self.assertEqual(p.invert, False)
+        self.assertEqual(list(p), ["A0", "A1", "A2"])
+
+    def test_invert(self):
+        p = PinsN("A0")
+        self.assertEqual(repr(p), "(pins-n io A0)")
+        self.assertEqual(p.invert, True)
+
+    def test_invert_arg(self):
+        p = Pins("A0", invert=True)
+        self.assertEqual(p.invert, True)
+
+    def test_conn(self):
+        p = Pins("0 1 2", conn=("pmod", 0))
+        self.assertEqual(list(p), ["pmod_0:0", "pmod_0:1", "pmod_0:2"])
+        p = Pins("0 1 2", conn=("pmod", "a"))
+        self.assertEqual(list(p), ["pmod_a:0", "pmod_a:1", "pmod_a:2"])
+
+    def test_map_names(self):
+        p = Pins("0 1 2", conn=("pmod", 0))
+        mapping = {
+            "pmod_0:0": "A0",
+            "pmod_0:1": "A1",
+            "pmod_0:2": "A2",
+        }
+        self.assertEqual(p.map_names(mapping, p), ["A0", "A1", "A2"])
+
+    def test_map_names_recur(self):
+        p = Pins("0", conn=("pmod", 0))
+        mapping = {
+            "pmod_0:0": "ext_0:1",
+            "ext_0:1":  "A1",
+        }
+        self.assertEqual(p.map_names(mapping, p), ["A1"])
+
+    def test_wrong_names(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Names must be a whitespace-separated string, not \['A0', 'A1', 'A2'\]$"):
+            p = Pins(["A0", "A1", "A2"])
+
+    def test_wrong_dir(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not 'wrong'$"):
+            p = Pins("A0 A1", dir="wrong")
+
+    def test_wrong_conn(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Connector must be None or a pair of string \(connector name\) and "
+                    r"integer\/string \(connector number\), not \('foo', None\)$")):
+            p = Pins("A0 A1", conn=("foo", None))
+
+    def test_wrong_map_names(self):
+        p = Pins("0 1 2", conn=("pmod", 0))
+        mapping = {
+            "pmod_0:0": "A0",
+        }
+        with self.assertRaisesRegex(NameError,
+                (r"^Resource \(pins io pmod_0:0 pmod_0:1 pmod_0:2\) refers to nonexistent "
+                    r"connector pin pmod_0:1$")):
+            p.map_names(mapping, p)
+
+    def test_wrong_assert_width(self):
+        with self.assertRaisesRegex(AssertionError,
+                r"^3 names are specified \(0 1 2\), but 4 names are expected$"):
+            Pins("0 1 2", assert_width=4)
+
+
+class DiffPairsTestCase(FHDLTestCase):
+    def test_basic(self):
+        dp = DiffPairs(p="A0 A1", n="B0 B1")
+        self.assertEqual(repr(dp), "(diffpairs io (p A0 A1) (n B0 B1))")
+        self.assertEqual(dp.p.names, ["A0", "A1"])
+        self.assertEqual(dp.n.names, ["B0", "B1"])
+        self.assertEqual(dp.dir, "io")
+        self.assertEqual(list(dp), [("A0", "B0"), ("A1", "B1")])
+
+    def test_invert(self):
+        dp = DiffPairsN(p="A0", n="B0")
+        self.assertEqual(repr(dp), "(diffpairs-n io (p A0) (n B0))")
+        self.assertEqual(dp.p.names, ["A0"])
+        self.assertEqual(dp.n.names, ["B0"])
+        self.assertEqual(dp.invert, True)
+
+    def test_conn(self):
+        dp = DiffPairs(p="0 1 2", n="3 4 5", conn=("pmod", 0))
+        self.assertEqual(list(dp), [
+            ("pmod_0:0", "pmod_0:3"),
+            ("pmod_0:1", "pmod_0:4"),
+            ("pmod_0:2", "pmod_0:5"),
+        ])
+
+    def test_dir(self):
+        dp = DiffPairs("A0", "B0", dir="o")
+        self.assertEqual(dp.dir, "o")
+        self.assertEqual(dp.p.dir, "o")
+        self.assertEqual(dp.n.dir, "o")
+
+    def test_wrong_width(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Positive and negative pins must have the same width, but \(pins io A0\) "
+                    r"and \(pins io B0 B1\) do not$")):
+            dp = DiffPairs("A0", "B0 B1")
+
+    def test_wrong_assert_width(self):
+        with self.assertRaisesRegex(AssertionError,
+                r"^3 names are specified \(0 1 2\), but 4 names are expected$"):
+            DiffPairs("0 1 2", "3 4 5", assert_width=4)
+
+
+class AttrsTestCase(FHDLTestCase):
+    def test_basic(self):
+        a = Attrs(IO_STANDARD="LVCMOS33", PULLUP=1)
+        self.assertEqual(a["IO_STANDARD"], "LVCMOS33")
+        self.assertEqual(repr(a), "(attrs IO_STANDARD='LVCMOS33' PULLUP=1)")
+
+    def test_remove(self):
+        a = Attrs(FOO=None)
+        self.assertEqual(a["FOO"], None)
+        self.assertEqual(repr(a), "(attrs !FOO)")
+
+    def test_callable(self):
+        fn = lambda self: "FOO"
+        a = Attrs(FOO=fn)
+        self.assertEqual(a["FOO"], fn)
+        self.assertEqual(repr(a), "(attrs FOO={!r})".format(fn))
+
+    def test_wrong_value(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Value of attribute FOO must be None, int, str, or callable, not 1\.0$"):
+            a = Attrs(FOO=1.0)
+
+
+class ClockTestCase(FHDLTestCase):
+    def test_basic(self):
+        c = Clock(1_000_000)
+        self.assertEqual(c.frequency, 1e6)
+        self.assertEqual(c.period, 1e-6)
+        self.assertEqual(repr(c), "(clock 1000000.0)")
+
+
+class SubsignalTestCase(FHDLTestCase):
+    def test_basic_pins(self):
+        s = Subsignal("a", Pins("A0"), Attrs(IOSTANDARD="LVCMOS33"))
+        self.assertEqual(repr(s),
+            "(subsignal a (pins io A0) (attrs IOSTANDARD='LVCMOS33'))")
+
+    def test_basic_diffpairs(self):
+        s = Subsignal("a", DiffPairs("A0", "B0"))
+        self.assertEqual(repr(s),
+            "(subsignal a (diffpairs io (p A0) (n B0)))")
+
+    def test_basic_subsignals(self):
+        s = Subsignal("a",
+                Subsignal("b", Pins("A0")),
+                Subsignal("c", Pins("A1")))
+        self.assertEqual(repr(s),
+            "(subsignal a (subsignal b (pins io A0)) "
+                         "(subsignal c (pins io A1)))")
+
+    def test_attrs(self):
+        s = Subsignal("a",
+                Subsignal("b", Pins("A0")),
+                Subsignal("c", Pins("A0"), Attrs(SLEW="FAST")),
+                Attrs(IOSTANDARD="LVCMOS33"))
+        self.assertEqual(s.attrs, {"IOSTANDARD": "LVCMOS33"})
+        self.assertEqual(s.ios[0].attrs, {})
+        self.assertEqual(s.ios[1].attrs, {"SLEW": "FAST"})
+
+    def test_attrs_many(self):
+        s = Subsignal("a", Pins("A0"), Attrs(SLEW="FAST"), Attrs(PULLUP="1"))
+        self.assertEqual(s.attrs, {"SLEW": "FAST", "PULLUP": "1"})
+
+    def test_clock(self):
+        s = Subsignal("a", Pins("A0"), Clock(1e6))
+        self.assertEqual(s.clock.frequency, 1e6)
+
+    def test_wrong_empty_io(self):
+        with self.assertRaisesRegex(ValueError, r"^Missing I\/O constraints$"):
+            s = Subsignal("a")
+
+    def test_wrong_io(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Constraint must be one of Pins, DiffPairs, Subsignal, Attrs, or Clock, "
+                    r"not 'wrong'$")):
+            s = Subsignal("a", "wrong")
+
+    def test_wrong_pins(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Pins and DiffPairs are incompatible with other location or subsignal "
+                    r"constraints, but \(pins io A1\) appears after \(pins io A0\)$")):
+            s = Subsignal("a", Pins("A0"), Pins("A1"))
+
+    def test_wrong_diffpairs(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Pins and DiffPairs are incompatible with other location or subsignal "
+                    r"constraints, but \(pins io A1\) appears after \(diffpairs io \(p A0\) \(n B0\)\)$")):
+            s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1"))
+
+    def test_wrong_subsignals(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Pins and DiffPairs are incompatible with other location or subsignal "
+                    r"constraints, but \(pins io B0\) appears after \(subsignal b \(pins io A0\)\)$")):
+            s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0"))
+
+    def test_wrong_clock(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Clock constraint can only be applied to Pins or DiffPairs, not "
+                    r"\(subsignal b \(pins io A0\)\)$")):
+            s = Subsignal("a", Subsignal("b", Pins("A0")), Clock(1e6))
+
+    def test_wrong_clock_many(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Clock constraint can be applied only once$"):
+            s = Subsignal("a", Pins("A0"), Clock(1e6), Clock(1e7))
+
+
+class ResourceTestCase(FHDLTestCase):
+    def test_basic(self):
+        r = Resource("serial", 0,
+                Subsignal("tx", Pins("A0", dir="o")),
+                Subsignal("rx", Pins("A1", dir="i")),
+                Attrs(IOSTANDARD="LVCMOS33"))
+        self.assertEqual(repr(r), "(resource serial 0"
+                                  " (subsignal tx (pins o A0))"
+                                  " (subsignal rx (pins i A1))"
+                                  " (attrs IOSTANDARD='LVCMOS33'))")
+
+    def test_family(self):
+        ios = [Subsignal("clk", Pins("A0", dir="o"))]
+        r1  = Resource.family(0, default_name="spi", ios=ios)
+        r2  = Resource.family("spi_flash", 0, default_name="spi", ios=ios)
+        r3  = Resource.family("spi_flash", 0, default_name="spi", ios=ios, name_suffix="4x")
+        r4  = Resource.family(0, default_name="spi", ios=ios, name_suffix="2x")
+        self.assertEqual(r1.name, "spi")
+        self.assertEqual(r1.ios, ios)
+        self.assertEqual(r2.name, "spi_flash")
+        self.assertEqual(r2.ios, ios)
+        self.assertEqual(r3.name, "spi_flash_4x")
+        self.assertEqual(r3.ios, ios)
+        self.assertEqual(r4.name, "spi_2x")
+        self.assertEqual(r4.ios, ios)
+
+
+class ConnectorTestCase(FHDLTestCase):
+    def test_string(self):
+        c = Connector("pmod", 0, "A0 A1 A2 A3 - - A4 A5 A6 A7 - -")
+        self.assertEqual(c.name, "pmod")
+        self.assertEqual(c.number, 0)
+        self.assertEqual(c.mapping, OrderedDict([
+            ("1", "A0"),
+            ("2", "A1"),
+            ("3", "A2"),
+            ("4", "A3"),
+            ("7", "A4"),
+            ("8", "A5"),
+            ("9", "A6"),
+            ("10", "A7"),
+        ]))
+        self.assertEqual(list(c), [
+            ("pmod_0:1", "A0"),
+            ("pmod_0:2", "A1"),
+            ("pmod_0:3", "A2"),
+            ("pmod_0:4", "A3"),
+            ("pmod_0:7", "A4"),
+            ("pmod_0:8", "A5"),
+            ("pmod_0:9", "A6"),
+            ("pmod_0:10", "A7"),
+        ])
+        self.assertEqual(repr(c),
+            "(connector pmod 0 1=>A0 2=>A1 3=>A2 4=>A3 7=>A4 8=>A5 9=>A6 10=>A7)")
+
+    def test_dict(self):
+        c = Connector("ext", 1, {"DP0": "A0", "DP1": "A1"})
+        self.assertEqual(c.name, "ext")
+        self.assertEqual(c.number, 1)
+        self.assertEqual(c.mapping, OrderedDict([
+            ("DP0", "A0"),
+            ("DP1", "A1"),
+        ]))
+
+    def test_conn(self):
+        c = Connector("pmod", 0, "0 1 2 3 - - 4 5 6 7 - -", conn=("expansion", 0))
+        self.assertEqual(c.mapping, OrderedDict([
+            ("1", "expansion_0:0"),
+            ("2", "expansion_0:1"),
+            ("3", "expansion_0:2"),
+            ("4", "expansion_0:3"),
+            ("7", "expansion_0:4"),
+            ("8", "expansion_0:5"),
+            ("9", "expansion_0:6"),
+            ("10", "expansion_0:7"),
+        ]))
+
+    def test_str_name(self):
+        c = Connector("ext", "A", "0 1 2")
+        self.assertEqual(c.name, "ext")
+        self.assertEqual(c.number, "A")
+
+    def test_conn_wrong_name(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Connector must be None or a pair of string \(connector name\) and "
+                    r"integer\/string \(connector number\), not \('foo', None\)$")):
+            Connector("ext", "A", "0 1 2", conn=("foo", None))
+
+    def test_wrong_io(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Connector I\/Os must be a dictionary or a string, not \[\]$"):
+            Connector("pmod", 0, [])
+
+    def test_wrong_dict_key_value(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Connector pin name must be a string, not 0$"):
+            Connector("pmod", 0, {0: "A"})
+        with self.assertRaisesRegex(TypeError,
+                r"^Platform pin name must be a string, not 0$"):
+            Connector("pmod", 0, {"A": 0})
diff --git a/tests/test_build_plat.py b/tests/test_build_plat.py
new file mode 100644 (file)
index 0000000..31dc5c9
--- /dev/null
@@ -0,0 +1,53 @@
+from nmigen import *
+from nmigen.build.plat import *
+
+from .utils import *
+
+
+class MockPlatform(Platform):
+    resources  = []
+    connectors = []
+
+    required_tools = []
+
+    def toolchain_prepare(self, fragment, name, **kwargs):
+        raise NotImplementedError
+
+
+class PlatformTestCase(FHDLTestCase):
+    def setUp(self):
+        self.platform = MockPlatform()
+
+    def test_add_file_str(self):
+        self.platform.add_file("x.txt", "foo")
+        self.assertEqual(self.platform.extra_files["x.txt"], "foo")
+
+    def test_add_file_bytes(self):
+        self.platform.add_file("x.txt", b"foo")
+        self.assertEqual(self.platform.extra_files["x.txt"], b"foo")
+
+    def test_add_file_exact_duplicate(self):
+        self.platform.add_file("x.txt", b"foo")
+        self.platform.add_file("x.txt", b"foo")
+
+    def test_add_file_io(self):
+        with open(__file__) as f:
+            self.platform.add_file("x.txt", f)
+        with open(__file__) as f:
+            self.assertEqual(self.platform.extra_files["x.txt"], f.read())
+
+    def test_add_file_wrong_filename(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^File name must be a string, not 1$"):
+            self.platform.add_file(1, "")
+
+    def test_add_file_wrong_contents(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^File contents must be str, bytes, or a file-like object, not 1$"):
+            self.platform.add_file("foo", 1)
+
+    def test_add_file_wrong_duplicate(self):
+        self.platform.add_file("foo", "")
+        with self.assertRaisesRegex(ValueError,
+                r"^File 'foo' already exists$"):
+            self.platform.add_file("foo", "bar")
diff --git a/tests/test_build_res.py b/tests/test_build_res.py
new file mode 100644 (file)
index 0000000..ac8f7bd
--- /dev/null
@@ -0,0 +1,314 @@
+# nmigen: UnusedElaboratable=no
+
+from nmigen import *
+from nmigen.hdl.rec import *
+from nmigen.lib.io import *
+from nmigen.build.dsl import *
+from nmigen.build.res import *
+
+from .utils import *
+
+
+class ResourceManagerTestCase(FHDLTestCase):
+    def setUp(self):
+        self.resources = [
+            Resource("clk100", 0, DiffPairs("H1", "H2", dir="i"), Clock(100e6)),
+            Resource("clk50", 0, Pins("K1"), Clock(50e6)),
+            Resource("user_led", 0, Pins("A0", dir="o")),
+            Resource("i2c", 0,
+                Subsignal("scl", Pins("N10", dir="o")),
+                Subsignal("sda", Pins("N11"))
+            )
+        ]
+        self.connectors = [
+            Connector("pmod", 0, "B0 B1 B2 B3 - -"),
+        ]
+        self.cm = ResourceManager(self.resources, self.connectors)
+
+    def test_basic(self):
+        self.cm = ResourceManager(self.resources, self.connectors)
+        self.assertEqual(self.cm.resources, {
+            ("clk100",   0): self.resources[0],
+            ("clk50",    0): self.resources[1],
+            ("user_led", 0): self.resources[2],
+            ("i2c",      0): self.resources[3]
+        })
+        self.assertEqual(self.cm.connectors, {
+            ("pmod", 0): self.connectors[0],
+        })
+
+    def test_add_resources(self):
+        new_resources = [
+            Resource("user_led", 1, Pins("A1", dir="o"))
+        ]
+        self.cm.add_resources(new_resources)
+        self.assertEqual(self.cm.resources, {
+            ("clk100",   0): self.resources[0],
+            ("clk50",    0): self.resources[1],
+            ("user_led", 0): self.resources[2],
+            ("i2c",      0): self.resources[3],
+            ("user_led", 1): new_resources[0]
+        })
+
+    def test_lookup(self):
+        r = self.cm.lookup("user_led", 0)
+        self.assertIs(r, self.cm.resources["user_led", 0])
+
+    def test_request_basic(self):
+        r = self.cm.lookup("user_led", 0)
+        user_led = self.cm.request("user_led", 0)
+
+        self.assertIsInstance(user_led, Pin)
+        self.assertEqual(user_led.name, "user_led_0")
+        self.assertEqual(user_led.width, 1)
+        self.assertEqual(user_led.dir, "o")
+
+        ports = list(self.cm.iter_ports())
+        self.assertEqual(len(ports), 1)
+
+        self.assertEqual(list(self.cm.iter_port_constraints()), [
+            ("user_led_0__io", ["A0"], {})
+        ])
+
+    def test_request_with_dir(self):
+        i2c = self.cm.request("i2c", 0, dir={"sda": "o"})
+        self.assertIsInstance(i2c, Record)
+        self.assertIsInstance(i2c.sda, Pin)
+        self.assertEqual(i2c.sda.dir, "o")
+
+    def test_request_tristate(self):
+        i2c = self.cm.request("i2c", 0)
+        self.assertEqual(i2c.sda.dir, "io")
+
+        ports = list(self.cm.iter_ports())
+        self.assertEqual(len(ports), 2)
+        scl, sda = ports
+        self.assertEqual(ports[1].name, "i2c_0__sda__io")
+        self.assertEqual(ports[1].width, 1)
+
+        scl_info, sda_info = self.cm.iter_single_ended_pins()
+        self.assertIs(scl_info[0], i2c.scl)
+        self.assertIs(scl_info[1].io, scl)
+        self.assertEqual(scl_info[2], {})
+        self.assertEqual(scl_info[3], False)
+        self.assertIs(sda_info[0], i2c.sda)
+        self.assertIs(sda_info[1].io, sda)
+
+        self.assertEqual(list(self.cm.iter_port_constraints()), [
+            ("i2c_0__scl__io", ["N10"], {}),
+            ("i2c_0__sda__io", ["N11"], {})
+        ])
+
+    def test_request_diffpairs(self):
+        clk100 = self.cm.request("clk100", 0)
+        self.assertIsInstance(clk100, Pin)
+        self.assertEqual(clk100.dir, "i")
+        self.assertEqual(clk100.width, 1)
+
+        ports = list(self.cm.iter_ports())
+        self.assertEqual(len(ports), 2)
+        p, n = ports
+        self.assertEqual(p.name, "clk100_0__p")
+        self.assertEqual(p.width, clk100.width)
+        self.assertEqual(n.name, "clk100_0__n")
+        self.assertEqual(n.width, clk100.width)
+
+        clk100_info, = self.cm.iter_differential_pins()
+        self.assertIs(clk100_info[0], clk100)
+        self.assertIs(clk100_info[1].p, p)
+        self.assertIs(clk100_info[1].n, n)
+        self.assertEqual(clk100_info[2], {})
+        self.assertEqual(clk100_info[3], False)
+
+        self.assertEqual(list(self.cm.iter_port_constraints()), [
+            ("clk100_0__p", ["H1"], {}),
+            ("clk100_0__n", ["H2"], {}),
+        ])
+
+    def test_request_inverted(self):
+        new_resources = [
+            Resource("cs", 0, PinsN("X0")),
+            Resource("clk", 0, DiffPairsN("Y0", "Y1")),
+        ]
+        self.cm.add_resources(new_resources)
+
+        cs = self.cm.request("cs")
+        clk = self.cm.request("clk")
+        cs_io, clk_p, clk_n = self.cm.iter_ports()
+
+        cs_info, = self.cm.iter_single_ended_pins()
+        self.assertIs(cs_info[0], cs)
+        self.assertIs(cs_info[1].io, cs_io)
+        self.assertEqual(cs_info[2], {})
+        self.assertEqual(cs_info[3], True)
+
+        clk_info, = self.cm.iter_differential_pins()
+        self.assertIs(clk_info[0], clk)
+        self.assertIs(clk_info[1].p, clk_p)
+        self.assertIs(clk_info[1].n, clk_n)
+        self.assertEqual(clk_info[2], {})
+        self.assertEqual(clk_info[3], True)
+
+    def test_request_raw(self):
+        clk50 = self.cm.request("clk50", 0, dir="-")
+        self.assertIsInstance(clk50, Record)
+        self.assertIsInstance(clk50.io, Signal)
+
+        ports = list(self.cm.iter_ports())
+        self.assertEqual(len(ports), 1)
+        self.assertIs(ports[0], clk50.io)
+
+    def test_request_raw_diffpairs(self):
+        clk100 = self.cm.request("clk100", 0, dir="-")
+        self.assertIsInstance(clk100, Record)
+        self.assertIsInstance(clk100.p, Signal)
+        self.assertIsInstance(clk100.n, Signal)
+
+        ports = list(self.cm.iter_ports())
+        self.assertEqual(len(ports), 2)
+        self.assertIs(ports[0], clk100.p)
+        self.assertIs(ports[1], clk100.n)
+
+    def test_request_via_connector(self):
+        self.cm.add_resources([
+            Resource("spi", 0,
+                Subsignal("ss",   Pins("1", conn=("pmod", 0))),
+                Subsignal("clk",  Pins("2", conn=("pmod", 0))),
+                Subsignal("miso", Pins("3", conn=("pmod", 0))),
+                Subsignal("mosi", Pins("4", conn=("pmod", 0))),
+            )
+        ])
+        spi0 = self.cm.request("spi", 0)
+        self.assertEqual(list(self.cm.iter_port_constraints()), [
+            ("spi_0__ss__io",   ["B0"], {}),
+            ("spi_0__clk__io",  ["B1"], {}),
+            ("spi_0__miso__io", ["B2"], {}),
+            ("spi_0__mosi__io", ["B3"], {}),
+        ])
+
+    def test_request_via_nested_connector(self):
+        new_connectors = [
+            Connector("pmod_extension", 0, "1 2 3 4 - -", conn=("pmod", 0)),
+        ]
+        self.cm.add_connectors(new_connectors)
+        self.cm.add_resources([
+            Resource("spi", 0,
+                Subsignal("ss",   Pins("1", conn=("pmod_extension", 0))),
+                Subsignal("clk",  Pins("2", conn=("pmod_extension", 0))),
+                Subsignal("miso", Pins("3", conn=("pmod_extension", 0))),
+                Subsignal("mosi", Pins("4", conn=("pmod_extension", 0))),
+            )
+        ])
+        spi0 = self.cm.request("spi", 0)
+        self.assertEqual(list(self.cm.iter_port_constraints()), [
+            ("spi_0__ss__io",   ["B0"], {}),
+            ("spi_0__clk__io",  ["B1"], {}),
+            ("spi_0__miso__io", ["B2"], {}),
+            ("spi_0__mosi__io", ["B3"], {}),
+        ])
+
+    def test_request_clock(self):
+        clk100 = self.cm.request("clk100", 0)
+        clk50 = self.cm.request("clk50", 0, dir="i")
+        clk100_port_p, clk100_port_n, clk50_port = self.cm.iter_ports()
+        self.assertEqual(list(self.cm.iter_clock_constraints()), [
+            (clk100.i, clk100_port_p, 100e6),
+            (clk50.i, clk50_port, 50e6)
+        ])
+
+    def test_add_clock(self):
+        i2c = self.cm.request("i2c")
+        self.cm.add_clock_constraint(i2c.scl.o, 100e3)
+        self.assertEqual(list(self.cm.iter_clock_constraints()), [
+            (i2c.scl.o, None, 100e3)
+        ])
+
+    def test_wrong_resources(self):
+        with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Resource$"):
+            self.cm.add_resources(['wrong'])
+
+    def test_wrong_resources_duplicate(self):
+        with self.assertRaisesRegex(NameError,
+                (r"^Trying to add \(resource user_led 0 \(pins o A1\)\), but "
+                    r"\(resource user_led 0 \(pins o A0\)\) has the same name and number$")):
+            self.cm.add_resources([Resource("user_led", 0, Pins("A1", dir="o"))])
+
+    def test_wrong_connectors(self):
+        with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Connector$"):
+            self.cm.add_connectors(['wrong'])
+
+    def test_wrong_connectors_duplicate(self):
+        with self.assertRaisesRegex(NameError,
+                (r"^Trying to add \(connector pmod 0 1=>1 2=>2\), but "
+                    r"\(connector pmod 0 1=>B0 2=>B1 3=>B2 4=>B3\) has the same name and number$")):
+            self.cm.add_connectors([Connector("pmod", 0, "1 2")])
+
+    def test_wrong_lookup(self):
+        with self.assertRaisesRegex(ResourceError,
+                r"^Resource user_led#1 does not exist$"):
+            r = self.cm.lookup("user_led", 1)
+
+    def test_wrong_clock_signal(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Object None is not a Signal$"):
+            self.cm.add_clock_constraint(None, 10e6)
+
+    def test_wrong_clock_frequency(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Frequency must be a number, not None$"):
+            self.cm.add_clock_constraint(Signal(), None)
+
+    def test_wrong_request_duplicate(self):
+        with self.assertRaisesRegex(ResourceError,
+                r"^Resource user_led#0 has already been requested$"):
+            self.cm.request("user_led", 0)
+            self.cm.request("user_led", 0)
+
+    def test_wrong_request_duplicate_physical(self):
+        self.cm.add_resources([
+            Resource("clk20", 0, Pins("H1", dir="i")),
+        ])
+        self.cm.request("clk100", 0)
+        with self.assertRaisesRegex(ResourceError,
+                (r"^Resource component clk20_0 uses physical pin H1, but it is already "
+                    r"used by resource component clk100_0 that was requested earlier$")):
+            self.cm.request("clk20", 0)
+
+    def test_wrong_request_with_dir(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Direction must be one of \"i\", \"o\", \"oe\", \"io\", or \"-\", "
+                    r"not 'wrong'$")):
+            user_led = self.cm.request("user_led", 0, dir="wrong")
+
+    def test_wrong_request_with_dir_io(self):
+        with self.assertRaisesRegex(ValueError,
+                (r"^Direction of \(pins o A0\) cannot be changed from \"o\" to \"i\"; direction "
+                    r"can be changed from \"io\" to \"i\", \"o\", or \"oe\", or from anything "
+                    r"to \"-\"$")):
+            user_led = self.cm.request("user_led", 0, dir="i")
+
+    def test_wrong_request_with_dir_dict(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Directions must be a dict, not 'i', because \(resource i2c 0 \(subsignal scl "
+                    r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) "
+                    r"has subsignals$")):
+            i2c = self.cm.request("i2c", 0, dir="i")
+
+    def test_wrong_request_with_wrong_xdr(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Data rate of \(pins o A0\) must be a non-negative integer, not -1$"):
+            user_led = self.cm.request("user_led", 0, xdr=-1)
+
+    def test_wrong_request_with_xdr_dict(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Data rate must be a dict, not 2, because \(resource i2c 0 \(subsignal scl "
+                    r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) "
+                    r"has subsignals$"):
+            i2c = self.cm.request("i2c", 0, xdr=2)
+
+    def test_wrong_clock_constraint_twice(self):
+        clk100 = self.cm.request("clk100")
+        with self.assertRaisesRegex(ValueError,
+                (r"^Cannot add clock constraint on \(sig clk100_0__i\), which is already "
+                    r"constrained to 100000000\.0 Hz$")):
+            self.cm.add_clock_constraint(clk100.i, 1e6)
diff --git a/tests/test_compat.py b/tests/test_compat.py
new file mode 100644 (file)
index 0000000..aeb1617
--- /dev/null
@@ -0,0 +1,10 @@
+from nmigen.hdl.ir import Fragment
+from nmigen.compat import *
+
+from .utils import *
+
+
+class CompatTestCase(FHDLTestCase):
+    def test_fragment_get(self):
+        m = Module()
+        f = Fragment.get(m, platform=None)
diff --git a/tests/test_examples.py b/tests/test_examples.py
new file mode 100644 (file)
index 0000000..7bc1198
--- /dev/null
@@ -0,0 +1,33 @@
+import sys
+import subprocess
+from pathlib import Path
+
+from .utils import *
+
+
+def example_test(name):
+    path = (Path(__file__).parent / ".." / "examples" / name).resolve()
+    def test_function(self):
+        subprocess.check_call([sys.executable, str(path), "generate", "-t", "v"],
+                              stdout=subprocess.DEVNULL)
+    return test_function
+
+
+class ExamplesTestCase(FHDLTestCase):
+    test_alu        = example_test("basic/alu.py")
+    test_alu_hier   = example_test("basic/alu_hier.py")
+    test_arst       = example_test("basic/arst.py")
+    test_cdc        = example_test("basic/cdc.py")
+    test_ctr        = example_test("basic/ctr.py")
+    test_ctr_en     = example_test("basic/ctr_en.py")
+    test_fsm        = example_test("basic/fsm.py")
+    test_gpio       = example_test("basic/gpio.py")
+    test_inst       = example_test("basic/inst.py")
+    test_mem        = example_test("basic/mem.py")
+    test_pmux       = example_test("basic/pmux.py")
+    test_por        = example_test("basic/por.py")
+
+    def test_uart(self):
+        path = (Path(__file__).parent / ".." / "examples" / "basic" / "uart.py").resolve()
+        subprocess.check_call([sys.executable, str(path), "generate"],
+                              stdout=subprocess.DEVNULL)
diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
new file mode 100644 (file)
index 0000000..f14c07b
--- /dev/null
@@ -0,0 +1,1059 @@
+import warnings
+from enum import Enum
+
+from nmigen.hdl.ast import *
+
+from .utils import *
+
+
+class UnsignedEnum(Enum):
+    FOO = 1
+    BAR = 2
+    BAZ = 3
+
+
+class SignedEnum(Enum):
+    FOO = -1
+    BAR =  0
+    BAZ = +1
+
+
+class StringEnum(Enum):
+    FOO = "a"
+    BAR = "b"
+
+
+class ShapeTestCase(FHDLTestCase):
+    def test_make(self):
+        s1 = Shape()
+        self.assertEqual(s1.width, 1)
+        self.assertEqual(s1.signed, False)
+        s2 = Shape(signed=True)
+        self.assertEqual(s2.width, 1)
+        self.assertEqual(s2.signed, True)
+        s3 = Shape(3, True)
+        self.assertEqual(s3.width, 3)
+        self.assertEqual(s3.signed, True)
+
+    def test_make_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Width must be a non-negative integer, not -1$"):
+            Shape(-1)
+
+    def test_compare_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Shapes may be compared with other Shapes and \(int, bool\) tuples, not 'hi'$"):
+            Shape(1, True) == 'hi'
+
+    def test_compare_tuple_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Shapes may be compared with other Shapes and \(int, bool\) tuples, not \(2, 3\)$"):
+            Shape(1, True) == (2, 3)
+
+    def test_repr(self):
+        self.assertEqual(repr(Shape()), "unsigned(1)")
+        self.assertEqual(repr(Shape(2, True)), "signed(2)")
+
+    def test_tuple(self):
+        width, signed = Shape()
+        self.assertEqual(width, 1)
+        self.assertEqual(signed, False)
+
+    def test_unsigned(self):
+        s1 = unsigned(2)
+        self.assertIsInstance(s1, Shape)
+        self.assertEqual(s1.width, 2)
+        self.assertEqual(s1.signed, False)
+
+    def test_signed(self):
+        s1 = signed(2)
+        self.assertIsInstance(s1, Shape)
+        self.assertEqual(s1.width, 2)
+        self.assertEqual(s1.signed, True)
+
+    def test_cast_shape(self):
+        s1 = Shape.cast(unsigned(1))
+        self.assertEqual(s1.width, 1)
+        self.assertEqual(s1.signed, False)
+        s2 = Shape.cast(signed(3))
+        self.assertEqual(s2.width, 3)
+        self.assertEqual(s2.signed, True)
+
+    def test_cast_int(self):
+        s1 = Shape.cast(2)
+        self.assertEqual(s1.width, 2)
+        self.assertEqual(s1.signed, False)
+
+    def test_cast_int_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Width must be a non-negative integer, not -1$"):
+            Shape.cast(-1)
+
+    def test_cast_tuple(self):
+        with warnings.catch_warnings():
+            warnings.filterwarnings(action="ignore", category=DeprecationWarning)
+            s1 = Shape.cast((1, True))
+            self.assertEqual(s1.width, 1)
+            self.assertEqual(s1.signed, True)
+
+    def test_cast_tuple_wrong(self):
+        with warnings.catch_warnings():
+            warnings.filterwarnings(action="ignore", category=DeprecationWarning)
+            with self.assertRaisesRegex(TypeError,
+                    r"^Width must be a non-negative integer, not -1$"):
+                Shape.cast((-1, True))
+
+    def test_cast_range(self):
+        s1 = Shape.cast(range(0, 8))
+        self.assertEqual(s1.width, 3)
+        self.assertEqual(s1.signed, False)
+        s2 = Shape.cast(range(0, 9))
+        self.assertEqual(s2.width, 4)
+        self.assertEqual(s2.signed, False)
+        s3 = Shape.cast(range(-7, 8))
+        self.assertEqual(s3.width, 4)
+        self.assertEqual(s3.signed, True)
+        s4 = Shape.cast(range(0, 1))
+        self.assertEqual(s4.width, 1)
+        self.assertEqual(s4.signed, False)
+        s5 = Shape.cast(range(-1, 0))
+        self.assertEqual(s5.width, 1)
+        self.assertEqual(s5.signed, True)
+        s6 = Shape.cast(range(0, 0))
+        self.assertEqual(s6.width, 0)
+        self.assertEqual(s6.signed, False)
+        s7 = Shape.cast(range(-1, -1))
+        self.assertEqual(s7.width, 0)
+        self.assertEqual(s7.signed, True)
+
+    def test_cast_enum(self):
+        s1 = Shape.cast(UnsignedEnum)
+        self.assertEqual(s1.width, 2)
+        self.assertEqual(s1.signed, False)
+        s2 = Shape.cast(SignedEnum)
+        self.assertEqual(s2.width, 2)
+        self.assertEqual(s2.signed, True)
+
+    def test_cast_enum_bad(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Only enumerations with integer values can be used as value shapes$"):
+            Shape.cast(StringEnum)
+
+    def test_cast_bad(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Object 'foo' cannot be used as value shape$"):
+            Shape.cast("foo")
+
+
+class ValueTestCase(FHDLTestCase):
+    def test_cast(self):
+        self.assertIsInstance(Value.cast(0), Const)
+        self.assertIsInstance(Value.cast(True), Const)
+        c = Const(0)
+        self.assertIs(Value.cast(c), c)
+        with self.assertRaisesRegex(TypeError,
+                r"^Object 'str' cannot be converted to an nMigen value$"):
+            Value.cast("str")
+
+    def test_cast_enum(self):
+        e1 = Value.cast(UnsignedEnum.FOO)
+        self.assertIsInstance(e1, Const)
+        self.assertEqual(e1.shape(), unsigned(2))
+        e2 = Value.cast(SignedEnum.FOO)
+        self.assertIsInstance(e2, Const)
+        self.assertEqual(e2.shape(), signed(2))
+
+    def test_cast_enum_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Only enumerations with integer values can be used as value shapes$"):
+            Value.cast(StringEnum.FOO)
+
+    def test_bool(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Attempted to convert nMigen value to Python boolean$"):
+            if Const(0):
+                pass
+
+    def test_len(self):
+        self.assertEqual(len(Const(10)), 4)
+
+    def test_getitem_int(self):
+        s1 = Const(10)[0]
+        self.assertIsInstance(s1, Slice)
+        self.assertEqual(s1.start, 0)
+        self.assertEqual(s1.stop, 1)
+        s2 = Const(10)[-1]
+        self.assertIsInstance(s2, Slice)
+        self.assertEqual(s2.start, 3)
+        self.assertEqual(s2.stop, 4)
+        with self.assertRaisesRegex(IndexError,
+                r"^Cannot index 5 bits into 4-bit value$"):
+            Const(10)[5]
+
+    def test_getitem_slice(self):
+        s1 = Const(10)[1:3]
+        self.assertIsInstance(s1, Slice)
+        self.assertEqual(s1.start, 1)
+        self.assertEqual(s1.stop, 3)
+        s2 = Const(10)[1:-2]
+        self.assertIsInstance(s2, Slice)
+        self.assertEqual(s2.start, 1)
+        self.assertEqual(s2.stop, 2)
+        s3 = Const(31)[::2]
+        self.assertIsInstance(s3, Cat)
+        self.assertIsInstance(s3.parts[0], Slice)
+        self.assertEqual(s3.parts[0].start, 0)
+        self.assertEqual(s3.parts[0].stop, 1)
+        self.assertIsInstance(s3.parts[1], Slice)
+        self.assertEqual(s3.parts[1].start, 2)
+        self.assertEqual(s3.parts[1].stop, 3)
+        self.assertIsInstance(s3.parts[2], Slice)
+        self.assertEqual(s3.parts[2].start, 4)
+        self.assertEqual(s3.parts[2].stop, 5)
+
+    def test_getitem_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Cannot index value with 'str'$"):
+            Const(31)["str"]
+
+    def test_shift_left(self):
+        self.assertRepr(Const(256, unsigned(9)).shift_left(0),
+                        "(cat (const 0'd0) (const 9'd256))")
+
+        self.assertRepr(Const(256, unsigned(9)).shift_left(1),
+                        "(cat (const 1'd0) (const 9'd256))")
+        self.assertRepr(Const(256, unsigned(9)).shift_left(5),
+                        "(cat (const 5'd0) (const 9'd256))")
+        self.assertRepr(Const(256, signed(9)).shift_left(1),
+                        "(s (cat (const 1'd0) (const 9'sd-256)))")
+        self.assertRepr(Const(256, signed(9)).shift_left(5),
+                        "(s (cat (const 5'd0) (const 9'sd-256)))")
+
+        self.assertRepr(Const(256, unsigned(9)).shift_left(-1),
+                        "(slice (const 9'd256) 1:9)")
+        self.assertRepr(Const(256, unsigned(9)).shift_left(-5),
+                        "(slice (const 9'd256) 5:9)")
+        self.assertRepr(Const(256, signed(9)).shift_left(-1),
+                        "(s (slice (const 9'sd-256) 1:9))")
+        self.assertRepr(Const(256, signed(9)).shift_left(-5),
+                        "(s (slice (const 9'sd-256) 5:9))")
+        self.assertRepr(Const(256, signed(9)).shift_left(-15),
+                        "(s (slice (const 9'sd-256) 9:9))")
+
+    def test_shift_left_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Shift amount must be an integer, not 'str'$"):
+            Const(31).shift_left("str")
+
+    def test_shift_right(self):
+        self.assertRepr(Const(256, unsigned(9)).shift_right(0),
+                        "(slice (const 9'd256) 0:9)")
+
+        self.assertRepr(Const(256, unsigned(9)).shift_right(-1),
+                        "(cat (const 1'd0) (const 9'd256))")
+        self.assertRepr(Const(256, unsigned(9)).shift_right(-5),
+                        "(cat (const 5'd0) (const 9'd256))")
+        self.assertRepr(Const(256, signed(9)).shift_right(-1),
+                        "(s (cat (const 1'd0) (const 9'sd-256)))")
+        self.assertRepr(Const(256, signed(9)).shift_right(-5),
+                        "(s (cat (const 5'd0) (const 9'sd-256)))")
+
+        self.assertRepr(Const(256, unsigned(9)).shift_right(1),
+                        "(slice (const 9'd256) 1:9)")
+        self.assertRepr(Const(256, unsigned(9)).shift_right(5),
+                        "(slice (const 9'd256) 5:9)")
+        self.assertRepr(Const(256, signed(9)).shift_right(1),
+                        "(s (slice (const 9'sd-256) 1:9))")
+        self.assertRepr(Const(256, signed(9)).shift_right(5),
+                        "(s (slice (const 9'sd-256) 5:9))")
+        self.assertRepr(Const(256, signed(9)).shift_right(15),
+                        "(s (slice (const 9'sd-256) 9:9))")
+
+    def test_shift_right_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Shift amount must be an integer, not 'str'$"):
+            Const(31).shift_left("str")
+
+    def test_rotate_left(self):
+        self.assertRepr(Const(256).rotate_left(1),
+                        "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))")
+        self.assertRepr(Const(256).rotate_left(7),
+                        "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))")
+        self.assertRepr(Const(256).rotate_left(-1),
+                        "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))")
+        self.assertRepr(Const(256).rotate_left(-7),
+                        "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))")
+
+    def test_rotate_left_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Rotate amount must be an integer, not 'str'$"):
+            Const(31).rotate_left("str")
+
+    def test_rotate_right(self):
+        self.assertRepr(Const(256).rotate_right(1),
+                        "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))")
+        self.assertRepr(Const(256).rotate_right(7),
+                        "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))")
+        self.assertRepr(Const(256).rotate_right(-1),
+                        "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))")
+        self.assertRepr(Const(256).rotate_right(-7),
+                        "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))")
+
+    def test_rotate_right_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Rotate amount must be an integer, not 'str'$"):
+            Const(31).rotate_right("str")
+
+
+class ConstTestCase(FHDLTestCase):
+    def test_shape(self):
+        self.assertEqual(Const(0).shape(),   unsigned(1))
+        self.assertIsInstance(Const(0).shape(), Shape)
+        self.assertEqual(Const(1).shape(),   unsigned(1))
+        self.assertEqual(Const(10).shape(),  unsigned(4))
+        self.assertEqual(Const(-10).shape(), signed(5))
+
+        self.assertEqual(Const(1, 4).shape(),          unsigned(4))
+        self.assertEqual(Const(-1, 4).shape(),         signed(4))
+        self.assertEqual(Const(1, signed(4)).shape(),  signed(4))
+        self.assertEqual(Const(0, unsigned(0)).shape(), unsigned(0))
+
+    def test_shape_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Width must be a non-negative integer, not -1$"):
+            Const(1, -1)
+
+    def test_normalization(self):
+        self.assertEqual(Const(0b10110, signed(5)).value, -10)
+
+    def test_value(self):
+        self.assertEqual(Const(10).value, 10)
+
+    def test_repr(self):
+        self.assertEqual(repr(Const(10)),  "(const 4'd10)")
+        self.assertEqual(repr(Const(-10)), "(const 5'sd-10)")
+
+    def test_hash(self):
+        with self.assertRaises(TypeError):
+            hash(Const(0))
+
+
+class OperatorTestCase(FHDLTestCase):
+    def test_bool(self):
+        v = Const(0, 4).bool()
+        self.assertEqual(repr(v), "(b (const 4'd0))")
+        self.assertEqual(v.shape(), unsigned(1))
+
+    def test_invert(self):
+        v = ~Const(0, 4)
+        self.assertEqual(repr(v), "(~ (const 4'd0))")
+        self.assertEqual(v.shape(), unsigned(4))
+
+    def test_as_unsigned(self):
+        v = Const(-1, signed(4)).as_unsigned()
+        self.assertEqual(repr(v), "(u (const 4'sd-1))")
+        self.assertEqual(v.shape(), unsigned(4))
+
+    def test_as_signed(self):
+        v = Const(1, unsigned(4)).as_signed()
+        self.assertEqual(repr(v), "(s (const 4'd1))")
+        self.assertEqual(v.shape(), signed(4))
+
+    def test_neg(self):
+        v1 = -Const(0, unsigned(4))
+        self.assertEqual(repr(v1), "(- (const 4'd0))")
+        self.assertEqual(v1.shape(), signed(5))
+        v2 = -Const(0, signed(4))
+        self.assertEqual(repr(v2), "(- (const 4'sd0))")
+        self.assertEqual(v2.shape(), signed(5))
+
+    def test_add(self):
+        v1 = Const(0, unsigned(4)) + Const(0, unsigned(6))
+        self.assertEqual(repr(v1), "(+ (const 4'd0) (const 6'd0))")
+        self.assertEqual(v1.shape(), unsigned(7))
+        v2 = Const(0, signed(4)) + Const(0, signed(6))
+        self.assertEqual(v2.shape(), signed(7))
+        v3 = Const(0, signed(4)) + Const(0, unsigned(4))
+        self.assertEqual(v3.shape(), signed(6))
+        v4 = Const(0, unsigned(4)) + Const(0, signed(4))
+        self.assertEqual(v4.shape(), signed(6))
+        v5 = 10 + Const(0, 4)
+        self.assertEqual(v5.shape(), unsigned(5))
+
+    def test_sub(self):
+        v1 = Const(0, unsigned(4)) - Const(0, unsigned(6))
+        self.assertEqual(repr(v1), "(- (const 4'd0) (const 6'd0))")
+        self.assertEqual(v1.shape(), unsigned(7))
+        v2 = Const(0, signed(4)) - Const(0, signed(6))
+        self.assertEqual(v2.shape(), signed(7))
+        v3 = Const(0, signed(4)) - Const(0, unsigned(4))
+        self.assertEqual(v3.shape(), signed(6))
+        v4 = Const(0, unsigned(4)) - Const(0, signed(4))
+        self.assertEqual(v4.shape(), signed(6))
+        v5 = 10 - Const(0, 4)
+        self.assertEqual(v5.shape(), unsigned(5))
+
+    def test_mul(self):
+        v1 = Const(0, unsigned(4)) * Const(0, unsigned(6))
+        self.assertEqual(repr(v1), "(* (const 4'd0) (const 6'd0))")
+        self.assertEqual(v1.shape(), unsigned(10))
+        v2 = Const(0, signed(4)) * Const(0, signed(6))
+        self.assertEqual(v2.shape(), signed(10))
+        v3 = Const(0, signed(4)) * Const(0, unsigned(4))
+        self.assertEqual(v3.shape(), signed(8))
+        v5 = 10 * Const(0, 4)
+        self.assertEqual(v5.shape(), unsigned(8))
+
+    def test_mod(self):
+        v1 = Const(0, unsigned(4)) % Const(0, unsigned(6))
+        self.assertEqual(repr(v1), "(% (const 4'd0) (const 6'd0))")
+        self.assertEqual(v1.shape(), unsigned(4))
+        v3 = Const(0, signed(4)) % Const(0, unsigned(4))
+        self.assertEqual(v3.shape(), signed(4))
+        v5 = 10 % Const(0, 4)
+        self.assertEqual(v5.shape(), unsigned(4))
+
+    def test_mod_wrong(self):
+        with self.assertRaisesRegex(NotImplementedError,
+                r"^Division by a signed value is not supported$"):
+            Const(0, signed(4)) % Const(0, signed(6))
+
+    def test_floordiv(self):
+        v1 = Const(0, unsigned(4)) // Const(0, unsigned(6))
+        self.assertEqual(repr(v1), "(// (const 4'd0) (const 6'd0))")
+        self.assertEqual(v1.shape(), unsigned(4))
+        v3 = Const(0, signed(4)) // Const(0, unsigned(4))
+        self.assertEqual(v3.shape(), signed(4))
+        v5 = 10 // Const(0, 4)
+        self.assertEqual(v5.shape(), unsigned(4))
+
+    def test_floordiv_wrong(self):
+        with self.assertRaisesRegex(NotImplementedError,
+                r"^Division by a signed value is not supported$"):
+            Const(0, signed(4)) // Const(0, signed(6))
+
+    def test_and(self):
+        v1 = Const(0, unsigned(4)) & Const(0, unsigned(6))
+        self.assertEqual(repr(v1), "(& (const 4'd0) (const 6'd0))")
+        self.assertEqual(v1.shape(), unsigned(6))
+        v2 = Const(0, signed(4)) & Const(0, signed(6))
+        self.assertEqual(v2.shape(), signed(6))
+        v3 = Const(0, signed(4)) & Const(0, unsigned(4))
+        self.assertEqual(v3.shape(), signed(5))
+        v4 = Const(0, unsigned(4)) & Const(0, signed(4))
+        self.assertEqual(v4.shape(), signed(5))
+        v5 = 10 & Const(0, 4)
+        self.assertEqual(v5.shape(), unsigned(4))
+
+    def test_or(self):
+        v1 = Const(0, unsigned(4)) | Const(0, unsigned(6))
+        self.assertEqual(repr(v1), "(| (const 4'd0) (const 6'd0))")
+        self.assertEqual(v1.shape(), unsigned(6))
+        v2 = Const(0, signed(4)) | Const(0, signed(6))
+        self.assertEqual(v2.shape(), signed(6))
+        v3 = Const(0, signed(4)) | Const(0, unsigned(4))
+        self.assertEqual(v3.shape(), signed(5))
+        v4 = Const(0, unsigned(4)) | Const(0, signed(4))
+        self.assertEqual(v4.shape(), signed(5))
+        v5 = 10 | Const(0, 4)
+        self.assertEqual(v5.shape(), unsigned(4))
+
+    def test_xor(self):
+        v1 = Const(0, unsigned(4)) ^ Const(0, unsigned(6))
+        self.assertEqual(repr(v1), "(^ (const 4'd0) (const 6'd0))")
+        self.assertEqual(v1.shape(), unsigned(6))
+        v2 = Const(0, signed(4)) ^ Const(0, signed(6))
+        self.assertEqual(v2.shape(), signed(6))
+        v3 = Const(0, signed(4)) ^ Const(0, unsigned(4))
+        self.assertEqual(v3.shape(), signed(5))
+        v4 = Const(0, unsigned(4)) ^ Const(0, signed(4))
+        self.assertEqual(v4.shape(), signed(5))
+        v5 = 10 ^ Const(0, 4)
+        self.assertEqual(v5.shape(), unsigned(4))
+
+    def test_shl(self):
+        v1 = Const(1, 4) << Const(4)
+        self.assertEqual(repr(v1), "(<< (const 4'd1) (const 3'd4))")
+        self.assertEqual(v1.shape(), unsigned(11))
+
+    def test_shl_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Shift amount must be unsigned$"):
+            1 << Const(0, signed(6))
+        with self.assertRaisesRegex(TypeError,
+                r"^Shift amount must be unsigned$"):
+            Const(1, unsigned(4)) << -1
+
+    def test_shr(self):
+        v1 = Const(1, 4) >> Const(4)
+        self.assertEqual(repr(v1), "(>> (const 4'd1) (const 3'd4))")
+        self.assertEqual(v1.shape(), unsigned(4))
+
+    def test_shr_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Shift amount must be unsigned$"):
+            1 << Const(0, signed(6))
+        with self.assertRaisesRegex(TypeError,
+                r"^Shift amount must be unsigned$"):
+            Const(1, unsigned(4)) << -1
+
+    def test_lt(self):
+        v = Const(0, 4) < Const(0, 6)
+        self.assertEqual(repr(v), "(< (const 4'd0) (const 6'd0))")
+        self.assertEqual(v.shape(), unsigned(1))
+
+    def test_le(self):
+        v = Const(0, 4) <= Const(0, 6)
+        self.assertEqual(repr(v), "(<= (const 4'd0) (const 6'd0))")
+        self.assertEqual(v.shape(), unsigned(1))
+
+    def test_gt(self):
+        v = Const(0, 4) > Const(0, 6)
+        self.assertEqual(repr(v), "(> (const 4'd0) (const 6'd0))")
+        self.assertEqual(v.shape(), unsigned(1))
+
+    def test_ge(self):
+        v = Const(0, 4) >= Const(0, 6)
+        self.assertEqual(repr(v), "(>= (const 4'd0) (const 6'd0))")
+        self.assertEqual(v.shape(), unsigned(1))
+
+    def test_eq(self):
+        v = Const(0, 4) == Const(0, 6)
+        self.assertEqual(repr(v), "(== (const 4'd0) (const 6'd0))")
+        self.assertEqual(v.shape(), unsigned(1))
+
+    def test_ne(self):
+        v = Const(0, 4) != Const(0, 6)
+        self.assertEqual(repr(v), "(!= (const 4'd0) (const 6'd0))")
+        self.assertEqual(v.shape(), unsigned(1))
+
+    def test_mux(self):
+        s  = Const(0)
+        v1 = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6)))
+        self.assertEqual(repr(v1), "(m (const 1'd0) (const 4'd0) (const 6'd0))")
+        self.assertEqual(v1.shape(), unsigned(6))
+        v2 = Mux(s, Const(0, signed(4)), Const(0, signed(6)))
+        self.assertEqual(v2.shape(), signed(6))
+        v3 = Mux(s, Const(0, signed(4)), Const(0, unsigned(4)))
+        self.assertEqual(v3.shape(), signed(5))
+        v4 = Mux(s, Const(0, unsigned(4)), Const(0, signed(4)))
+        self.assertEqual(v4.shape(), signed(5))
+
+    def test_mux_wide(self):
+        s = Const(0b100)
+        v = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6)))
+        self.assertEqual(repr(v), "(m (b (const 3'd4)) (const 4'd0) (const 6'd0))")
+
+    def test_mux_bool(self):
+        v = Mux(True, Const(0), Const(0))
+        self.assertEqual(repr(v), "(m (const 1'd1) (const 1'd0) (const 1'd0))")
+
+    def test_bool(self):
+        v = Const(0).bool()
+        self.assertEqual(repr(v), "(b (const 1'd0))")
+        self.assertEqual(v.shape(), unsigned(1))
+
+    def test_any(self):
+        v = Const(0b101).any()
+        self.assertEqual(repr(v), "(r| (const 3'd5))")
+
+    def test_all(self):
+        v = Const(0b101).all()
+        self.assertEqual(repr(v), "(r& (const 3'd5))")
+
+    def test_xor(self):
+        v = Const(0b101).xor()
+        self.assertEqual(repr(v), "(r^ (const 3'd5))")
+
+    def test_matches(self):
+        s = Signal(4)
+        self.assertRepr(s.matches(), "(const 1'd0)")
+        self.assertRepr(s.matches(1), """
+        (== (sig s) (const 1'd1))
+        """)
+        self.assertRepr(s.matches(0, 1), """
+        (r| (cat (== (sig s) (const 1'd0)) (== (sig s) (const 1'd1))))
+        """)
+        self.assertRepr(s.matches("10--"), """
+        (== (& (sig s) (const 4'd12)) (const 4'd8))
+        """)
+        self.assertRepr(s.matches("1 0--"), """
+        (== (& (sig s) (const 4'd12)) (const 4'd8))
+        """)
+
+    def test_matches_enum(self):
+        s = Signal(SignedEnum)
+        self.assertRepr(s.matches(SignedEnum.FOO), """
+        (== (sig s) (const 1'sd-1))
+        """)
+
+    def test_matches_width_wrong(self):
+        s = Signal(4)
+        with self.assertRaisesRegex(SyntaxError,
+                r"^Match pattern '--' must have the same width as match value \(which is 4\)$"):
+            s.matches("--")
+        with self.assertWarnsRegex(SyntaxWarning,
+                (r"^Match pattern '10110' is wider than match value \(which has width 4\); "
+                    r"comparison will never be true$")):
+            s.matches(0b10110)
+
+    def test_matches_bits_wrong(self):
+        s = Signal(4)
+        with self.assertRaisesRegex(SyntaxError,
+                (r"^Match pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
+                    r"and may include whitespace$")):
+            s.matches("abc")
+
+    def test_matches_pattern_wrong(self):
+        s = Signal(4)
+        with self.assertRaisesRegex(SyntaxError,
+                r"^Match pattern must be an integer, a string, or an enumeration, not 1\.0$"):
+            s.matches(1.0)
+
+    def test_hash(self):
+        with self.assertRaises(TypeError):
+            hash(Const(0) + Const(0))
+
+
+class SliceTestCase(FHDLTestCase):
+    def test_shape(self):
+        s1 = Const(10)[2]
+        self.assertEqual(s1.shape(), unsigned(1))
+        self.assertIsInstance(s1.shape(), Shape)
+        s2 = Const(-10)[0:2]
+        self.assertEqual(s2.shape(), unsigned(2))
+
+    def test_start_end_negative(self):
+        c  = Const(0, 8)
+        s1 = Slice(c, 0, -1)
+        self.assertEqual((s1.start, s1.stop), (0, 7))
+        s1 = Slice(c, -4, -1)
+        self.assertEqual((s1.start, s1.stop), (4, 7))
+
+    def test_start_end_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Slice start must be an integer, not 'x'$"):
+            Slice(0, "x", 1)
+        with self.assertRaisesRegex(TypeError,
+                r"^Slice stop must be an integer, not 'x'$"):
+            Slice(0, 1, "x")
+
+    def test_start_end_out_of_range(self):
+        c = Const(0, 8)
+        with self.assertRaisesRegex(IndexError,
+                r"^Cannot start slice 10 bits into 8-bit value$"):
+            Slice(c, 10, 12)
+        with self.assertRaisesRegex(IndexError,
+                r"^Cannot stop slice 12 bits into 8-bit value$"):
+            Slice(c, 0, 12)
+        with self.assertRaisesRegex(IndexError,
+                r"^Slice start 4 must be less than slice stop 2$"):
+            Slice(c, 4, 2)
+
+    def test_repr(self):
+        s1 = Const(10)[2]
+        self.assertEqual(repr(s1), "(slice (const 4'd10) 2:3)")
+
+
+class BitSelectTestCase(FHDLTestCase):
+    def setUp(self):
+        self.c = Const(0, 8)
+        self.s = Signal(range(self.c.width))
+
+    def test_shape(self):
+        s1 = self.c.bit_select(self.s, 2)
+        self.assertIsInstance(s1, Part)
+        self.assertEqual(s1.shape(), unsigned(2))
+        self.assertIsInstance(s1.shape(), Shape)
+        s2 = self.c.bit_select(self.s, 0)
+        self.assertIsInstance(s2, Part)
+        self.assertEqual(s2.shape(), unsigned(0))
+
+    def test_stride(self):
+        s1 = self.c.bit_select(self.s, 2)
+        self.assertIsInstance(s1, Part)
+        self.assertEqual(s1.stride, 1)
+
+    def test_const(self):
+        s1 = self.c.bit_select(1, 2)
+        self.assertIsInstance(s1, Slice)
+        self.assertRepr(s1, """(slice (const 8'd0) 1:3)""")
+
+    def test_width_wrong(self):
+        with self.assertRaises(TypeError):
+            self.c.bit_select(self.s, -1)
+
+    def test_repr(self):
+        s = self.c.bit_select(self.s, 2)
+        self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 1)")
+
+
+class WordSelectTestCase(FHDLTestCase):
+    def setUp(self):
+        self.c = Const(0, 8)
+        self.s = Signal(range(self.c.width))
+
+    def test_shape(self):
+        s1 = self.c.word_select(self.s, 2)
+        self.assertIsInstance(s1, Part)
+        self.assertEqual(s1.shape(), unsigned(2))
+        self.assertIsInstance(s1.shape(), Shape)
+
+    def test_stride(self):
+        s1 = self.c.word_select(self.s, 2)
+        self.assertIsInstance(s1, Part)
+        self.assertEqual(s1.stride, 2)
+
+    def test_const(self):
+        s1 = self.c.word_select(1, 2)
+        self.assertIsInstance(s1, Slice)
+        self.assertRepr(s1, """(slice (const 8'd0) 2:4)""")
+
+    def test_width_wrong(self):
+        with self.assertRaises(TypeError):
+            self.c.word_select(self.s, 0)
+        with self.assertRaises(TypeError):
+            self.c.word_select(self.s, -1)
+
+    def test_repr(self):
+        s = self.c.word_select(self.s, 2)
+        self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 2)")
+
+
+class CatTestCase(FHDLTestCase):
+    def test_shape(self):
+        c0 = Cat()
+        self.assertEqual(c0.shape(), unsigned(0))
+        self.assertIsInstance(c0.shape(), Shape)
+        c1 = Cat(Const(10))
+        self.assertEqual(c1.shape(), unsigned(4))
+        c2 = Cat(Const(10), Const(1))
+        self.assertEqual(c2.shape(), unsigned(5))
+        c3 = Cat(Const(10), Const(1), Const(0))
+        self.assertEqual(c3.shape(), unsigned(6))
+
+    def test_repr(self):
+        c1 = Cat(Const(10), Const(1))
+        self.assertEqual(repr(c1), "(cat (const 4'd10) (const 1'd1))")
+
+
+class ReplTestCase(FHDLTestCase):
+    def test_shape(self):
+        s1 = Repl(Const(10), 3)
+        self.assertEqual(s1.shape(), unsigned(12))
+        self.assertIsInstance(s1.shape(), Shape)
+        s2 = Repl(Const(10), 0)
+        self.assertEqual(s2.shape(), unsigned(0))
+
+    def test_count_wrong(self):
+        with self.assertRaises(TypeError):
+            Repl(Const(10), -1)
+        with self.assertRaises(TypeError):
+            Repl(Const(10), "str")
+
+    def test_repr(self):
+        s = Repl(Const(10), 3)
+        self.assertEqual(repr(s), "(repl (const 4'd10) 3)")
+
+
+class ArrayTestCase(FHDLTestCase):
+    def test_acts_like_array(self):
+        a = Array([1,2,3])
+        self.assertSequenceEqual(a, [1,2,3])
+        self.assertEqual(a[1], 2)
+        a[1] = 4
+        self.assertSequenceEqual(a, [1,4,3])
+        del a[1]
+        self.assertSequenceEqual(a, [1,3])
+        a.insert(1, 2)
+        self.assertSequenceEqual(a, [1,2,3])
+
+    def test_becomes_immutable(self):
+        a = Array([1,2,3])
+        s1 = Signal(range(len(a)))
+        s2 = Signal(range(len(a)))
+        v1 = a[s1]
+        v2 = a[s2]
+        with self.assertRaisesRegex(ValueError,
+                r"^Array can no longer be mutated after it was indexed with a value at "):
+            a[1] = 2
+        with self.assertRaisesRegex(ValueError,
+                r"^Array can no longer be mutated after it was indexed with a value at "):
+            del a[1]
+        with self.assertRaisesRegex(ValueError,
+                r"^Array can no longer be mutated after it was indexed with a value at "):
+            a.insert(1, 2)
+
+    def test_repr(self):
+        a = Array([1,2,3])
+        self.assertEqual(repr(a), "(array mutable [1, 2, 3])")
+        s = Signal(range(len(a)))
+        v = a[s]
+        self.assertEqual(repr(a), "(array [1, 2, 3])")
+
+
+class ArrayProxyTestCase(FHDLTestCase):
+    def test_index_shape(self):
+        m = Array(Array(x * y for y in range(1, 4)) for x in range(1, 4))
+        a = Signal(range(3))
+        b = Signal(range(3))
+        v = m[a][b]
+        self.assertEqual(v.shape(), unsigned(4))
+
+    def test_attr_shape(self):
+        from collections import namedtuple
+        pair = namedtuple("pair", ("p", "n"))
+        a = Array(pair(i, -i) for i in range(10))
+        s = Signal(range(len(a)))
+        v = a[s]
+        self.assertEqual(v.p.shape(), unsigned(4))
+        self.assertEqual(v.n.shape(), signed(5))
+
+    def test_attr_shape_signed(self):
+        # [unsigned(1), unsigned(1)] â†’ unsigned(1)
+        a1 = Array([1, 1])
+        v1 = a1[Const(0)]
+        self.assertEqual(v1.shape(), unsigned(1))
+        # [signed(1), signed(1)] â†’ signed(1)
+        a2 = Array([-1, -1])
+        v2 = a2[Const(0)]
+        self.assertEqual(v2.shape(), signed(1))
+        # [unsigned(1), signed(2)] â†’ signed(2)
+        a3 = Array([1, -2])
+        v3 = a3[Const(0)]
+        self.assertEqual(v3.shape(), signed(2))
+        # [unsigned(1), signed(1)] â†’ signed(2); 1st operand padded with sign bit!
+        a4 = Array([1, -1])
+        v4 = a4[Const(0)]
+        self.assertEqual(v4.shape(), signed(2))
+        # [unsigned(2), signed(1)] â†’ signed(3); 1st operand padded with sign bit!
+        a5 = Array([1, -1])
+        v5 = a5[Const(0)]
+        self.assertEqual(v5.shape(), signed(2))
+
+    def test_repr(self):
+        a = Array([1, 2, 3])
+        s = Signal(range(3))
+        v = a[s]
+        self.assertEqual(repr(v), "(proxy (array [1, 2, 3]) (sig s))")
+
+
+class SignalTestCase(FHDLTestCase):
+    def test_shape(self):
+        s1 = Signal()
+        self.assertEqual(s1.shape(), unsigned(1))
+        self.assertIsInstance(s1.shape(), Shape)
+        s2 = Signal(2)
+        self.assertEqual(s2.shape(), unsigned(2))
+        s3 = Signal(unsigned(2))
+        self.assertEqual(s3.shape(), unsigned(2))
+        s4 = Signal(signed(2))
+        self.assertEqual(s4.shape(), signed(2))
+        s5 = Signal(0)
+        self.assertEqual(s5.shape(), unsigned(0))
+        s6 = Signal(range(16))
+        self.assertEqual(s6.shape(), unsigned(4))
+        s7 = Signal(range(4, 16))
+        self.assertEqual(s7.shape(), unsigned(4))
+        s8 = Signal(range(-4, 16))
+        self.assertEqual(s8.shape(), signed(5))
+        s9 = Signal(range(-20, 16))
+        self.assertEqual(s9.shape(), signed(6))
+        s10 = Signal(range(0))
+        self.assertEqual(s10.shape(), unsigned(0))
+        s11 = Signal(range(1))
+        self.assertEqual(s11.shape(), unsigned(1))
+
+    def test_shape_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Width must be a non-negative integer, not -10$"):
+            Signal(-10)
+
+    def test_name(self):
+        s1 = Signal()
+        self.assertEqual(s1.name, "s1")
+        s2 = Signal(name="sig")
+        self.assertEqual(s2.name, "sig")
+
+    def test_reset(self):
+        s1 = Signal(4, reset=0b111, reset_less=True)
+        self.assertEqual(s1.reset, 0b111)
+        self.assertEqual(s1.reset_less, True)
+
+    def test_reset_enum(self):
+        s1 = Signal(2, reset=UnsignedEnum.BAR)
+        self.assertEqual(s1.reset, 2)
+        with self.assertRaisesRegex(TypeError,
+                r"^Reset value has to be an int or an integral Enum$"
+        ):
+            Signal(1, reset=StringEnum.FOO)
+
+    def test_reset_narrow(self):
+        with self.assertWarnsRegex(SyntaxWarning,
+                r"^Reset value 8 requires 4 bits to represent, but the signal only has 3 bits$"):
+            Signal(3, reset=8)
+        with self.assertWarnsRegex(SyntaxWarning,
+                r"^Reset value 4 requires 4 bits to represent, but the signal only has 3 bits$"):
+            Signal(signed(3), reset=4)
+        with self.assertWarnsRegex(SyntaxWarning,
+                r"^Reset value -5 requires 4 bits to represent, but the signal only has 3 bits$"):
+            Signal(signed(3), reset=-5)
+
+    def test_attrs(self):
+        s1 = Signal()
+        self.assertEqual(s1.attrs, {})
+        s2 = Signal(attrs={"no_retiming": True})
+        self.assertEqual(s2.attrs, {"no_retiming": True})
+
+    def test_repr(self):
+        s1 = Signal()
+        self.assertEqual(repr(s1), "(sig s1)")
+
+    def test_like(self):
+        s1 = Signal.like(Signal(4))
+        self.assertEqual(s1.shape(), unsigned(4))
+        s2 = Signal.like(Signal(range(-15, 1)))
+        self.assertEqual(s2.shape(), signed(5))
+        s3 = Signal.like(Signal(4, reset=0b111, reset_less=True))
+        self.assertEqual(s3.reset, 0b111)
+        self.assertEqual(s3.reset_less, True)
+        s4 = Signal.like(Signal(attrs={"no_retiming": True}))
+        self.assertEqual(s4.attrs, {"no_retiming": True})
+        s5 = Signal.like(Signal(decoder=str))
+        self.assertEqual(s5.decoder, str)
+        s6 = Signal.like(10)
+        self.assertEqual(s6.shape(), unsigned(4))
+        s7 = [Signal.like(Signal(4))][0]
+        self.assertEqual(s7.name, "$like")
+        s8 = Signal.like(s1, name_suffix="_ff")
+        self.assertEqual(s8.name, "s1_ff")
+
+    def test_decoder(self):
+        class Color(Enum):
+            RED  = 1
+            BLUE = 2
+        s = Signal(decoder=Color)
+        self.assertEqual(s.decoder(1), "RED/1")
+        self.assertEqual(s.decoder(3), "3")
+
+    def test_enum(self):
+        s1 = Signal(UnsignedEnum)
+        self.assertEqual(s1.shape(), unsigned(2))
+        s2 = Signal(SignedEnum)
+        self.assertEqual(s2.shape(), signed(2))
+        self.assertEqual(s2.decoder(SignedEnum.FOO), "FOO/-1")
+
+
+class ClockSignalTestCase(FHDLTestCase):
+    def test_domain(self):
+        s1 = ClockSignal()
+        self.assertEqual(s1.domain, "sync")
+        s2 = ClockSignal("pix")
+        self.assertEqual(s2.domain, "pix")
+
+        with self.assertRaisesRegex(TypeError,
+                r"^Clock domain name must be a string, not 1$"):
+            ClockSignal(1)
+
+    def test_shape(self):
+        s1 = ClockSignal()
+        self.assertEqual(s1.shape(), unsigned(1))
+        self.assertIsInstance(s1.shape(), Shape)
+
+    def test_repr(self):
+        s1 = ClockSignal()
+        self.assertEqual(repr(s1), "(clk sync)")
+
+    def test_wrong_name_comb(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Domain 'comb' does not have a clock$"):
+            ClockSignal("comb")
+
+
+class ResetSignalTestCase(FHDLTestCase):
+    def test_domain(self):
+        s1 = ResetSignal()
+        self.assertEqual(s1.domain, "sync")
+        s2 = ResetSignal("pix")
+        self.assertEqual(s2.domain, "pix")
+
+        with self.assertRaisesRegex(TypeError,
+                r"^Clock domain name must be a string, not 1$"):
+            ResetSignal(1)
+
+    def test_shape(self):
+        s1 = ResetSignal()
+        self.assertEqual(s1.shape(), unsigned(1))
+        self.assertIsInstance(s1.shape(), Shape)
+
+    def test_repr(self):
+        s1 = ResetSignal()
+        self.assertEqual(repr(s1), "(rst sync)")
+
+    def test_wrong_name_comb(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Domain 'comb' does not have a reset$"):
+            ResetSignal("comb")
+
+
+class MockUserValue(UserValue):
+    def __init__(self, lowered):
+        super().__init__()
+        self.lower_count = 0
+        self.lowered     = lowered
+
+    def lower(self):
+        self.lower_count += 1
+        return self.lowered
+
+
+class UserValueTestCase(FHDLTestCase):
+    def test_shape(self):
+        uv = MockUserValue(1)
+        self.assertEqual(uv.shape(), unsigned(1))
+        self.assertIsInstance(uv.shape(), Shape)
+        uv.lowered = 2
+        self.assertEqual(uv.shape(), unsigned(1))
+        self.assertEqual(uv.lower_count, 1)
+
+    def test_lower_to_user_value(self):
+        uv = MockUserValue(MockUserValue(1))
+        self.assertEqual(uv.shape(), unsigned(1))
+        self.assertIsInstance(uv.shape(), Shape)
+        uv.lowered = MockUserValue(2)
+        self.assertEqual(uv.shape(), unsigned(1))
+        self.assertEqual(uv.lower_count, 1)
+
+
+class SampleTestCase(FHDLTestCase):
+    def test_const(self):
+        s = Sample(1, 1, "sync")
+        self.assertEqual(s.shape(), unsigned(1))
+
+    def test_signal(self):
+        s1 = Sample(Signal(2), 1, "sync")
+        self.assertEqual(s1.shape(), unsigned(2))
+        s2 = Sample(ClockSignal(), 1, "sync")
+        s3 = Sample(ResetSignal(), 1, "sync")
+
+    def test_wrong_value_operator(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Sampled value must be a signal or a constant, not "
+                r"\(\+ \(sig \$signal\) \(const 1'd1\)\)$")):
+            Sample(Signal() + 1, 1, "sync")
+
+    def test_wrong_clocks_neg(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Cannot sample a value 1 cycles in the future$"):
+            Sample(Signal(), -1, "sync")
+
+    def test_wrong_domain(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Domain name must be a string or None, not 0$"):
+            Sample(Signal(), 1, 0)
+
+
+class InitialTestCase(FHDLTestCase):
+    def test_initial(self):
+        i = Initial()
+        self.assertEqual(i.shape(), unsigned(1))
diff --git a/tests/test_hdl_cd.py b/tests/test_hdl_cd.py
new file mode 100644 (file)
index 0000000..4e93fbc
--- /dev/null
@@ -0,0 +1,79 @@
+from nmigen.hdl.cd import *
+
+from .utils import *
+
+
+class ClockDomainTestCase(FHDLTestCase):
+    def test_name(self):
+        sync = ClockDomain()
+        self.assertEqual(sync.name, "sync")
+        self.assertEqual(sync.clk.name, "clk")
+        self.assertEqual(sync.rst.name, "rst")
+        self.assertEqual(sync.local, False)
+        pix = ClockDomain()
+        self.assertEqual(pix.name, "pix")
+        self.assertEqual(pix.clk.name, "pix_clk")
+        self.assertEqual(pix.rst.name, "pix_rst")
+        cd_pix = ClockDomain()
+        self.assertEqual(pix.name, "pix")
+        dom = [ClockDomain("foo")][0]
+        self.assertEqual(dom.name, "foo")
+        with self.assertRaisesRegex(ValueError,
+                r"^Clock domain name must be specified explicitly$"):
+            ClockDomain()
+        cd_reset = ClockDomain(local=True)
+        self.assertEqual(cd_reset.local, True)
+
+    def test_edge(self):
+        sync = ClockDomain()
+        self.assertEqual(sync.clk_edge, "pos")
+        sync = ClockDomain(clk_edge="pos")
+        self.assertEqual(sync.clk_edge, "pos")
+        sync = ClockDomain(clk_edge="neg")
+        self.assertEqual(sync.clk_edge, "neg")
+
+    def test_edge_wrong(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Domain clock edge must be one of 'pos' or 'neg', not 'xxx'$"):
+            ClockDomain("sync", clk_edge="xxx")
+
+    def test_with_reset(self):
+        pix = ClockDomain()
+        self.assertIsNotNone(pix.clk)
+        self.assertIsNotNone(pix.rst)
+        self.assertFalse(pix.async_reset)
+
+    def test_without_reset(self):
+        pix = ClockDomain(reset_less=True)
+        self.assertIsNotNone(pix.clk)
+        self.assertIsNone(pix.rst)
+        self.assertFalse(pix.async_reset)
+
+    def test_async_reset(self):
+        pix = ClockDomain(async_reset=True)
+        self.assertIsNotNone(pix.clk)
+        self.assertIsNotNone(pix.rst)
+        self.assertTrue(pix.async_reset)
+
+    def test_rename(self):
+        sync = ClockDomain()
+        self.assertEqual(sync.name, "sync")
+        self.assertEqual(sync.clk.name, "clk")
+        self.assertEqual(sync.rst.name, "rst")
+        sync.rename("pix")
+        self.assertEqual(sync.name, "pix")
+        self.assertEqual(sync.clk.name, "pix_clk")
+        self.assertEqual(sync.rst.name, "pix_rst")
+
+    def test_rename_reset_less(self):
+        sync = ClockDomain(reset_less=True)
+        self.assertEqual(sync.name, "sync")
+        self.assertEqual(sync.clk.name, "clk")
+        sync.rename("pix")
+        self.assertEqual(sync.name, "pix")
+        self.assertEqual(sync.clk.name, "pix_clk")
+
+    def test_wrong_name_comb(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Domain 'comb' may not be clocked$"):
+            comb = ClockDomain()
diff --git a/tests/test_hdl_dsl.py b/tests/test_hdl_dsl.py
new file mode 100644 (file)
index 0000000..8ec1be7
--- /dev/null
@@ -0,0 +1,770 @@
+# nmigen: UnusedElaboratable=no
+
+from collections import OrderedDict
+from enum import Enum
+
+from nmigen.hdl.ast import *
+from nmigen.hdl.cd import *
+from nmigen.hdl.dsl import *
+
+from .utils 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_cant_inherit(self):
+        with self.assertRaisesRegex(SyntaxError,
+                (r"^Instead of inheriting from `Module`, inherit from `Elaboratable` and "
+                    r"return a `Module` from the `elaborate\(self, platform\)` method$")):
+            class ORGate(Module):
+                pass
+
+    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.assertRaisesRegex(SyntaxError,
+                (r"^Driver-driver conflict: trying to drive \(sig c1\) from d\.sync, but it "
+                    r"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.assertRaisesRegex(AttributeError,
+                r"^Cannot assign 'd\.pix' attribute; did you mean 'd.pix \+='\?$"):
+            m.d.pix = None
+
+    def test_d_asgn_wrong(self):
+        m = Module()
+        with self.assertRaisesRegex(SyntaxError,
+                r"^Only assignments and property checks may be appended to d\.sync$"):
+            m.d.sync += Switch(self.s1, {})
+
+    def test_comb_wrong(self):
+        m = Module()
+        with self.assertRaisesRegex(AttributeError,
+                r"^'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.assertRaisesRegex(AttributeError,
+                r"^'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.assertRaisesRegex(AttributeError,
+                r"^'Module' object has no attribute 'nonexistentattr'$"):
+            m.nonexistentattr
+
+    def test_d_suspicious(self):
+        m = Module()
+        with self.assertWarnsRegex(SyntaxWarning,
+                (r"^Using '<module>\.d\.submodules' would add statements to clock domain "
+                    r"'submodules'; did you mean <module>\.submodules instead\?$")):
+            m.d.submodules += []
+
+    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)))
+                (default (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)))
+                    )
+                )
+                (default
+                    (eq (sig c3) (const 1'd1))
+                )
+            )
+        )
+        """)
+
+    def test_Elif_wrong(self):
+        m = Module()
+        with self.assertRaisesRegex(SyntaxError,
+                r"^Elif without preceding If$"):
+            with m.Elif(self.s2):
+                pass
+
+    def test_Else_wrong(self):
+        m = Module()
+        with self.assertRaisesRegex(SyntaxError,
+                r"^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_If_signed_suspicious(self):
+        m = Module()
+        with self.assertWarnsRegex(SyntaxWarning,
+                (r"^Signed values in If\/Elif conditions usually result from inverting Python "
+                    r"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
+                    r"`not flag`\. \(If this is a false positive, silence this warning with "
+                    r"`m\.If\(x\)` â†’ `m\.If\(x\.bool\(\)\)`\.\)$")):
+            with m.If(~True):
+                pass
+
+    def test_Elif_signed_suspicious(self):
+        m = Module()
+        with m.If(0):
+            pass
+        with self.assertWarnsRegex(SyntaxWarning,
+                (r"^Signed values in If\/Elif conditions usually result from inverting Python "
+                    r"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
+                    r"`not flag`\. \(If this is a false positive, silence this warning with "
+                    r"`m\.If\(x\)` â†’ `m\.If\(x\.bool\(\)\)`\.\)$")):
+            with m.Elif(~True):
+                pass
+
+    def test_if_If_Elif_Else(self):
+        m = Module()
+        with self.assertRaisesRegex(SyntaxError,
+                r"^`if m\.If\(\.\.\.\):` does not work; use `with m\.If\(\.\.\.\)`$"):
+            if m.If(0):
+                pass
+        with m.If(0):
+            pass
+        with self.assertRaisesRegex(SyntaxError,
+                r"^`if m\.Elif\(\.\.\.\):` does not work; use `with m\.Elif\(\.\.\.\)`$"):
+            if m.Elif(0):
+                pass
+        with self.assertRaisesRegex(SyntaxError,
+                r"^`if m\.Else\(\.\.\.\):` does not work; use `with m\.Else\(\.\.\.\)`$"):
+            if m.Else():
+                pass
+
+    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)
+            with m.Case("1 0--"):
+                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)))
+                (case 10-- (eq (sig c2) (const 1'd1)))
+            )
+        )
+        """)
+
+    def test_Switch_default_Case(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)))
+                (default (eq (sig c2) (const 1'd1)))
+            )
+        )
+        """)
+
+    def test_Switch_default_Default(self):
+        m = Module()
+        with m.Switch(self.w1):
+            with m.Case(3):
+                m.d.comb += self.c1.eq(1)
+            with m.Default():
+                m.d.comb += self.c2.eq(1)
+        m._flush()
+        self.assertRepr(m._statements, """
+        (
+            (switch (sig w1)
+                (case 0011 (eq (sig c1) (const 1'd1)))
+                (default (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_Switch_enum(self):
+        class Color(Enum):
+            RED  = 1
+            BLUE = 2
+        m = Module()
+        se = Signal(Color)
+        with m.Switch(se):
+            with m.Case(Color.RED):
+                m.d.comb += self.c1.eq(1)
+        self.assertRepr(m._statements, """
+        (
+            (switch (sig se)
+                (case 01 (eq (sig c1) (const 1'd1)))
+            )
+        )
+        """)
+
+    def test_Case_width_wrong(self):
+        class Color(Enum):
+            RED = 0b10101010
+        m = Module()
+        with m.Switch(self.w1):
+            with self.assertRaisesRegex(SyntaxError,
+                    r"^Case pattern '--' must have the same width as switch value \(which is 4\)$"):
+                with m.Case("--"):
+                    pass
+            with self.assertWarnsRegex(SyntaxWarning,
+                    (r"^Case pattern '10110' is wider than switch value \(which has width 4\); "
+                        r"comparison will never be true$")):
+                with m.Case(0b10110):
+                    pass
+            with self.assertWarnsRegex(SyntaxWarning,
+                    (r"^Case pattern '10101010' \(Color\.RED\) is wider than switch value "
+                        r"\(which has width 4\); comparison will never be true$")):
+                with m.Case(Color.RED):
+                    pass
+        self.assertRepr(m._statements, """
+        (
+            (switch (sig w1) )
+        )
+        """)
+
+    def test_Case_bits_wrong(self):
+        m = Module()
+        with m.Switch(self.w1):
+            with self.assertRaisesRegex(SyntaxError,
+                    (r"^Case pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
+                        r"and may include whitespace$")):
+                with m.Case("abc"):
+                    pass
+
+    def test_Case_pattern_wrong(self):
+        m = Module()
+        with m.Switch(self.w1):
+            with self.assertRaisesRegex(SyntaxError,
+                    r"^Case pattern must be an integer, a string, or an enumeration, not 1\.0$"):
+                with m.Case(1.0):
+                    pass
+
+    def test_Case_outside_Switch_wrong(self):
+        m = Module()
+        with self.assertRaisesRegex(SyntaxError,
+                r"^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.assertRaisesRegex(SyntaxError,
+                    (r"^If is not permitted directly inside of Switch; "
+                        r"it is permitted inside of Switch Case$")):
+                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_empty(self):
+        m = Module()
+        with m.FSM():
+            pass
+        self.assertRepr(m._statements, """
+        ()
+        """)
+
+    def test_FSM_wrong_domain(self):
+        m = Module()
+        with self.assertRaisesRegex(ValueError,
+                r"^FSM may not be driven by the 'comb' domain$"):
+            with m.FSM(domain="comb"):
+                pass
+
+    def test_FSM_wrong_undefined(self):
+        m = Module()
+        with self.assertRaisesRegex(NameError,
+                r"^FSM state 'FOO' is referenced but not defined$"):
+            with m.FSM() as fsm:
+                fsm.ongoing("FOO")
+
+    def test_FSM_wrong_redefined(self):
+        m = Module()
+        with m.FSM():
+            with m.State("FOO"):
+                pass
+            with self.assertRaisesRegex(NameError,
+                    r"^FSM state 'FOO' is already defined$"):
+                with m.State("FOO"):
+                    pass
+
+    def test_FSM_wrong_next(self):
+        m = Module()
+        with self.assertRaisesRegex(SyntaxError,
+                r"^Only assignment to `m\.next` is permitted$"):
+            m.next
+        with self.assertRaisesRegex(SyntaxError,
+                r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
+            m.next = "FOO"
+        with self.assertRaisesRegex(SyntaxError,
+                r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
+            with m.FSM():
+                m.next = "FOO"
+
+    def test_If_inside_FSM_wrong(self):
+        m = Module()
+        with m.FSM():
+            with m.State("FOO"):
+                pass
+            with self.assertRaisesRegex(SyntaxError,
+                    (r"^If is not permitted directly inside of FSM; "
+                        r"it is permitted inside of FSM State$")):
+                with m.If(self.s2):
+                    pass
+
+    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._anon_submodules, [m2])
+        self.assertEqual(m1._named_submodules, {})
+
+    def test_submodule_anon_multi(self):
+        m1 = Module()
+        m2 = Module()
+        m3 = Module()
+        m1.submodules += m2, m3
+        self.assertEqual(m1._anon_submodules, [m2, m3])
+        self.assertEqual(m1._named_submodules, {})
+
+    def test_submodule_named(self):
+        m1 = Module()
+        m2 = Module()
+        m1.submodules.foo = m2
+        self.assertEqual(m1._anon_submodules, [])
+        self.assertEqual(m1._named_submodules, {"foo": m2})
+
+    def test_submodule_named_index(self):
+        m1 = Module()
+        m2 = Module()
+        m1.submodules["foo"] = m2
+        self.assertEqual(m1._anon_submodules, [])
+        self.assertEqual(m1._named_submodules, {"foo": m2})
+
+    def test_submodule_wrong(self):
+        m = Module()
+        with self.assertRaisesRegex(TypeError,
+                r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
+            m.submodules.foo = 1
+        with self.assertRaisesRegex(TypeError,
+                r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
+            m.submodules += 1
+
+    def test_submodule_named_conflict(self):
+        m1 = Module()
+        m2 = Module()
+        m1.submodules.foo = m2
+        with self.assertRaisesRegex(NameError, r"^Submodule named 'foo' already exists$"):
+            m1.submodules.foo = m2
+
+    def test_submodule_get(self):
+        m1 = Module()
+        m2 = Module()
+        m1.submodules.foo = m2
+        m3 = m1.submodules.foo
+        self.assertEqual(m2, m3)
+
+    def test_submodule_get_index(self):
+        m1 = Module()
+        m2 = Module()
+        m1.submodules["foo"] = m2
+        m3 = m1.submodules["foo"]
+        self.assertEqual(m2, m3)
+
+    def test_submodule_get_unset(self):
+        m1 = Module()
+        with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"):
+            m2 = m1.submodules.foo
+        with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"):
+            m2 = m1.submodules["foo"]
+
+    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["foo"].name, "foo")
+
+    def test_domain_add_wrong(self):
+        m = Module()
+        with self.assertRaisesRegex(TypeError,
+                r"^Only clock domains may be added to `m\.domains`, not 1$"):
+            m.domains.foo = 1
+        with self.assertRaisesRegex(TypeError,
+                r"^Only clock domains may be added to `m\.domains`, not 1$"):
+            m.domains += 1
+
+    def test_domain_add_wrong_name(self):
+        m = Module()
+        with self.assertRaisesRegex(NameError,
+                r"^Clock domain name 'bar' must match name in `m\.domains\.foo \+= \.\.\.` syntax$"):
+            m.domains.foo = ClockDomain("bar")
+
+    def test_domain_add_wrong_duplicate(self):
+        m = Module()
+        m.domains += ClockDomain("foo")
+        with self.assertRaisesRegex(NameError,
+                r"^Clock domain named 'foo' already exists$"):
+            m.domains += ClockDomain("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)
diff --git a/tests/test_hdl_ir.py b/tests/test_hdl_ir.py
new file mode 100644 (file)
index 0000000..dc3b730
--- /dev/null
@@ -0,0 +1,863 @@
+# nmigen: UnusedElaboratable=no
+
+from collections import OrderedDict
+
+from nmigen.hdl.ast import *
+from nmigen.hdl.cd import *
+from nmigen.hdl.ir import *
+from nmigen.hdl.mem import *
+
+from .utils import *
+
+
+class BadElaboratable(Elaboratable):
+    def elaborate(self, platform):
+        return
+
+
+class FragmentGetTestCase(FHDLTestCase):
+    def test_get_wrong(self):
+        with self.assertRaisesRegex(AttributeError,
+                r"^Object None cannot be elaborated$"):
+            Fragment.get(None, platform=None)
+
+        with self.assertWarnsRegex(UserWarning,
+                r"^\.elaborate\(\) returned None; missing return statement\?$"):
+            with self.assertRaisesRegex(AttributeError,
+                    r"^Object None cannot be elaborated$"):
+                Fragment.get(BadElaboratable(), platform=None)
+
+
+class FragmentGeneratedTestCase(FHDLTestCase):
+    def test_find_subfragment(self):
+        f1 = Fragment()
+        f2 = Fragment()
+        f1.add_subfragment(f2, "f2")
+
+        self.assertEqual(f1.find_subfragment(0), f2)
+        self.assertEqual(f1.find_subfragment("f2"), f2)
+
+    def test_find_subfragment_wrong(self):
+        f1 = Fragment()
+        f2 = Fragment()
+        f1.add_subfragment(f2, "f2")
+
+        with self.assertRaisesRegex(NameError,
+                r"^No subfragment at index #1$"):
+            f1.find_subfragment(1)
+        with self.assertRaisesRegex(NameError,
+                r"^No subfragment with name 'fx'$"):
+            f1.find_subfragment("fx")
+
+    def test_find_generated(self):
+        f1 = Fragment()
+        f2 = Fragment()
+        f2.generated["sig"] = sig = Signal()
+        f1.add_subfragment(f2, "f2")
+
+        self.assertEqual(SignalKey(f1.find_generated("f2", "sig")),
+                         SignalKey(sig))
+
+
+class FragmentDriversTestCase(FHDLTestCase):
+    def test_empty(self):
+        f = Fragment()
+        self.assertEqual(list(f.iter_comb()), [])
+        self.assertEqual(list(f.iter_sync()), [])
+
+
+class FragmentPortsTestCase(FHDLTestCase):
+    def setUp(self):
+        self.s1 = Signal()
+        self.s2 = Signal()
+        self.s3 = Signal()
+        self.c1 = Signal()
+        self.c2 = Signal()
+        self.c3 = Signal()
+
+    def test_empty(self):
+        f = Fragment()
+        self.assertEqual(list(f.iter_ports()), [])
+
+        f._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f.ports, SignalDict([]))
+
+    def test_iter_signals(self):
+        f = Fragment()
+        f.add_ports(self.s1, self.s2, dir="io")
+        self.assertEqual(SignalSet((self.s1, self.s2)), f.iter_signals())
+
+    def test_self_contained(self):
+        f = Fragment()
+        f.add_statements(
+            self.c1.eq(self.s1),
+            self.s1.eq(self.c1)
+        )
+
+        f._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f.ports, SignalDict([]))
+
+    def test_infer_input(self):
+        f = Fragment()
+        f.add_statements(
+            self.c1.eq(self.s1)
+        )
+
+        f._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f.ports, SignalDict([
+            (self.s1, "i")
+        ]))
+
+    def test_request_output(self):
+        f = Fragment()
+        f.add_statements(
+            self.c1.eq(self.s1)
+        )
+
+        f._propagate_ports(ports=(self.c1,), all_undef_as_ports=True)
+        self.assertEqual(f.ports, SignalDict([
+            (self.s1, "i"),
+            (self.c1, "o")
+        ]))
+
+    def test_input_in_subfragment(self):
+        f1 = Fragment()
+        f1.add_statements(
+            self.c1.eq(self.s1)
+        )
+        f2 = Fragment()
+        f2.add_statements(
+            self.s1.eq(0)
+        )
+        f1.add_subfragment(f2)
+        f1._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f1.ports, SignalDict())
+        self.assertEqual(f2.ports, SignalDict([
+            (self.s1, "o"),
+        ]))
+
+    def test_input_only_in_subfragment(self):
+        f1 = Fragment()
+        f2 = Fragment()
+        f2.add_statements(
+            self.c1.eq(self.s1)
+        )
+        f1.add_subfragment(f2)
+        f1._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f1.ports, SignalDict([
+            (self.s1, "i"),
+        ]))
+        self.assertEqual(f2.ports, SignalDict([
+            (self.s1, "i"),
+        ]))
+
+    def test_output_from_subfragment(self):
+        f1 = Fragment()
+        f1.add_statements(
+            self.c1.eq(0)
+        )
+        f2 = Fragment()
+        f2.add_statements(
+            self.c2.eq(1)
+        )
+        f1.add_subfragment(f2)
+
+        f1._propagate_ports(ports=(self.c2,), all_undef_as_ports=True)
+        self.assertEqual(f1.ports, SignalDict([
+            (self.c2, "o"),
+        ]))
+        self.assertEqual(f2.ports, SignalDict([
+            (self.c2, "o"),
+        ]))
+
+    def test_output_from_subfragment_2(self):
+        f1 = Fragment()
+        f1.add_statements(
+            self.c1.eq(self.s1)
+        )
+        f2 = Fragment()
+        f2.add_statements(
+            self.c2.eq(self.s1)
+        )
+        f1.add_subfragment(f2)
+        f3 = Fragment()
+        f3.add_statements(
+            self.s1.eq(0)
+        )
+        f2.add_subfragment(f3)
+
+        f1._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f2.ports, SignalDict([
+            (self.s1, "o"),
+        ]))
+
+    def test_input_output_sibling(self):
+        f1 = Fragment()
+        f2 = Fragment()
+        f2.add_statements(
+            self.c1.eq(self.c2)
+        )
+        f1.add_subfragment(f2)
+        f3 = Fragment()
+        f3.add_statements(
+            self.c2.eq(0)
+        )
+        f3.add_driver(self.c2)
+        f1.add_subfragment(f3)
+
+        f1._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f1.ports, SignalDict())
+
+    def test_output_input_sibling(self):
+        f1 = Fragment()
+        f2 = Fragment()
+        f2.add_statements(
+            self.c2.eq(0)
+        )
+        f2.add_driver(self.c2)
+        f1.add_subfragment(f2)
+        f3 = Fragment()
+        f3.add_statements(
+            self.c1.eq(self.c2)
+        )
+        f1.add_subfragment(f3)
+
+        f1._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f1.ports, SignalDict())
+
+    def test_input_cd(self):
+        sync = ClockDomain()
+        f = Fragment()
+        f.add_statements(
+            self.c1.eq(self.s1)
+        )
+        f.add_domains(sync)
+        f.add_driver(self.c1, "sync")
+
+        f._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f.ports, SignalDict([
+            (self.s1,  "i"),
+            (sync.clk, "i"),
+            (sync.rst, "i"),
+        ]))
+
+    def test_input_cd_reset_less(self):
+        sync = ClockDomain(reset_less=True)
+        f = Fragment()
+        f.add_statements(
+            self.c1.eq(self.s1)
+        )
+        f.add_domains(sync)
+        f.add_driver(self.c1, "sync")
+
+        f._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f.ports, SignalDict([
+            (self.s1,  "i"),
+            (sync.clk, "i"),
+        ]))
+
+    def test_inout(self):
+        s = Signal()
+        f1 = Fragment()
+        f2 = Instance("foo", io_x=s)
+        f1.add_subfragment(f2)
+
+        f1._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f1.ports, SignalDict([
+            (s, "io")
+        ]))
+
+    def test_in_out_same_signal(self):
+        s = Signal()
+
+        f1 = Instance("foo", i_x=s, o_y=s)
+        f2 = Fragment()
+        f2.add_subfragment(f1)
+
+        f2._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f1.ports, SignalDict([
+            (s, "o")
+        ]))
+
+        f3 = Instance("foo", o_y=s, i_x=s)
+        f4 = Fragment()
+        f4.add_subfragment(f3)
+
+        f4._propagate_ports(ports=(), all_undef_as_ports=True)
+        self.assertEqual(f3.ports, SignalDict([
+            (s, "o")
+        ]))
+
+    def test_clk_rst(self):
+        sync = ClockDomain()
+        f = Fragment()
+        f.add_domains(sync)
+
+        f = f.prepare(ports=(ClockSignal("sync"), ResetSignal("sync")))
+        self.assertEqual(f.ports, SignalDict([
+            (sync.clk, "i"),
+            (sync.rst, "i"),
+        ]))
+
+    def test_port_wrong(self):
+        f = Fragment()
+        with self.assertRaisesRegex(TypeError,
+                r"^Only signals may be added as ports, not \(const 1'd1\)$"):
+            f.prepare(ports=(Const(1),))
+
+    def test_port_not_iterable(self):
+        f = Fragment()
+        with self.assertRaisesRegex(TypeError,
+                r"^`ports` must be either a list or a tuple, not 1$"):
+            f.prepare(ports=1)
+        with self.assertRaisesRegex(TypeError,
+                (r"^`ports` must be either a list or a tuple, not \(const 1'd1\)"
+                    r" \(did you mean `ports=\(<signal>,\)`, rather than `ports=<signal>`\?\)$")):
+            f.prepare(ports=Const(1))
+
+class FragmentDomainsTestCase(FHDLTestCase):
+    def test_iter_signals(self):
+        cd1 = ClockDomain()
+        cd2 = ClockDomain(reset_less=True)
+        s1 = Signal()
+        s2 = Signal()
+
+        f = Fragment()
+        f.add_domains(cd1, cd2)
+        f.add_driver(s1, "cd1")
+        self.assertEqual(SignalSet((cd1.clk, cd1.rst, s1)), f.iter_signals())
+        f.add_driver(s2, "cd2")
+        self.assertEqual(SignalSet((cd1.clk, cd1.rst, cd2.clk, s1, s2)), f.iter_signals())
+
+    def test_propagate_up(self):
+        cd = ClockDomain()
+
+        f1 = Fragment()
+        f2 = Fragment()
+        f1.add_subfragment(f2)
+        f2.add_domains(cd)
+
+        f1._propagate_domains_up()
+        self.assertEqual(f1.domains, {"cd": cd})
+
+    def test_propagate_up_local(self):
+        cd = ClockDomain(local=True)
+
+        f1 = Fragment()
+        f2 = Fragment()
+        f1.add_subfragment(f2)
+        f2.add_domains(cd)
+
+        f1._propagate_domains_up()
+        self.assertEqual(f1.domains, {})
+
+    def test_domain_conflict(self):
+        cda = ClockDomain("sync")
+        cdb = ClockDomain("sync")
+
+        fa = Fragment()
+        fa.add_domains(cda)
+        fb = Fragment()
+        fb.add_domains(cdb)
+        f = Fragment()
+        f.add_subfragment(fa, "a")
+        f.add_subfragment(fb, "b")
+
+        f._propagate_domains_up()
+        self.assertEqual(f.domains, {"a_sync": cda, "b_sync": cdb})
+        (fa, _), (fb, _) = f.subfragments
+        self.assertEqual(fa.domains, {"a_sync": cda})
+        self.assertEqual(fb.domains, {"b_sync": cdb})
+
+    def test_domain_conflict_anon(self):
+        cda = ClockDomain("sync")
+        cdb = ClockDomain("sync")
+
+        fa = Fragment()
+        fa.add_domains(cda)
+        fb = Fragment()
+        fb.add_domains(cdb)
+        f = Fragment()
+        f.add_subfragment(fa, "a")
+        f.add_subfragment(fb)
+
+        with self.assertRaisesRegex(DomainError,
+                (r"^Domain 'sync' is defined by subfragments 'a', <unnamed #1> of fragment "
+                    r"'top'; it is necessary to either rename subfragment domains explicitly, "
+                    r"or give names to subfragments$")):
+            f._propagate_domains_up()
+
+    def test_domain_conflict_name(self):
+        cda = ClockDomain("sync")
+        cdb = ClockDomain("sync")
+
+        fa = Fragment()
+        fa.add_domains(cda)
+        fb = Fragment()
+        fb.add_domains(cdb)
+        f = Fragment()
+        f.add_subfragment(fa, "x")
+        f.add_subfragment(fb, "x")
+
+        with self.assertRaisesRegex(DomainError,
+                (r"^Domain 'sync' is defined by subfragments #0, #1 of fragment 'top', some "
+                    r"of which have identical names; it is necessary to either rename subfragment "
+                    r"domains explicitly, or give distinct names to subfragments$")):
+            f._propagate_domains_up()
+
+    def test_domain_conflict_rename_drivers(self):
+        cda = ClockDomain("sync")
+        cdb = ClockDomain("sync")
+
+        fa = Fragment()
+        fa.add_domains(cda)
+        fb = Fragment()
+        fb.add_domains(cdb)
+        fb.add_driver(ResetSignal("sync"), None)
+        f = Fragment()
+        f.add_subfragment(fa, "a")
+        f.add_subfragment(fb, "b")
+
+        f._propagate_domains_up()
+        fb_new, _ = f.subfragments[1]
+        self.assertEqual(fb_new.drivers, OrderedDict({
+            None: SignalSet((ResetSignal("b_sync"),))
+        }))
+
+    def test_domain_conflict_rename_drivers(self):
+        cda = ClockDomain("sync")
+        cdb = ClockDomain("sync")
+        s = Signal()
+
+        fa = Fragment()
+        fa.add_domains(cda)
+        fb = Fragment()
+        fb.add_domains(cdb)
+        f = Fragment()
+        f.add_subfragment(fa, "a")
+        f.add_subfragment(fb, "b")
+        f.add_driver(s, "b_sync")
+
+        f._propagate_domains(lambda name: ClockDomain(name))
+
+    def test_propagate_down(self):
+        cd = ClockDomain()
+
+        f1 = Fragment()
+        f2 = Fragment()
+        f1.add_domains(cd)
+        f1.add_subfragment(f2)
+
+        f1._propagate_domains_down()
+        self.assertEqual(f2.domains, {"cd": cd})
+
+    def test_propagate_down_idempotent(self):
+        cd = ClockDomain()
+
+        f1 = Fragment()
+        f1.add_domains(cd)
+        f2 = Fragment()
+        f2.add_domains(cd)
+        f1.add_subfragment(f2)
+
+        f1._propagate_domains_down()
+        self.assertEqual(f1.domains, {"cd": cd})
+        self.assertEqual(f2.domains, {"cd": cd})
+
+    def test_propagate(self):
+        cd = ClockDomain()
+
+        f1 = Fragment()
+        f2 = Fragment()
+        f1.add_domains(cd)
+        f1.add_subfragment(f2)
+
+        new_domains = f1._propagate_domains(missing_domain=lambda name: None)
+        self.assertEqual(f1.domains, {"cd": cd})
+        self.assertEqual(f2.domains, {"cd": cd})
+        self.assertEqual(new_domains, [])
+
+    def test_propagate_missing(self):
+        s1 = Signal()
+        f1 = Fragment()
+        f1.add_driver(s1, "sync")
+
+        with self.assertRaisesRegex(DomainError,
+                r"^Domain 'sync' is used but not defined$"):
+            f1._propagate_domains(missing_domain=lambda name: None)
+
+    def test_propagate_create_missing(self):
+        s1 = Signal()
+        f1 = Fragment()
+        f1.add_driver(s1, "sync")
+        f2 = Fragment()
+        f1.add_subfragment(f2)
+
+        new_domains = f1._propagate_domains(missing_domain=lambda name: ClockDomain(name))
+        self.assertEqual(f1.domains.keys(), {"sync"})
+        self.assertEqual(f2.domains.keys(), {"sync"})
+        self.assertEqual(f1.domains["sync"], f2.domains["sync"])
+        self.assertEqual(new_domains, [f1.domains["sync"]])
+
+    def test_propagate_create_missing_fragment(self):
+        s1 = Signal()
+        f1 = Fragment()
+        f1.add_driver(s1, "sync")
+
+        cd = ClockDomain("sync")
+        f2 = Fragment()
+        f2.add_domains(cd)
+
+        new_domains = f1._propagate_domains(missing_domain=lambda name: f2)
+        self.assertEqual(f1.domains.keys(), {"sync"})
+        self.assertEqual(f1.domains["sync"], f2.domains["sync"])
+        self.assertEqual(new_domains, [])
+        self.assertEqual(f1.subfragments, [
+            (f2, "cd_sync")
+        ])
+
+    def test_propagate_create_missing_fragment_many_domains(self):
+        s1 = Signal()
+        f1 = Fragment()
+        f1.add_driver(s1, "sync")
+
+        cd_por  = ClockDomain("por")
+        cd_sync = ClockDomain("sync")
+        f2 = Fragment()
+        f2.add_domains(cd_por, cd_sync)
+
+        new_domains = f1._propagate_domains(missing_domain=lambda name: f2)
+        self.assertEqual(f1.domains.keys(), {"sync", "por"})
+        self.assertEqual(f2.domains.keys(), {"sync", "por"})
+        self.assertEqual(f1.domains["sync"], f2.domains["sync"])
+        self.assertEqual(new_domains, [])
+        self.assertEqual(f1.subfragments, [
+            (f2, "cd_sync")
+        ])
+
+    def test_propagate_create_missing_fragment_wrong(self):
+        s1 = Signal()
+        f1 = Fragment()
+        f1.add_driver(s1, "sync")
+
+        f2 = Fragment()
+        f2.add_domains(ClockDomain("foo"))
+
+        with self.assertRaisesRegex(DomainError,
+                (r"^Fragment returned by missing domain callback does not define requested "
+                    r"domain 'sync' \(defines 'foo'\)\.$")):
+            f1._propagate_domains(missing_domain=lambda name: f2)
+
+
+class FragmentHierarchyConflictTestCase(FHDLTestCase):
+    def setUp_self_sub(self):
+        self.s1 = Signal()
+        self.c1 = Signal()
+        self.c2 = Signal()
+
+        self.f1 = Fragment()
+        self.f1.add_statements(self.c1.eq(0))
+        self.f1.add_driver(self.s1)
+        self.f1.add_driver(self.c1, "sync")
+
+        self.f1a = Fragment()
+        self.f1.add_subfragment(self.f1a, "f1a")
+
+        self.f2 = Fragment()
+        self.f2.add_statements(self.c2.eq(1))
+        self.f2.add_driver(self.s1)
+        self.f2.add_driver(self.c2, "sync")
+        self.f1.add_subfragment(self.f2)
+
+        self.f1b = Fragment()
+        self.f1.add_subfragment(self.f1b, "f1b")
+
+        self.f2a = Fragment()
+        self.f2.add_subfragment(self.f2a, "f2a")
+
+    def test_conflict_self_sub(self):
+        self.setUp_self_sub()
+
+        self.f1._resolve_hierarchy_conflicts(mode="silent")
+        self.assertEqual(self.f1.subfragments, [
+            (self.f1a, "f1a"),
+            (self.f1b, "f1b"),
+            (self.f2a, "f2a"),
+        ])
+        self.assertRepr(self.f1.statements, """
+        (
+            (eq (sig c1) (const 1'd0))
+            (eq (sig c2) (const 1'd1))
+        )
+        """)
+        self.assertEqual(self.f1.drivers, {
+            None:   SignalSet((self.s1,)),
+            "sync": SignalSet((self.c1, self.c2)),
+        })
+
+    def test_conflict_self_sub_error(self):
+        self.setUp_self_sub()
+
+        with self.assertRaisesRegex(DriverConflict,
+               r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.<unnamed #1>$"):
+            self.f1._resolve_hierarchy_conflicts(mode="error")
+
+    def test_conflict_self_sub_warning(self):
+        self.setUp_self_sub()
+
+        with self.assertWarnsRegex(DriverConflict,
+                (r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.<unnamed #1>; "
+                    r"hierarchy will be flattened$")):
+            self.f1._resolve_hierarchy_conflicts(mode="warn")
+
+    def setUp_sub_sub(self):
+        self.s1 = Signal()
+        self.c1 = Signal()
+        self.c2 = Signal()
+
+        self.f1 = Fragment()
+
+        self.f2 = Fragment()
+        self.f2.add_driver(self.s1)
+        self.f2.add_statements(self.c1.eq(0))
+        self.f1.add_subfragment(self.f2)
+
+        self.f3 = Fragment()
+        self.f3.add_driver(self.s1)
+        self.f3.add_statements(self.c2.eq(1))
+        self.f1.add_subfragment(self.f3)
+
+    def test_conflict_sub_sub(self):
+        self.setUp_sub_sub()
+
+        self.f1._resolve_hierarchy_conflicts(mode="silent")
+        self.assertEqual(self.f1.subfragments, [])
+        self.assertRepr(self.f1.statements, """
+        (
+            (eq (sig c1) (const 1'd0))
+            (eq (sig c2) (const 1'd1))
+        )
+        """)
+
+    def setUp_self_subsub(self):
+        self.s1 = Signal()
+        self.c1 = Signal()
+        self.c2 = Signal()
+
+        self.f1 = Fragment()
+        self.f1.add_driver(self.s1)
+
+        self.f2 = Fragment()
+        self.f2.add_statements(self.c1.eq(0))
+        self.f1.add_subfragment(self.f2)
+
+        self.f3 = Fragment()
+        self.f3.add_driver(self.s1)
+        self.f3.add_statements(self.c2.eq(1))
+        self.f2.add_subfragment(self.f3)
+
+    def test_conflict_self_subsub(self):
+        self.setUp_self_subsub()
+
+        self.f1._resolve_hierarchy_conflicts(mode="silent")
+        self.assertEqual(self.f1.subfragments, [])
+        self.assertRepr(self.f1.statements, """
+        (
+            (eq (sig c1) (const 1'd0))
+            (eq (sig c2) (const 1'd1))
+        )
+        """)
+
+    def setUp_memory(self):
+        self.m = Memory(width=8, depth=4)
+        self.fr = self.m.read_port().elaborate(platform=None)
+        self.fw = self.m.write_port().elaborate(platform=None)
+        self.f1 = Fragment()
+        self.f2 = Fragment()
+        self.f2.add_subfragment(self.fr)
+        self.f1.add_subfragment(self.f2)
+        self.f3 = Fragment()
+        self.f3.add_subfragment(self.fw)
+        self.f1.add_subfragment(self.f3)
+
+    def test_conflict_memory(self):
+        self.setUp_memory()
+
+        self.f1._resolve_hierarchy_conflicts(mode="silent")
+        self.assertEqual(self.f1.subfragments, [
+            (self.fr, None),
+            (self.fw, None),
+        ])
+
+    def test_conflict_memory_error(self):
+        self.setUp_memory()
+
+        with self.assertRaisesRegex(DriverConflict,
+                r"^Memory 'm' is accessed from multiple fragments: top\.<unnamed #0>, "
+                    r"top\.<unnamed #1>$"):
+            self.f1._resolve_hierarchy_conflicts(mode="error")
+
+    def test_conflict_memory_warning(self):
+        self.setUp_memory()
+
+        with self.assertWarnsRegex(DriverConflict,
+                (r"^Memory 'm' is accessed from multiple fragments: top.<unnamed #0>, "
+                    r"top.<unnamed #1>; hierarchy will be flattened$")):
+            self.f1._resolve_hierarchy_conflicts(mode="warn")
+
+    def test_explicit_flatten(self):
+        self.f1 = Fragment()
+        self.f2 = Fragment()
+        self.f2.flatten = True
+        self.f1.add_subfragment(self.f2)
+
+        self.f1._resolve_hierarchy_conflicts(mode="silent")
+        self.assertEqual(self.f1.subfragments, [])
+
+    def test_no_conflict_local_domains(self):
+        f1 = Fragment()
+        cd1 = ClockDomain("d", local=True)
+        f1.add_domains(cd1)
+        f1.add_driver(ClockSignal("d"))
+        f2 = Fragment()
+        cd2 = ClockDomain("d", local=True)
+        f2.add_domains(cd2)
+        f2.add_driver(ClockSignal("d"))
+        f3 = Fragment()
+        f3.add_subfragment(f1)
+        f3.add_subfragment(f2)
+        f3.prepare()
+
+
+class InstanceTestCase(FHDLTestCase):
+    def test_construct(self):
+        s1 = Signal()
+        s2 = Signal()
+        s3 = Signal()
+        s4 = Signal()
+        s5 = Signal()
+        s6 = Signal()
+        inst = Instance("foo",
+            ("a", "ATTR1", 1),
+            ("p", "PARAM1", 0x1234),
+            ("i", "s1", s1),
+            ("o", "s2", s2),
+            ("io", "s3", s3),
+            a_ATTR2=2,
+            p_PARAM2=0x5678,
+            i_s4=s4,
+            o_s5=s5,
+            io_s6=s6,
+        )
+        self.assertEqual(inst.attrs, OrderedDict([
+            ("ATTR1", 1),
+            ("ATTR2", 2),
+        ]))
+        self.assertEqual(inst.parameters, OrderedDict([
+            ("PARAM1", 0x1234),
+            ("PARAM2", 0x5678),
+        ]))
+        self.assertEqual(inst.named_ports, OrderedDict([
+            ("s1", (s1, "i")),
+            ("s2", (s2, "o")),
+            ("s3", (s3, "io")),
+            ("s4", (s4, "i")),
+            ("s5", (s5, "o")),
+            ("s6", (s6, "io")),
+        ]))
+
+    def test_cast_ports(self):
+        inst = Instance("foo",
+            ("i", "s1", 1),
+            ("o", "s2", 2),
+            ("io", "s3", 3),
+            i_s4=4,
+            o_s5=5,
+            io_s6=6,
+        )
+        self.assertRepr(inst.named_ports["s1"][0], "(const 1'd1)")
+        self.assertRepr(inst.named_ports["s2"][0], "(const 2'd2)")
+        self.assertRepr(inst.named_ports["s3"][0], "(const 2'd3)")
+        self.assertRepr(inst.named_ports["s4"][0], "(const 3'd4)")
+        self.assertRepr(inst.named_ports["s5"][0], "(const 3'd5)")
+        self.assertRepr(inst.named_ports["s6"][0], "(const 3'd6)")
+
+    def test_wrong_construct_arg(self):
+        s = Signal()
+        with self.assertRaisesRegex(NameError,
+                (r"^Instance argument \('', 's1', \(sig s\)\) should be a tuple "
+                    r"\(kind, name, value\) where kind is one of \"p\", \"i\", \"o\", or \"io\"$")):
+            Instance("foo", ("", "s1", s))
+
+    def test_wrong_construct_kwarg(self):
+        s = Signal()
+        with self.assertRaisesRegex(NameError,
+                (r"^Instance keyword argument x_s1=\(sig s\) does not start with one of "
+                    r"\"p_\", \"i_\", \"o_\", or \"io_\"$")):
+            Instance("foo", x_s1=s)
+
+    def setUp_cpu(self):
+        self.rst = Signal()
+        self.stb = Signal()
+        self.pins = Signal(8)
+        self.datal = Signal(4)
+        self.datah = Signal(4)
+        self.inst = Instance("cpu",
+            p_RESET=0x1234,
+            i_clk=ClockSignal(),
+            i_rst=self.rst,
+            o_stb=self.stb,
+            o_data=Cat(self.datal, self.datah),
+            io_pins=self.pins[:]
+        )
+        self.wrap = Fragment()
+        self.wrap.add_subfragment(self.inst)
+
+    def test_init(self):
+        self.setUp_cpu()
+        f = self.inst
+        self.assertEqual(f.type, "cpu")
+        self.assertEqual(f.parameters, OrderedDict([("RESET", 0x1234)]))
+        self.assertEqual(list(f.named_ports.keys()), ["clk", "rst", "stb", "data", "pins"])
+        self.assertEqual(f.ports, SignalDict([]))
+
+    def test_prepare(self):
+        self.setUp_cpu()
+        f = self.wrap.prepare()
+        sync_clk = f.domains["sync"].clk
+        self.assertEqual(f.ports, SignalDict([
+            (sync_clk, "i"),
+            (self.rst, "i"),
+            (self.pins, "io"),
+        ]))
+
+    def test_prepare_explicit_ports(self):
+        self.setUp_cpu()
+        f = self.wrap.prepare(ports=[self.rst, self.stb])
+        sync_clk = f.domains["sync"].clk
+        sync_rst = f.domains["sync"].rst
+        self.assertEqual(f.ports, SignalDict([
+            (sync_clk, "i"),
+            (sync_rst, "i"),
+            (self.rst, "i"),
+            (self.stb, "o"),
+            (self.pins, "io"),
+        ]))
+
+    def test_prepare_slice_in_port(self):
+        s = Signal(2)
+        f = Fragment()
+        f.add_subfragment(Instance("foo", o_O=s[0]))
+        f.add_subfragment(Instance("foo", o_O=s[1]))
+        fp = f.prepare(ports=[s], missing_domain=lambda name: None)
+        self.assertEqual(fp.ports, SignalDict([
+            (s, "o"),
+        ]))
+
+    def test_prepare_attrs(self):
+        self.setUp_cpu()
+        self.inst.attrs["ATTR"] = 1
+        f = self.inst.prepare()
+        self.assertEqual(f.attrs, OrderedDict([
+            ("ATTR", 1),
+        ]))
diff --git a/tests/test_hdl_mem.py b/tests/test_hdl_mem.py
new file mode 100644 (file)
index 0000000..ef0a885
--- /dev/null
@@ -0,0 +1,138 @@
+# nmigen: UnusedElaboratable=no
+
+from nmigen.hdl.ast import *
+from nmigen.hdl.mem import *
+
+from .utils import *
+
+
+class MemoryTestCase(FHDLTestCase):
+    def test_name(self):
+        m1 = Memory(width=8, depth=4)
+        self.assertEqual(m1.name, "m1")
+        m2 = [Memory(width=8, depth=4)][0]
+        self.assertEqual(m2.name, "$memory")
+        m3 = Memory(width=8, depth=4, name="foo")
+        self.assertEqual(m3.name, "foo")
+
+    def test_geometry(self):
+        m = Memory(width=8, depth=4)
+        self.assertEqual(m.width, 8)
+        self.assertEqual(m.depth, 4)
+
+    def test_geometry_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Memory width must be a non-negative integer, not -1$"):
+            m = Memory(width=-1, depth=4)
+        with self.assertRaisesRegex(TypeError,
+                r"^Memory depth must be a non-negative integer, not -1$"):
+            m = Memory(width=8, depth=-1)
+
+    def test_init(self):
+        m = Memory(width=8, depth=4, init=range(4))
+        self.assertEqual(m.init, [0, 1, 2, 3])
+
+    def test_init_wrong_count(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Memory initialization value count exceed memory depth \(8 > 4\)$"):
+            m = Memory(width=8, depth=4, init=range(8))
+
+    def test_init_wrong_type(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Memory initialization value at address 1: "
+                    r"'str' object cannot be interpreted as an integer$")):
+            m = Memory(width=8, depth=4, init=[1, "0"])
+
+    def test_attrs(self):
+        m1 = Memory(width=8, depth=4)
+        self.assertEqual(m1.attrs, {})
+        m2 = Memory(width=8, depth=4, attrs={"ram_block": True})
+        self.assertEqual(m2.attrs, {"ram_block": True})
+
+    def test_read_port_transparent(self):
+        mem    = Memory(width=8, depth=4)
+        rdport = mem.read_port()
+        self.assertEqual(rdport.memory, mem)
+        self.assertEqual(rdport.domain, "sync")
+        self.assertEqual(rdport.transparent, True)
+        self.assertEqual(len(rdport.addr), 2)
+        self.assertEqual(len(rdport.data), 8)
+        self.assertEqual(len(rdport.en), 1)
+        self.assertIsInstance(rdport.en, Const)
+        self.assertEqual(rdport.en.value, 1)
+
+    def test_read_port_non_transparent(self):
+        mem    = Memory(width=8, depth=4)
+        rdport = mem.read_port(transparent=False)
+        self.assertEqual(rdport.memory, mem)
+        self.assertEqual(rdport.domain, "sync")
+        self.assertEqual(rdport.transparent, False)
+        self.assertEqual(len(rdport.en), 1)
+        self.assertIsInstance(rdport.en, Signal)
+        self.assertEqual(rdport.en.reset, 1)
+
+    def test_read_port_asynchronous(self):
+        mem    = Memory(width=8, depth=4)
+        rdport = mem.read_port(domain="comb")
+        self.assertEqual(rdport.memory, mem)
+        self.assertEqual(rdport.domain, "comb")
+        self.assertEqual(rdport.transparent, True)
+        self.assertEqual(len(rdport.en), 1)
+        self.assertIsInstance(rdport.en, Const)
+        self.assertEqual(rdport.en.value, 1)
+
+    def test_read_port_wrong(self):
+        mem = Memory(width=8, depth=4)
+        with self.assertRaisesRegex(ValueError,
+                r"^Read port cannot be simultaneously asynchronous and non-transparent$"):
+            mem.read_port(domain="comb", transparent=False)
+
+    def test_write_port(self):
+        mem    = Memory(width=8, depth=4)
+        wrport = mem.write_port()
+        self.assertEqual(wrport.memory, mem)
+        self.assertEqual(wrport.domain, "sync")
+        self.assertEqual(wrport.granularity, 8)
+        self.assertEqual(len(wrport.addr), 2)
+        self.assertEqual(len(wrport.data), 8)
+        self.assertEqual(len(wrport.en), 1)
+
+    def test_write_port_granularity(self):
+        mem    = Memory(width=8, depth=4)
+        wrport = mem.write_port(granularity=2)
+        self.assertEqual(wrport.memory, mem)
+        self.assertEqual(wrport.domain, "sync")
+        self.assertEqual(wrport.granularity, 2)
+        self.assertEqual(len(wrport.addr), 2)
+        self.assertEqual(len(wrport.data), 8)
+        self.assertEqual(len(wrport.en), 4)
+
+    def test_write_port_granularity_wrong(self):
+        mem = Memory(width=8, depth=4)
+        with self.assertRaisesRegex(TypeError,
+                r"^Write port granularity must be a non-negative integer, not -1$"):
+            mem.write_port(granularity=-1)
+        with self.assertRaisesRegex(ValueError,
+                r"^Write port granularity must not be greater than memory width \(10 > 8\)$"):
+            mem.write_port(granularity=10)
+        with self.assertRaisesRegex(ValueError,
+                r"^Write port granularity must divide memory width evenly$"):
+            mem.write_port(granularity=3)
+
+
+class DummyPortTestCase(FHDLTestCase):
+    def test_name(self):
+        p1 = DummyPort(data_width=8, addr_width=2)
+        self.assertEqual(p1.addr.name, "p1_addr")
+        p2 = [DummyPort(data_width=8, addr_width=2)][0]
+        self.assertEqual(p2.addr.name, "dummy_addr")
+        p3 = DummyPort(data_width=8, addr_width=2, name="foo")
+        self.assertEqual(p3.addr.name, "foo_addr")
+
+    def test_sizes(self):
+        p1 = DummyPort(data_width=8, addr_width=2)
+        self.assertEqual(p1.addr.width, 2)
+        self.assertEqual(p1.data.width, 8)
+        self.assertEqual(p1.en.width, 1)
+        p2 = DummyPort(data_width=8, addr_width=2, granularity=2)
+        self.assertEqual(p2.en.width, 4)
diff --git a/tests/test_hdl_rec.py b/tests/test_hdl_rec.py
new file mode 100644 (file)
index 0000000..718fa4a
--- /dev/null
@@ -0,0 +1,330 @@
+from enum import Enum
+
+from nmigen.hdl.ast import *
+from nmigen.hdl.rec import *
+
+from .utils import *
+
+
+class UnsignedEnum(Enum):
+    FOO = 1
+    BAR = 2
+    BAZ = 3
+
+
+class LayoutTestCase(FHDLTestCase):
+    def assertFieldEqual(self, field, expected):
+        (shape, dir) = field
+        shape = Shape.cast(shape)
+        self.assertEqual((shape, dir), expected)
+
+    def test_fields(self):
+        layout = Layout.cast([
+            ("cyc",  1),
+            ("data", signed(32)),
+            ("stb",  1, DIR_FANOUT),
+            ("ack",  1, DIR_FANIN),
+            ("info", [
+                ("a", 1),
+                ("b", 1),
+            ])
+        ])
+
+        self.assertFieldEqual(layout["cyc"], ((1, False), DIR_NONE))
+        self.assertFieldEqual(layout["data"], ((32, True), DIR_NONE))
+        self.assertFieldEqual(layout["stb"], ((1, False), DIR_FANOUT))
+        self.assertFieldEqual(layout["ack"], ((1, False), DIR_FANIN))
+        sublayout = layout["info"][0]
+        self.assertEqual(layout["info"][1], DIR_NONE)
+        self.assertFieldEqual(sublayout["a"], ((1, False), DIR_NONE))
+        self.assertFieldEqual(sublayout["b"], ((1, False), DIR_NONE))
+
+    def test_enum_field(self):
+        layout = Layout.cast([
+            ("enum", UnsignedEnum),
+            ("enum_dir", UnsignedEnum, DIR_FANOUT),
+        ])
+        self.assertFieldEqual(layout["enum"], ((2, False), DIR_NONE))
+        self.assertFieldEqual(layout["enum_dir"], ((2, False), DIR_FANOUT))
+
+    def test_range_field(self):
+        layout = Layout.cast([
+            ("range", range(0, 7)),
+        ])
+        self.assertFieldEqual(layout["range"], ((3, False), DIR_NONE))
+
+    def test_slice_tuple(self):
+        layout = Layout.cast([
+            ("a", 1),
+            ("b", 2),
+            ("c", 3)
+        ])
+        expect = Layout.cast([
+            ("a", 1),
+            ("c", 3)
+        ])
+        self.assertEqual(layout["a", "c"], expect)
+
+    def test_repr(self):
+        self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", signed(2))])),
+                         "Layout([('a', unsigned(1)), ('b', signed(2))])")
+        self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", [("c", signed(3))])])),
+                         "Layout([('a', unsigned(1)), "
+                            "('b', Layout([('c', signed(3))]))])")
+
+    def test_wrong_field(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Field \(1,\) has invalid layout: should be either \(name, shape\) or "
+                    r"\(name, shape, direction\)$")):
+            Layout.cast([(1,)])
+
+    def test_wrong_name(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Field \(1, 1\) has invalid name: should be a string$"):
+            Layout.cast([(1, 1)])
+
+    def test_wrong_name_duplicate(self):
+        with self.assertRaisesRegex(NameError,
+                r"^Field \('a', 2\) has a name that is already present in the layout$"):
+            Layout.cast([("a", 1), ("a", 2)])
+
+    def test_wrong_direction(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Field \('a', 1, 0\) has invalid direction: should be a Direction "
+                    r"instance like DIR_FANIN$")):
+            Layout.cast([("a", 1, 0)])
+
+    def test_wrong_shape(self):
+        with self.assertRaisesRegex(TypeError,
+                (r"^Field \('a', 'x'\) has invalid shape: should be castable to Shape or "
+                    r"a list of fields of a nested record$")):
+            Layout.cast([("a", "x")])
+
+
+class RecordTestCase(FHDLTestCase):
+    def test_basic(self):
+        r = Record([
+            ("stb",  1),
+            ("data", 32),
+            ("info", [
+                ("a", 1),
+                ("b", 1),
+            ])
+        ])
+
+        self.assertEqual(repr(r), "(rec r stb data (rec r__info a b))")
+        self.assertEqual(len(r),  35)
+        self.assertIsInstance(r.stb, Signal)
+        self.assertEqual(r.stb.name, "r__stb")
+        self.assertEqual(r["stb"].name, "r__stb")
+
+        self.assertTrue(hasattr(r, "stb"))
+        self.assertFalse(hasattr(r, "xxx"))
+
+    def test_unnamed(self):
+        r = [Record([
+            ("stb", 1)
+        ])][0]
+
+        self.assertEqual(repr(r), "(rec <unnamed> stb)")
+        self.assertEqual(r.stb.name, "stb")
+
+    def test_iter(self):
+        r = Record([
+            ("data", 4),
+            ("stb",  1),
+        ])
+
+        self.assertEqual(repr(r[0]),   "(slice (rec r data stb) 0:1)")
+        self.assertEqual(repr(r[0:3]), "(slice (rec r data stb) 0:3)")
+
+    def test_wrong_field(self):
+        r = Record([
+            ("stb", 1),
+            ("ack", 1),
+        ])
+        with self.assertRaisesRegex(AttributeError,
+                r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
+            r["en"]
+        with self.assertRaisesRegex(AttributeError,
+                r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
+            r.en
+
+    def test_wrong_field_unnamed(self):
+        r = [Record([
+            ("stb", 1),
+            ("ack", 1),
+        ])][0]
+        with self.assertRaisesRegex(AttributeError,
+                r"^Unnamed record does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
+            r.en
+
+    def test_construct_with_fields(self):
+        ns = Signal(1)
+        nr = Record([
+            ("burst", 1)
+        ])
+        r = Record([
+            ("stb", 1),
+            ("info", [
+                ("burst", 1)
+            ])
+        ], fields={
+            "stb":  ns,
+            "info": nr
+        })
+        self.assertIs(r.stb, ns)
+        self.assertIs(r.info, nr)
+
+    def test_like(self):
+        r1 = Record([("a", 1), ("b", 2)])
+        r2 = Record.like(r1)
+        self.assertEqual(r1.layout, r2.layout)
+        self.assertEqual(r2.name, "r2")
+        r3 = Record.like(r1, name="foo")
+        self.assertEqual(r3.name, "foo")
+        r4 = Record.like(r1, name_suffix="foo")
+        self.assertEqual(r4.name, "r1foo")
+
+    def test_like_modifications(self):
+        r1 = Record([("a", 1), ("b", [("s", 1)])])
+        self.assertEqual(r1.a.name, "r1__a")
+        self.assertEqual(r1.b.name, "r1__b")
+        self.assertEqual(r1.b.s.name, "r1__b__s")
+        r1.a.reset = 1
+        r1.b.s.reset = 1
+        r2 = Record.like(r1)
+        self.assertEqual(r2.a.reset, 1)
+        self.assertEqual(r2.b.s.reset, 1)
+        self.assertEqual(r2.a.name, "r2__a")
+        self.assertEqual(r2.b.name, "r2__b")
+        self.assertEqual(r2.b.s.name, "r2__b__s")
+
+    def test_slice_tuple(self):
+        r1 = Record([("a", 1), ("b", 2), ("c", 3)])
+        r2 = r1["a", "c"]
+        self.assertEqual(r2.layout, Layout([("a", 1), ("c", 3)]))
+        self.assertIs(r2.a, r1.a)
+        self.assertIs(r2.c, r1.c)
+
+    def test_enum_decoder(self):
+        r1 = Record([("a", UnsignedEnum)])
+        self.assertEqual(r1.a.decoder(UnsignedEnum.FOO), "FOO/1")
+
+
+class ConnectTestCase(FHDLTestCase):
+    def setUp_flat(self):
+        self.core_layout = [
+            ("addr",   32, DIR_FANOUT),
+            ("data_r", 32, DIR_FANIN),
+            ("data_w", 32, DIR_FANIN),
+        ]
+        self.periph_layout = [
+            ("addr",   32, DIR_FANOUT),
+            ("data_r", 32, DIR_FANIN),
+            ("data_w", 32, DIR_FANIN),
+        ]
+
+    def setUp_nested(self):
+        self.core_layout = [
+            ("addr",   32, DIR_FANOUT),
+            ("data", [
+                ("r",  32, DIR_FANIN),
+                ("w",  32, DIR_FANIN),
+            ]),
+        ]
+        self.periph_layout = [
+            ("addr",   32, DIR_FANOUT),
+            ("data", [
+                ("r",  32, DIR_FANIN),
+                ("w",  32, DIR_FANIN),
+            ]),
+        ]
+
+    def test_flat(self):
+        self.setUp_flat()
+
+        core    = Record(self.core_layout)
+        periph1 = Record(self.periph_layout)
+        periph2 = Record(self.periph_layout)
+
+        stmts = core.connect(periph1, periph2)
+        self.assertRepr(stmts, """(
+            (eq (sig periph1__addr) (sig core__addr))
+            (eq (sig periph2__addr) (sig core__addr))
+            (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r)))
+            (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w)))
+        )""")
+
+    def test_flat_include(self):
+        self.setUp_flat()
+
+        core    = Record(self.core_layout)
+        periph1 = Record(self.periph_layout)
+        periph2 = Record(self.periph_layout)
+
+        stmts = core.connect(periph1, periph2, include={"addr": True})
+        self.assertRepr(stmts, """(
+            (eq (sig periph1__addr) (sig core__addr))
+            (eq (sig periph2__addr) (sig core__addr))
+        )""")
+
+    def test_flat_exclude(self):
+        self.setUp_flat()
+
+        core    = Record(self.core_layout)
+        periph1 = Record(self.periph_layout)
+        periph2 = Record(self.periph_layout)
+
+        stmts = core.connect(periph1, periph2, exclude={"addr": True})
+        self.assertRepr(stmts, """(
+            (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r)))
+            (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w)))
+        )""")
+
+    def test_nested(self):
+        self.setUp_nested()
+
+        core    = Record(self.core_layout)
+        periph1 = Record(self.periph_layout)
+        periph2 = Record(self.periph_layout)
+
+        stmts = core.connect(periph1, periph2)
+        self.maxDiff = None
+        self.assertRepr(stmts, """(
+            (eq (sig periph1__addr) (sig core__addr))
+            (eq (sig periph2__addr) (sig core__addr))
+            (eq (sig core__data__r) (| (sig periph1__data__r) (sig periph2__data__r)))
+            (eq (sig core__data__w) (| (sig periph1__data__w) (sig periph2__data__w)))
+        )""")
+
+    def test_wrong_include_exclude(self):
+        self.setUp_flat()
+
+        core   = Record(self.core_layout)
+        periph = Record(self.periph_layout)
+
+        with self.assertRaisesRegex(AttributeError,
+                r"^Cannot include field 'foo' because it is not present in record 'core'$"):
+            core.connect(periph, include={"foo": True})
+
+        with self.assertRaisesRegex(AttributeError,
+                r"^Cannot exclude field 'foo' because it is not present in record 'core'$"):
+            core.connect(periph, exclude={"foo": True})
+
+    def test_wrong_direction(self):
+        recs = [Record([("x", 1)]) for _ in range(2)]
+
+        with self.assertRaisesRegex(TypeError,
+                (r"^Cannot connect field 'x' of unnamed record because it does not have "
+                    r"a direction$")):
+            recs[0].connect(recs[1])
+
+    def test_wrong_missing_field(self):
+        core   = Record([("addr", 32, DIR_FANOUT)])
+        periph = Record([])
+
+        with self.assertRaisesRegex(AttributeError,
+                (r"^Cannot connect field 'addr' of record 'core' to subordinate record 'periph' "
+                    r"because the subordinate record does not have this field$")):
+            core.connect(periph)
diff --git a/tests/test_hdl_xfrm.py b/tests/test_hdl_xfrm.py
new file mode 100644 (file)
index 0000000..c8b885f
--- /dev/null
@@ -0,0 +1,650 @@
+# nmigen: UnusedElaboratable=no
+
+from nmigen.hdl.ast import *
+from nmigen.hdl.cd import *
+from nmigen.hdl.ir import *
+from nmigen.hdl.xfrm import *
+from nmigen.hdl.mem import *
+
+from .utils import *
+
+
+class DomainRenamerTestCase(FHDLTestCase):
+    def setUp(self):
+        self.s1 = Signal()
+        self.s2 = Signal()
+        self.s3 = Signal()
+        self.s4 = Signal()
+        self.s5 = Signal()
+        self.c1 = Signal()
+
+    def test_rename_signals(self):
+        f = Fragment()
+        f.add_statements(
+            self.s1.eq(ClockSignal()),
+            ResetSignal().eq(self.s2),
+            self.s3.eq(0),
+            self.s4.eq(ClockSignal("other")),
+            self.s5.eq(ResetSignal("other")),
+        )
+        f.add_driver(self.s1, None)
+        f.add_driver(self.s2, None)
+        f.add_driver(self.s3, "sync")
+
+        f = DomainRenamer("pix")(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s1) (clk pix))
+            (eq (rst pix) (sig s2))
+            (eq (sig s3) (const 1'd0))
+            (eq (sig s4) (clk other))
+            (eq (sig s5) (rst other))
+        )
+        """)
+        self.assertEqual(f.drivers, {
+            None: SignalSet((self.s1, self.s2)),
+            "pix": SignalSet((self.s3,)),
+        })
+
+    def test_rename_multi(self):
+        f = Fragment()
+        f.add_statements(
+            self.s1.eq(ClockSignal()),
+            self.s2.eq(ResetSignal("other")),
+        )
+
+        f = DomainRenamer({"sync": "pix", "other": "pix2"})(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s1) (clk pix))
+            (eq (sig s2) (rst pix2))
+        )
+        """)
+
+    def test_rename_cd(self):
+        cd_sync = ClockDomain()
+        cd_pix  = ClockDomain()
+
+        f = Fragment()
+        f.add_domains(cd_sync, cd_pix)
+
+        f = DomainRenamer("ext")(f)
+        self.assertEqual(cd_sync.name, "ext")
+        self.assertEqual(f.domains, {
+            "ext": cd_sync,
+            "pix": cd_pix,
+        })
+
+    def test_rename_cd_preserves_allow_reset_less(self):
+        cd_pix  = ClockDomain(reset_less=True)
+
+        f = Fragment()
+        f.add_domains(cd_pix)
+        f.add_statements(
+            self.s1.eq(ResetSignal(allow_reset_less=True)),
+        )
+
+        f = DomainRenamer("pix")(f)
+        f = DomainLowerer()(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s1) (const 1'd0))
+        )
+        """)
+
+
+    def test_rename_cd_subfragment(self):
+        cd_sync = ClockDomain()
+        cd_pix  = ClockDomain()
+
+        f1 = Fragment()
+        f1.add_domains(cd_sync, cd_pix)
+        f2 = Fragment()
+        f2.add_domains(cd_sync)
+        f1.add_subfragment(f2)
+
+        f1 = DomainRenamer("ext")(f1)
+        self.assertEqual(cd_sync.name, "ext")
+        self.assertEqual(f1.domains, {
+            "ext": cd_sync,
+            "pix": cd_pix,
+        })
+
+    def test_rename_wrong_to_comb(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Domain 'sync' may not be renamed to 'comb'$"):
+            DomainRenamer("comb")
+
+    def test_rename_wrong_from_comb(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^Domain 'comb' may not be renamed$"):
+            DomainRenamer({"comb": "sync"})
+
+
+class DomainLowererTestCase(FHDLTestCase):
+    def setUp(self):
+        self.s = Signal()
+
+    def test_lower_clk(self):
+        sync = ClockDomain()
+        f = Fragment()
+        f.add_domains(sync)
+        f.add_statements(
+            self.s.eq(ClockSignal("sync"))
+        )
+
+        f = DomainLowerer()(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s) (sig clk))
+        )
+        """)
+
+    def test_lower_rst(self):
+        sync = ClockDomain()
+        f = Fragment()
+        f.add_domains(sync)
+        f.add_statements(
+            self.s.eq(ResetSignal("sync"))
+        )
+
+        f = DomainLowerer()(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s) (sig rst))
+        )
+        """)
+
+    def test_lower_rst_reset_less(self):
+        sync = ClockDomain(reset_less=True)
+        f = Fragment()
+        f.add_domains(sync)
+        f.add_statements(
+            self.s.eq(ResetSignal("sync", allow_reset_less=True))
+        )
+
+        f = DomainLowerer()(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s) (const 1'd0))
+        )
+        """)
+
+    def test_lower_drivers(self):
+        sync = ClockDomain()
+        pix = ClockDomain()
+        f = Fragment()
+        f.add_domains(sync, pix)
+        f.add_driver(ClockSignal("pix"), None)
+        f.add_driver(ResetSignal("pix"), "sync")
+
+        f = DomainLowerer()(f)
+        self.assertEqual(f.drivers, {
+            None: SignalSet((pix.clk,)),
+            "sync": SignalSet((pix.rst,))
+        })
+
+    def test_lower_wrong_domain(self):
+        f = Fragment()
+        f.add_statements(
+            self.s.eq(ClockSignal("xxx"))
+        )
+
+        with self.assertRaisesRegex(DomainError,
+                r"^Signal \(clk xxx\) refers to nonexistent domain 'xxx'$"):
+            DomainLowerer()(f)
+
+    def test_lower_wrong_reset_less_domain(self):
+        sync = ClockDomain(reset_less=True)
+        f = Fragment()
+        f.add_domains(sync)
+        f.add_statements(
+            self.s.eq(ResetSignal("sync"))
+        )
+
+        with self.assertRaisesRegex(DomainError,
+                r"^Signal \(rst sync\) refers to reset of reset-less domain 'sync'$"):
+            DomainLowerer()(f)
+
+
+class SampleLowererTestCase(FHDLTestCase):
+    def setUp(self):
+        self.i = Signal()
+        self.o1 = Signal()
+        self.o2 = Signal()
+        self.o3 = Signal()
+
+    def test_lower_signal(self):
+        f = Fragment()
+        f.add_statements(
+            self.o1.eq(Sample(self.i, 2, "sync")),
+            self.o2.eq(Sample(self.i, 1, "sync")),
+            self.o3.eq(Sample(self.i, 1, "pix")),
+        )
+
+        f = SampleLowerer()(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig o1) (sig $sample$s$i$sync$2))
+            (eq (sig o2) (sig $sample$s$i$sync$1))
+            (eq (sig o3) (sig $sample$s$i$pix$1))
+            (eq (sig $sample$s$i$sync$1) (sig i))
+            (eq (sig $sample$s$i$sync$2) (sig $sample$s$i$sync$1))
+            (eq (sig $sample$s$i$pix$1) (sig i))
+        )
+        """)
+        self.assertEqual(len(f.drivers["sync"]), 2)
+        self.assertEqual(len(f.drivers["pix"]), 1)
+
+    def test_lower_const(self):
+        f = Fragment()
+        f.add_statements(
+            self.o1.eq(Sample(1, 2, "sync")),
+        )
+
+        f = SampleLowerer()(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig o1) (sig $sample$c$1$sync$2))
+            (eq (sig $sample$c$1$sync$1) (const 1'd1))
+            (eq (sig $sample$c$1$sync$2) (sig $sample$c$1$sync$1))
+        )
+        """)
+        self.assertEqual(len(f.drivers["sync"]), 2)
+
+
+class SwitchCleanerTestCase(FHDLTestCase):
+    def test_clean(self):
+        a = Signal()
+        b = Signal()
+        c = Signal()
+        stmts = [
+            Switch(a, {
+                1: a.eq(0),
+                0: [
+                    b.eq(1),
+                    Switch(b, {1: [
+                        Switch(a|b, {})
+                    ]})
+                ]
+            })
+        ]
+
+        self.assertRepr(SwitchCleaner()(stmts), """
+        (
+            (switch (sig a)
+                (case 1
+                    (eq (sig a) (const 1'd0)))
+                (case 0
+                    (eq (sig b) (const 1'd1)))
+            )
+        )
+        """)
+
+
+class LHSGroupAnalyzerTestCase(FHDLTestCase):
+    def test_no_group_unrelated(self):
+        a = Signal()
+        b = Signal()
+        stmts = [
+            a.eq(0),
+            b.eq(0),
+        ]
+
+        groups = LHSGroupAnalyzer()(stmts)
+        self.assertEqual(list(groups.values()), [
+            SignalSet((a,)),
+            SignalSet((b,)),
+        ])
+
+    def test_group_related(self):
+        a = Signal()
+        b = Signal()
+        stmts = [
+            a.eq(0),
+            Cat(a, b).eq(0),
+        ]
+
+        groups = LHSGroupAnalyzer()(stmts)
+        self.assertEqual(list(groups.values()), [
+            SignalSet((a, b)),
+        ])
+
+    def test_no_loops(self):
+        a = Signal()
+        b = Signal()
+        stmts = [
+            a.eq(0),
+            Cat(a, b).eq(0),
+            Cat(a, b).eq(0),
+        ]
+
+        groups = LHSGroupAnalyzer()(stmts)
+        self.assertEqual(list(groups.values()), [
+            SignalSet((a, b)),
+        ])
+
+    def test_switch(self):
+        a = Signal()
+        b = Signal()
+        stmts = [
+            a.eq(0),
+            Switch(a, {
+                1: b.eq(0),
+            })
+        ]
+
+        groups = LHSGroupAnalyzer()(stmts)
+        self.assertEqual(list(groups.values()), [
+            SignalSet((a,)),
+            SignalSet((b,)),
+        ])
+
+    def test_lhs_empty(self):
+        stmts = [
+            Cat().eq(0)
+        ]
+
+        groups = LHSGroupAnalyzer()(stmts)
+        self.assertEqual(list(groups.values()), [
+        ])
+
+
+class LHSGroupFilterTestCase(FHDLTestCase):
+    def test_filter(self):
+        a = Signal()
+        b = Signal()
+        c = Signal()
+        stmts = [
+            Switch(a, {
+                1: a.eq(0),
+                0: [
+                    b.eq(1),
+                    Switch(b, {1: []})
+                ]
+            })
+        ]
+
+        self.assertRepr(LHSGroupFilter(SignalSet((a,)))(stmts), """
+        (
+            (switch (sig a)
+                (case 1
+                    (eq (sig a) (const 1'd0)))
+                (case 0 )
+            )
+        )
+        """)
+
+    def test_lhs_empty(self):
+        stmts = [
+            Cat().eq(0)
+        ]
+
+        self.assertRepr(LHSGroupFilter(SignalSet())(stmts), "()")
+
+
+class ResetInserterTestCase(FHDLTestCase):
+    def setUp(self):
+        self.s1 = Signal()
+        self.s2 = Signal(reset=1)
+        self.s3 = Signal(reset=1, reset_less=True)
+        self.c1 = Signal()
+
+    def test_reset_default(self):
+        f = Fragment()
+        f.add_statements(
+            self.s1.eq(1)
+        )
+        f.add_driver(self.s1, "sync")
+
+        f = ResetInserter(self.c1)(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s1) (const 1'd1))
+            (switch (sig c1)
+                (case 1 (eq (sig s1) (const 1'd0)))
+            )
+        )
+        """)
+
+    def test_reset_cd(self):
+        f = Fragment()
+        f.add_statements(
+            self.s1.eq(1),
+            self.s2.eq(0),
+        )
+        f.add_domains(ClockDomain("sync"))
+        f.add_driver(self.s1, "sync")
+        f.add_driver(self.s2, "pix")
+
+        f = ResetInserter({"pix": self.c1})(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s1) (const 1'd1))
+            (eq (sig s2) (const 1'd0))
+            (switch (sig c1)
+                (case 1 (eq (sig s2) (const 1'd1)))
+            )
+        )
+        """)
+
+    def test_reset_value(self):
+        f = Fragment()
+        f.add_statements(
+            self.s2.eq(0)
+        )
+        f.add_driver(self.s2, "sync")
+
+        f = ResetInserter(self.c1)(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s2) (const 1'd0))
+            (switch (sig c1)
+                (case 1 (eq (sig s2) (const 1'd1)))
+            )
+        )
+        """)
+
+    def test_reset_less(self):
+        f = Fragment()
+        f.add_statements(
+            self.s3.eq(0)
+        )
+        f.add_driver(self.s3, "sync")
+
+        f = ResetInserter(self.c1)(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s3) (const 1'd0))
+            (switch (sig c1)
+                (case 1 )
+            )
+        )
+        """)
+
+
+class EnableInserterTestCase(FHDLTestCase):
+    def setUp(self):
+        self.s1 = Signal()
+        self.s2 = Signal()
+        self.s3 = Signal()
+        self.c1 = Signal()
+
+    def test_enable_default(self):
+        f = Fragment()
+        f.add_statements(
+            self.s1.eq(1)
+        )
+        f.add_driver(self.s1, "sync")
+
+        f = EnableInserter(self.c1)(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s1) (const 1'd1))
+            (switch (sig c1)
+                (case 0 (eq (sig s1) (sig s1)))
+            )
+        )
+        """)
+
+    def test_enable_cd(self):
+        f = Fragment()
+        f.add_statements(
+            self.s1.eq(1),
+            self.s2.eq(0),
+        )
+        f.add_driver(self.s1, "sync")
+        f.add_driver(self.s2, "pix")
+
+        f = EnableInserter({"pix": self.c1})(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s1) (const 1'd1))
+            (eq (sig s2) (const 1'd0))
+            (switch (sig c1)
+                (case 0 (eq (sig s2) (sig s2)))
+            )
+        )
+        """)
+
+    def test_enable_subfragment(self):
+        f1 = Fragment()
+        f1.add_statements(
+            self.s1.eq(1)
+        )
+        f1.add_driver(self.s1, "sync")
+
+        f2 = Fragment()
+        f2.add_statements(
+            self.s2.eq(1)
+        )
+        f2.add_driver(self.s2, "sync")
+        f1.add_subfragment(f2)
+
+        f1 = EnableInserter(self.c1)(f1)
+        (f2, _), = f1.subfragments
+        self.assertRepr(f1.statements, """
+        (
+            (eq (sig s1) (const 1'd1))
+            (switch (sig c1)
+                (case 0 (eq (sig s1) (sig s1)))
+            )
+        )
+        """)
+        self.assertRepr(f2.statements, """
+        (
+            (eq (sig s2) (const 1'd1))
+            (switch (sig c1)
+                (case 0 (eq (sig s2) (sig s2)))
+            )
+        )
+        """)
+
+    def test_enable_read_port(self):
+        mem = Memory(width=8, depth=4)
+        f = EnableInserter(self.c1)(mem.read_port(transparent=False)).elaborate(platform=None)
+        self.assertRepr(f.named_ports["EN"][0], """
+        (m (sig c1) (sig mem_r_en) (const 1'd0))
+        """)
+
+    def test_enable_write_port(self):
+        mem = Memory(width=8, depth=4)
+        f = EnableInserter(self.c1)(mem.write_port()).elaborate(platform=None)
+        self.assertRepr(f.named_ports["EN"][0], """
+        (m (sig c1) (cat (repl (slice (sig mem_w_en) 0:1) 8)) (const 8'd0))
+        """)
+
+
+class _MockElaboratable(Elaboratable):
+    def __init__(self):
+        self.s1 = Signal()
+
+    def elaborate(self, platform):
+        f = Fragment()
+        f.add_statements(
+            self.s1.eq(1)
+        )
+        f.add_driver(self.s1, "sync")
+        return f
+
+
+class TransformedElaboratableTestCase(FHDLTestCase):
+    def setUp(self):
+        self.c1 = Signal()
+        self.c2 = Signal()
+
+    def test_getattr(self):
+        e = _MockElaboratable()
+        te = EnableInserter(self.c1)(e)
+
+        self.assertIs(te.s1, e.s1)
+
+    def test_composition(self):
+        e = _MockElaboratable()
+        te1 = EnableInserter(self.c1)(e)
+        te2 = ResetInserter(self.c2)(te1)
+
+        self.assertIsInstance(te1, TransformedElaboratable)
+        self.assertIs(te1, te2)
+
+        f = Fragment.get(te2, None)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s1) (const 1'd1))
+            (switch (sig c1)
+                (case 0 (eq (sig s1) (sig s1)))
+            )
+            (switch (sig c2)
+                (case 1 (eq (sig s1) (const 1'd0)))
+            )
+        )
+        """)
+
+
+class MockUserValue(UserValue):
+    def __init__(self, lowered):
+        super().__init__()
+        self.lowered = lowered
+
+    def lower(self):
+        return self.lowered
+
+
+class UserValueTestCase(FHDLTestCase):
+    def setUp(self):
+        self.s  = Signal()
+        self.c  = Signal()
+        self.uv = MockUserValue(self.s)
+
+    def test_lower(self):
+        sync = ClockDomain()
+        f = Fragment()
+        f.add_domains(sync)
+        f.add_statements(
+            self.uv.eq(1)
+        )
+        for signal in self.uv._lhs_signals():
+            f.add_driver(signal, "sync")
+
+        f = ResetInserter(self.c)(f)
+        f = DomainLowerer()(f)
+        self.assertRepr(f.statements, """
+        (
+            (eq (sig s) (const 1'd1))
+            (switch (sig c)
+                (case 1 (eq (sig s) (const 1'd0)))
+            )
+            (switch (sig rst)
+                (case 1 (eq (sig s) (const 1'd0)))
+            )
+        )
+        """)
+
+
+class UserValueRecursiveTestCase(UserValueTestCase):
+    def setUp(self):
+        self.s = Signal()
+        self.c = Signal()
+        self.uv = MockUserValue(MockUserValue(self.s))
+
+    # inherit the test_lower method from UserValueTestCase because the checks are the same
diff --git a/tests/test_lib_cdc.py b/tests/test_lib_cdc.py
new file mode 100644 (file)
index 0000000..f29052a
--- /dev/null
@@ -0,0 +1,231 @@
+# nmigen: UnusedElaboratable=no
+
+from nmigen.hdl import *
+from nmigen.back.pysim import *
+from nmigen.lib.cdc import *
+
+from .utils import *
+
+
+class FFSynchronizerTestCase(FHDLTestCase):
+    def test_stages_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Synchronization stage count must be a positive integer, not 0$"):
+            FFSynchronizer(Signal(), Signal(), stages=0)
+        with self.assertRaisesRegex(ValueError,
+                r"^Synchronization stage count may not safely be less than 2$"):
+            FFSynchronizer(Signal(), Signal(), stages=1)
+
+    def test_basic(self):
+        i = Signal()
+        o = Signal()
+        frag = FFSynchronizer(i, o)
+
+        sim = Simulator(frag)
+        sim.add_clock(1e-6)
+        def process():
+            self.assertEqual((yield o), 0)
+            yield i.eq(1)
+            yield Tick()
+            self.assertEqual((yield o), 0)
+            yield Tick()
+            self.assertEqual((yield o), 0)
+            yield Tick()
+            self.assertEqual((yield o), 1)
+        sim.add_process(process)
+        sim.run()
+
+    def test_reset_value(self):
+        i = Signal(reset=1)
+        o = Signal()
+        frag = FFSynchronizer(i, o, reset=1)
+
+        sim = Simulator(frag)
+        sim.add_clock(1e-6)
+        def process():
+            self.assertEqual((yield o), 1)
+            yield i.eq(0)
+            yield Tick()
+            self.assertEqual((yield o), 1)
+            yield Tick()
+            self.assertEqual((yield o), 1)
+            yield Tick()
+            self.assertEqual((yield o), 0)
+        sim.add_process(process)
+        sim.run()
+
+
+class AsyncFFSynchronizerTestCase(FHDLTestCase):
+    def test_stages_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Synchronization stage count must be a positive integer, not 0$"):
+            ResetSynchronizer(Signal(), stages=0)
+        with self.assertRaisesRegex(ValueError,
+                r"^Synchronization stage count may not safely be less than 2$"):
+            ResetSynchronizer(Signal(), stages=1)
+
+    def test_edge_wrong(self):
+        with self.assertRaisesRegex(ValueError,
+                r"^AsyncFFSynchronizer async edge must be one of 'pos' or 'neg', not 'xxx'$"):
+            AsyncFFSynchronizer(Signal(), Signal(), o_domain="sync", async_edge="xxx")
+
+    def test_pos_edge(self):
+        i = Signal()
+        o = Signal()
+        m = Module()
+        m.domains += ClockDomain("sync")
+        m.submodules += AsyncFFSynchronizer(i, o)
+
+        sim = Simulator(m)
+        sim.add_clock(1e-6)
+        def process():
+            # initial reset
+            self.assertEqual((yield i), 0)
+            self.assertEqual((yield o), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 0)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 0)
+            yield Tick(); yield Delay(1e-8)
+
+            yield i.eq(1)
+            yield Delay(1e-8)
+            self.assertEqual((yield o), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 1)
+            yield i.eq(0)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 0)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 0)
+            yield Tick(); yield Delay(1e-8)
+        sim.add_process(process)
+        with sim.write_vcd("test.vcd"):
+            sim.run()
+
+    def test_neg_edge(self):
+        i = Signal(reset=1)
+        o = Signal()
+        m = Module()
+        m.domains += ClockDomain("sync")
+        m.submodules += AsyncFFSynchronizer(i, o, async_edge="neg")
+
+        sim = Simulator(m)
+        sim.add_clock(1e-6)
+        def process():
+            # initial reset
+            self.assertEqual((yield i), 1)
+            self.assertEqual((yield o), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 0)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 0)
+            yield Tick(); yield Delay(1e-8)
+
+            yield i.eq(0)
+            yield Delay(1e-8)
+            self.assertEqual((yield o), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 1)
+            yield i.eq(1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 0)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield o), 0)
+            yield Tick(); yield Delay(1e-8)
+        sim.add_process(process)
+        with sim.write_vcd("test.vcd"):
+            sim.run()
+
+
+class ResetSynchronizerTestCase(FHDLTestCase):
+    def test_stages_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Synchronization stage count must be a positive integer, not 0$"):
+            ResetSynchronizer(Signal(), stages=0)
+        with self.assertRaisesRegex(ValueError,
+                r"^Synchronization stage count may not safely be less than 2$"):
+            ResetSynchronizer(Signal(), stages=1)
+
+    def test_basic(self):
+        arst = Signal()
+        m = Module()
+        m.domains += ClockDomain("sync")
+        m.submodules += ResetSynchronizer(arst)
+        s = Signal(reset=1)
+        m.d.sync += s.eq(0)
+
+        sim = Simulator(m)
+        sim.add_clock(1e-6)
+        def process():
+            # initial reset
+            self.assertEqual((yield s), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield s), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield s), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield s), 0)
+            yield Tick(); yield Delay(1e-8)
+
+            yield arst.eq(1)
+            yield Delay(1e-8)
+            self.assertEqual((yield s), 0)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield s), 1)
+            yield arst.eq(0)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield s), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield s), 1)
+            yield Tick(); yield Delay(1e-8)
+            self.assertEqual((yield s), 0)
+            yield Tick(); yield Delay(1e-8)
+        sim.add_process(process)
+        with sim.write_vcd("test.vcd"):
+            sim.run()
+
+
+# TODO: test with distinct clocks
+class PulseSynchronizerTestCase(FHDLTestCase):
+    def test_stages_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^Synchronization stage count must be a positive integer, not 0$"):
+            PulseSynchronizer("w", "r", stages=0)
+        with self.assertRaisesRegex(ValueError,
+                r"^Synchronization stage count may not safely be less than 2$"):
+            PulseSynchronizer("w", "r", stages=1)
+
+    def test_smoke(self):
+        m = Module()
+        m.domains += ClockDomain("sync")
+        ps = m.submodules.dut = PulseSynchronizer("sync", "sync")
+
+        sim = Simulator(m)
+        sim.add_clock(1e-6)
+        def process():
+            yield ps.i.eq(0)
+            # TODO: think about reset
+            for n in range(5):
+                yield Tick()
+            # Make sure no pulses are generated in quiescent state
+            for n in range(3):
+                yield Tick()
+                self.assertEqual((yield ps.o), 0)
+            # Check conservation of pulses
+            accum = 0
+            for n in range(10):
+                yield ps.i.eq(1 if n < 4 else 0)
+                yield Tick()
+                accum += yield ps.o
+            self.assertEqual(accum, 4)
+        sim.add_process(process)
+        sim.run()
diff --git a/tests/test_lib_coding.py b/tests/test_lib_coding.py
new file mode 100644 (file)
index 0000000..c914e9e
--- /dev/null
@@ -0,0 +1,127 @@
+from nmigen.hdl import *
+from nmigen.asserts import *
+from nmigen.back.pysim import *
+from nmigen.lib.coding import *
+
+from .utils import *
+
+
+class EncoderTestCase(FHDLTestCase):
+    def test_basic(self):
+        enc = Encoder(4)
+        def process():
+            self.assertEqual((yield enc.n), 1)
+            self.assertEqual((yield enc.o), 0)
+
+            yield enc.i.eq(0b0001)
+            yield Settle()
+            self.assertEqual((yield enc.n), 0)
+            self.assertEqual((yield enc.o), 0)
+
+            yield enc.i.eq(0b0100)
+            yield Settle()
+            self.assertEqual((yield enc.n), 0)
+            self.assertEqual((yield enc.o), 2)
+
+            yield enc.i.eq(0b0110)
+            yield Settle()
+            self.assertEqual((yield enc.n), 1)
+            self.assertEqual((yield enc.o), 0)
+
+        sim = Simulator(enc)
+        sim.add_process(process)
+        sim.run()
+
+
+class PriorityEncoderTestCase(FHDLTestCase):
+    def test_basic(self):
+        enc = PriorityEncoder(4)
+        def process():
+            self.assertEqual((yield enc.n), 1)
+            self.assertEqual((yield enc.o), 0)
+
+            yield enc.i.eq(0b0001)
+            yield Settle()
+            self.assertEqual((yield enc.n), 0)
+            self.assertEqual((yield enc.o), 0)
+
+            yield enc.i.eq(0b0100)
+            yield Settle()
+            self.assertEqual((yield enc.n), 0)
+            self.assertEqual((yield enc.o), 2)
+
+            yield enc.i.eq(0b0110)
+            yield Settle()
+            self.assertEqual((yield enc.n), 0)
+            self.assertEqual((yield enc.o), 1)
+
+        sim = Simulator(enc)
+        sim.add_process(process)
+        sim.run()
+
+
+class DecoderTestCase(FHDLTestCase):
+    def test_basic(self):
+        dec = Decoder(4)
+        def process():
+            self.assertEqual((yield dec.o), 0b0001)
+
+            yield dec.i.eq(1)
+            yield Settle()
+            self.assertEqual((yield dec.o), 0b0010)
+
+            yield dec.i.eq(3)
+            yield Settle()
+            self.assertEqual((yield dec.o), 0b1000)
+
+            yield dec.n.eq(1)
+            yield Settle()
+            self.assertEqual((yield dec.o), 0b0000)
+
+        sim = Simulator(dec)
+        sim.add_process(process)
+        sim.run()
+
+
+class ReversibleSpec(Elaboratable):
+    def __init__(self, encoder_cls, decoder_cls, args):
+        self.encoder_cls = encoder_cls
+        self.decoder_cls = decoder_cls
+        self.coder_args  = args
+
+    def elaborate(self, platform):
+        m = Module()
+        enc, dec = self.encoder_cls(*self.coder_args), self.decoder_cls(*self.coder_args)
+        m.submodules += enc, dec
+        m.d.comb += [
+            dec.i.eq(enc.o),
+            Assert(enc.i == dec.o)
+        ]
+        return m
+
+
+class HammingDistanceSpec(Elaboratable):
+    def __init__(self, distance, encoder_cls, args):
+        self.distance    = distance
+        self.encoder_cls = encoder_cls
+        self.coder_args  = args
+
+    def elaborate(self, platform):
+        m = Module()
+        enc1, enc2 = self.encoder_cls(*self.coder_args), self.encoder_cls(*self.coder_args)
+        m.submodules += enc1, enc2
+        m.d.comb += [
+            Assume(enc1.i + 1 == enc2.i),
+            Assert(sum(enc1.o ^ enc2.o) == self.distance)
+        ]
+        return m
+
+
+class GrayCoderTestCase(FHDLTestCase):
+    def test_reversible(self):
+        spec = ReversibleSpec(encoder_cls=GrayEncoder, decoder_cls=GrayDecoder, args=(16,))
+        self.assertFormal(spec, mode="prove")
+
+    def test_distance(self):
+        spec = HammingDistanceSpec(distance=1, encoder_cls=GrayEncoder, args=(16,))
+        self.assertFormal(spec, mode="prove")
diff --git a/tests/test_lib_fifo.py b/tests/test_lib_fifo.py
new file mode 100644 (file)
index 0000000..83dc825
--- /dev/null
@@ -0,0 +1,282 @@
+# nmigen: UnusedElaboratable=no
+
+from nmigen.hdl import *
+from nmigen.asserts import *
+from nmigen.back.pysim import *
+from nmigen.lib.fifo import *
+
+from .utils import *
+
+
+class FIFOTestCase(FHDLTestCase):
+    def test_depth_wrong(self):
+        with self.assertRaisesRegex(TypeError,
+                r"^FIFO width must be a non-negative integer, not -1$"):
+            FIFOInterface(width=-1, depth=8, fwft=True)
+        with self.assertRaisesRegex(TypeError,
+                r"^FIFO depth must be a non-negative integer, not -1$"):
+            FIFOInterface(width=8, depth=-1, fwft=True)
+
+    def test_sync_depth(self):
+        self.assertEqual(SyncFIFO(width=8, depth=0).depth, 0)
+        self.assertEqual(SyncFIFO(width=8, depth=1).depth, 1)
+        self.assertEqual(SyncFIFO(width=8, depth=2).depth, 2)
+
+    def test_sync_buffered_depth(self):
+        self.assertEqual(SyncFIFOBuffered(width=8, depth=0).depth, 0)
+        self.assertEqual(SyncFIFOBuffered(width=8, depth=1).depth, 1)
+        self.assertEqual(SyncFIFOBuffered(width=8, depth=2).depth, 2)
+
+    def test_async_depth(self):
+        self.assertEqual(AsyncFIFO(width=8, depth=0 ).depth, 0)
+        self.assertEqual(AsyncFIFO(width=8, depth=1 ).depth, 1)
+        self.assertEqual(AsyncFIFO(width=8, depth=2 ).depth, 2)
+        self.assertEqual(AsyncFIFO(width=8, depth=3 ).depth, 4)
+        self.assertEqual(AsyncFIFO(width=8, depth=4 ).depth, 4)
+        self.assertEqual(AsyncFIFO(width=8, depth=15).depth, 16)
+        self.assertEqual(AsyncFIFO(width=8, depth=16).depth, 16)
+        self.assertEqual(AsyncFIFO(width=8, depth=17).depth, 32)
+
+    def test_async_depth_wrong(self):
+        with self.assertRaisesRegex(ValueError,
+                (r"^AsyncFIFO only supports depths that are powers of 2; "
+                    r"requested exact depth 15 is not$")):
+            AsyncFIFO(width=8, depth=15, exact_depth=True)
+
+    def test_async_buffered_depth(self):
+        self.assertEqual(AsyncFIFOBuffered(width=8, depth=0 ).depth, 0)
+        self.assertEqual(AsyncFIFOBuffered(width=8, depth=1 ).depth, 2)
+        self.assertEqual(AsyncFIFOBuffered(width=8, depth=2 ).depth, 2)
+        self.assertEqual(AsyncFIFOBuffered(width=8, depth=3 ).depth, 3)
+        self.assertEqual(AsyncFIFOBuffered(width=8, depth=4 ).depth, 5)
+        self.assertEqual(AsyncFIFOBuffered(width=8, depth=15).depth, 17)
+        self.assertEqual(AsyncFIFOBuffered(width=8, depth=16).depth, 17)
+        self.assertEqual(AsyncFIFOBuffered(width=8, depth=17).depth, 17)
+        self.assertEqual(AsyncFIFOBuffered(width=8, depth=18).depth, 33)
+
+    def test_async_buffered_depth_wrong(self):
+        with self.assertRaisesRegex(ValueError,
+                (r"^AsyncFIFOBuffered only supports depths that are one higher than powers of 2; "
+                    r"requested exact depth 16 is not$")):
+            AsyncFIFOBuffered(width=8, depth=16, exact_depth=True)
+
+class FIFOModel(Elaboratable, FIFOInterface):
+    """
+    Non-synthesizable first-in first-out queue, implemented naively as a chain of registers.
+    """
+    def __init__(self, *, width, depth, fwft, r_domain, w_domain):
+        super().__init__(width=width, depth=depth, fwft=fwft)
+
+        self.r_domain = r_domain
+        self.w_domain = w_domain
+
+        self.level = Signal(range(self.depth + 1))
+        self.r_level = Signal(range(self.depth + 1))
+        self.w_level = Signal(range(self.depth + 1))
+
+    def elaborate(self, platform):
+        m = Module()
+
+        storage = Memory(width=self.width, depth=self.depth)
+        w_port  = m.submodules.w_port = storage.write_port(domain=self.w_domain)
+        r_port  = m.submodules.r_port = storage.read_port (domain="comb")
+
+        produce = Signal(range(self.depth))
+        consume = Signal(range(self.depth))
+
+        m.d.comb += self.r_rdy.eq(self.level > 0)
+        m.d.comb += r_port.addr.eq((consume + 1) % self.depth)
+        if self.fwft:
+            m.d.comb += self.r_data.eq(r_port.data)
+        with m.If(self.r_en & self.r_rdy):
+            if not self.fwft:
+                m.d[self.r_domain] += self.r_data.eq(r_port.data)
+            m.d[self.r_domain] += consume.eq(r_port.addr)
+
+        m.d.comb += self.w_rdy.eq(self.level < self.depth)
+        m.d.comb += w_port.data.eq(self.w_data)
+        with m.If(self.w_en & self.w_rdy):
+            m.d.comb += w_port.addr.eq((produce + 1) % self.depth)
+            m.d.comb += w_port.en.eq(1)
+            m.d[self.w_domain] += produce.eq(w_port.addr)
+
+        with m.If(ResetSignal(self.r_domain) | ResetSignal(self.w_domain)):
+            m.d.sync += self.level.eq(0)
+        with m.Else():
+            m.d.sync += self.level.eq(self.level
+                + (self.w_rdy & self.w_en)
+                - (self.r_rdy & self.r_en))
+
+        m.d.comb += [
+            self.r_level.eq(self.level),
+            self.w_level.eq(self.level),
+        ]
+        m.d.comb += Assert(ResetSignal(self.r_domain) == ResetSignal(self.w_domain))
+
+        return m
+
+
+class FIFOModelEquivalenceSpec(Elaboratable):
+    """
+    The first-in first-out queue model equivalence specification: for any inputs and control
+    signals, the behavior of the implementation under test exactly matches the ideal model,
+    except for behavior not defined by the model.
+    """
+    def __init__(self, fifo, r_domain, w_domain):
+        self.fifo = fifo
+
+        self.r_domain = r_domain
+        self.w_domain = w_domain
+
+    def elaborate(self, platform):
+        m = Module()
+        m.submodules.dut  = dut  = self.fifo
+        m.submodules.gold = gold = FIFOModel(width=dut.width, depth=dut.depth, fwft=dut.fwft,
+                                             r_domain=self.r_domain, w_domain=self.w_domain)
+
+        m.d.comb += [
+            gold.r_en.eq(dut.r_rdy & dut.r_en),
+            gold.w_en.eq(dut.w_en),
+            gold.w_data.eq(dut.w_data),
+        ]
+
+        m.d.comb += Assert(dut.r_rdy.implies(gold.r_rdy))
+        m.d.comb += Assert(dut.w_rdy.implies(gold.w_rdy))
+        m.d.comb += Assert(dut.r_level == gold.r_level)
+        m.d.comb += Assert(dut.w_level == gold.w_level)
+
+        if dut.fwft:
+            m.d.comb += Assert(dut.r_rdy
+                               .implies(dut.r_data == gold.r_data))
+        else:
+            m.d.comb += Assert((Past(dut.r_rdy, domain=self.r_domain) &
+                                Past(dut.r_en, domain=self.r_domain))
+                               .implies(dut.r_data == gold.r_data))
+
+        return m
+
+
+class FIFOContractSpec(Elaboratable):
+    """
+    The first-in first-out queue contract specification: if two elements are written to the queue
+    consecutively, they must be read out consecutively at some later point, no matter all other
+    circumstances, with the exception of reset.
+    """
+    def __init__(self, fifo, *, r_domain, w_domain, bound):
+        self.fifo     = fifo
+        self.r_domain = r_domain
+        self.w_domain = w_domain
+        self.bound    = bound
+
+    def elaborate(self, platform):
+        m = Module()
+        m.submodules.dut = fifo = self.fifo
+
+        m.domains += ClockDomain("sync")
+        m.d.comb += ResetSignal().eq(0)
+        if self.w_domain != "sync":
+            m.domains += ClockDomain(self.w_domain)
+            m.d.comb += ResetSignal(self.w_domain).eq(0)
+        if self.r_domain != "sync":
+            m.domains += ClockDomain(self.r_domain)
+            m.d.comb += ResetSignal(self.r_domain).eq(0)
+
+        entry_1 = AnyConst(fifo.width)
+        entry_2 = AnyConst(fifo.width)
+
+        with m.FSM(domain=self.w_domain) as write_fsm:
+            with m.State("WRITE-1"):
+                with m.If(fifo.w_rdy):
+                    m.d.comb += [
+                        fifo.w_data.eq(entry_1),
+                        fifo.w_en.eq(1)
+                    ]
+                    m.next = "WRITE-2"
+            with m.State("WRITE-2"):
+                with m.If(fifo.w_rdy):
+                    m.d.comb += [
+                        fifo.w_data.eq(entry_2),
+                        fifo.w_en.eq(1)
+                    ]
+                    m.next = "DONE"
+            with m.State("DONE"):
+                pass
+
+        with m.FSM(domain=self.r_domain) as read_fsm:
+            read_1 = Signal(fifo.width)
+            read_2 = Signal(fifo.width)
+            with m.State("READ"):
+                m.d.comb += fifo.r_en.eq(1)
+                if fifo.fwft:
+                    r_rdy = fifo.r_rdy
+                else:
+                    r_rdy = Past(fifo.r_rdy, domain=self.r_domain)
+                with m.If(r_rdy):
+                    m.d.sync += [
+                        read_1.eq(read_2),
+                        read_2.eq(fifo.r_data),
+                    ]
+                with m.If((read_1 == entry_1) & (read_2 == entry_2)):
+                    m.next = "DONE"
+            with m.State("DONE"):
+                pass
+
+        with m.If(Initial()):
+            m.d.comb += Assume(write_fsm.ongoing("WRITE-1"))
+            m.d.comb += Assume(read_fsm.ongoing("READ"))
+        with m.If(Past(Initial(), self.bound - 1)):
+            m.d.comb += Assert(read_fsm.ongoing("DONE"))
+
+        with m.If(ResetSignal(domain=self.w_domain)):
+            m.d.comb += Assert(~fifo.r_rdy)
+
+        if self.w_domain != "sync" or self.r_domain != "sync":
+            m.d.comb += Assume(Rose(ClockSignal(self.w_domain)) |
+                               Rose(ClockSignal(self.r_domain)))
+
+        return m
+
+
+class FIFOFormalCase(FHDLTestCase):
+    def check_sync_fifo(self, fifo):
+        self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="sync", w_domain="sync"),
+                          mode="bmc", depth=fifo.depth + 1)
+        self.assertFormal(FIFOContractSpec(fifo, r_domain="sync", w_domain="sync",
+                                           bound=fifo.depth * 2 + 1),
+                          mode="hybrid", depth=fifo.depth * 2 + 1)
+
+    def test_sync_fwft_pot(self):
+        self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=True))
+
+    def test_sync_fwft_npot(self):
+        self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=True))
+
+    def test_sync_not_fwft_pot(self):
+        self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=False))
+
+    def test_sync_not_fwft_npot(self):
+        self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=False))
+
+    def test_sync_buffered_pot(self):
+        self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=4))
+
+    def test_sync_buffered_potp1(self):
+        self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=5))
+
+    def test_sync_buffered_potm1(self):
+        self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=3))
+
+    def check_async_fifo(self, fifo):
+        # TODO: properly doing model equivalence checking on this likely requires multiclock,
+        # which is not really documented nor is it clear how to use it.
+        # self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="read", w_domain="write"),
+        #                   mode="bmc", depth=fifo.depth * 3 + 1)
+        self.assertFormal(FIFOContractSpec(fifo, r_domain="read", w_domain="write",
+                                           bound=fifo.depth * 4 + 1),
+                          mode="hybrid", depth=fifo.depth * 4 + 1)
+
+    def test_async(self):
+        self.check_async_fifo(AsyncFIFO(width=8, depth=4))
+
+    def test_async_buffered(self):
+        self.check_async_fifo(AsyncFIFOBuffered(width=8, depth=4))
diff --git a/tests/test_lib_io.py b/tests/test_lib_io.py
new file mode 100644 (file)
index 0000000..234df1d
--- /dev/null
@@ -0,0 +1,209 @@
+from nmigen.hdl import *
+from nmigen.hdl.rec import *
+from nmigen.back.pysim import *
+from nmigen.lib.io import *
+
+from .utils import *
+
+
+class PinLayoutTestCase(FHDLTestCase):
+    def assertLayoutEqual(self, layout, expected):
+        casted_layout = {}
+        for name, (shape, dir) in layout.items():
+            casted_layout[name] = (Shape.cast(shape), dir)
+
+        self.assertEqual(casted_layout, expected)
+
+
+class PinLayoutCombTestCase(PinLayoutTestCase):
+    def test_pin_layout_i(self):
+        layout_1 = pin_layout(1, dir="i")
+        self.assertLayoutEqual(layout_1.fields, {
+            "i": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="i")
+        self.assertLayoutEqual(layout_2.fields, {
+            "i": ((2, False), DIR_NONE),
+        })
+
+    def test_pin_layout_o(self):
+        layout_1 = pin_layout(1, dir="o")
+        self.assertLayoutEqual(layout_1.fields, {
+            "o": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="o")
+        self.assertLayoutEqual(layout_2.fields, {
+            "o": ((2, False), DIR_NONE),
+        })
+
+    def test_pin_layout_oe(self):
+        layout_1 = pin_layout(1, dir="oe")
+        self.assertLayoutEqual(layout_1.fields, {
+            "o":  ((1, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="oe")
+        self.assertLayoutEqual(layout_2.fields, {
+            "o":  ((2, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+    def test_pin_layout_io(self):
+        layout_1 = pin_layout(1, dir="io")
+        self.assertLayoutEqual(layout_1.fields, {
+            "i":  ((1, False), DIR_NONE),
+            "o":  ((1, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="io")
+        self.assertLayoutEqual(layout_2.fields, {
+            "i":  ((2, False), DIR_NONE),
+            "o":  ((2, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+
+class PinLayoutSDRTestCase(PinLayoutTestCase):
+    def test_pin_layout_i(self):
+        layout_1 = pin_layout(1, dir="i", xdr=1)
+        self.assertLayoutEqual(layout_1.fields, {
+            "i_clk": ((1, False), DIR_NONE),
+            "i": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="i", xdr=1)
+        self.assertLayoutEqual(layout_2.fields, {
+            "i_clk": ((1, False), DIR_NONE),
+            "i": ((2, False), DIR_NONE),
+        })
+
+    def test_pin_layout_o(self):
+        layout_1 = pin_layout(1, dir="o", xdr=1)
+        self.assertLayoutEqual(layout_1.fields, {
+            "o_clk": ((1, False), DIR_NONE),
+            "o": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="o", xdr=1)
+        self.assertLayoutEqual(layout_2.fields, {
+            "o_clk": ((1, False), DIR_NONE),
+            "o": ((2, False), DIR_NONE),
+        })
+
+    def test_pin_layout_oe(self):
+        layout_1 = pin_layout(1, dir="oe", xdr=1)
+        self.assertLayoutEqual(layout_1.fields, {
+            "o_clk": ((1, False), DIR_NONE),
+            "o":  ((1, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="oe", xdr=1)
+        self.assertLayoutEqual(layout_2.fields, {
+            "o_clk": ((1, False), DIR_NONE),
+            "o":  ((2, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+    def test_pin_layout_io(self):
+        layout_1 = pin_layout(1, dir="io", xdr=1)
+        self.assertLayoutEqual(layout_1.fields, {
+            "i_clk": ((1, False), DIR_NONE),
+            "i":  ((1, False), DIR_NONE),
+            "o_clk": ((1, False), DIR_NONE),
+            "o":  ((1, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="io", xdr=1)
+        self.assertLayoutEqual(layout_2.fields, {
+            "i_clk": ((1, False), DIR_NONE),
+            "i":  ((2, False), DIR_NONE),
+            "o_clk": ((1, False), DIR_NONE),
+            "o":  ((2, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+
+class PinLayoutDDRTestCase(PinLayoutTestCase):
+    def test_pin_layout_i(self):
+        layout_1 = pin_layout(1, dir="i", xdr=2)
+        self.assertLayoutEqual(layout_1.fields, {
+            "i_clk": ((1, False), DIR_NONE),
+            "i0": ((1, False), DIR_NONE),
+            "i1": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="i", xdr=2)
+        self.assertLayoutEqual(layout_2.fields, {
+            "i_clk": ((1, False), DIR_NONE),
+            "i0": ((2, False), DIR_NONE),
+            "i1": ((2, False), DIR_NONE),
+        })
+
+    def test_pin_layout_o(self):
+        layout_1 = pin_layout(1, dir="o", xdr=2)
+        self.assertLayoutEqual(layout_1.fields, {
+            "o_clk": ((1, False), DIR_NONE),
+            "o0": ((1, False), DIR_NONE),
+            "o1": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="o", xdr=2)
+        self.assertLayoutEqual(layout_2.fields, {
+            "o_clk": ((1, False), DIR_NONE),
+            "o0": ((2, False), DIR_NONE),
+            "o1": ((2, False), DIR_NONE),
+        })
+
+    def test_pin_layout_oe(self):
+        layout_1 = pin_layout(1, dir="oe", xdr=2)
+        self.assertLayoutEqual(layout_1.fields, {
+            "o_clk": ((1, False), DIR_NONE),
+            "o0": ((1, False), DIR_NONE),
+            "o1": ((1, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="oe", xdr=2)
+        self.assertLayoutEqual(layout_2.fields, {
+            "o_clk": ((1, False), DIR_NONE),
+            "o0": ((2, False), DIR_NONE),
+            "o1": ((2, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+    def test_pin_layout_io(self):
+        layout_1 = pin_layout(1, dir="io", xdr=2)
+        self.assertLayoutEqual(layout_1.fields, {
+            "i_clk": ((1, False), DIR_NONE),
+            "i0": ((1, False), DIR_NONE),
+            "i1": ((1, False), DIR_NONE),
+            "o_clk": ((1, False), DIR_NONE),
+            "o0": ((1, False), DIR_NONE),
+            "o1": ((1, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+        layout_2 = pin_layout(2, dir="io", xdr=2)
+        self.assertLayoutEqual(layout_2.fields, {
+            "i_clk": ((1, False), DIR_NONE),
+            "i0": ((2, False), DIR_NONE),
+            "i1": ((2, False), DIR_NONE),
+            "o_clk": ((1, False), DIR_NONE),
+            "o0": ((2, False), DIR_NONE),
+            "o1": ((2, False), DIR_NONE),
+            "oe": ((1, False), DIR_NONE),
+        })
+
+
+class PinTestCase(FHDLTestCase):
+    def test_attributes(self):
+        pin = Pin(2, dir="io", xdr=2)
+        self.assertEqual(pin.width, 2)
+        self.assertEqual(pin.dir,   "io")
+        self.assertEqual(pin.xdr,   2)
diff --git a/tests/test_lib_scheduler.py b/tests/test_lib_scheduler.py
new file mode 100644 (file)
index 0000000..3128ad1
--- /dev/null
@@ -0,0 +1,96 @@
+# nmigen: UnusedElaboratable=no
+
+import unittest
+
+from nmigen.hdl import *
+from nmigen.asserts import *
+from nmigen.sim.pysim import *
+from nmigen.lib.scheduler import *
+
+from .utils import *
+
+
+class RoundRobinTestCase(unittest.TestCase):
+    def test_count(self):
+        dut = RoundRobin(count=32)
+        self.assertEqual(dut.count, 32)
+        self.assertEqual(len(dut.requests), 32)
+        self.assertEqual(len(dut.grant), 5)
+
+    def test_wrong_count(self):
+        with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not 'foo'"):
+            dut = RoundRobin(count="foo")
+        with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not -1"):
+            dut = RoundRobin(count=-1)
+
+
+class RoundRobinSimulationTestCase(unittest.TestCase):
+    def test_count_one(self):
+        dut = RoundRobin(count=1)
+        sim = Simulator(dut)
+        def process():
+            yield dut.requests.eq(0)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 0)
+            self.assertFalse((yield dut.valid))
+
+            yield dut.requests.eq(1)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 0)
+            self.assertTrue((yield dut.valid))
+        sim.add_sync_process(process)
+        sim.add_clock(1e-6)
+        with sim.write_vcd("test.vcd"):
+            sim.run()
+
+    def test_transitions(self):
+        dut = RoundRobin(count=3)
+        sim = Simulator(dut)
+        def process():
+            yield dut.requests.eq(0b111)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 1)
+            self.assertTrue((yield dut.valid))
+
+            yield dut.requests.eq(0b110)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 2)
+            self.assertTrue((yield dut.valid))
+
+            yield dut.requests.eq(0b010)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 1)
+            self.assertTrue((yield dut.valid))
+
+            yield dut.requests.eq(0b011)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 0)
+            self.assertTrue((yield dut.valid))
+
+            yield dut.requests.eq(0b001)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 0)
+            self.assertTrue((yield dut.valid))
+
+            yield dut.requests.eq(0b101)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 2)
+            self.assertTrue((yield dut.valid))
+
+            yield dut.requests.eq(0b100)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 2)
+            self.assertTrue((yield dut.valid))
+
+            yield dut.requests.eq(0b000)
+            yield; yield Delay(1e-8)
+            self.assertFalse((yield dut.valid))
+
+            yield dut.requests.eq(0b001)
+            yield; yield Delay(1e-8)
+            self.assertEqual((yield dut.grant), 0)
+            self.assertTrue((yield dut.valid))
+        sim.add_sync_process(process)
+        sim.add_clock(1e-6)
+        with sim.write_vcd("test.vcd"):
+            sim.run()
diff --git a/tests/test_sim.py b/tests/test_sim.py
new file mode 100644 (file)
index 0000000..6c125ba
--- /dev/null
@@ -0,0 +1,799 @@
+import os
+from contextlib import contextmanager
+
+from nmigen._utils import flatten, union
+from nmigen.hdl.ast import *
+from nmigen.hdl.cd import  *
+from nmigen.hdl.mem import *
+from nmigen.hdl.rec import *
+from nmigen.hdl.dsl import  *
+from nmigen.hdl.ir import *
+from nmigen.back.pysim import *
+
+from .utils import *
+
+
+class SimulatorUnitTestCase(FHDLTestCase):
+    def assertStatement(self, stmt, inputs, output, reset=0):
+        inputs = [Value.cast(i) for i in inputs]
+        output = Value.cast(output)
+
+        isigs = [Signal(i.shape(), name=n) for i, n in zip(inputs, "abcd")]
+        osig  = Signal(output.shape(), name="y", reset=reset)
+
+        stmt = stmt(osig, *isigs)
+        frag = Fragment()
+        frag.add_statements(stmt)
+        for signal in flatten(s._lhs_signals() for s in Statement.cast(stmt)):
+            frag.add_driver(signal)
+
+        sim = Simulator(frag)
+        def process():
+            for isig, input in zip(isigs, inputs):
+                yield isig.eq(input)
+            yield Settle()
+            self.assertEqual((yield osig), output.value)
+        sim.add_process(process)
+        with sim.write_vcd("test.vcd", "test.gtkw", traces=[*isigs, osig]):
+            sim.run()
+
+    def test_invert(self):
+        stmt = lambda y, a: y.eq(~a)
+        self.assertStatement(stmt, [C(0b0000, 4)], C(0b1111, 4))
+        self.assertStatement(stmt, [C(0b1010, 4)], C(0b0101, 4))
+        self.assertStatement(stmt, [C(0,      4)], C(-1,     4))
+
+    def test_neg(self):
+        stmt = lambda y, a: y.eq(-a)
+        self.assertStatement(stmt, [C(0b0000, 4)], C(0b0000, 4))
+        self.assertStatement(stmt, [C(0b0001, 4)], C(0b1111, 4))
+        self.assertStatement(stmt, [C(0b1010, 4)], C(0b0110, 4))
+        self.assertStatement(stmt, [C(1,      4)], C(-1,     4))
+        self.assertStatement(stmt, [C(5,      4)], C(-5,     4))
+
+    def test_bool(self):
+        stmt = lambda y, a: y.eq(a.bool())
+        self.assertStatement(stmt, [C(0, 4)], C(0))
+        self.assertStatement(stmt, [C(1, 4)], C(1))
+        self.assertStatement(stmt, [C(2, 4)], C(1))
+
+    def test_as_unsigned(self):
+        stmt = lambda y, a, b: y.eq(a.as_unsigned() == b)
+        self.assertStatement(stmt, [C(0b01, signed(2)), C(0b0001, unsigned(4))], C(1))
+        self.assertStatement(stmt, [C(0b11, signed(2)), C(0b0011, unsigned(4))], C(1))
+
+    def test_as_signed(self):
+        stmt = lambda y, a, b: y.eq(a.as_signed() == b)
+        self.assertStatement(stmt, [C(0b01, unsigned(2)), C(0b0001, signed(4))], C(1))
+        self.assertStatement(stmt, [C(0b11, unsigned(2)), C(0b1111, signed(4))], C(1))
+
+    def test_any(self):
+        stmt = lambda y, a: y.eq(a.any())
+        self.assertStatement(stmt, [C(0b00, 2)], C(0))
+        self.assertStatement(stmt, [C(0b01, 2)], C(1))
+        self.assertStatement(stmt, [C(0b10, 2)], C(1))
+        self.assertStatement(stmt, [C(0b11, 2)], C(1))
+
+    def test_all(self):
+        stmt = lambda y, a: y.eq(a.all())
+        self.assertStatement(stmt, [C(0b00, 2)], C(0))
+        self.assertStatement(stmt, [C(0b01, 2)], C(0))
+        self.assertStatement(stmt, [C(0b10, 2)], C(0))
+        self.assertStatement(stmt, [C(0b11, 2)], C(1))
+
+    def test_xor_unary(self):
+        stmt = lambda y, a: y.eq(a.xor())
+        self.assertStatement(stmt, [C(0b00, 2)], C(0))
+        self.assertStatement(stmt, [C(0b01, 2)], C(1))
+        self.assertStatement(stmt, [C(0b10, 2)], C(1))
+        self.assertStatement(stmt, [C(0b11, 2)], C(0))
+
+    def test_add(self):
+        stmt = lambda y, a, b: y.eq(a + b)
+        self.assertStatement(stmt, [C(0,  4), C(1,  4)], C(1,   4))
+        self.assertStatement(stmt, [C(-5, 4), C(-5, 4)], C(-10, 5))
+
+    def test_sub(self):
+        stmt = lambda y, a, b: y.eq(a - b)
+        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(1,   4))
+        self.assertStatement(stmt, [C(0,  4), C(1,  4)], C(-1,  4))
+        self.assertStatement(stmt, [C(0,  4), C(10, 4)], C(-10, 5))
+
+    def test_mul(self):
+        stmt = lambda y, a, b: y.eq(a * b)
+        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(2,   8))
+        self.assertStatement(stmt, [C(2,  4), C(2,  4)], C(4,   8))
+        self.assertStatement(stmt, [C(7,  4), C(7,  4)], C(49,  8))
+
+    def test_floordiv(self):
+        stmt = lambda y, a, b: y.eq(a // b)
+        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(2,   8))
+        self.assertStatement(stmt, [C(2,  4), C(2,  4)], C(1,   8))
+        self.assertStatement(stmt, [C(7,  4), C(2,  4)], C(3,   8))
+
+    def test_mod(self):
+        stmt = lambda y, a, b: y.eq(a % b)
+        self.assertStatement(stmt, [C(2,  4), C(0,  4)], C(0,   8))
+        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(0,   8))
+        self.assertStatement(stmt, [C(2,  4), C(2,  4)], C(0,   8))
+        self.assertStatement(stmt, [C(7,  4), C(2,  4)], C(1,   8))
+
+    def test_and(self):
+        stmt = lambda y, a, b: y.eq(a & b)
+        self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1000, 4))
+
+    def test_or(self):
+        stmt = lambda y, a, b: y.eq(a | b)
+        self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1110, 4))
+
+    def test_xor_binary(self):
+        stmt = lambda y, a, b: y.eq(a ^ b)
+        self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b0110, 4))
+
+    def test_shl(self):
+        stmt = lambda y, a, b: y.eq(a << b)
+        self.assertStatement(stmt, [C(0b1001, 4), C(0)],  C(0b1001,    5))
+        self.assertStatement(stmt, [C(0b1001, 4), C(3)],  C(0b1001000, 7))
+
+    def test_shr(self):
+        stmt = lambda y, a, b: y.eq(a >> b)
+        self.assertStatement(stmt, [C(0b1001, 4), C(0)],  C(0b1001,    4))
+        self.assertStatement(stmt, [C(0b1001, 4), C(2)],  C(0b10,      4))
+
+    def test_eq(self):
+        stmt = lambda y, a, b: y.eq(a == b)
+        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1))
+        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0))
+        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0))
+
+    def test_ne(self):
+        stmt = lambda y, a, b: y.eq(a != b)
+        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0))
+        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1))
+        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1))
+
+    def test_lt(self):
+        stmt = lambda y, a, b: y.eq(a < b)
+        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0))
+        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1))
+        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0))
+
+    def test_ge(self):
+        stmt = lambda y, a, b: y.eq(a >= b)
+        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1))
+        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0))
+        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1))
+
+    def test_gt(self):
+        stmt = lambda y, a, b: y.eq(a > b)
+        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0))
+        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0))
+        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1))
+
+    def test_le(self):
+        stmt = lambda y, a, b: y.eq(a <= b)
+        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1))
+        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1))
+        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0))
+
+    def test_mux(self):
+        stmt = lambda y, a, b, c: y.eq(Mux(c, a, b))
+        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(0)], C(3, 4))
+        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(1)], C(2, 4))
+
+    def test_abs(self):
+        stmt = lambda y, a: y.eq(abs(a))
+        self.assertStatement(stmt, [C(3,  unsigned(8))], C(3,  unsigned(8)))
+        self.assertStatement(stmt, [C(-3, unsigned(8))], C(-3, unsigned(8)))
+        self.assertStatement(stmt, [C(3,  signed(8))],   C(3,  signed(8)))
+        self.assertStatement(stmt, [C(-3, signed(8))],   C(3,  signed(8)))
+
+    def test_slice(self):
+        stmt1 = lambda y, a: y.eq(a[2])
+        self.assertStatement(stmt1, [C(0b10110100, 8)], C(0b1,  1))
+        stmt2 = lambda y, a: y.eq(a[2:4])
+        self.assertStatement(stmt2, [C(0b10110100, 8)], C(0b01, 2))
+
+    def test_slice_lhs(self):
+        stmt1 = lambda y, a: y[2].eq(a)
+        self.assertStatement(stmt1, [C(0b0,  1)], C(0b11111011, 8), reset=0b11111111)
+        stmt2 = lambda y, a: y[2:4].eq(a)
+        self.assertStatement(stmt2, [C(0b01, 2)], C(0b11110111, 8), reset=0b11111011)
+
+    def test_bit_select(self):
+        stmt = lambda y, a, b: y.eq(a.bit_select(b, 3))
+        self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3))
+        self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b101, 3))
+        self.assertStatement(stmt, [C(0b10110100, 8), C(3)], C(0b110, 3))
+
+    def test_bit_select_lhs(self):
+        stmt = lambda y, a, b: y.bit_select(a, 3).eq(b)
+        self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111)
+        self.assertStatement(stmt, [C(2), C(0b101, 3)], C(0b11110111, 8), reset=0b11111111)
+        self.assertStatement(stmt, [C(3), C(0b110, 3)], C(0b11110111, 8), reset=0b11111111)
+
+    def test_word_select(self):
+        stmt = lambda y, a, b: y.eq(a.word_select(b, 3))
+        self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3))
+        self.assertStatement(stmt, [C(0b10110100, 8), C(1)], C(0b110, 3))
+        self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b010, 3))
+
+    def test_word_select_lhs(self):
+        stmt = lambda y, a, b: y.word_select(a, 3).eq(b)
+        self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111)
+        self.assertStatement(stmt, [C(1), C(0b101, 3)], C(0b11101111, 8), reset=0b11111111)
+        self.assertStatement(stmt, [C(2), C(0b110, 3)], C(0b10111111, 8), reset=0b11111111)
+
+    def test_cat(self):
+        stmt = lambda y, *xs: y.eq(Cat(*xs))
+        self.assertStatement(stmt, [C(0b10, 2), C(0b01, 2)], C(0b0110, 4))
+
+    def test_cat_lhs(self):
+        l = Signal(3)
+        m = Signal(3)
+        n = Signal(3)
+        stmt = lambda y, a: [Cat(l, m, n).eq(a), y.eq(Cat(n, m, l))]
+        self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9))
+
+    def test_nested_cat_lhs(self):
+        l = Signal(3)
+        m = Signal(3)
+        n = Signal(3)
+        stmt = lambda y, a: [Cat(Cat(l, Cat(m)), n).eq(a), y.eq(Cat(n, m, l))]
+        self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9))
+
+    def test_record(self):
+        rec = Record([
+            ("l", 1),
+            ("m", 2),
+        ])
+        stmt = lambda y, a: [rec.eq(a), y.eq(rec)]
+        self.assertStatement(stmt, [C(0b101, 3)], C(0b101, 3))
+
+    def test_repl(self):
+        stmt = lambda y, a: y.eq(Repl(a, 3))
+        self.assertStatement(stmt, [C(0b10, 2)], C(0b101010, 6))
+
+    def test_array(self):
+        array = Array([1, 4, 10])
+        stmt = lambda y, a: y.eq(array[a])
+        self.assertStatement(stmt, [C(0)], C(1))
+        self.assertStatement(stmt, [C(1)], C(4))
+        self.assertStatement(stmt, [C(2)], C(10))
+
+    def test_array_oob(self):
+        array = Array([1, 4, 10])
+        stmt = lambda y, a: y.eq(array[a])
+        self.assertStatement(stmt, [C(3)], C(10))
+        self.assertStatement(stmt, [C(4)], C(10))
+
+    def test_array_lhs(self):
+        l = Signal(3, reset=1)
+        m = Signal(3, reset=4)
+        n = Signal(3, reset=7)
+        array = Array([l, m, n])
+        stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))]
+        self.assertStatement(stmt, [C(0), C(0b000)], C(0b111100000))
+        self.assertStatement(stmt, [C(1), C(0b010)], C(0b111010001))
+        self.assertStatement(stmt, [C(2), C(0b100)], C(0b100100001))
+
+    def test_array_lhs_oob(self):
+        l = Signal(3)
+        m = Signal(3)
+        n = Signal(3)
+        array = Array([l, m, n])
+        stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))]
+        self.assertStatement(stmt, [C(3), C(0b001)], C(0b001000000))
+        self.assertStatement(stmt, [C(4), C(0b010)], C(0b010000000))
+
+    def test_array_index(self):
+        array = Array(Array(x * y for y in range(10)) for x in range(10))
+        stmt = lambda y, a, b: y.eq(array[a][b])
+        for x in range(10):
+            for y in range(10):
+                self.assertStatement(stmt, [C(x), C(y)], C(x * y))
+
+    def test_array_attr(self):
+        from collections import namedtuple
+        pair = namedtuple("pair", ("p", "n"))
+
+        array = Array(pair(x, -x) for x in range(10))
+        stmt = lambda y, a: y.eq(array[a].p + array[a].n)
+        for i in range(10):
+            self.assertStatement(stmt, [C(i)], C(0))
+
+    def test_shift_left(self):
+        stmt1 = lambda y, a: y.eq(a.shift_left(1))
+        self.assertStatement(stmt1, [C(0b10100010, 8)], C(   0b101000100, 9))
+        stmt2 = lambda y, a: y.eq(a.shift_left(4))
+        self.assertStatement(stmt2, [C(0b10100010, 8)], C(0b101000100000, 12))
+
+    def test_shift_right(self):
+        stmt1 = lambda y, a: y.eq(a.shift_right(1))
+        self.assertStatement(stmt1, [C(0b10100010, 8)], C(0b1010001, 7))
+        stmt2 = lambda y, a: y.eq(a.shift_right(4))
+        self.assertStatement(stmt2, [C(0b10100010, 8)], C(   0b1010, 4))
+
+    def test_rotate_left(self):
+        stmt = lambda y, a: y.eq(a.rotate_left(1))
+        self.assertStatement(stmt, [C(0b1)], C(0b1))
+        self.assertStatement(stmt, [C(0b1001000)], C(0b0010001))
+        stmt = lambda y, a: y.eq(a.rotate_left(5))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
+        stmt = lambda y, a: y.eq(a.rotate_left(7))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
+        stmt = lambda y, a: y.eq(a.rotate_left(9))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
+        stmt = lambda y, a: y.eq(a.rotate_left(-1))
+        self.assertStatement(stmt, [C(0b1)], C(0b1))
+        self.assertStatement(stmt, [C(0b1001000)], C(0b0100100))
+        stmt = lambda y, a: y.eq(a.rotate_left(-5))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
+        stmt = lambda y, a: y.eq(a.rotate_left(-7))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
+        stmt = lambda y, a: y.eq(a.rotate_left(-9))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
+
+    def test_rotate_right(self):
+        stmt = lambda y, a: y.eq(a.rotate_right(1))
+        self.assertStatement(stmt, [C(0b1)], C(0b1))
+        self.assertStatement(stmt, [C(0b1001000)], C(0b0100100))
+        stmt = lambda y, a: y.eq(a.rotate_right(5))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
+        stmt = lambda y, a: y.eq(a.rotate_right(7))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
+        stmt = lambda y, a: y.eq(a.rotate_right(9))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
+        stmt = lambda y, a: y.eq(a.rotate_right(-1))
+        self.assertStatement(stmt, [C(0b1)], C(0b1))
+        self.assertStatement(stmt, [C(0b1001000)], C(0b0010001))
+        stmt = lambda y, a: y.eq(a.rotate_right(-5))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
+        stmt = lambda y, a: y.eq(a.rotate_right(-7))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
+        stmt = lambda y, a: y.eq(a.rotate_right(-9))
+        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
+        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
+
+
+class SimulatorIntegrationTestCase(FHDLTestCase):
+    @contextmanager
+    def assertSimulation(self, module, deadline=None):
+        sim = Simulator(module)
+        yield sim
+        with sim.write_vcd("test.vcd", "test.gtkw"):
+            if deadline is None:
+                sim.run()
+            else:
+                sim.run_until(deadline)
+
+    def setUp_counter(self):
+        self.count = Signal(3, reset=4)
+        self.sync  = ClockDomain()
+
+        self.m = Module()
+        self.m.d.sync  += self.count.eq(self.count + 1)
+        self.m.domains += self.sync
+
+    def test_counter_process(self):
+        self.setUp_counter()
+        with self.assertSimulation(self.m) as sim:
+            def process():
+                self.assertEqual((yield self.count), 4)
+                yield Delay(1e-6)
+                self.assertEqual((yield self.count), 4)
+                yield self.sync.clk.eq(1)
+                self.assertEqual((yield self.count), 4)
+                yield Settle()
+                self.assertEqual((yield self.count), 5)
+                yield Delay(1e-6)
+                self.assertEqual((yield self.count), 5)
+                yield self.sync.clk.eq(0)
+                self.assertEqual((yield self.count), 5)
+                yield Settle()
+                self.assertEqual((yield self.count), 5)
+                for _ in range(3):
+                    yield Delay(1e-6)
+                    yield self.sync.clk.eq(1)
+                    yield Delay(1e-6)
+                    yield self.sync.clk.eq(0)
+                self.assertEqual((yield self.count), 0)
+            sim.add_process(process)
+
+    def test_counter_clock_and_sync_process(self):
+        self.setUp_counter()
+        with self.assertSimulation(self.m) as sim:
+            sim.add_clock(1e-6, domain="sync")
+            def process():
+                self.assertEqual((yield self.count), 4)
+                self.assertEqual((yield self.sync.clk), 1)
+                yield
+                self.assertEqual((yield self.count), 5)
+                self.assertEqual((yield self.sync.clk), 1)
+                for _ in range(3):
+                    yield
+                self.assertEqual((yield self.count), 0)
+            sim.add_sync_process(process)
+
+    def test_reset(self):
+        self.setUp_counter()
+        sim = Simulator(self.m)
+        sim.add_clock(1e-6)
+        times = 0
+        def process():
+            nonlocal times
+            self.assertEqual((yield self.count), 4)
+            yield
+            self.assertEqual((yield self.count), 5)
+            yield
+            self.assertEqual((yield self.count), 6)
+            yield
+            times += 1
+        sim.add_sync_process(process)
+        sim.run()
+        sim.reset()
+        sim.run()
+        self.assertEqual(times, 2)
+
+    def setUp_alu(self):
+        self.a = Signal(8)
+        self.b = Signal(8)
+        self.o = Signal(8)
+        self.x = Signal(8)
+        self.s = Signal(2)
+        self.sync = ClockDomain(reset_less=True)
+
+        self.m = Module()
+        self.m.d.comb += self.x.eq(self.a ^ self.b)
+        with self.m.Switch(self.s):
+            with self.m.Case(0):
+                self.m.d.sync += self.o.eq(self.a + self.b)
+            with self.m.Case(1):
+                self.m.d.sync += self.o.eq(self.a - self.b)
+            with self.m.Case():
+                self.m.d.sync += self.o.eq(0)
+        self.m.domains += self.sync
+
+    def test_alu(self):
+        self.setUp_alu()
+        with self.assertSimulation(self.m) as sim:
+            sim.add_clock(1e-6)
+            def process():
+                yield self.a.eq(5)
+                yield self.b.eq(1)
+                yield
+                self.assertEqual((yield self.x), 4)
+                yield
+                self.assertEqual((yield self.o), 6)
+                yield self.s.eq(1)
+                yield
+                yield
+                self.assertEqual((yield self.o), 4)
+                yield self.s.eq(2)
+                yield
+                yield
+                self.assertEqual((yield self.o), 0)
+            sim.add_sync_process(process)
+
+    def setUp_multiclock(self):
+        self.sys = ClockDomain()
+        self.pix = ClockDomain()
+
+        self.m = Module()
+        self.m.domains += self.sys, self.pix
+
+    def test_multiclock(self):
+        self.setUp_multiclock()
+        with self.assertSimulation(self.m) as sim:
+            sim.add_clock(1e-6, domain="sys")
+            sim.add_clock(0.3e-6, domain="pix")
+
+            def sys_process():
+                yield Passive()
+                yield
+                yield
+                self.fail()
+            def pix_process():
+                yield
+                yield
+                yield
+            sim.add_sync_process(sys_process, domain="sys")
+            sim.add_sync_process(pix_process, domain="pix")
+
+    def setUp_lhs_rhs(self):
+        self.i = Signal(8)
+        self.o = Signal(8)
+
+        self.m = Module()
+        self.m.d.comb += self.o.eq(self.i)
+
+    def test_complex_lhs_rhs(self):
+        self.setUp_lhs_rhs()
+        with self.assertSimulation(self.m) as sim:
+            def process():
+                yield self.i.eq(0b10101010)
+                yield self.i[:4].eq(-1)
+                yield Settle()
+                self.assertEqual((yield self.i[:4]), 0b1111)
+                self.assertEqual((yield self.i), 0b10101111)
+            sim.add_process(process)
+
+    def test_run_until(self):
+        m = Module()
+        s = Signal()
+        m.d.sync += s.eq(0)
+        with self.assertSimulation(m, deadline=100e-6) as sim:
+            sim.add_clock(1e-6)
+            def process():
+                for _ in range(101):
+                    yield Delay(1e-6)
+                self.fail()
+            sim.add_process(process)
+
+    def test_add_process_wrong(self):
+        with self.assertSimulation(Module()) as sim:
+            with self.assertRaisesRegex(TypeError,
+                    r"^Cannot add a process 1 because it is not a generator function$"):
+                sim.add_process(1)
+
+    def test_add_process_wrong_generator(self):
+        with self.assertSimulation(Module()) as sim:
+            with self.assertRaisesRegex(TypeError,
+                    r"^Cannot add a process <.+?> because it is not a generator function$"):
+                def process():
+                    yield Delay()
+                sim.add_process(process())
+
+    def test_add_clock_wrong_twice(self):
+        m = Module()
+        s = Signal()
+        m.d.sync += s.eq(0)
+        with self.assertSimulation(m) as sim:
+            sim.add_clock(1)
+            with self.assertRaisesRegex(ValueError,
+                    r"^Domain 'sync' already has a clock driving it$"):
+                sim.add_clock(1)
+
+    def test_add_clock_wrong_missing(self):
+        m = Module()
+        with self.assertSimulation(m) as sim:
+            with self.assertRaisesRegex(ValueError,
+                    r"^Domain 'sync' is not present in simulation$"):
+                sim.add_clock(1)
+
+    def test_add_clock_if_exists(self):
+        m = Module()
+        with self.assertSimulation(m) as sim:
+            sim.add_clock(1, if_exists=True)
+
+    def test_command_wrong(self):
+        survived = False
+        with self.assertSimulation(Module()) as sim:
+            def process():
+                nonlocal survived
+                with self.assertRaisesRegex(TypeError,
+                        r"Received unsupported command 1 from process .+?"):
+                    yield 1
+                yield Settle()
+                survived = True
+            sim.add_process(process)
+        self.assertTrue(survived)
+
+    def setUp_memory(self, rd_synchronous=True, rd_transparent=True, wr_granularity=None):
+        self.m = Module()
+        self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55])
+        self.m.submodules.rdport = self.rdport = \
+            self.memory.read_port(domain="sync" if rd_synchronous else "comb",
+                                  transparent=rd_transparent)
+        self.m.submodules.wrport = self.wrport = \
+            self.memory.write_port(granularity=wr_granularity)
+
+    def test_memory_init(self):
+        self.setUp_memory()
+        with self.assertSimulation(self.m) as sim:
+            def process():
+                self.assertEqual((yield self.rdport.data), 0xaa)
+                yield self.rdport.addr.eq(1)
+                yield
+                yield
+                self.assertEqual((yield self.rdport.data), 0x55)
+                yield self.rdport.addr.eq(2)
+                yield
+                yield
+                self.assertEqual((yield self.rdport.data), 0x00)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+
+    def test_memory_write(self):
+        self.setUp_memory()
+        with self.assertSimulation(self.m) as sim:
+            def process():
+                yield self.wrport.addr.eq(4)
+                yield self.wrport.data.eq(0x33)
+                yield self.wrport.en.eq(1)
+                yield
+                yield self.wrport.en.eq(0)
+                yield self.rdport.addr.eq(4)
+                yield
+                self.assertEqual((yield self.rdport.data), 0x33)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+
+    def test_memory_write_granularity(self):
+        self.setUp_memory(wr_granularity=4)
+        with self.assertSimulation(self.m) as sim:
+            def process():
+                yield self.wrport.data.eq(0x50)
+                yield self.wrport.en.eq(0b00)
+                yield
+                yield self.wrport.en.eq(0)
+                yield
+                self.assertEqual((yield self.rdport.data), 0xaa)
+                yield self.wrport.en.eq(0b10)
+                yield
+                yield self.wrport.en.eq(0)
+                yield
+                self.assertEqual((yield self.rdport.data), 0x5a)
+                yield self.wrport.data.eq(0x33)
+                yield self.wrport.en.eq(0b01)
+                yield
+                yield self.wrport.en.eq(0)
+                yield
+                self.assertEqual((yield self.rdport.data), 0x53)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+
+    def test_memory_read_before_write(self):
+        self.setUp_memory(rd_transparent=False)
+        with self.assertSimulation(self.m) as sim:
+            def process():
+                yield self.wrport.data.eq(0x33)
+                yield self.wrport.en.eq(1)
+                yield
+                self.assertEqual((yield self.rdport.data), 0xaa)
+                yield
+                self.assertEqual((yield self.rdport.data), 0xaa)
+                yield Settle()
+                self.assertEqual((yield self.rdport.data), 0x33)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+
+    def test_memory_write_through(self):
+        self.setUp_memory(rd_transparent=True)
+        with self.assertSimulation(self.m) as sim:
+            def process():
+                yield self.wrport.data.eq(0x33)
+                yield self.wrport.en.eq(1)
+                yield
+                self.assertEqual((yield self.rdport.data), 0xaa)
+                yield Settle()
+                self.assertEqual((yield self.rdport.data), 0x33)
+                yield
+                yield self.rdport.addr.eq(1)
+                yield Settle()
+                self.assertEqual((yield self.rdport.data), 0x33)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+
+    def test_memory_async_read_write(self):
+        self.setUp_memory(rd_synchronous=False)
+        with self.assertSimulation(self.m) as sim:
+            def process():
+                yield self.rdport.addr.eq(0)
+                yield Settle()
+                self.assertEqual((yield self.rdport.data), 0xaa)
+                yield self.rdport.addr.eq(1)
+                yield Settle()
+                self.assertEqual((yield self.rdport.data), 0x55)
+                yield self.rdport.addr.eq(0)
+                yield self.wrport.addr.eq(0)
+                yield self.wrport.data.eq(0x33)
+                yield self.wrport.en.eq(1)
+                yield Tick("sync")
+                self.assertEqual((yield self.rdport.data), 0xaa)
+                yield Settle()
+                self.assertEqual((yield self.rdport.data), 0x33)
+            sim.add_clock(1e-6)
+            sim.add_process(process)
+
+    def test_memory_read_only(self):
+        self.m = Module()
+        self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55])
+        self.m.submodules.rdport = self.rdport = self.memory.read_port()
+        with self.assertSimulation(self.m) as sim:
+            def process():
+                self.assertEqual((yield self.rdport.data), 0xaa)
+                yield self.rdport.addr.eq(1)
+                yield
+                yield
+                self.assertEqual((yield self.rdport.data), 0x55)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+
+    def test_sample_helpers(self):
+        m = Module()
+        s = Signal(2)
+        def mk(x):
+            y = Signal.like(x)
+            m.d.comb += y.eq(x)
+            return y
+        p0, r0, f0, s0 = mk(Past(s, 0)), mk(Rose(s)),    mk(Fell(s)),    mk(Stable(s))
+        p1, r1, f1, s1 = mk(Past(s)),    mk(Rose(s, 1)), mk(Fell(s, 1)), mk(Stable(s, 1))
+        p2, r2, f2, s2 = mk(Past(s, 2)), mk(Rose(s, 2)), mk(Fell(s, 2)), mk(Stable(s, 2))
+        p3, r3, f3, s3 = mk(Past(s, 3)), mk(Rose(s, 3)), mk(Fell(s, 3)), mk(Stable(s, 3))
+        with self.assertSimulation(m) as sim:
+            def process_gen():
+                yield s.eq(0b10)
+                yield
+                yield
+                yield s.eq(0b01)
+                yield
+            def process_check():
+                yield
+                yield
+                yield
+
+                self.assertEqual((yield p0), 0b01)
+                self.assertEqual((yield p1), 0b10)
+                self.assertEqual((yield p2), 0b10)
+                self.assertEqual((yield p3), 0b00)
+
+                self.assertEqual((yield s0), 0b0)
+                self.assertEqual((yield s1), 0b1)
+                self.assertEqual((yield s2), 0b0)
+                self.assertEqual((yield s3), 0b1)
+
+                self.assertEqual((yield r0), 0b01)
+                self.assertEqual((yield r1), 0b00)
+                self.assertEqual((yield r2), 0b10)
+                self.assertEqual((yield r3), 0b00)
+
+                self.assertEqual((yield f0), 0b10)
+                self.assertEqual((yield f1), 0b00)
+                self.assertEqual((yield f2), 0b00)
+                self.assertEqual((yield f3), 0b00)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process_gen)
+            sim.add_sync_process(process_check)
+
+    def test_vcd_wrong_nonzero_time(self):
+        s = Signal()
+        m = Module()
+        m.d.sync += s.eq(s)
+        sim = Simulator(m)
+        sim.add_clock(1e-6)
+        sim.run_until(1e-5)
+        with self.assertRaisesRegex(ValueError,
+                r"^Cannot start writing waveforms after advancing simulation time$"):
+            with sim.write_vcd(open(os.path.devnull, "wt")):
+                pass
+
+
+class SimulatorRegressionTestCase(FHDLTestCase):
+    def test_bug_325(self):
+        dut = Module()
+        dut.d.comb += Signal().eq(Cat())
+        Simulator(dut).run()
+
+    def test_bug_325_bis(self):
+        dut = Module()
+        dut.d.comb += Signal().eq(Repl(Const(1), 0))
+        Simulator(dut).run()
+
+    def test_bug_473(self):
+        sim = Simulator(Module())
+        def process():
+            self.assertEqual((yield -(Const(0b11, 2).as_signed())), 1)
+        sim.add_process(process)
+        sim.run()
diff --git a/tests/utils.py b/tests/utils.py
new file mode 100644 (file)
index 0000000..c165cf9
--- /dev/null
@@ -0,0 +1,77 @@
+import os
+import re
+import shutil
+import subprocess
+import textwrap
+import traceback
+import unittest
+from contextlib import contextmanager
+
+from nmigen.hdl.ast import *
+from nmigen.hdl.ir import *
+from nmigen.back import rtlil
+from nmigen._toolchain import require_tool
+
+
+__all__ = ["FHDLTestCase"]
+
+
+class FHDLTestCase(unittest.TestCase):
+    def assertRepr(self, obj, repr_str):
+        if isinstance(obj, list):
+            obj = Statement.cast(obj)
+        def prepare_repr(repr_str):
+            repr_str = re.sub(r"\s+",   " ",  repr_str)
+            repr_str = re.sub(r"\( (?=\()", "(", repr_str)
+            repr_str = re.sub(r"\) (?=\))", ")", repr_str)
+            return repr_str.strip()
+        self.assertEqual(prepare_repr(repr(obj)), prepare_repr(repr_str))
+
+    def assertFormal(self, spec, mode="bmc", depth=1):
+        caller, *_ = traceback.extract_stack(limit=2)
+        spec_root, _ = os.path.splitext(caller.filename)
+        spec_dir = os.path.dirname(spec_root)
+        spec_name = "{}_{}".format(
+            os.path.basename(spec_root).replace("test_", "spec_"),
+            caller.name.replace("test_", "")
+        )
+
+        # The sby -f switch seems not fully functional when sby is reading from stdin.
+        if os.path.exists(os.path.join(spec_dir, spec_name)):
+            shutil.rmtree(os.path.join(spec_dir, spec_name))
+
+        if mode == "hybrid":
+            # A mix of BMC and k-induction, as per personal communication with Clifford Wolf.
+            script = "setattr -unset init w:* a:nmigen.sample_reg %d"
+            mode   = "bmc"
+        else:
+            script = ""
+
+        config = textwrap.dedent("""\
+        [options]
+        mode {mode}
+        depth {depth}
+        wait on
+
+        [engines]
+        smtbmc
+
+        [script]
+        read_ilang top.il
+        prep
+        {script}
+
+        [file top.il]
+        {rtlil}
+        """).format(
+            mode=mode,
+            depth=depth,
+            script=script,
+            rtlil=rtlil.convert(Fragment.get(spec, platform="formal"))
+        )
+        with subprocess.Popen([require_tool("sby"), "-f", "-d", spec_name], cwd=spec_dir,
+                              universal_newlines=True,
+                              stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
+            stdout, stderr = proc.communicate(config)
+            if proc.returncode != 0:
+                self.fail("Formal verification failed:\n" + stdout)