hdl.ir: detect elaboratables that are created but not used.

Requres every elaboratable to inherit from Elaboratable, but still
accepts ones that do not, with a warning.

Fixes #3.
This commit is contained in:
whitequark 2019-04-21 08:52:57 +00:00
parent 85ae99c1b4
commit 44711b7d08
22 changed files with 79 additions and 45 deletions

View 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)

View 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)

View 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()

View 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()

View 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()

View 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

View 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

View 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)

View 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)

View 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)

View 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

View 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)

View 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):
while True:
if isinstance(obj, Fragment):
return obj
elif isinstance(obj, Elaboratable):
obj._Elaboratable__used = True
obj = obj.elaborate(platform)
elif hasattr(obj, "elaborate"):
frag = obj.elaborate(platform)
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))
return Fragment.get(frag, platform)
def __init__(self):
self.ports = SignalDict()

View 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

View 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")

View 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

View 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

View 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.

View 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__

View file

@ -488,7 +488,7 @@ class CEInserterTestCase(FHDLTestCase):
""")
class _MockElaboratable:
class _MockElaboratable(Elaboratable):
def __init__(self):
self.s1 = Signal()

View 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

View 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