Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: amaranth-lang/amaranth
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 687d3a3df74b
Choose a base ref
...
head repository: amaranth-lang/amaranth
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: afece150016e
Choose a head ref
  • 2 commits
  • 6 files changed
  • 1 contributor

Commits on Feb 1, 2020

  1. Copy the full SHA
    9fb4a4f View commit details
  2. hdl.ast: warn on unused property statements (Assert, Assume, etc).

    A property statement that is created but not added to a module is
    virtually always a serious bug, since it can make formal verification
    pass when it should not. Therefore, add a warning to it, similar to
    UnusedElaboratable.
    
    Doing this to all statements is possible, but many temporary ones are
    created internally by nMigen, and the extensive changes required to
    remove false positives are likely not worth the true positives.
    We can revisit this in the future.
    
    Fixes #303.
    whitequark committed Feb 1, 2020
    Copy the full SHA
    afece15 View commit details
Showing with 76 additions and 46 deletions.
  1. +45 −0 nmigen/_unused.py
  2. +1 −1 nmigen/compat/fhdl/module.py
  3. +13 −6 nmigen/hdl/ast.py
  4. +7 −5 nmigen/hdl/dsl.py
  5. +8 −34 nmigen/hdl/ir.py
  6. +2 −0 nmigen/hdl/xfrm.py
45 changes: 45 additions & 0 deletions nmigen/_unused.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import sys
import warnings

from ._utils import get_linter_option


__all__ = ["UnusedMustUse", "MustUse"]


class UnusedMustUse(Warning):
pass


class MustUse:
_MustUse__silence = False
_MustUse__warning = UnusedMustUse

def __new__(cls, *args, src_loc_at=0, **kwargs):
frame = sys._getframe(1 + src_loc_at)
self = super().__new__(cls)
self._MustUse__used = False
self._MustUse__context = dict(
filename=frame.f_code.co_filename,
lineno=frame.f_lineno,
source=self)
return self

def __del__(self):
if self._MustUse__silence:
return
if hasattr(self, "_MustUse__used") and not self._MustUse__used:
if get_linter_option(self._MustUse__context["filename"],
self._MustUse__warning.__name__, bool, True):
warnings.warn_explicit(
"{!r} created but never used".format(self), self._MustUse__warning,
**self._MustUse__context)


_old_excepthook = sys.excepthook
def _silence_elaboratable(type, value, traceback):
# Don't show anything if the interpreter crashed; that'd just obscure the exception
# traceback instead of helping.
MustUse._MustUse__silence = True
_old_excepthook(type, value, traceback)
sys.excepthook = _silence_elaboratable
2 changes: 1 addition & 1 deletion nmigen/compat/fhdl/module.py
Original file line number Diff line number Diff line change
@@ -95,7 +95,7 @@ def __iadd__(self, other):


class CompatModule(ir.Elaboratable):
_Elaboratable__silence = True
_MustUse__silence = True

# Actually returns another nMigen Elaboratable (nmigen.dsl.Module), not a Fragment.
def get_fragment(self):
19 changes: 13 additions & 6 deletions nmigen/hdl/ast.py
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@

from .. import tracer
from .._utils import *
from .._unused import *


__all__ = [
@@ -17,9 +18,9 @@
"Signal", "ClockSignal", "ResetSignal",
"UserValue",
"Sample", "Past", "Stable", "Rose", "Fell", "Initial",
"Statement", "Assign", "Assert", "Assume", "Cover", "Switch",
"ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict",
"SignalSet",
"Statement", "Switch",
"Property", "Assign", "Assert", "Assume", "Cover",
"ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", "SignalSet",
]


@@ -493,13 +494,13 @@ def _rhs_signals(self):
@final
class AnyConst(AnyValue):
def __repr__(self):
return "(anyconst {}'{})".format(self.nbits, "s" if self.signed else "")
return "(anyconst {}'{})".format(self.width, "s" if self.signed else "")


@final
class AnySeq(AnyValue):
def __repr__(self):
return "(anyseq {}'{})".format(self.nbits, "s" if self.signed else "")
return "(anyseq {}'{})".format(self.width, "s" if self.signed else "")


@final
@@ -1221,7 +1222,13 @@ def __repr__(self):
return "(eq {!r} {!r})".format(self.lhs, self.rhs)


class Property(Statement):
class UnusedProperty(UnusedMustUse):
pass


class Property(Statement, MustUse):
_MustUse__warning = UnusedProperty

def __init__(self, test, *, _check=None, _en=None, src_loc_at=0):
super().__init__(src_loc_at=src_loc_at)
self.test = Value.cast(test)
12 changes: 7 additions & 5 deletions nmigen/hdl/dsl.py
Original file line number Diff line number Diff line change
@@ -462,14 +462,16 @@ def domain_name(domain):
while len(self._ctrl_stack) > self.domain._depth:
self._pop_ctrl()

for assign in Statement.cast(assigns):
if not compat_mode and not isinstance(assign, (Assign, Assert, Assume, Cover)):
for stmt in Statement.cast(assigns):
if not compat_mode and not isinstance(stmt, (Assign, Assert, Assume, Cover)):
raise SyntaxError(
"Only assignments and property checks may be appended to d.{}"
.format(domain_name(domain)))

assign = SampleDomainInjector(domain)(assign)
for signal in assign._lhs_signals():
stmt._MustUse__used = True
stmt = SampleDomainInjector(domain)(stmt)

for signal in stmt._lhs_signals():
if signal not in self._driving:
self._driving[signal] = domain
elif self._driving[signal] != domain:
@@ -479,7 +481,7 @@ def domain_name(domain):
"already driven from d.{}"
.format(signal, domain_name(domain), domain_name(cd_curr)))

self._statements.append(assign)
self._statements.append(stmt)

def _add_submodule(self, submodule, name=None):
if not hasattr(submodule, "elaborate"):
42 changes: 8 additions & 34 deletions nmigen/hdl/ir.py
Original file line number Diff line number Diff line change
@@ -6,48 +6,20 @@
import sys

from .._utils import *
from .._unused import *
from .ast import *
from .cd import *


__all__ = ["UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance"]


class UnusedElaboratable(Warning):
class UnusedElaboratable(UnusedMustUse):
pass


class Elaboratable(metaclass=ABCMeta):
_Elaboratable__silence = False

def __new__(cls, *args, src_loc_at=0, **kwargs):
frame = sys._getframe(1 + src_loc_at)
self = super().__new__(cls)
self._Elaboratable__used = False
self._Elaboratable__context = dict(
filename=frame.f_code.co_filename,
lineno=frame.f_lineno,
source=self)
return self

def __del__(self):
if self._Elaboratable__silence:
return
if hasattr(self, "_Elaboratable__used") and not self._Elaboratable__used:
if get_linter_option(self._Elaboratable__context["filename"],
"UnusedElaboratable", bool, True):
warnings.warn_explicit(
"{!r} created but never used".format(self), UnusedElaboratable,
**self._Elaboratable__context)


_old_excepthook = sys.excepthook
def _silence_elaboratable(type, value, traceback):
# Don't show anything if the interpreter crashed; that'd just obscure the exception
# traceback instead of helping.
Elaboratable._Elaboratable__silence = True
_old_excepthook(type, value, traceback)
sys.excepthook = _silence_elaboratable
class Elaboratable(MustUse, metaclass=ABCMeta):
_MustUse__warning = UnusedElaboratable


class DriverConflict(UserWarning):
@@ -63,7 +35,7 @@ def get(obj, platform):
return obj
elif isinstance(obj, Elaboratable):
code = obj.elaborate.__code__
obj._Elaboratable__used = True
obj._MustUse__used = True
obj = obj.elaborate(platform)
elif hasattr(obj, "elaborate"):
warnings.warn(
@@ -149,7 +121,9 @@ def iter_domains(self):
yield from self.domains

def add_statements(self, *stmts):
self.statements += Statement.cast(stmts)
for stmt in Statement.cast(stmts):
stmt._MustUse__used = True
self.statements.append(stmt)

def add_subfragment(self, subfragment, name=None):
assert isinstance(subfragment, Fragment)
2 changes: 2 additions & 0 deletions nmigen/hdl/xfrm.py
Original file line number Diff line number Diff line change
@@ -234,6 +234,8 @@ def on_statement(self, stmt):
new_stmt.src_loc = stmt.src_loc
if isinstance(new_stmt, Switch) and isinstance(stmt, Switch):
new_stmt.case_src_locs = stmt.case_src_locs
if isinstance(new_stmt, Property):
new_stmt._MustUse__used = True
return new_stmt

def __call__(self, stmt):