{,_}tools→{,_}utils
authorwhitequark <cz@m-labs.hk>
Sun, 13 Oct 2019 18:53:38 +0000 (18:53 +0000)
committerwhitequark <cz@m-labs.hk>
Sun, 13 Oct 2019 18:53:38 +0000 (18:53 +0000)
In context of nMigen, "tools" means "parts of toolchain", so it is
confusing to have a completely unrelated module also called "tools".

42 files changed:
nmigen/_tools.py [deleted file]
nmigen/_utils.py [new file with mode: 0644]
nmigen/back/pysim.py
nmigen/back/rtlil.py
nmigen/compat/fhdl/bitcontainer.py
nmigen/compat/fhdl/decorators.py
nmigen/compat/fhdl/module.py
nmigen/compat/fhdl/specials.py
nmigen/compat/fhdl/structure.py
nmigen/compat/genlib/cdc.py
nmigen/compat/genlib/fifo.py
nmigen/compat/genlib/fsm.py
nmigen/compat/genlib/resetsync.py
nmigen/hdl/ast.py
nmigen/hdl/dsl.py
nmigen/hdl/ir.py
nmigen/hdl/rec.py
nmigen/hdl/xfrm.py
nmigen/lib/cdc.py
nmigen/lib/fifo.py
nmigen/test/compat/support.py
nmigen/test/compat/test_size.py
nmigen/test/test_build_dsl.py
nmigen/test/test_build_res.py
nmigen/test/test_compat.py
nmigen/test/test_examples.py
nmigen/test/test_hdl_ast.py
nmigen/test/test_hdl_cd.py
nmigen/test/test_hdl_dsl.py
nmigen/test/test_hdl_ir.py
nmigen/test/test_hdl_mem.py
nmigen/test/test_hdl_rec.py
nmigen/test/test_hdl_xfrm.py
nmigen/test/test_lib_cdc.py
nmigen/test/test_lib_coding.py
nmigen/test/test_lib_fifo.py
nmigen/test/test_lib_io.py
nmigen/test/test_sim.py
nmigen/test/tools.py [deleted file]
nmigen/test/utils.py [new file with mode: 0644]
nmigen/tools.py [deleted file]
nmigen/utils.py [new file with mode: 0644]

diff --git a/nmigen/_tools.py b/nmigen/_tools.py
deleted file mode 100644 (file)
index a62153d..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-import contextlib
-import functools
-import warnings
-from collections import OrderedDict
-from collections.abc import Iterable
-from contextlib import contextmanager
-
-from .tools import *
-
-
-__all__ = ["flatten", "union" , "log2_int", "bits_for", "memoize", "final", "deprecated"]
-
-
-def flatten(i):
-    for e in i:
-        if isinstance(e, Iterable):
-            yield from flatten(e)
-        else:
-            yield e
-
-
-def union(i, start=None):
-    r = start
-    for e in i:
-        if r is None:
-            r = e
-        else:
-            r |= e
-    return r
-
-
-def memoize(f):
-    memo = OrderedDict()
-    @functools.wraps(f)
-    def g(*args):
-        if args not in memo:
-            memo[args] = f(*args)
-        return memo[args]
-    return g
-
-
-def final(cls):
-    def init_subclass():
-        raise TypeError("Subclassing {}.{} is not supported"
-                        .format(cls.__module__, cls.__name__))
-    cls.__init_subclass__ = init_subclass
-    return cls
-
-
-def deprecated(message, stacklevel=2):
-    def decorator(f):
-        @functools.wraps(f)
-        def wrapper(*args, **kwargs):
-            warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
-            return f(*args, **kwargs)
-        return wrapper
-    return decorator
-
-
-def _ignore_deprecated(f=None):
-    if f is None:
-        @contextlib.contextmanager
-        def context_like():
-            with warnings.catch_warnings():
-                warnings.filterwarnings(action="ignore", category=DeprecationWarning)
-                yield
-        return context_like()
-    else:
-        @functools.wraps(f)
-        def decorator_like(*args, **kwargs):
-            with warnings.catch_warnings():
-                warnings.filterwarnings(action="ignore", category=DeprecationWarning)
-                f(*args, **kwargs)
-        return decorator_like
-
-
-def extend(cls):
-    def decorator(f):
-        if isinstance(f, property):
-            name = f.fget.__name__
-        else:
-            name = f.__name__
-        setattr(cls, name, f)
-    return decorator
diff --git a/nmigen/_utils.py b/nmigen/_utils.py
new file mode 100644 (file)
index 0000000..85c8cdb
--- /dev/null
@@ -0,0 +1,84 @@
+import contextlib
+import functools
+import warnings
+from collections import OrderedDict
+from collections.abc import Iterable
+from contextlib import contextmanager
+
+from .utils import *
+
+
+__all__ = ["flatten", "union" , "log2_int", "bits_for", "memoize", "final", "deprecated"]
+
+
+def flatten(i):
+    for e in i:
+        if isinstance(e, Iterable):
+            yield from flatten(e)
+        else:
+            yield e
+
+
+def union(i, start=None):
+    r = start
+    for e in i:
+        if r is None:
+            r = e
+        else:
+            r |= e
+    return r
+
+
+def memoize(f):
+    memo = OrderedDict()
+    @functools.wraps(f)
+    def g(*args):
+        if args not in memo:
+            memo[args] = f(*args)
+        return memo[args]
+    return g
+
+
+def final(cls):
+    def init_subclass():
+        raise TypeError("Subclassing {}.{} is not supported"
+                        .format(cls.__module__, cls.__name__))
+    cls.__init_subclass__ = init_subclass
+    return cls
+
+
+def deprecated(message, stacklevel=2):
+    def decorator(f):
+        @functools.wraps(f)
+        def wrapper(*args, **kwargs):
+            warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
+            return f(*args, **kwargs)
+        return wrapper
+    return decorator
+
+
+def _ignore_deprecated(f=None):
+    if f is None:
+        @contextlib.contextmanager
+        def context_like():
+            with warnings.catch_warnings():
+                warnings.filterwarnings(action="ignore", category=DeprecationWarning)
+                yield
+        return context_like()
+    else:
+        @functools.wraps(f)
+        def decorator_like(*args, **kwargs):
+            with warnings.catch_warnings():
+                warnings.filterwarnings(action="ignore", category=DeprecationWarning)
+                f(*args, **kwargs)
+        return decorator_like
+
+
+def extend(cls):
+    def decorator(f):
+        if isinstance(f, property):
+            name = f.fget.__name__
+        else:
+            name = f.__name__
+        setattr(cls, name, f)
+    return decorator
index 1ef813214a4207f12b30c69dcc5d671847d83e6a..dc51e7ca7ca73b48b4689a5386e157d8ef107cfd 100644 (file)
@@ -6,7 +6,7 @@ from bitarray import bitarray
 from vcd import VCDWriter
 from vcd.gtkw import GTKWSave
 
-from .._tools import flatten
+from .._utils import flatten
 from ..hdl.ast import *
 from ..hdl.ir import *
 from ..hdl.xfrm import ValueVisitor, StatementVisitor
index 2dfed0e04c9cffe1600feb1bf3e58d1575d390d7..5a847c598ea48a78b6a33631916f63625323862f 100644 (file)
@@ -3,7 +3,7 @@ import textwrap
 from collections import defaultdict, OrderedDict
 from contextlib import contextmanager
 
-from .._tools import bits_for, flatten
+from .._utils import bits_for, flatten
 from ..hdl import ast, rec, ir, mem, xfrm
 
 
index b52887d2e3b50dfb23bac67c7f7b0140b4504fe6..648b129df0c65c28aa2036a8ce1ee6a89fa8e818 100644 (file)
@@ -1,19 +1,19 @@
-from ... import tools
+from ... import utils
 from ...hdl import ast
-from ..._tools import deprecated
+from ..._utils import deprecated
 
 
 __all__ = ["log2_int", "bits_for", "value_bits_sign"]
 
 
-@deprecated("instead of `log2_int`, use `nmigen.tools.log2_int`")
+@deprecated("instead of `log2_int`, use `nmigen.utils.log2_int`")
 def log2_int(n, need_pow2=True):
-    return tools.log2_int(n, need_pow2)
+    return utils.log2_int(n, need_pow2)
 
 
-@deprecated("instead of `bits_for`, use `nmigen.tools.bits_for`")
+@deprecated("instead of `bits_for`, use `nmigen.utils.bits_for`")
 def bits_for(n, require_sign_bit=False):
-    return tools.bits_for(n, require_sign_bit)
+    return utils.bits_for(n, require_sign_bit)
 
 
 @deprecated("instead of `value_bits_sign(v)`, use `v.shape()`")
index 83f39b05af92ec33565cc5cd34f6843ecde613a4..e61ccb581436845bf7b39ba8e912f3f7a6f58964 100644 (file)
@@ -2,7 +2,7 @@ from ...hdl.ast import *
 from ...hdl.xfrm import ResetInserter as NativeResetInserter
 from ...hdl.xfrm import EnableInserter as NativeEnableInserter
 from ...hdl.xfrm import DomainRenamer as NativeDomainRenamer
-from ..._tools import deprecated
+from ..._utils import deprecated
 
 
 __all__ = ["ResetInserter", "CEInserter", "ClockDomainsRenamer"]
index 55dc96c7d7fede31fbf724ee7e4f2e020c5f1054..c403a5e9880bbc61dcc64843f43d77fd53e1e367 100644 (file)
@@ -1,6 +1,6 @@
 from collections.abc import Iterable
 
-from ..._tools import flatten, deprecated
+from ..._utils import flatten, deprecated
 from ...hdl import dsl, ir
 
 
index 2a49ffc28427dc3a4dacfba069ad364d2d30f1ec..93111c212adee5b44a014aebf8aa9edb45999460 100644 (file)
@@ -1,6 +1,6 @@
 import warnings
 
-from ..._tools import deprecated, extend
+from ..._utils import deprecated, extend
 from ...hdl.ast import *
 from ...hdl.ir import Elaboratable
 from ...hdl.mem import Memory as NativeMemory
index 91aea7f290e33e3b77ff2ce5fcc302a5cc9a864c..27f639c2320c134d796564ed97fae25e25630ee0 100644 (file)
@@ -1,6 +1,6 @@
 from collections import OrderedDict
 
-from ..._tools import deprecated, extend
+from ..._utils import deprecated, extend
 from ...hdl import ast
 from ...hdl.ast import (DUID, Value, Signal, Mux, Slice as _Slice, Cat, Repl, Const, C,
                         ClockSignal, ResetSignal,
index 7bfcd8c48ca8921a47efdc14525021014faf1ee2..3bdddb18f2ea95a6070597a9e628c9e9beb3f982 100644 (file)
@@ -1,6 +1,6 @@
 import warnings
 
-from ..._tools import deprecated
+from ..._utils import deprecated
 from ...lib.cdc import FFSynchronizer as NativeFFSynchronizer
 from ...hdl.ast import *
 from ..fhdl.module import CompatModule
index 0e4b55dd35f9cd7b7afe4a0deda37f01eca6eb53..5c9bed7f8957d56b816132599a2671ecc30d5299 100644 (file)
@@ -1,4 +1,4 @@
-from ..._tools import deprecated, extend
+from ..._utils import deprecated, extend
 from ...lib.fifo import (FIFOInterface as NativeFIFOInterface,
   SyncFIFO as NativeSyncFIFO, SyncFIFOBuffered as NativeSyncFIFOBuffered,
   AsyncFIFO as NativeAsyncFIFO, AsyncFIFOBuffered as NativeAsyncFIFOBuffered)
index 68b1d0a57bae00f16a0be454e7b339b23d50f0c4..fe8f525aad9cc0113b147eab128cabf1c6b64d81 100644 (file)
@@ -1,6 +1,6 @@
 from collections import OrderedDict
 
-from ..._tools import deprecated, _ignore_deprecated
+from ..._utils import deprecated, _ignore_deprecated
 from ...hdl.xfrm import ValueTransformer, StatementTransformer
 from ...hdl.ast import *
 from ..fhdl.module import CompatModule, CompatFinalizeError
index f7b5c4e61d54e0773bccc032ccf977764015a3b1..66f0bae010b143055e87ec9690663d5ccbaacbaa 100644 (file)
@@ -1,4 +1,4 @@
-from ..._tools import deprecated
+from ..._utils import deprecated
 from ...lib.cdc import ResetSynchronizer as NativeResetSynchronizer
 
 
index 5d616c1fd90dbcc2cc28cd32ef4ca7a613a8b7db..e0314cd0fd3f2f9cd9d23a0fcafef3f7ab9114b1 100644 (file)
@@ -8,7 +8,7 @@ from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequenc
 from enum import Enum
 
 from .. import tracer
-from .._tools import *
+from .._utils import *
 
 
 __all__ = [
index b0634825f228adffb50740e79a30cd408d573ec0..598d812d8f116b361adc669d245d0c27f2bf6e15 100644 (file)
@@ -4,7 +4,7 @@ from contextlib import contextmanager
 from enum import Enum
 import warnings
 
-from .._tools import flatten, bits_for, deprecated
+from .._utils import flatten, bits_for, deprecated
 from .. import tracer
 from .ast import *
 from .ir import *
index 4232c52474c229cec975c911c958235c9a904635..44c3ecd9a412a93c1d929dea5f0b2e0cce51b8c4 100644 (file)
@@ -5,7 +5,7 @@ import warnings
 import traceback
 import sys
 
-from .._tools import *
+from .._utils import *
 from .ast import *
 from .cd import *
 
index 7448ecdf4fa358e58c0bc5bc3593ecb135cc73c7..d6c46868ccc949e853dc251b6d634e02343a3300 100644 (file)
@@ -3,7 +3,7 @@ from collections import OrderedDict
 from functools import reduce
 
 from .. import tracer
-from .._tools import union, deprecated
+from .._utils import union, deprecated
 from .ast import *
 
 
index 2d4e6391b1bca6cf119098118e096b84c6d73547..8cd69362a98bcea4a93e205f3ed542aa809d9126 100644 (file)
@@ -2,7 +2,7 @@ from abc import ABCMeta, abstractmethod
 from collections import OrderedDict
 from collections.abc import Iterable
 
-from .._tools import flatten, deprecated
+from .._utils import flatten, deprecated
 from .. import tracer
 from .ast import *
 from .ast import _StatementList
index 1294fb48c1aba57ba5fdbba3a44df123d60d4ee7..5dc865d6f61720e27a9f157efd44eaa54248131e 100644 (file)
@@ -1,4 +1,4 @@
-from .._tools import deprecated
+from .._utils import deprecated
 from .. import *
 
 
index 332d703c18a30cc1c18b46d392d63f1e05bdf1bd..f3244df208ff32a2716a31dfbcfb4a5dabd0321d 100644 (file)
@@ -2,7 +2,7 @@
 
 from .. import *
 from ..asserts import *
-from .._tools import log2_int, deprecated
+from .._utils import log2_int, deprecated
 from .coding import GrayEncoder
 from .cdc import FFSynchronizer
 
index 22b97ccd69713d074725c6f91beed0031bcdd5d5..8491652b6b2e074b6b76d62e794f87d9bf2d2664 100644 (file)
@@ -1,4 +1,4 @@
-from ..._tools import _ignore_deprecated
+from ..._utils import _ignore_deprecated
 from ...compat import *
 from ...compat.fhdl import verilog
 
index a739c2bbddb6479c122383275bef6615d7ed3216..e3864f9bda7b4098ec33019d9085e2e93cf7051d 100644 (file)
@@ -1,6 +1,6 @@
 import unittest
 
-from ..._tools import _ignore_deprecated
+from ..._utils import _ignore_deprecated
 from ...compat import *
 
 
index 1e37d1910dd6a531eb74111f38b38a6d3da42e32..a613d12795c76be64d60805fb0bb9048315dbf77 100644 (file)
@@ -1,7 +1,7 @@
 from collections import OrderedDict
 
 from ..build.dsl import *
-from .tools import *
+from .utils import *
 
 
 class PinsTestCase(FHDLTestCase):
index fc111c8385a0e84539fe3d5b350a6f8a38b90db2..40f423582300da5fc1b9a3dd1eb944a8b6a01cc3 100644 (file)
@@ -3,7 +3,7 @@ from ..hdl.rec import *
 from ..lib.io import *
 from ..build.dsl import *
 from ..build.res import *
-from .tools import *
+from .utils import *
 
 
 class ResourceManagerTestCase(FHDLTestCase):
index d42812c8b09f538459e47633c390eb6a3289aea3..62e2d9207f14138abcfa8023947eab788ca1bc82 100644 (file)
@@ -1,6 +1,6 @@
 from ..hdl.ir import Fragment
 from ..compat import *
-from .tools import *
+from .utils import *
 
 
 class CompatTestCase(FHDLTestCase):
index e428a5e934282f26abe9434154c157e927283dac..94b93771c6544e4d15344ec36196374d4c689ee5 100644 (file)
@@ -2,7 +2,7 @@ import sys
 import subprocess
 from pathlib import Path
 
-from .tools import *
+from .utils import *
 
 
 def example_test(name):
index b7e964759fc095a42af5e1f0f3d0d565fceb2830..6968910cf62fbdcd2d1d5854b9f320a42e56b969 100644 (file)
@@ -2,7 +2,7 @@ import warnings
 from enum import Enum
 
 from ..hdl.ast import *
-from .tools import *
+from .utils import *
 
 
 class UnsignedEnum(Enum):
index 60edc3b79343effda07c85e0c34a58835144981e..85bcfc85f927a6a0077f252a38a4b1a69b395706 100644 (file)
@@ -1,5 +1,5 @@
 from ..hdl.cd import *
-from .tools import *
+from .utils import *
 
 
 class ClockDomainTestCase(FHDLTestCase):
index 48993ddebb831fb944416ce68cf804869c347869..85d205c896b4cae3f2703e6484d189e8f6e40b3b 100644 (file)
@@ -4,7 +4,7 @@ from enum import Enum
 from ..hdl.ast import *
 from ..hdl.cd import *
 from ..hdl.dsl import *
-from .tools import *
+from .utils import *
 
 
 class DSLTestCase(FHDLTestCase):
index e51ae4e298e0e8e49d2a05cb51679bafc4d15f56..6a85ebe075f92807345e2b194654bb4b585a4035 100644 (file)
@@ -4,7 +4,7 @@ from ..hdl.ast import *
 from ..hdl.cd import *
 from ..hdl.ir import *
 from ..hdl.mem import *
-from .tools import *
+from .utils import *
 
 
 class BadElaboratable(Elaboratable):
index 8cf3bb11725e2edf2a8c3f601a7fa05539bc5305..1a0a007931564fcaf01dd33bc14614e698411afe 100644 (file)
@@ -1,6 +1,6 @@
 from ..hdl.ast import *
 from ..hdl.mem import *
-from .tools import *
+from .utils import *
 
 
 class MemoryTestCase(FHDLTestCase):
index 2e84107aa9869a7c9056ec80184a399c2f6c00d8..dd9dd404c49c08129864f2f4164a453a8743a6bc 100644 (file)
@@ -2,7 +2,7 @@ from enum import Enum
 
 from ..hdl.ast import *
 from ..hdl.rec import *
-from .tools import *
+from .utils import *
 
 
 class UnsignedEnum(Enum):
index d67dc6fe8f206ec05e31f427149584f420fd9bbb..c2e826aa3b6c86be90ac877a20e28634cc45ce60 100644 (file)
@@ -3,7 +3,7 @@ from ..hdl.cd import *
 from ..hdl.ir import *
 from ..hdl.xfrm import *
 from ..hdl.mem import *
-from .tools import *
+from .utils import *
 
 
 class DomainRenamerTestCase(FHDLTestCase):
index 23ebed557c993f395da1b667169eabd8cfe49cf6..1a394ea65fbda30aeade45737956c37894f9ed7f 100644 (file)
@@ -1,4 +1,4 @@
-from .tools import *
+from .utils import *
 from ..hdl import *
 from ..back.pysim import *
 from ..lib.cdc import *
index 6052c9a6fe14ca273a8fc929606a24bbc34bce20..874b87c0daa8b6a720812eb516d5a3816149b706 100644 (file)
@@ -1,4 +1,4 @@
-from .tools import *
+from .utils import *
 from ..hdl import *
 from ..asserts import *
 from ..back.pysim import *
index 2398f8f3ad41c72672267c20ddd3df6ed4da2cac..26c53910e50a0e9d44b9f9d14173c170cbff9300 100644 (file)
@@ -1,4 +1,4 @@
-from .tools import *
+from .utils import *
 from ..hdl import *
 from ..asserts import *
 from ..back.pysim import *
index 7ae44a3ec6e79917eaf8132d4619c2e9544c737d..cb1cd4686d131d21ceadc7742ed41a14c7c9e4fd 100644 (file)
@@ -1,4 +1,4 @@
-from .tools import *
+from .utils import *
 from ..hdl import *
 from ..hdl.rec import *
 from ..back.pysim import *
index 91fec3454256647df082b3501a5f351bd55cf9f2..7ae6d97ac8ce15f1aab6c13788cf76588ef9924f 100644 (file)
@@ -1,7 +1,7 @@
 from contextlib import contextmanager
 
-from .tools import *
-from .._tools import flatten, union
+from .utils import *
+from .._utils import flatten, union
 from ..hdl.ast import *
 from ..hdl.cd import  *
 from ..hdl.mem import *
diff --git a/nmigen/test/tools.py b/nmigen/test/tools.py
deleted file mode 100644 (file)
index c3907ce..0000000
+++ /dev/null
@@ -1,103 +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))
-
-    @contextmanager
-    def assertRaises(self, exception, msg=None):
-        with super().assertRaises(exception) as cm:
-            yield
-        if msg is not None:
-            # WTF? unittest.assertRaises is completely broken.
-            self.assertEqual(str(cm.exception), msg)
-
-    @contextmanager
-    def assertRaisesRegex(self, exception, regex=None):
-        with super().assertRaises(exception) as cm:
-            yield
-        if regex is not None:
-            # unittest.assertRaisesRegex also seems broken...
-            self.assertRegex(str(cm.exception), regex)
-
-    @contextmanager
-    def assertWarns(self, category, msg=None):
-        with warnings.catch_warnings(record=True) as warns:
-            yield
-        self.assertEqual(len(warns), 1)
-        self.assertEqual(warns[0].category, category)
-        if msg is not None:
-            self.assertEqual(str(warns[0].message), msg)
-
-    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)
diff --git a/nmigen/test/utils.py b/nmigen/test/utils.py
new file mode 100644 (file)
index 0000000..c3907ce
--- /dev/null
@@ -0,0 +1,103 @@
+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))
+
+    @contextmanager
+    def assertRaises(self, exception, msg=None):
+        with super().assertRaises(exception) as cm:
+            yield
+        if msg is not None:
+            # WTF? unittest.assertRaises is completely broken.
+            self.assertEqual(str(cm.exception), msg)
+
+    @contextmanager
+    def assertRaisesRegex(self, exception, regex=None):
+        with super().assertRaises(exception) as cm:
+            yield
+        if regex is not None:
+            # unittest.assertRaisesRegex also seems broken...
+            self.assertRegex(str(cm.exception), regex)
+
+    @contextmanager
+    def assertWarns(self, category, msg=None):
+        with warnings.catch_warnings(record=True) as warns:
+            yield
+        self.assertEqual(len(warns), 1)
+        self.assertEqual(warns[0].category, category)
+        if msg is not None:
+            self.assertEqual(str(warns[0].message), msg)
+
+    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)
diff --git a/nmigen/tools.py b/nmigen/tools.py
deleted file mode 100644 (file)
index 227258a..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-__all__ = ["log2_int", "bits_for"]
-
-
-def log2_int(n, need_pow2=True):
-    if n == 0:
-        return 0
-    r = (n - 1).bit_length()
-    if need_pow2 and (1 << r) != n:
-        raise ValueError("{} is not a power of 2".format(n))
-    return r
-
-
-def bits_for(n, require_sign_bit=False):
-    if n > 0:
-        r = log2_int(n + 1, False)
-    else:
-        require_sign_bit = True
-        r = log2_int(-n, False)
-    if require_sign_bit:
-        r += 1
-    return r
diff --git a/nmigen/utils.py b/nmigen/utils.py
new file mode 100644 (file)
index 0000000..227258a
--- /dev/null
@@ -0,0 +1,21 @@
+__all__ = ["log2_int", "bits_for"]
+
+
+def log2_int(n, need_pow2=True):
+    if n == 0:
+        return 0
+    r = (n - 1).bit_length()
+    if need_pow2 and (1 << r) != n:
+        raise ValueError("{} is not a power of 2".format(n))
+    return r
+
+
+def bits_for(n, require_sign_bit=False):
+    if n > 0:
+        r = log2_int(n + 1, False)
+    else:
+        require_sign_bit = True
+        r = log2_int(-n, False)
+    if require_sign_bit:
+        r += 1
+    return r