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: m-labs/nmigen
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 8b05b28f5a7c
Choose a base ref
...
head repository: m-labs/nmigen
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 75d0fcd639ae
Choose a head ref
  • 2 commits
  • 13 files changed
  • 1 contributor

Commits on Oct 26, 2019

  1. hdl.ir: allow disabling UnusedElaboratable warning in file scope.

    This warning is usually quite handy, but is problematic in tests:
    although it can be suppressed by using Fragment.get on elaboratable,
    that is not always possible, in particular when writing tests for
    exceptions raised by __init__, e.g.:
    
        def test_wrong_csr_bus(self):
            with self.assertRaisesRegex(ValueError, r"blah blah"):
                WishboneCSRBridge(csr_bus=object())
    
    In theory, it should be possible to suppress warnings per-module
    and even per-line using code such as:
    
        import re, warnings
        from nmigen.hdl.ir import UnusedElaboratable
        warnings.filterwarnings("ignore", category=UnusedElaboratable,
                                module=re.escape(__name__))
    
    Unfortunately, not only is this code quite convoluted, but it also
    does not actually work; we are using warnings.warn_explicit() because
    we collect source locations on our own, but it requires the caller
    to extract the __warningregistry__ dictionary from module globals,
    or warning suppression would not work. Not only is this not feasible
    in most diagnostic sites in nMigen, but also I never got it to work
    anyway, even when passing all of module, registry, and module_globals
    to warn_explicit().
    
    Instead, use a magic comment at the start of a file to do this job,
    which might not be elegant but is simple and practical. For now,
    only UnusedElaboratable can be suppressed with it, but in future,
    other linter parameters may become tweakable this way.
    whitequark committed Oct 26, 2019
    Copy the full SHA
    9786d0c View commit details
  2. test: use #nmigen: magic comment instead of monkey patch.

    Also, fix missing and incorrect src_loc_at arguments where
    appropriate so the testsuite passes without warnings.
    whitequark committed Oct 26, 2019
    Copy the full SHA
    75d0fcd View commit details
34 changes: 33 additions & 1 deletion nmigen/_utils.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import contextlib
import functools
import warnings
import linecache
import re
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"]
__all__ = ["flatten", "union" , "log2_int", "bits_for", "memoize", "final", "deprecated",
"get_linter_options", "get_linter_option"]


def flatten(i):
@@ -82,3 +85,32 @@ def decorator(f):
name = f.__name__
setattr(cls, name, f)
return decorator


def get_linter_options(filename):
first_line = linecache.getline(filename, 1)
if first_line:
match = re.match(r"^#\s*nmigen:\s*((?:\w+=\w+\s*)(?:,\s*\w+=\w+\s*)*)\n$", first_line)
if match:
return dict(map(lambda s: s.strip().split("=", 2), match.group(1).split(",")))
return dict()


def get_linter_option(filename, name, type, default):
options = get_linter_options(filename)
if name not in options:
return default

option = options[name]
if type is bool:
if option in ("1", "yes", "enable"):
return True
if option in ("0", "no", "disable"):
return False
return default
if type is int:
try:
return int(option, 0)
except ValueError:
return default
assert False
15 changes: 10 additions & 5 deletions nmigen/hdl/ir.py
Original file line number Diff line number Diff line change
@@ -21,19 +21,24 @@ 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__src_loc = traceback.extract_stack(limit=2 + src_loc_at)[0]
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:
warnings.warn_explicit("{!r} created but never used".format(self), UnusedElaboratable,
filename=self._Elaboratable__src_loc.filename,
lineno=self._Elaboratable__src_loc.lineno,
source=self)
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
25 changes: 13 additions & 12 deletions nmigen/hdl/mem.py
Original file line number Diff line number Diff line change
@@ -53,19 +53,19 @@ def init(self, new_init):
raise TypeError("Memory initialization value at address {:x}: {}"
.format(addr, e)) from None

def read_port(self, **kwargs):
return ReadPort(self, **kwargs)
def read_port(self, *, src_loc_at=0, **kwargs):
return ReadPort(self, src_loc_at=1 + src_loc_at, **kwargs)

def write_port(self, **kwargs):
return WritePort(self, **kwargs)
def write_port(self, *, src_loc_at=0, **kwargs):
return WritePort(self, src_loc_at=1 + src_loc_at, **kwargs)

def __getitem__(self, index):
"""Simulation only."""
return self._array[index]


class ReadPort(Elaboratable):
def __init__(self, memory, *, domain="sync", transparent=True):
def __init__(self, memory, *, domain="sync", transparent=True, src_loc_at=0):
if domain == "comb" and not transparent:
raise ValueError("Read port cannot be simultaneously asynchronous and non-transparent")

@@ -74,11 +74,12 @@ def __init__(self, memory, *, domain="sync", transparent=True):
self.transparent = transparent

self.addr = Signal(range(memory.depth),
name="{}_r_addr".format(memory.name), src_loc_at=2)
name="{}_r_addr".format(memory.name), src_loc_at=2 + src_loc_at)
self.data = Signal(memory.width,
name="{}_r_data".format(memory.name), src_loc_at=2)
name="{}_r_data".format(memory.name), src_loc_at=2 + src_loc_at)
if self.domain != "comb" and not transparent:
self.en = Signal(name="{}_r_en".format(memory.name), src_loc_at=2, reset=1)
self.en = Signal(name="{}_r_en".format(memory.name), reset=1,
src_loc_at=2 + src_loc_at)
else:
self.en = Const(1)

@@ -132,7 +133,7 @@ def elaborate(self, platform):


class WritePort(Elaboratable):
def __init__(self, memory, *, domain="sync", granularity=None):
def __init__(self, memory, *, domain="sync", granularity=None, src_loc_at=0):
if granularity is None:
granularity = memory.width
if not isinstance(granularity, int) or granularity < 0:
@@ -150,11 +151,11 @@ def __init__(self, memory, *, domain="sync", granularity=None):
self.granularity = granularity

self.addr = Signal(range(memory.depth),
name="{}_w_addr".format(memory.name), src_loc_at=2)
name="{}_w_addr".format(memory.name), src_loc_at=2 + src_loc_at)
self.data = Signal(memory.width,
name="{}_w_data".format(memory.name), src_loc_at=2)
name="{}_w_data".format(memory.name), src_loc_at=2 + src_loc_at)
self.en = Signal(memory.width // granularity,
name="{}_w_en".format(memory.name), src_loc_at=2)
name="{}_w_en".format(memory.name), src_loc_at=2 + src_loc_at)

def elaborate(self, platform):
f = Instance("$memwr",
12 changes: 6 additions & 6 deletions nmigen/hdl/xfrm.py
Original file line number Diff line number Diff line change
@@ -310,22 +310,22 @@ def on_fragment(self, fragment):
self.map_drivers(fragment, new_fragment)
return new_fragment

def __call__(self, value):
def __call__(self, value, *, src_loc_at=0):
if isinstance(value, Fragment):
return self.on_fragment(value)
elif isinstance(value, TransformedElaboratable):
value._transforms_.append(self)
return value
elif hasattr(value, "elaborate"):
value = TransformedElaboratable(value)
value = TransformedElaboratable(value, src_loc_at=1 + src_loc_at)
value._transforms_.append(self)
return value
else:
raise AttributeError("Object {!r} cannot be elaborated".format(value))


class TransformedElaboratable(Elaboratable):
def __init__(self, elaboratable):
def __init__(self, elaboratable, *, src_loc_at=0):
assert hasattr(elaboratable, "elaborate")

# Fields prefixed and suffixed with underscore to avoid as many conflicts with the inner
@@ -725,9 +725,9 @@ def on_fragment(self, fragment):
def _insert_control(self, fragment, domain, signals):
raise NotImplementedError # :nocov:

def __call__(self, value):
self.src_loc = tracer.get_src_loc()
return super().__call__(value)
def __call__(self, value, *, src_loc_at=0):
self.src_loc = tracer.get_src_loc(src_loc_at=src_loc_at)
return super().__call__(value, src_loc_at=1 + src_loc_at)


class ResetInserter(_ControlInserter):
6 changes: 0 additions & 6 deletions nmigen/test/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +0,0 @@
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__
2 changes: 2 additions & 0 deletions nmigen/test/compat/test_coding.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# nmigen: UnusedElaboratable=no

import unittest

from ...compat import *
2 changes: 2 additions & 0 deletions nmigen/test/test_build_res.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# nmigen: UnusedElaboratable=no

from .. import *
from ..hdl.rec import *
from ..lib.io import *
2 changes: 2 additions & 0 deletions nmigen/test/test_hdl_dsl.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# nmigen: UnusedElaboratable=no

from collections import OrderedDict
from enum import Enum

2 changes: 2 additions & 0 deletions nmigen/test/test_hdl_ir.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# nmigen: UnusedElaboratable=no

from collections import OrderedDict

from ..hdl.ast import *
2 changes: 2 additions & 0 deletions nmigen/test/test_hdl_mem.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# nmigen: UnusedElaboratable=no

from ..hdl.ast import *
from ..hdl.mem import *
from .utils import *
2 changes: 2 additions & 0 deletions nmigen/test/test_hdl_xfrm.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# nmigen: UnusedElaboratable=no

from ..hdl.ast import *
from ..hdl.cd import *
from ..hdl.ir import *
2 changes: 2 additions & 0 deletions nmigen/test/test_lib_cdc.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# nmigen: UnusedElaboratable=no

from .utils import *
from ..hdl import *
from ..back.pysim import *
2 changes: 2 additions & 0 deletions nmigen/test/test_lib_fifo.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# nmigen: UnusedElaboratable=no

from .utils import *
from ..hdl import *
from ..asserts import *