hdl.ir: detect elaboratables that are created but not used.
authorwhitequark <whitequark@whitequark.org>
Sun, 21 Apr 2019 08:52:57 +0000 (08:52 +0000)
committerwhitequark <whitequark@whitequark.org>
Sun, 21 Apr 2019 08:52:57 +0000 (08:52 +0000)
Requres every elaboratable to inherit from Elaboratable, but still
accepts ones that do not, with a warning.

Fixes #3.

22 files changed:
examples/alu.py
examples/alu_hier.py
examples/arst.py
examples/ctr.py
examples/ctr_ce.py
examples/fsm.py
examples/gpio.py
examples/inst.py
examples/mem.py
examples/pmux.py
nmigen/__init__.py
nmigen/hdl/dsl.py
nmigen/hdl/ir.py
nmigen/hdl/mem.py
nmigen/hdl/xfrm.py
nmigen/lib/cdc.py
nmigen/lib/coding.py
nmigen/lib/fifo.py
nmigen/test/__init__.py
nmigen/test/test_hdl_xfrm.py
nmigen/test/test_lib_coding.py
nmigen/test/test_lib_fifo.py

index 211acd2058dac540d78404b2edf2c59773ec535f..5c0e8e67f4227fa39495c3209d51fe573947def6 100644 (file)
@@ -2,7 +2,7 @@ from nmigen import *
 from nmigen.cli import main
 
 
-class ALU:
+class ALU(Elaboratable):
     def __init__(self, width):
         self.sel = Signal(2)
         self.a   = Signal(width)
index fc6beaec729128d23fdf85e85d2fb4a8c6ef780e..a3273af1de6ac6f6fc7ddbd4fcec75d8749c91b3 100644 (file)
@@ -2,7 +2,7 @@ from nmigen import *
 from nmigen.cli import main
 
 
-class Adder:
+class Adder(Elaboratable):
     def __init__(self, width):
         self.a   = Signal(width)
         self.b   = Signal(width)
@@ -14,7 +14,7 @@ class Adder:
         return m
 
 
-class Subtractor:
+class Subtractor(Elaboratable):
     def __init__(self, width):
         self.a   = Signal(width)
         self.b   = Signal(width)
@@ -26,7 +26,7 @@ class Subtractor:
         return m
 
 
-class ALU:
+class ALU(Elaboratable):
     def __init__(self, width):
         self.op  = Signal()
         self.a   = Signal(width)
index 405857bcf94fcd548352274009ea65b77f0d1fae..ef3ed5a45afca0d7ab5ae14c0c3772bbc01ca05c 100644 (file)
@@ -2,7 +2,7 @@ from nmigen import *
 from nmigen.cli import main
 
 
-class ClockDivisor:
+class ClockDivisor(Elaboratable):
     def __init__(self, factor):
         self.v = Signal(factor)
         self.o = Signal()
index 9505a61752ed1c1c188f86a3a5b33230656e5ba2..97522999c0eb498442cedf36e778f97670594a36 100644 (file)
@@ -2,7 +2,7 @@ from nmigen import *
 from nmigen.cli import main, pysim
 
 
-class Counter:
+class Counter(Elaboratable):
     def __init__(self, width):
         self.v = Signal(width, reset=2**width-1)
         self.o = Signal()
index ebf1db02bde0cf097471c59aaec08049062698b4..f839d67c28ee0d7f517c69425537e0bdd3ae8059 100644 (file)
@@ -2,7 +2,7 @@ from nmigen import *
 from nmigen.back import rtlil, verilog, pysim
 
 
-class Counter:
+class Counter(Elaboratable):
     def __init__(self, width):
         self.v = Signal(width, reset=2**width-1)
         self.o = Signal()
index 98f5045dd392ef9396398bf969405837e4735420..73e6f7d7a09f542bb6b87ee8799c827b38abbdf0 100644 (file)
@@ -2,7 +2,7 @@ from nmigen import *
 from nmigen.cli import main
 
 
-class UARTReceiver:
+class UARTReceiver(Elaboratable):
     def __init__(self, divisor):
         self.divisor = divisor
 
index c234ce00df0a0777038c8dd78fc279a3b0c39cf4..3dd0da08e0e850df0c1db4ee9b57ecf3d2c7d5b3 100644 (file)
@@ -3,7 +3,7 @@ from nmigen import *
 from nmigen.cli import main
 
 
-class GPIO:
+class GPIO(Elaboratable):
     def __init__(self, pins, bus):
         self.pins = pins
         self.bus  = bus
index 227a5da4e93794a3e0902deb3853333c2598c4d0..2fc519be392a32d6122e6ba56b0ab06d2a475246 100644 (file)
@@ -2,7 +2,7 @@ from nmigen import *
 from nmigen.cli import main
 
 
-class System:
+class System(Elaboratable):
     def __init__(self):
         self.adr   = Signal(16)
         self.dat_r = Signal(8)
index 1d97042f40186c9b0460dea47670e4cf1b3664e4..82105fc99d369202b7e4c94d495e7166c5fdc0f0 100644 (file)
@@ -2,7 +2,7 @@ from nmigen import *
 from nmigen.cli import main
 
 
-class RegisterFile:
+class RegisterFile(Elaboratable):
     def __init__(self):
         self.adr   = Signal(4)
         self.dat_r = Signal(8)
index 02a6155f4ff3ee528ea2973cb98354c1af85aa87..1e938b50151bdf18c1425c88416bf0da952a27dc 100644 (file)
@@ -2,7 +2,7 @@ from nmigen import *
 from nmigen.cli import main
 
 
-class ParMux:
+class ParMux(Elaboratable):
     def __init__(self, width):
         self.s = Signal(3)
         self.a = Signal(width)
index bc1560dee167293738b424fe0c025afe45f99669..b5899d73422fd3d9c8b1e8e8e89487c2befd9a4a 100644 (file)
@@ -1,7 +1,7 @@
 from .hdl.ast import Value, Const, C, Mux, Cat, Repl, Array, Signal, ClockSignal, ResetSignal
 from .hdl.dsl import Module
 from .hdl.cd import ClockDomain
-from .hdl.ir import Fragment, Instance
+from .hdl.ir import Elaboratable, Fragment, Instance
 from .hdl.mem import Memory
 from .hdl.rec import Record
 from .hdl.xfrm import ResetInserter, CEInserter
index 95f6eb04293d662b8c73af12f7daff3a9a84483b..03e7e231994b583e0f4b06d11d97315f30b7e5f1 100644 (file)
@@ -9,7 +9,7 @@ from .ir import *
 from .xfrm import *
 
 
-__all__ = ["Module", "SyntaxError", "SyntaxWarning"]
+__all__ = ["SyntaxError", "SyntaxWarning", "Module"]
 
 
 class SyntaxError(Exception):
@@ -109,7 +109,7 @@ class FSM:
         return self.state == self.encoding[name]
 
 
-class Module(_ModuleBuilderRoot):
+class Module(_ModuleBuilderRoot, Elaboratable):
     def __init__(self):
         _ModuleBuilderRoot.__init__(self, self, depth=0)
         self.submodules    = _ModuleBuilderSubmodules(self)
index 079bba264d463747896592da485252426f24be4b..164f5582ba696a95c08306d92f5d936706cbc48e 100644 (file)
@@ -1,12 +1,30 @@
-import warnings
+from abc import ABCMeta, abstractmethod
 from collections import defaultdict, OrderedDict
+import warnings
+import traceback
+import sys
 
 from ..tools import *
 from .ast import *
 from .cd import *
 
 
-__all__ = ["Fragment", "Instance", "DriverConflict"]
+__all__ = ["Elaboratable", "DriverConflict", "Fragment", "Instance"]
+
+
+class Elaboratable(metaclass=ABCMeta):
+    def __new__(cls, *args, **kwargs):
+        self = super().__new__(cls)
+        self._Elaboratable__traceback = traceback.extract_stack()[:-1]
+        self._Elaboratable__used      = False
+        return self
+
+    def __del__(self):
+        if hasattr(self, "_Elaboratable__used") and not self._Elaboratable__used:
+            print("Elaboratable created but never used\n",
+                  "Traceback (most recent call last):\n",
+                  *traceback.format_list(self._Elaboratable__traceback),
+                  file=sys.stderr, sep="")
 
 
 class DriverConflict(UserWarning):
@@ -16,13 +34,22 @@ class DriverConflict(UserWarning):
 class Fragment:
     @staticmethod
     def get(obj, platform):
-        if isinstance(obj, Fragment):
-            return obj
-        elif hasattr(obj, "elaborate"):
-            frag = obj.elaborate(platform)
-        else:
-            raise AttributeError("Object '{!r}' cannot be elaborated".format(obj))
-        return Fragment.get(frag, platform)
+        while True:
+            if isinstance(obj, Fragment):
+                return obj
+            elif isinstance(obj, Elaboratable):
+                obj._Elaboratable__used = True
+                obj = obj.elaborate(platform)
+            elif hasattr(obj, "elaborate"):
+                warnings.warn(
+                    message="Class {!r} is an elaboratable that does not explicitly inherit from "
+                            "Elaboratable; doing so would improve diagnostics"
+                            .format(type(obj)),
+                    category=RuntimeWarning,
+                    stacklevel=2)
+                obj = obj.elaborate(platform)
+            else:
+                raise AttributeError("Object '{!r}' cannot be elaborated".format(obj))
 
     def __init__(self):
         self.ports = SignalDict()
index bef80350405a911a317012613eaaaa568cda777b..076d2849323a306ed140463c6bc7668ce232514f 100644 (file)
@@ -1,6 +1,6 @@
 from .. import tracer
 from .ast import *
-from .ir import Instance
+from .ir import Elaboratable, Instance
 
 
 __all__ = ["Memory", "ReadPort", "WritePort", "DummyPort"]
@@ -70,7 +70,7 @@ class Memory:
         return self._array[index]
 
 
-class ReadPort:
+class ReadPort(Elaboratable):
     def __init__(self, memory, domain, synchronous, transparent):
         self.memory      = memory
         self.domain      = domain
@@ -135,7 +135,7 @@ class ReadPort:
         return f
 
 
-class WritePort:
+class WritePort(Elaboratable):
     def __init__(self, memory, domain, priority, granularity):
         self.memory       = memory
         self.domain       = domain
index 1385ce8c59f9da7631d4471a248f1bc91cbd11d8..c554bc6d9678111412e56a1e31b91918b8a7a7a8 100644 (file)
@@ -292,7 +292,7 @@ class FragmentTransformer:
             raise AttributeError("Object '{!r}' cannot be elaborated".format(value))
 
 
-class TransformedElaboratable:
+class TransformedElaboratable(Elaboratable):
     def __init__(self, elaboratable):
         assert hasattr(elaboratable, "elaborate")
 
index 3018b37da3aa72904a7d5caa088bb7625dd589aa..ec83317650adf67ff32c3e5d931bedeb9461557b 100644 (file)
@@ -4,7 +4,7 @@ from .. import *
 __all__ = ["MultiReg", "ResetSynchronizer"]
 
 
-class MultiReg:
+class MultiReg(Elaboratable):
     """Resynchronise a signal to a different clock domain.
 
     Consists of a chain of flip-flops. Eliminates metastabilities at the output, but provides
@@ -69,7 +69,7 @@ class MultiReg:
         return m
 
 
-class ResetSynchronizer:
+class ResetSynchronizer(Elaboratable):
     def __init__(self, arst, domain="sync", n=2):
         self.arst = arst
         self.domain = domain
index e06d5a716e966949f8fd49cf838f8a4541304222..eed02c96bc7a7eb6185fcb70e349a1cbeacddf10 100644 (file)
@@ -10,7 +10,7 @@ __all__ = [
 ]
 
 
-class Encoder:
+class Encoder(Elaboratable):
     """Encode one-hot to binary.
 
     If one bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the asserted bit.
@@ -48,7 +48,7 @@ class Encoder:
         return m
 
 
-class PriorityEncoder:
+class PriorityEncoder(Elaboratable):
     """Priority encode requests to binary.
 
     If any bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the least significant
@@ -85,7 +85,7 @@ class PriorityEncoder:
         return m
 
 
-class Decoder:
+class Decoder(Elaboratable):
     """Decode binary to one-hot.
 
     If ``n`` is low, only the ``i``th bit in ``o`` is asserted.
@@ -130,7 +130,7 @@ class PriorityDecoder(Decoder):
     """
 
 
-class GrayEncoder:
+class GrayEncoder(Elaboratable):
     """Encode binary to Gray code.
 
     Parameters
@@ -157,7 +157,7 @@ class GrayEncoder:
         return m
 
 
-class GrayDecoder:
+class GrayDecoder(Elaboratable):
     """Decode Gray code to binary.
 
     Parameters
index e484ec8c64bd876e94f0ceb8e35ef0a33e00483f..2844a0585c50e6e9e51021061f71e1107074f8e1 100644 (file)
@@ -102,7 +102,7 @@ def _decr(signal, modulo):
         return Mux(signal == 0, modulo - 1, signal - 1)
 
 
-class SyncFIFO(FIFOInterface):
+class SyncFIFO(Elaboratable, FIFOInterface):
     __doc__ = FIFOInterface._doc_template.format(
     description="""
     Synchronous first in, first out queue.
@@ -209,7 +209,7 @@ class SyncFIFO(FIFOInterface):
         return m
 
 
-class SyncFIFOBuffered(FIFOInterface):
+class SyncFIFOBuffered(Elaboratable, FIFOInterface):
     __doc__ = FIFOInterface._doc_template.format(
     description="""
     Buffered synchronous first in, first out queue.
@@ -265,7 +265,7 @@ class SyncFIFOBuffered(FIFOInterface):
         return m
 
 
-class AsyncFIFO(FIFOInterface):
+class AsyncFIFO(Elaboratable, FIFOInterface):
     __doc__ = FIFOInterface._doc_template.format(
     description="""
     Asynchronous first in, first out queue.
@@ -361,7 +361,7 @@ class AsyncFIFO(FIFOInterface):
         return m
 
 
-class AsyncFIFOBuffered(FIFOInterface):
+class AsyncFIFOBuffered(Elaboratable, FIFOInterface):
     __doc__ = FIFOInterface._doc_template.format(
     description="""
     Buffered asynchronous first in, first out queue.
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..724ad8a4812ac6c7f84477ddfd71386066545e3c 100644 (file)
@@ -0,0 +1,6 @@
+from ..hdl.ir import Elaboratable
+
+
+# The nMigen testsuite creates a lot of elaboratables that are intentionally unused.
+# Disable the unused elaboratable check, as in our case it provides nothing but noise.
+del Elaboratable.__del__
index 94af1f8a89d8b65f2d68c24b3c8d0fbc71fdedb2..3973b8df59a417fb307f4f63f8dbe46da11d0c73 100644 (file)
@@ -488,7 +488,7 @@ class CEInserterTestCase(FHDLTestCase):
         """)
 
 
-class _MockElaboratable:
+class _MockElaboratable(Elaboratable):
     def __init__(self):
         self.s1 = Signal()
 
index 83b7831bb95c03f1c7801f9cd02bceb0a622d9a5..8c6b624cc030babdc77b102b5b0cae4355d9dd05 100644 (file)
@@ -1,6 +1,7 @@
 from .tools import *
 from ..hdl.ast import *
 from ..hdl.dsl import *
+from ..hdl.ir import *
 from ..back.pysim import *
 from ..lib.coding import *
 
@@ -82,7 +83,7 @@ class DecoderTestCase(FHDLTestCase):
             sim.run()
 
 
-class ReversibleSpec:
+class ReversibleSpec(Elaboratable):
     def __init__(self, encoder_cls, decoder_cls, args):
         self.encoder_cls = encoder_cls
         self.decoder_cls = decoder_cls
@@ -99,7 +100,7 @@ class ReversibleSpec:
         return m
 
 
-class HammingDistanceSpec:
+class HammingDistanceSpec(Elaboratable):
     def __init__(self, distance, encoder_cls, args):
         self.distance    = distance
         self.encoder_cls = encoder_cls
index c7f4c2883f654c44fcae070a63b01dbc0f440715..34971865a1fd7a223151602e8b959b7dd93fdcdb 100644 (file)
@@ -45,7 +45,7 @@ class FIFOSmokeTestCase(FHDLTestCase):
         self.assertAsyncFIFOWorks(AsyncFIFOBuffered(width=8, depth=3))
 
 
-class FIFOModel(FIFOInterface):
+class FIFOModel(Elaboratable, FIFOInterface):
     """
     Non-synthesizable first-in first-out queue, implemented naively as a chain of registers.
     """
@@ -104,7 +104,7 @@ class FIFOModel(FIFOInterface):
         return m
 
 
-class FIFOModelEquivalenceSpec:
+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,
@@ -148,7 +148,7 @@ class FIFOModelEquivalenceSpec:
         return m
 
 
-class FIFOContractSpec:
+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