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 from nmigen.cli import main
class ALU: class ALU(Elaboratable):
def __init__(self, width): def __init__(self, width):
self.sel = Signal(2) self.sel = Signal(2)
self.a = Signal(width) self.a = Signal(width)

View file

@ -2,7 +2,7 @@ from nmigen import *
from nmigen.cli import main from nmigen.cli import main
class Adder: class Adder(Elaboratable):
def __init__(self, width): def __init__(self, width):
self.a = Signal(width) self.a = Signal(width)
self.b = Signal(width) self.b = Signal(width)
@ -14,7 +14,7 @@ class Adder:
return m return m
class Subtractor: class Subtractor(Elaboratable):
def __init__(self, width): def __init__(self, width):
self.a = Signal(width) self.a = Signal(width)
self.b = Signal(width) self.b = Signal(width)
@ -26,7 +26,7 @@ class Subtractor:
return m return m
class ALU: class ALU(Elaboratable):
def __init__(self, width): def __init__(self, width):
self.op = Signal() self.op = Signal()
self.a = Signal(width) self.a = Signal(width)

View file

@ -2,7 +2,7 @@ from nmigen import *
from nmigen.cli import main from nmigen.cli import main
class ClockDivisor: class ClockDivisor(Elaboratable):
def __init__(self, factor): def __init__(self, factor):
self.v = Signal(factor) self.v = Signal(factor)
self.o = Signal() self.o = Signal()

View file

@ -2,7 +2,7 @@ from nmigen import *
from nmigen.cli import main, pysim from nmigen.cli import main, pysim
class Counter: class Counter(Elaboratable):
def __init__(self, width): def __init__(self, width):
self.v = Signal(width, reset=2**width-1) self.v = Signal(width, reset=2**width-1)
self.o = Signal() self.o = Signal()

View file

@ -2,7 +2,7 @@ from nmigen import *
from nmigen.back import rtlil, verilog, pysim from nmigen.back import rtlil, verilog, pysim
class Counter: class Counter(Elaboratable):
def __init__(self, width): def __init__(self, width):
self.v = Signal(width, reset=2**width-1) self.v = Signal(width, reset=2**width-1)
self.o = Signal() self.o = Signal()

View file

@ -2,7 +2,7 @@ from nmigen import *
from nmigen.cli import main from nmigen.cli import main
class UARTReceiver: class UARTReceiver(Elaboratable):
def __init__(self, divisor): def __init__(self, divisor):
self.divisor = divisor self.divisor = divisor

View file

@ -3,7 +3,7 @@ from nmigen import *
from nmigen.cli import main from nmigen.cli import main
class GPIO: class GPIO(Elaboratable):
def __init__(self, pins, bus): def __init__(self, pins, bus):
self.pins = pins self.pins = pins
self.bus = bus self.bus = bus

View file

@ -2,7 +2,7 @@ from nmigen import *
from nmigen.cli import main from nmigen.cli import main
class System: class System(Elaboratable):
def __init__(self): def __init__(self):
self.adr = Signal(16) self.adr = Signal(16)
self.dat_r = Signal(8) self.dat_r = Signal(8)

View file

@ -2,7 +2,7 @@ from nmigen import *
from nmigen.cli import main from nmigen.cli import main
class RegisterFile: class RegisterFile(Elaboratable):
def __init__(self): def __init__(self):
self.adr = Signal(4) self.adr = Signal(4)
self.dat_r = Signal(8) self.dat_r = Signal(8)

View file

@ -2,7 +2,7 @@ from nmigen import *
from nmigen.cli import main from nmigen.cli import main
class ParMux: class ParMux(Elaboratable):
def __init__(self, width): def __init__(self, width):
self.s = Signal(3) self.s = Signal(3)
self.a = Signal(width) 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.ast import Value, Const, C, Mux, Cat, Repl, Array, Signal, ClockSignal, ResetSignal
from .hdl.dsl import Module from .hdl.dsl import Module
from .hdl.cd import ClockDomain 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.mem import Memory
from .hdl.rec import Record from .hdl.rec import Record
from .hdl.xfrm import ResetInserter, CEInserter from .hdl.xfrm import ResetInserter, CEInserter

View file

@ -9,7 +9,7 @@ from .ir import *
from .xfrm import * from .xfrm import *
__all__ = ["Module", "SyntaxError", "SyntaxWarning"] __all__ = ["SyntaxError", "SyntaxWarning", "Module"]
class SyntaxError(Exception): class SyntaxError(Exception):
@ -109,7 +109,7 @@ class FSM:
return self.state == self.encoding[name] return self.state == self.encoding[name]
class Module(_ModuleBuilderRoot): class Module(_ModuleBuilderRoot, Elaboratable):
def __init__(self): def __init__(self):
_ModuleBuilderRoot.__init__(self, self, depth=0) _ModuleBuilderRoot.__init__(self, self, depth=0)
self.submodules = _ModuleBuilderSubmodules(self) self.submodules = _ModuleBuilderSubmodules(self)

View file

@ -1,12 +1,30 @@
import warnings from abc import ABCMeta, abstractmethod
from collections import defaultdict, OrderedDict from collections import defaultdict, OrderedDict
import warnings
import traceback
import sys
from ..tools import * from ..tools import *
from .ast import * from .ast import *
from .cd 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): class DriverConflict(UserWarning):
@ -16,13 +34,22 @@ class DriverConflict(UserWarning):
class Fragment: class Fragment:
@staticmethod @staticmethod
def get(obj, platform): def get(obj, platform):
if isinstance(obj, Fragment): while True:
return obj if isinstance(obj, Fragment):
elif hasattr(obj, "elaborate"): return obj
frag = obj.elaborate(platform) elif isinstance(obj, Elaboratable):
else: obj._Elaboratable__used = True
raise AttributeError("Object '{!r}' cannot be elaborated".format(obj)) obj = obj.elaborate(platform)
return Fragment.get(frag, 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): def __init__(self):
self.ports = SignalDict() self.ports = SignalDict()

View file

@ -1,6 +1,6 @@
from .. import tracer from .. import tracer
from .ast import * from .ast import *
from .ir import Instance from .ir import Elaboratable, Instance
__all__ = ["Memory", "ReadPort", "WritePort", "DummyPort"] __all__ = ["Memory", "ReadPort", "WritePort", "DummyPort"]
@ -70,7 +70,7 @@ class Memory:
return self._array[index] return self._array[index]
class ReadPort: class ReadPort(Elaboratable):
def __init__(self, memory, domain, synchronous, transparent): def __init__(self, memory, domain, synchronous, transparent):
self.memory = memory self.memory = memory
self.domain = domain self.domain = domain
@ -135,7 +135,7 @@ class ReadPort:
return f return f
class WritePort: class WritePort(Elaboratable):
def __init__(self, memory, domain, priority, granularity): def __init__(self, memory, domain, priority, granularity):
self.memory = memory self.memory = memory
self.domain = domain self.domain = domain

View file

@ -292,7 +292,7 @@ class FragmentTransformer:
raise AttributeError("Object '{!r}' cannot be elaborated".format(value)) raise AttributeError("Object '{!r}' cannot be elaborated".format(value))
class TransformedElaboratable: class TransformedElaboratable(Elaboratable):
def __init__(self, elaboratable): def __init__(self, elaboratable):
assert hasattr(elaboratable, "elaborate") assert hasattr(elaboratable, "elaborate")

View file

@ -4,7 +4,7 @@ from .. import *
__all__ = ["MultiReg", "ResetSynchronizer"] __all__ = ["MultiReg", "ResetSynchronizer"]
class MultiReg: class MultiReg(Elaboratable):
"""Resynchronise a signal to a different clock domain. """Resynchronise a signal to a different clock domain.
Consists of a chain of flip-flops. Eliminates metastabilities at the output, but provides Consists of a chain of flip-flops. Eliminates metastabilities at the output, but provides
@ -69,7 +69,7 @@ class MultiReg:
return m return m
class ResetSynchronizer: class ResetSynchronizer(Elaboratable):
def __init__(self, arst, domain="sync", n=2): def __init__(self, arst, domain="sync", n=2):
self.arst = arst self.arst = arst
self.domain = domain self.domain = domain

View file

@ -10,7 +10,7 @@ __all__ = [
] ]
class Encoder: class Encoder(Elaboratable):
"""Encode one-hot to binary. """Encode one-hot to binary.
If one bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the asserted bit. If one bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the asserted bit.
@ -48,7 +48,7 @@ class Encoder:
return m return m
class PriorityEncoder: class PriorityEncoder(Elaboratable):
"""Priority encode requests to binary. """Priority encode requests to binary.
If any bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the least significant If any bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the least significant
@ -85,7 +85,7 @@ class PriorityEncoder:
return m return m
class Decoder: class Decoder(Elaboratable):
"""Decode binary to one-hot. """Decode binary to one-hot.
If ``n`` is low, only the ``i``th bit in ``o`` is asserted. 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. """Encode binary to Gray code.
Parameters Parameters
@ -157,7 +157,7 @@ class GrayEncoder:
return m return m
class GrayDecoder: class GrayDecoder(Elaboratable):
"""Decode Gray code to binary. """Decode Gray code to binary.
Parameters Parameters

View file

@ -102,7 +102,7 @@ def _decr(signal, modulo):
return Mux(signal == 0, modulo - 1, signal - 1) return Mux(signal == 0, modulo - 1, signal - 1)
class SyncFIFO(FIFOInterface): class SyncFIFO(Elaboratable, FIFOInterface):
__doc__ = FIFOInterface._doc_template.format( __doc__ = FIFOInterface._doc_template.format(
description=""" description="""
Synchronous first in, first out queue. Synchronous first in, first out queue.
@ -209,7 +209,7 @@ class SyncFIFO(FIFOInterface):
return m return m
class SyncFIFOBuffered(FIFOInterface): class SyncFIFOBuffered(Elaboratable, FIFOInterface):
__doc__ = FIFOInterface._doc_template.format( __doc__ = FIFOInterface._doc_template.format(
description=""" description="""
Buffered synchronous first in, first out queue. Buffered synchronous first in, first out queue.
@ -265,7 +265,7 @@ class SyncFIFOBuffered(FIFOInterface):
return m return m
class AsyncFIFO(FIFOInterface): class AsyncFIFO(Elaboratable, FIFOInterface):
__doc__ = FIFOInterface._doc_template.format( __doc__ = FIFOInterface._doc_template.format(
description=""" description="""
Asynchronous first in, first out queue. Asynchronous first in, first out queue.
@ -361,7 +361,7 @@ class AsyncFIFO(FIFOInterface):
return m return m
class AsyncFIFOBuffered(FIFOInterface): class AsyncFIFOBuffered(Elaboratable, FIFOInterface):
__doc__ = FIFOInterface._doc_template.format( __doc__ = FIFOInterface._doc_template.format(
description=""" description="""
Buffered asynchronous first in, first out queue. 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): def __init__(self):
self.s1 = Signal() self.s1 = Signal()

View file

@ -1,6 +1,7 @@
from .tools import * from .tools import *
from ..hdl.ast import * from ..hdl.ast import *
from ..hdl.dsl import * from ..hdl.dsl import *
from ..hdl.ir import *
from ..back.pysim import * from ..back.pysim import *
from ..lib.coding import * from ..lib.coding import *
@ -82,7 +83,7 @@ class DecoderTestCase(FHDLTestCase):
sim.run() sim.run()
class ReversibleSpec: class ReversibleSpec(Elaboratable):
def __init__(self, encoder_cls, decoder_cls, args): def __init__(self, encoder_cls, decoder_cls, args):
self.encoder_cls = encoder_cls self.encoder_cls = encoder_cls
self.decoder_cls = decoder_cls self.decoder_cls = decoder_cls
@ -99,7 +100,7 @@ class ReversibleSpec:
return m return m
class HammingDistanceSpec: class HammingDistanceSpec(Elaboratable):
def __init__(self, distance, encoder_cls, args): def __init__(self, distance, encoder_cls, args):
self.distance = distance self.distance = distance
self.encoder_cls = encoder_cls self.encoder_cls = encoder_cls

View file

@ -45,7 +45,7 @@ class FIFOSmokeTestCase(FHDLTestCase):
self.assertAsyncFIFOWorks(AsyncFIFOBuffered(width=8, depth=3)) 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. Non-synthesizable first-in first-out queue, implemented naively as a chain of registers.
""" """
@ -104,7 +104,7 @@ class FIFOModel(FIFOInterface):
return m return m
class FIFOModelEquivalenceSpec: class FIFOModelEquivalenceSpec(Elaboratable):
""" """
The first-in first-out queue model equivalence specification: for any inputs and control 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, signals, the behavior of the implementation under test exactly matches the ideal model,
@ -148,7 +148,7 @@ class FIFOModelEquivalenceSpec:
return m return m
class FIFOContractSpec: class FIFOContractSpec(Elaboratable):
""" """
The first-in first-out queue contract specification: if two elements are written to the queue 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 consecutively, they must be read out consecutively at some later point, no matter all other