hdl.ast: implement ValueCastable.
[nmigen.git] / nmigen / hdl / ast.py
index 622c0fd04659886e98531d7835453f74f817cad9..0b86e7153da0cac1dd5d62c83cca15caaf7357e2 100644 (file)
 from abc import ABCMeta, abstractmethod
 from abc import ABCMeta, abstractmethod
-import builtins
 import traceback
 import traceback
+import sys
+import warnings
+import typing
+import functools
 from collections import OrderedDict
 from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
 from enum import Enum
 
 from .. import tracer
 from collections import OrderedDict
 from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
 from enum import Enum
 
 from .. import tracer
-from ..tools import *
+from .._utils import *
+from .._unused import *
 
 
 __all__ = [
 
 
 __all__ = [
+    "Shape", "signed", "unsigned",
     "Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
     "Array", "ArrayProxy",
     "Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
     "Array", "ArrayProxy",
-    "Sample", "Past", "Stable", "Rose", "Fell",
     "Signal", "ClockSignal", "ResetSignal",
     "Signal", "ClockSignal", "ResetSignal",
-    "UserValue",
-    "Statement", "Assign", "Assert", "Assume", "Switch", "Delay", "Tick",
-    "Passive", "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict",
-    "SignalSet",
+    "UserValue", "ValueCastable",
+    "Sample", "Past", "Stable", "Rose", "Fell", "Initial",
+    "Statement", "Switch",
+    "Property", "Assign", "Assert", "Assume", "Cover",
+    "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", "SignalSet",
 ]
 
 
 class DUID:
 ]
 
 
 class DUID:
-    """Deterministic Unique IDentifier"""
+    """Deterministic Unique IDentifier."""
     __next_uid = 0
     def __init__(self):
         self.duid = DUID.__next_uid
         DUID.__next_uid += 1
 
 
     __next_uid = 0
     def __init__(self):
         self.duid = DUID.__next_uid
         DUID.__next_uid += 1
 
 
+class Shape:
+    """Bit width and signedness of a value.
+
+    A ``Shape`` can be constructed using:
+      * explicit bit width and signedness;
+      * aliases :func:`signed` and :func:`unsigned`;
+      * casting from a variety of objects.
+
+    A ``Shape`` can be cast from:
+      * an integer, where the integer specifies the bit width;
+      * a range, where the result is wide enough to represent any element of the range, and is
+        signed if any element of the range is signed;
+      * an :class:`Enum` with all integer members or :class:`IntEnum`, where the result is wide
+        enough to represent any member of the enumeration, and is signed if any member of
+        the enumeration is signed.
+
+    Parameters
+    ----------
+    width : int
+        The number of bits in the representation, including the sign bit (if any).
+    signed : bool
+        If ``False``, the value is unsigned. If ``True``, the value is signed two's complement.
+    """
+    def __init__(self, width=1, signed=False):
+        if not isinstance(width, int) or width < 0:
+            raise TypeError("Width must be a non-negative integer, not {!r}"
+                            .format(width))
+        self.width = width
+        self.signed = signed
+
+    def __iter__(self):
+        return iter((self.width, self.signed))
+
+    @staticmethod
+    def cast(obj, *, src_loc_at=0):
+        if isinstance(obj, Shape):
+            return obj
+        if isinstance(obj, int):
+            return Shape(obj)
+        if isinstance(obj, tuple):
+            width, signed = obj
+            warnings.warn("instead of `{tuple}`, use `{constructor}({width})`"
+                          .format(constructor="signed" if signed else "unsigned", width=width,
+                                  tuple=obj),
+                          DeprecationWarning, stacklevel=2 + src_loc_at)
+            return Shape(width, signed)
+        if isinstance(obj, range):
+            if len(obj) == 0:
+                return Shape(0, obj.start < 0)
+            signed = obj.start < 0 or (obj.stop - obj.step) < 0
+            width  = max(bits_for(obj.start, signed),
+                         bits_for(obj.stop - obj.step, signed))
+            return Shape(width, signed)
+        if isinstance(obj, type) and issubclass(obj, Enum):
+            min_value = min(member.value for member in obj)
+            max_value = max(member.value for member in obj)
+            if not isinstance(min_value, int) or not isinstance(max_value, int):
+                raise TypeError("Only enumerations with integer values can be used "
+                                "as value shapes")
+            signed = min_value < 0 or max_value < 0
+            width  = max(bits_for(min_value, signed), bits_for(max_value, signed))
+            return Shape(width, signed)
+        raise TypeError("Object {!r} cannot be used as value shape".format(obj))
+
+    def __repr__(self):
+        if self.signed:
+            return "signed({})".format(self.width)
+        else:
+            return "unsigned({})".format(self.width)
+
+    def __eq__(self, other):
+        if isinstance(other, tuple) and len(other) == 2:
+            width, signed = other
+            if isinstance(width, int) and isinstance(signed, bool):
+                return self.width == width and self.signed == signed
+            else:
+                raise TypeError("Shapes may be compared with other Shapes and (int, bool) tuples, "
+                        "not {!r}"
+                        .format(other))
+        if not isinstance(other, Shape):
+            raise TypeError("Shapes may be compared with other Shapes and (int, bool) tuples, "
+                    "not {!r}"
+                    .format(other))
+        return self.width == other.width and self.signed == other.signed
+
+
+def unsigned(width):
+    """Shorthand for ``Shape(width, signed=False)``."""
+    return Shape(width, signed=False)
+
+
+def signed(width):
+    """Shorthand for ``Shape(width, signed=True)``."""
+    return Shape(width, signed=True)
+
+
 class Value(metaclass=ABCMeta):
     @staticmethod
 class Value(metaclass=ABCMeta):
     @staticmethod
-    def wrap(obj):
-        """Ensures that the passed object is an nMigen value. Booleans and integers
-        are automatically wrapped into ``Const``."""
+    def cast(obj):
+        """Converts ``obj`` to an nMigen value.
+
+        Booleans and integers are wrapped into a :class:`Const`. Enumerations whose members are
+        all integers are converted to a :class:`Const` with a shape that fits every member.
+        """
         if isinstance(obj, Value):
             return obj
         if isinstance(obj, Value):
             return obj
-        elif isinstance(obj, (bool, int)):
+        if isinstance(obj, int):
             return Const(obj)
             return Const(obj)
-        else:
-            raise TypeError("Object '{!r}' is not an nMigen value".format(obj))
+        if isinstance(obj, Enum):
+            return Const(obj.value, Shape.cast(type(obj)))
+        if isinstance(obj, ValueCastable):
+            return obj.as_value()
+        raise TypeError("Object {!r} cannot be converted to an nMigen value".format(obj))
 
 
-    def __init__(self, src_loc_at=0):
+    def __init__(self, *, src_loc_at=0):
         super().__init__()
         self.src_loc = tracer.get_src_loc(1 + src_loc_at)
 
     def __bool__(self):
         super().__init__()
         self.src_loc = tracer.get_src_loc(1 + src_loc_at)
 
     def __bool__(self):
-        raise TypeError("Attempted to convert nMigen value to boolean")
+        raise TypeError("Attempted to convert nMigen value to Python boolean")
 
     def __invert__(self):
         return Operator("~", [self])
 
     def __invert__(self):
         return Operator("~", [self])
@@ -61,26 +168,56 @@ class Value(metaclass=ABCMeta):
         return Operator("-", [self, other])
     def __rsub__(self, other):
         return Operator("-", [other, self])
         return Operator("-", [self, other])
     def __rsub__(self, other):
         return Operator("-", [other, self])
+
     def __mul__(self, other):
         return Operator("*", [self, other])
     def __rmul__(self, other):
         return Operator("*", [other, self])
     def __mul__(self, other):
         return Operator("*", [self, other])
     def __rmul__(self, other):
         return Operator("*", [other, self])
+
+    def __check_divisor(self):
+        width, signed = self.shape()
+        if signed:
+            # Python's division semantics and Verilog's division semantics differ for negative
+            # divisors (Python uses div/mod, Verilog uses quo/rem); for now, avoid the issue
+            # completely by prohibiting such division operations.
+            raise NotImplementedError("Division by a signed value is not supported")
     def __mod__(self, other):
     def __mod__(self, other):
+        other = Value.cast(other)
+        other.__check_divisor()
         return Operator("%", [self, other])
     def __rmod__(self, other):
         return Operator("%", [self, other])
     def __rmod__(self, other):
+        self.__check_divisor()
         return Operator("%", [other, self])
         return Operator("%", [other, self])
-    def __div__(self, other):
-        return Operator("/", [self, other])
-    def __rdiv__(self, other):
-        return Operator("/", [other, self])
+    def __floordiv__(self, other):
+        other = Value.cast(other)
+        other.__check_divisor()
+        return Operator("//", [self, other])
+    def __rfloordiv__(self, other):
+        self.__check_divisor()
+        return Operator("//", [other, self])
+
+    def __check_shamt(self):
+        width, signed = self.shape()
+        if signed:
+            # Neither Python nor HDLs implement shifts by negative values; prohibit any shifts
+            # by a signed value to make sure the shift amount can always be interpreted as
+            # an unsigned value.
+            raise TypeError("Shift amount must be unsigned")
     def __lshift__(self, other):
     def __lshift__(self, other):
+        other = Value.cast(other)
+        other.__check_shamt()
         return Operator("<<", [self, other])
     def __rlshift__(self, other):
         return Operator("<<", [self, other])
     def __rlshift__(self, other):
+        self.__check_shamt()
         return Operator("<<", [other, self])
     def __rshift__(self, other):
         return Operator("<<", [other, self])
     def __rshift__(self, other):
+        other = Value.cast(other)
+        other.__check_shamt()
         return Operator(">>", [self, other])
     def __rrshift__(self, other):
         return Operator(">>", [self, other])
     def __rrshift__(self, other):
+        self.__check_shamt()
         return Operator(">>", [other, self])
         return Operator(">>", [other, self])
+
     def __and__(self, other):
         return Operator("&", [self, other])
     def __rand__(self, other):
     def __and__(self, other):
         return Operator("&", [self, other])
     def __rand__(self, other):
@@ -107,14 +244,21 @@ class Value(metaclass=ABCMeta):
     def __ge__(self, other):
         return Operator(">=", [self, other])
 
     def __ge__(self, other):
         return Operator(">=", [self, other])
 
+    def __abs__(self):
+        width, signed = self.shape()
+        if signed:
+            return Mux(self >= 0, self, -self)
+        else:
+            return self
+
     def __len__(self):
     def __len__(self):
-        return self.shape()[0]
+        return self.shape().width
 
     def __getitem__(self, key):
         n = len(self)
         if isinstance(key, int):
             if key not in range(-n, n):
 
     def __getitem__(self, key):
         n = len(self)
         if isinstance(key, int):
             if key not in range(-n, n):
-                raise IndexError("Cannot index {} bits into {}-bit value".format(key, n))
+                raise IndexError(f"Index {key} is out of bounds for a {n}-bit value")
             if key < 0:
                 key += n
             return Slice(self, key, key + 1)
             if key < 0:
                 key += n
             return Slice(self, key, key + 1)
@@ -126,16 +270,66 @@ class Value(metaclass=ABCMeta):
         else:
             raise TypeError("Cannot index value with {}".format(repr(key)))
 
         else:
             raise TypeError("Cannot index value with {}".format(repr(key)))
 
+    def as_unsigned(self):
+        """Conversion to unsigned.
+
+        Returns
+        -------
+        Value, out
+            This ``Value`` reinterpreted as a unsigned integer.
+        """
+        return Operator("u", [self])
+
+    def as_signed(self):
+        """Conversion to signed.
+
+        Returns
+        -------
+        Value, out
+            This ``Value`` reinterpreted as a signed integer.
+        """
+        return Operator("s", [self])
+
     def bool(self):
         """Conversion to boolean.
 
         Returns
         -------
         Value, out
     def bool(self):
         """Conversion to boolean.
 
         Returns
         -------
         Value, out
-            Output ``Value``. If any bits are set, returns ``1``, else ``0``.
+            ``1`` if any bits are set, ``0`` otherwise.
         """
         return Operator("b", [self])
 
         """
         return Operator("b", [self])
 
+    def any(self):
+        """Check if any bits are ``1``.
+
+        Returns
+        -------
+        Value, out
+            ``1`` if any bits are set, ``0`` otherwise.
+        """
+        return Operator("r|", [self])
+
+    def all(self):
+        """Check if all bits are ``1``.
+
+        Returns
+        -------
+        Value, out
+            ``1`` if all bits are set, ``0`` otherwise.
+        """
+        return Operator("r&", [self])
+
+    def xor(self):
+        """Compute pairwise exclusive-or of every bit.
+
+        Returns
+        -------
+        Value, out
+            ``1`` if an odd number of bits are set, ``0`` if an even number of bits are set.
+        """
+        return Operator("r^", [self])
+
     def implies(premise, conclusion):
         """Implication.
 
     def implies(premise, conclusion):
         """Implication.
 
@@ -146,24 +340,186 @@ class Value(metaclass=ABCMeta):
         """
         return ~premise | conclusion
 
         """
         return ~premise | conclusion
 
-    def part(self, offset, width):
-        """Indexed part-select.
+    def bit_select(self, offset, width):
+        """Part-select with bit granularity.
+
+        Selects a constant width but variable offset part of a ``Value``, such that successive
+        parts overlap by all but 1 bit.
+
+        Parameters
+        ----------
+        offset : Value, int
+            Index of first selected bit.
+        width : int
+            Number of selected bits.
+
+        Returns
+        -------
+        Part, out
+            Selected part of the ``Value``
+        """
+        offset = Value.cast(offset)
+        if type(offset) is Const and isinstance(width, int):
+            return self[offset.value:offset.value + width]
+        return Part(self, offset, width, stride=1, src_loc_at=1)
+
+    def word_select(self, offset, width):
+        """Part-select with word granularity.
 
 
-        Selects a constant width but variable offset part of a ``Value``.
+        Selects a constant width but variable offset part of a ``Value``, such that successive
+        parts do not overlap.
 
         Parameters
         ----------
 
         Parameters
         ----------
-        offset : Value, in
-            start point of the selected bits
+        offset : Value, int
+            Index of first selected word.
         width : int
         width : int
-            number of selected bits
+            Number of selected bits.
 
         Returns
         -------
         Part, out
             Selected part of the ``Value``
         """
 
         Returns
         -------
         Part, out
             Selected part of the ``Value``
         """
-        return Part(self, offset, width)
+        offset = Value.cast(offset)
+        if type(offset) is Const and isinstance(width, int):
+            return self[offset.value * width:(offset.value + 1) * width]
+        return Part(self, offset, width, stride=width, src_loc_at=1)
+
+    def matches(self, *patterns):
+        """Pattern matching.
+
+        Matches against a set of patterns, which may be integers or bit strings, recognizing
+        the same grammar as ``Case()``.
+
+        Parameters
+        ----------
+        patterns : int or str
+            Patterns to match against.
+
+        Returns
+        -------
+        Value, out
+            ``1`` if any pattern matches the value, ``0`` otherwise.
+        """
+        matches = []
+        for pattern in patterns:
+            if not isinstance(pattern, (int, str, Enum)):
+                raise SyntaxError("Match pattern must be an integer, a string, or an enumeration, "
+                                  "not {!r}"
+                                  .format(pattern))
+            if isinstance(pattern, str) and any(bit not in "01- \t" for bit in pattern):
+                raise SyntaxError("Match pattern '{}' must consist of 0, 1, and - (don't care) "
+                                  "bits, and may include whitespace"
+                                  .format(pattern))
+            if (isinstance(pattern, str) and
+                    len("".join(pattern.split())) != len(self)):
+                raise SyntaxError("Match pattern '{}' must have the same width as match value "
+                                  "(which is {})"
+                                  .format(pattern, len(self)))
+            if isinstance(pattern, int) and bits_for(pattern) > len(self):
+                warnings.warn("Match pattern '{:b}' is wider than match value "
+                              "(which has width {}); comparison will never be true"
+                              .format(pattern, len(self)),
+                              SyntaxWarning, stacklevel=3)
+                continue
+            if isinstance(pattern, str):
+                pattern = "".join(pattern.split()) # remove whitespace
+                mask    = int(pattern.replace("0", "1").replace("-", "0"), 2)
+                pattern = int(pattern.replace("-", "0"), 2)
+                matches.append((self & mask) == pattern)
+            elif isinstance(pattern, int):
+                matches.append(self == pattern)
+            elif isinstance(pattern, Enum):
+                matches.append(self == pattern.value)
+            else:
+                assert False
+        if not matches:
+            return Const(0)
+        elif len(matches) == 1:
+            return matches[0]
+        else:
+            return Cat(*matches).any()
+
+    def shift_left(self, amount):
+        """Shift left by constant amount.
+
+        Parameters
+        ----------
+        amount : int
+            Amount to shift by.
+
+        Returns
+        -------
+        Value, out
+            If the amount is positive, the input shifted left. Otherwise, the input shifted right.
+        """
+        if not isinstance(amount, int):
+            raise TypeError("Shift amount must be an integer, not {!r}".format(amount))
+        if amount < 0:
+            return self.shift_right(-amount)
+        if self.shape().signed:
+            return Cat(Const(0, amount), self).as_signed()
+        else:
+            return Cat(Const(0, amount), self) # unsigned
+
+    def shift_right(self, amount):
+        """Shift right by constant amount.
+
+        Parameters
+        ----------
+        amount : int
+            Amount to shift by.
+
+        Returns
+        -------
+        Value, out
+            If the amount is positive, the input shifted right. Otherwise, the input shifted left.
+        """
+        if not isinstance(amount, int):
+            raise TypeError("Shift amount must be an integer, not {!r}".format(amount))
+        if amount < 0:
+            return self.shift_left(-amount)
+        if self.shape().signed:
+            return self[amount:].as_signed()
+        else:
+            return self[amount:] # unsigned
+
+    def rotate_left(self, amount):
+        """Rotate left by constant amount.
+
+        Parameters
+        ----------
+        amount : int
+            Amount to rotate by.
+
+        Returns
+        -------
+        Value, out
+            If the amount is positive, the input rotated left. Otherwise, the input rotated right.
+        """
+        if not isinstance(amount, int):
+            raise TypeError("Rotate amount must be an integer, not {!r}".format(amount))
+        amount %= len(self)
+        return Cat(self[-amount:], self[:-amount]) # meow :3
+
+    def rotate_right(self, amount):
+        """Rotate right by constant amount.
+
+        Parameters
+        ----------
+        amount : int
+            Amount to rotate by.
+
+        Returns
+        -------
+        Value, out
+            If the amount is positive, the input rotated right. Otherwise, the input rotated right.
+        """
+        if not isinstance(amount, int):
+            raise TypeError("Rotate amount must be an integer, not {!r}".format(amount))
+        amount %= len(self)
+        return Cat(self[amount:], self[:amount])
 
     def eq(self, value):
         """Assignment.
 
     def eq(self, value):
         """Assignment.
@@ -182,20 +538,19 @@ class Value(metaclass=ABCMeta):
 
     @abstractmethod
     def shape(self):
 
     @abstractmethod
     def shape(self):
-        """Bit length and signedness of a value.
+        """Bit width and signedness of a value.
 
         Returns
         -------
 
         Returns
         -------
-        int, bool
-            Number of bits required to store `v` or available in `v`, followed by
-            whether `v` has a sign bit (included in the bit count).
+        Shape
+            See :class:`Shape`.
 
         Examples
         --------
 
         Examples
         --------
-        >>> Value.shape(Signal(8))
-        8, False
-        >>> Value.shape(C(0xaa))
-        8, False
+        >>> Signal(8).shape()
+        Shape(width=8, signed=False)
+        >>> Const(0xaa).shape()
+        Shape(width=8, signed=False)
         """
         pass # :nocov:
 
         """
         pass # :nocov:
 
@@ -220,158 +575,156 @@ class Const(Value):
     ----------
     value : int
     shape : int or tuple or None
     ----------
     value : int
     shape : int or tuple or None
-        Either an integer `bits` or a tuple `(bits, signed)`
-        specifying the number of bits in this `Const` and whether it is
-        signed (can represent negative values). `shape` defaults
-        to the minimum width and signedness of `value`.
+        Either an integer ``width`` or a tuple ``(width, signed)`` specifying the number of bits
+        in this constant and whether it is signed (can represent negative values).
+        ``shape`` defaults to the minimum possible width and signedness of ``value``.
 
     Attributes
     ----------
 
     Attributes
     ----------
-    nbits : int
+    width : int
     signed : bool
     """
     src_loc = None
 
     @staticmethod
     def normalize(value, shape):
     signed : bool
     """
     src_loc = None
 
     @staticmethod
     def normalize(value, shape):
-        nbits, signed = shape
-        mask = (1 << nbits) - 1
+        width, signed = shape
+        mask = (1 << width) - 1
         value &= mask
         value &= mask
-        if signed and value >> (nbits - 1):
+        if signed and value >> (width - 1):
             value |= ~mask
         return value
 
             value |= ~mask
         return value
 
-    def __init__(self, value, shape=None):
+    def __init__(self, value, shape=None, *, src_loc_at=0):
+        # We deliberately do not call Value.__init__ here.
         self.value = int(value)
         if shape is None:
         self.value = int(value)
         if shape is None:
-            shape = bits_for(self.value), self.value < 0
-        if isinstance(shape, int):
-            shape = shape, self.value < 0
-        self.nbits, self.signed = shape
-        if not isinstance(self.nbits, int) or self.nbits < 0:
-            raise TypeError("Width must be a non-negative integer, not '{!r}'"
-                            .format(self.nbits))
+            shape = Shape(bits_for(self.value), signed=self.value < 0)
+        elif isinstance(shape, int):
+            shape = Shape(shape, signed=self.value < 0)
+        else:
+            shape = Shape.cast(shape, src_loc_at=1 + src_loc_at)
+        self.width, self.signed = shape
         self.value = self.normalize(self.value, shape)
 
     def shape(self):
         self.value = self.normalize(self.value, shape)
 
     def shape(self):
-        return self.nbits, self.signed
+        return Shape(self.width, self.signed)
 
     def _rhs_signals(self):
 
     def _rhs_signals(self):
-        return ValueSet()
+        return SignalSet()
 
     def _as_const(self):
         return self.value
 
     def __repr__(self):
 
     def _as_const(self):
         return self.value
 
     def __repr__(self):
-        return "(const {}'{}d{})".format(self.nbits, "s" if self.signed else "", self.value)
+        return "(const {}'{}d{})".format(self.width, "s" if self.signed else "", self.value)
 
 
 C = Const  # shorthand
 
 
 class AnyValue(Value, DUID):
 
 
 C = Const  # shorthand
 
 
 class AnyValue(Value, DUID):
-    def __init__(self, shape):
-        super().__init__(src_loc_at=0)
-        if isinstance(shape, int):
-            shape = shape, False
-        self.nbits, self.signed = shape
-        if not isinstance(self.nbits, int) or self.nbits < 0:
-            raise TypeError("Width must be a non-negative integer, not '{!r}'"
-                            .format(self.nbits))
+    def __init__(self, shape, *, src_loc_at=0):
+        super().__init__(src_loc_at=src_loc_at)
+        self.width, self.signed = Shape.cast(shape, src_loc_at=1 + src_loc_at)
+        if not isinstance(self.width, int) or self.width < 0:
+            raise TypeError("Width must be a non-negative integer, not {!r}"
+                            .format(self.width))
 
     def shape(self):
 
     def shape(self):
-        return self.nbits, self.signed
+        return Shape(self.width, self.signed)
 
     def _rhs_signals(self):
 
     def _rhs_signals(self):
-        return ValueSet()
+        return SignalSet()
 
 
 @final
 class AnyConst(AnyValue):
     def __repr__(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):
 
 
 @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
 class Operator(Value):
 
 
 @final
 class Operator(Value):
-    def __init__(self, op, operands, src_loc_at=0):
+    def __init__(self, operator, operands, *, src_loc_at=0):
         super().__init__(src_loc_at=1 + src_loc_at)
         super().__init__(src_loc_at=1 + src_loc_at)
-        self.op = op
-        self.operands = [Value.wrap(o) for o in operands]
-
-    @staticmethod
-    def _bitwise_binary_shape(a_shape, b_shape):
-        a_bits, a_sign = a_shape
-        b_bits, b_sign = b_shape
-        if not a_sign and not b_sign:
-            # both operands unsigned
-            return max(a_bits, b_bits), False
-        elif a_sign and b_sign:
-            # both operands signed
-            return max(a_bits, b_bits), True
-        elif not a_sign and b_sign:
-            # first operand unsigned (add sign bit), second operand signed
-            return max(a_bits + 1, b_bits), True
-        else:
-            # first signed, second operand unsigned (add sign bit)
-            return max(a_bits, b_bits + 1), True
+        self.operator = operator
+        self.operands = [Value.cast(op) for op in operands]
 
     def shape(self):
 
     def shape(self):
+        def _bitwise_binary_shape(a_shape, b_shape):
+            a_bits, a_sign = a_shape
+            b_bits, b_sign = b_shape
+            if not a_sign and not b_sign:
+                # both operands unsigned
+                return Shape(max(a_bits, b_bits), False)
+            elif a_sign and b_sign:
+                # both operands signed
+                return Shape(max(a_bits, b_bits), True)
+            elif not a_sign and b_sign:
+                # first operand unsigned (add sign bit), second operand signed
+                return Shape(max(a_bits + 1, b_bits), True)
+            else:
+                # first signed, second operand unsigned (add sign bit)
+                return Shape(max(a_bits, b_bits + 1), True)
+
         op_shapes = list(map(lambda x: x.shape(), self.operands))
         if len(op_shapes) == 1:
         op_shapes = list(map(lambda x: x.shape(), self.operands))
         if len(op_shapes) == 1:
-            (a_bits, a_sign), = op_shapes
-            if self.op in ("+", "~"):
-                return a_bits, a_sign
-            if self.op == "-":
-                if not a_sign:
-                    return a_bits + 1, True
-                else:
-                    return a_bits, a_sign
-            if self.op == "b":
-                return 1, False
+            (a_width, a_signed), = op_shapes
+            if self.operator in ("+", "~"):
+                return Shape(a_width, a_signed)
+            if self.operator == "-":
+                return Shape(a_width + 1, True)
+            if self.operator in ("b", "r|", "r&", "r^"):
+                return Shape(1, False)
+            if self.operator == "u":
+                return Shape(a_width, False)
+            if self.operator == "s":
+                return Shape(a_width, True)
         elif len(op_shapes) == 2:
         elif len(op_shapes) == 2:
-            (a_bits, a_sign), (b_bits, b_sign) = op_shapes
-            if self.op == "+" or self.op == "-":
-                bits, sign = self._bitwise_binary_shape(*op_shapes)
-                return bits + 1, sign
-            if self.op == "*":
-                return a_bits + b_bits, a_sign or b_sign
-            if self.op == "%":
-                return a_bits, a_sign
-            if self.op in ("<", "<=", "==", "!=", ">", ">=", "b"):
-                return 1, False
-            if self.op in ("&", "^", "|"):
-                return self._bitwise_binary_shape(*op_shapes)
-            if self.op == "<<":
-                if b_sign:
-                    extra = 2 ** (b_bits - 1) - 1
+            (a_width, a_signed), (b_width, b_signed) = op_shapes
+            if self.operator in ("+", "-"):
+                width, signed = _bitwise_binary_shape(*op_shapes)
+                return Shape(width + 1, signed)
+            if self.operator == "*":
+                return Shape(a_width + b_width, a_signed or b_signed)
+            if self.operator in ("//", "%"):
+                assert not b_signed
+                return Shape(a_width, a_signed)
+            if self.operator in ("<", "<=", "==", "!=", ">", ">="):
+                return Shape(1, False)
+            if self.operator in ("&", "^", "|"):
+                return _bitwise_binary_shape(*op_shapes)
+            if self.operator == "<<":
+                if b_signed:
+                    extra = 2 ** (b_width - 1) - 1
                 else:
                 else:
-                    extra = 2 ** (b_bits)     - 1
-                return a_bits + extra, a_sign
-            if self.op == ">>":
-                if b_sign:
-                    extra = 2 ** (b_bits - 1)
+                    extra = 2 ** (b_width)     - 1
+                return Shape(a_width + extra, a_signed)
+            if self.operator == ">>":
+                if b_signed:
+                    extra = 2 ** (b_width - 1)
                 else:
                     extra = 0
                 else:
                     extra = 0
-                return a_bits + extra, a_sign
+                return Shape(a_width + extra, a_signed)
         elif len(op_shapes) == 3:
         elif len(op_shapes) == 3:
-            if self.op == "m":
+            if self.operator == "m":
                 s_shape, a_shape, b_shape = op_shapes
                 s_shape, a_shape, b_shape = op_shapes
-                return self._bitwise_binary_shape(a_shape, b_shape)
+                return _bitwise_binary_shape(a_shape, b_shape)
         raise NotImplementedError("Operator {}/{} not implemented"
         raise NotImplementedError("Operator {}/{} not implemented"
-                                  .format(self.op, len(op_shapes))) # :nocov:
+                                  .format(self.operator, len(op_shapes))) # :nocov:
 
     def _rhs_signals(self):
         return union(op._rhs_signals() for op in self.operands)
 
     def __repr__(self):
 
     def _rhs_signals(self):
         return union(op._rhs_signals() for op in self.operands)
 
     def __repr__(self):
-        return "({} {})".format(self.op, " ".join(map(repr, self.operands)))
+        return "({} {})".format(self.operator, " ".join(map(repr, self.operands)))
 
 
 def Mux(sel, val1, val0):
 
 
 def Mux(sel, val1, val0):
@@ -390,36 +743,39 @@ def Mux(sel, val1, val0):
     Value, out
         Output ``Value``. If ``sel`` is asserted, the Mux returns ``val1``, else ``val0``.
     """
     Value, out
         Output ``Value``. If ``sel`` is asserted, the Mux returns ``val1``, else ``val0``.
     """
+    sel = Value.cast(sel)
+    if len(sel) != 1:
+        sel = sel.bool()
     return Operator("m", [sel, val1, val0])
 
 
 @final
 class Slice(Value):
     return Operator("m", [sel, val1, val0])
 
 
 @final
 class Slice(Value):
-    def __init__(self, value, start, end):
+    def __init__(self, value, start, stop, *, src_loc_at=0):
         if not isinstance(start, int):
         if not isinstance(start, int):
-            raise TypeError("Slice start must be an integer, not '{!r}'".format(start))
-        if not isinstance(end, int):
-            raise TypeError("Slice end must be an integer, not '{!r}'".format(end))
+            raise TypeError("Slice start must be an integer, not {!r}".format(start))
+        if not isinstance(stop, int):
+            raise TypeError("Slice stop must be an integer, not {!r}".format(stop))
 
         n = len(value)
         if start not in range(-(n+1), n+1):
             raise IndexError("Cannot start slice {} bits into {}-bit value".format(start, n))
         if start < 0:
             start += n
 
         n = len(value)
         if start not in range(-(n+1), n+1):
             raise IndexError("Cannot start slice {} bits into {}-bit value".format(start, n))
         if start < 0:
             start += n
-        if end not in range(-(n+1), n+1):
-            raise IndexError("Cannot end slice {} bits into {}-bit value".format(end, n))
-        if end < 0:
-            end += n
-        if start > end:
-            raise IndexError("Slice start {} must be less than slice end {}".format(start, end))
+        if stop not in range(-(n+1), n+1):
+            raise IndexError("Cannot stop slice {} bits into {}-bit value".format(stop, n))
+        if stop < 0:
+            stop += n
+        if start > stop:
+            raise IndexError("Slice start {} must be less than slice stop {}".format(start, stop))
 
 
-        super().__init__()
-        self.value = Value.wrap(value)
+        super().__init__(src_loc_at=src_loc_at)
+        self.value = Value.cast(value)
         self.start = start
         self.start = start
-        self.end   = end
+        self.stop  = stop
 
     def shape(self):
 
     def shape(self):
-        return self.end - self.start, False
+        return Shape(self.stop - self.start)
 
     def _lhs_signals(self):
         return self.value._lhs_signals()
 
     def _lhs_signals(self):
         return self.value._lhs_signals()
@@ -428,22 +784,25 @@ class Slice(Value):
         return self.value._rhs_signals()
 
     def __repr__(self):
         return self.value._rhs_signals()
 
     def __repr__(self):
-        return "(slice {} {}:{})".format(repr(self.value), self.start, self.end)
+        return "(slice {} {}:{})".format(repr(self.value), self.start, self.stop)
 
 
 @final
 class Part(Value):
 
 
 @final
 class Part(Value):
-    def __init__(self, value, offset, width):
+    def __init__(self, value, offset, width, stride=1, *, src_loc_at=0):
         if not isinstance(width, int) or width < 0:
         if not isinstance(width, int) or width < 0:
-            raise TypeError("Part width must be a non-negative integer, not '{!r}'".format(width))
+            raise TypeError("Part width must be a non-negative integer, not {!r}".format(width))
+        if not isinstance(stride, int) or stride <= 0:
+            raise TypeError("Part stride must be a positive integer, not {!r}".format(stride))
 
 
-        super().__init__()
+        super().__init__(src_loc_at=src_loc_at)
         self.value  = value
         self.value  = value
-        self.offset = Value.wrap(offset)
+        self.offset = Value.cast(offset)
         self.width  = width
         self.width  = width
+        self.stride = stride
 
     def shape(self):
 
     def shape(self):
-        return self.width, False
+        return Shape(self.width)
 
     def _lhs_signals(self):
         return self.value._lhs_signals()
 
     def _lhs_signals(self):
         return self.value._lhs_signals()
@@ -452,7 +811,8 @@ class Part(Value):
         return self.value._rhs_signals() | self.offset._rhs_signals()
 
     def __repr__(self):
         return self.value._rhs_signals() | self.offset._rhs_signals()
 
     def __repr__(self):
-        return "(part {} {} {})".format(repr(self.value), repr(self.offset), self.width)
+        return "(part {} {} {} {})".format(repr(self.value), repr(self.offset),
+                                           self.width, self.stride)
 
 
 @final
 
 
 @final
@@ -480,18 +840,18 @@ class Cat(Value):
     Value, inout
         Resulting ``Value`` obtained by concatentation.
     """
     Value, inout
         Resulting ``Value`` obtained by concatentation.
     """
-    def __init__(self, *args):
-        super().__init__()
-        self.parts = [Value.wrap(v) for v in flatten(args)]
+    def __init__(self, *args, src_loc_at=0):
+        super().__init__(src_loc_at=src_loc_at)
+        self.parts = [Value.cast(v) for v in flatten(args)]
 
     def shape(self):
 
     def shape(self):
-        return sum(len(part) for part in self.parts), False
+        return Shape(sum(len(part) for part in self.parts))
 
     def _lhs_signals(self):
 
     def _lhs_signals(self):
-        return union((part._lhs_signals() for part in self.parts), start=ValueSet())
+        return union((part._lhs_signals() for part in self.parts), start=SignalSet())
 
     def _rhs_signals(self):
 
     def _rhs_signals(self):
-        return union((part._rhs_signals() for part in self.parts), start=ValueSet())
+        return union((part._rhs_signals() for part in self.parts), start=SignalSet())
 
     def _as_const(self):
         value = 0
 
     def _as_const(self):
         value = 0
@@ -525,17 +885,17 @@ class Repl(Value):
     Repl, out
         Replicated value.
     """
     Repl, out
         Replicated value.
     """
-    def __init__(self, value, count):
+    def __init__(self, value, count, *, src_loc_at=0):
         if not isinstance(count, int) or count < 0:
         if not isinstance(count, int) or count < 0:
-            raise TypeError("Replication count must be a non-negative integer, not '{!r}'"
+            raise TypeError("Replication count must be a non-negative integer, not {!r}"
                             .format(count))
 
                             .format(count))
 
-        super().__init__()
-        self.value = Value.wrap(value)
+        super().__init__(src_loc_at=src_loc_at)
+        self.value = Value.cast(value)
         self.count = count
 
     def shape(self):
         self.count = count
 
     def shape(self):
-        return len(self.value) * self.count, False
+        return Shape(len(self.value) * self.count)
 
     def _rhs_signals(self):
         return self.value._rhs_signals()
 
     def _rhs_signals(self):
         return self.value._rhs_signals()
@@ -544,22 +904,20 @@ class Repl(Value):
         return "(repl {!r} {})".format(self.value, self.count)
 
 
         return "(repl {!r} {})".format(self.value, self.count)
 
 
-@final
+@final
 class Signal(Value, DUID):
     """A varying integer value.
 
     Parameters
     ----------
 class Signal(Value, DUID):
     """A varying integer value.
 
     Parameters
     ----------
-    shape : int or tuple or None
-        Either an integer ``bits`` or a tuple ``(bits, signed)`` specifying the number of bits
-        in this ``Signal`` and whether it is signed (can represent negative values).
-        ``shape`` defaults to 1-bit and non-signed.
+    shape : ``Shape``-castable object or None
+        Specification for the number of bits in this ``Signal`` and its signedness (whether it
+        can represent negative values). See ``Shape.cast`` for details.
+        If not specified, ``shape`` defaults to 1-bit and non-signed.
     name : str
         Name hint for this signal. If ``None`` (default) the name is inferred from the variable
     name : str
         Name hint for this signal. If ``None`` (default) the name is inferred from the variable
-        name this ``Signal`` is assigned to. Name collisions are automatically resolved by
-        prepending names of objects that contain this ``Signal`` and by appending integer
-        sequences.
-    reset : int
+        name this ``Signal`` is assigned to.
+    reset : int or integral Enum
         Reset (synchronous) or default (combinatorial) value.
         When this ``Signal`` is assigned to in synchronous context and the corresponding clock
         domain is reset, the ``Signal`` assumes the given value. When this ``Signal`` is unassigned
         Reset (synchronous) or default (combinatorial) value.
         When this ``Signal`` is assigned to in synchronous context and the corresponding clock
         domain is reset, the ``Signal`` assumes the given value. When this ``Signal`` is unassigned
@@ -569,11 +927,6 @@ class Signal(Value, DUID):
         If ``True``, do not generate reset logic for this ``Signal`` in synchronous statements.
         The ``reset`` value is only used as a combinatorial default or as the initial value.
         Defaults to ``False``.
         If ``True``, do not generate reset logic for this ``Signal`` in synchronous statements.
         The ``reset`` value is only used as a combinatorial default or as the initial value.
         Defaults to ``False``.
-    min : int or None
-    max : int or None
-        If ``shape`` is ``None``, the signal bit width and signedness are
-        determined by the integer range given by ``min`` (inclusive,
-        defaults to 0) and ``max`` (exclusive, defaults to 2).
     attrs : dict
         Dictionary of synthesis attributes.
     decoder : function or Enum
     attrs : dict
         Dictionary of synthesis attributes.
     decoder : function or Enum
@@ -584,52 +937,46 @@ class Signal(Value, DUID):
 
     Attributes
     ----------
 
     Attributes
     ----------
-    nbits : int
+    width : int
     signed : bool
     name : str
     reset : int
     reset_less : bool
     attrs : dict
     signed : bool
     name : str
     reset : int
     reset_less : bool
     attrs : dict
+    decoder : function
     """
 
     """
 
-    def __init__(self, shape=None, name=None, reset=0, reset_less=False, min=None, max=None,
+    def __init__(self, shape=None, *, name=None, reset=0, reset_less=False,
                  attrs=None, decoder=None, src_loc_at=0):
         super().__init__(src_loc_at=src_loc_at)
 
         if name is not None and not isinstance(name, str):
                  attrs=None, decoder=None, src_loc_at=0):
         super().__init__(src_loc_at=src_loc_at)
 
         if name is not None and not isinstance(name, str):
-            raise TypeError("Name must be a string, not '{!r}'".format(name))
+            raise TypeError("Name must be a string, not {!r}".format(name))
         self.name = name or tracer.get_var_name(depth=2 + src_loc_at, default="$signal")
 
         if shape is None:
         self.name = name or tracer.get_var_name(depth=2 + src_loc_at, default="$signal")
 
         if shape is None:
-            if min is None:
-                min = 0
-            if max is None:
-                max = 2
-            max -= 1  # make both bounds inclusive
-            if min > max:
-                raise ValueError("Lower bound {} should be less or equal to higher bound {}"
-                                 .format(min, max + 1))
-            self.signed = min < 0 or max < 0
-            if min == max:
-                self.nbits = 0
-            else:
-                self.nbits = builtins.max(bits_for(min, self.signed),
-                                          bits_for(max, self.signed))
-
-        else:
-            if not (min is None and max is None):
-                raise ValueError("Only one of bits/signedness or bounds may be specified")
-            if isinstance(shape, int):
-                self.nbits, self.signed = shape, False
-            else:
-                self.nbits, self.signed = shape
-
-        if not isinstance(self.nbits, int) or self.nbits < 0:
-            raise TypeError("Width must be a non-negative integer, not '{!r}'".format(self.nbits))
-        self.reset = int(reset)
+            shape = unsigned(1)
+        self.width, self.signed = Shape.cast(shape, src_loc_at=1 + src_loc_at)
+
+        if isinstance(reset, Enum):
+            reset = reset.value
+        if not isinstance(reset, int):
+            raise TypeError("Reset value has to be an int or an integral Enum")
+
+        reset_width = bits_for(reset, self.signed)
+        if reset != 0 and reset_width > self.width:
+            warnings.warn("Reset value {!r} requires {} bits to represent, but the signal "
+                          "only has {} bits"
+                          .format(reset, reset_width, self.width),
+                          SyntaxWarning, stacklevel=2 + src_loc_at)
+
+        self.reset = reset
         self.reset_less = bool(reset_less)
 
         self.attrs = OrderedDict(() if attrs is None else attrs)
         self.reset_less = bool(reset_less)
 
         self.attrs = OrderedDict(() if attrs is None else attrs)
+
+        if decoder is None and isinstance(shape, type) and issubclass(shape, Enum):
+            decoder = shape
         if isinstance(decoder, type) and issubclass(decoder, Enum):
             def enum_decoder(value):
                 try:
         if isinstance(decoder, type) and issubclass(decoder, Enum):
             def enum_decoder(value):
                 try:
@@ -637,11 +984,14 @@ class Signal(Value, DUID):
                 except ValueError:
                     return str(value)
             self.decoder = enum_decoder
                 except ValueError:
                     return str(value)
             self.decoder = enum_decoder
+            self._enum_class = decoder
         else:
             self.decoder = decoder
         else:
             self.decoder = decoder
+            self._enum_class = None
 
 
-    @classmethod
-    def like(cls, other, name=None, name_suffix=None, src_loc_at=0, **kwargs):
+    # Not a @classmethod because nmigen.compat requires it.
+    @staticmethod
+    def like(other, *, name=None, name_suffix=None, src_loc_at=0, **kwargs):
         """Create Signal based on another.
 
         Parameters
         """Create Signal based on another.
 
         Parameters
@@ -655,21 +1005,21 @@ class Signal(Value, DUID):
             new_name = other.name + str(name_suffix)
         else:
             new_name = tracer.get_var_name(depth=2 + src_loc_at, default="$like")
             new_name = other.name + str(name_suffix)
         else:
             new_name = tracer.get_var_name(depth=2 + src_loc_at, default="$like")
-        kw = dict(shape=cls.wrap(other).shape(), name=new_name)
-        if isinstance(other, cls):
+        kw = dict(shape=Value.cast(other).shape(), name=new_name)
+        if isinstance(other, Signal):
             kw.update(reset=other.reset, reset_less=other.reset_less,
                       attrs=other.attrs, decoder=other.decoder)
         kw.update(kwargs)
             kw.update(reset=other.reset, reset_less=other.reset_less,
                       attrs=other.attrs, decoder=other.decoder)
         kw.update(kwargs)
-        return cls(**kw, src_loc_at=1 + src_loc_at)
+        return Signal(**kw, src_loc_at=1 + src_loc_at)
 
     def shape(self):
 
     def shape(self):
-        return self.nbits, self.signed
+        return Shape(self.width, self.signed)
 
     def _lhs_signals(self):
 
     def _lhs_signals(self):
-        return ValueSet((self,))
+        return SignalSet((self,))
 
     def _rhs_signals(self):
 
     def _rhs_signals(self):
-        return ValueSet((self,))
+        return SignalSet((self,))
 
     def __repr__(self):
         return "(sig {})".format(self.name)
 
     def __repr__(self):
         return "(sig {})".format(self.name)
@@ -688,17 +1038,19 @@ class ClockSignal(Value):
     domain : str
         Clock domain to obtain a clock signal for. Defaults to ``"sync"``.
     """
     domain : str
         Clock domain to obtain a clock signal for. Defaults to ``"sync"``.
     """
-    def __init__(self, domain="sync"):
-        super().__init__()
+    def __init__(self, domain="sync", *, src_loc_at=0):
+        super().__init__(src_loc_at=src_loc_at)
         if not isinstance(domain, str):
         if not isinstance(domain, str):
-            raise TypeError("Clock domain name must be a string, not '{!r}'".format(domain))
+            raise TypeError("Clock domain name must be a string, not {!r}".format(domain))
+        if domain == "comb":
+            raise ValueError("Domain '{}' does not have a clock".format(domain))
         self.domain = domain
 
     def shape(self):
         self.domain = domain
 
     def shape(self):
-        return 1, False
+        return Shape(1)
 
     def _lhs_signals(self):
 
     def _lhs_signals(self):
-        return ValueSet((self,))
+        return SignalSet((self,))
 
     def _rhs_signals(self):
         raise NotImplementedError("ClockSignal must be lowered to a concrete signal") # :nocov:
 
     def _rhs_signals(self):
         raise NotImplementedError("ClockSignal must be lowered to a concrete signal") # :nocov:
@@ -722,18 +1074,20 @@ class ResetSignal(Value):
     allow_reset_less : bool
         If the clock domain is reset-less, act as a constant ``0`` instead of reporting an error.
     """
     allow_reset_less : bool
         If the clock domain is reset-less, act as a constant ``0`` instead of reporting an error.
     """
-    def __init__(self, domain="sync", allow_reset_less=False):
-        super().__init__()
+    def __init__(self, domain="sync", allow_reset_less=False, *, src_loc_at=0):
+        super().__init__(src_loc_at=src_loc_at)
         if not isinstance(domain, str):
         if not isinstance(domain, str):
-            raise TypeError("Clock domain name must be a string, not '{!r}'".format(domain))
+            raise TypeError("Clock domain name must be a string, not {!r}".format(domain))
+        if domain == "comb":
+            raise ValueError("Domain '{}' does not have a reset".format(domain))
         self.domain = domain
         self.allow_reset_less = allow_reset_less
 
     def shape(self):
         self.domain = domain
         self.allow_reset_less = allow_reset_less
 
     def shape(self):
-        return 1, False
+        return Shape(1)
 
     def _lhs_signals(self):
 
     def _lhs_signals(self):
-        return ValueSet((self,))
+        return SignalSet((self,))
 
     def _rhs_signals(self):
         raise NotImplementedError("ResetSignal must be lowered to a concrete signal") # :nocov:
 
     def _rhs_signals(self):
         raise NotImplementedError("ResetSignal must be lowered to a concrete signal") # :nocov:
@@ -766,29 +1120,29 @@ class Array(MutableSequence):
 
         gpios = Array(Signal() for _ in range(10))
         with m.If(bus.we):
 
         gpios = Array(Signal() for _ in range(10))
         with m.If(bus.we):
-            m.d.sync += gpios[bus.adr].eq(bus.dat_w)
+            m.d.sync += gpios[bus.addr].eq(bus.w_data)
         with m.Else():
         with m.Else():
-            m.d.sync += bus.dat_r.eq(gpios[bus.adr])
+            m.d.sync += bus.r_data.eq(gpios[bus.addr])
 
     Multidimensional array::
 
         mult = Array(Array(x * y for y in range(10)) for x in range(10))
 
     Multidimensional array::
 
         mult = Array(Array(x * y for y in range(10)) for x in range(10))
-        a = Signal(max=10)
-        b = Signal(max=10)
+        a = Signal.range(10)
+        b = Signal.range(10)
         r = Signal(8)
         m.d.comb += r.eq(mult[a][b])
 
     Array of records::
 
         layout = [
         r = Signal(8)
         m.d.comb += r.eq(mult[a][b])
 
     Array of records::
 
         layout = [
-            ("re",     1),
-            ("dat_r", 16),
+            ("r_data", 16),
+            ("r_en",   1),
         ]
         buses  = Array(Record(layout) for busno in range(4))
         master = Record(layout)
         m.d.comb += [
         ]
         buses  = Array(Record(layout) for busno in range(4))
         master = Record(layout)
         m.d.comb += [
-            buses[sel].re.eq(master.re),
-            master.dat_r.eq(buses[sel].dat_r),
+            buses[sel].r_en.eq(master.r_en),
+            master.r_data.eq(buses[sel].r_data),
         ]
     """
     def __init__(self, iterable=()):
         ]
     """
     def __init__(self, iterable=()):
@@ -832,10 +1186,10 @@ class Array(MutableSequence):
 
 @final
 class ArrayProxy(Value):
 
 @final
 class ArrayProxy(Value):
-    def __init__(self, elems, index):
-        super().__init__(src_loc_at=1)
+    def __init__(self, elems, index, *, src_loc_at=0):
+        super().__init__(src_loc_at=1 + src_loc_at)
         self.elems = elems
         self.elems = elems
-        self.index = Value.wrap(index)
+        self.index = Value.cast(index)
 
     def __getattr__(self, attr):
         return ArrayProxy([getattr(elem, attr) for elem in self.elems], self.index)
 
     def __getattr__(self, attr):
         return ArrayProxy([getattr(elem, attr) for elem in self.elems], self.index)
@@ -844,21 +1198,39 @@ class ArrayProxy(Value):
         return ArrayProxy([        elem[index] for elem in self.elems], self.index)
 
     def _iter_as_values(self):
         return ArrayProxy([        elem[index] for elem in self.elems], self.index)
 
     def _iter_as_values(self):
-        return (Value.wrap(elem) for elem in self.elems)
+        return (Value.cast(elem) for elem in self.elems)
 
     def shape(self):
 
     def shape(self):
-        bits, sign = 0, False
-        for elem_bits, elem_sign in (elem.shape() for elem in self._iter_as_values()):
-            bits = max(bits, elem_bits + elem_sign)
-            sign = max(sign, elem_sign)
-        return bits, sign
+        unsigned_width = signed_width = 0
+        has_unsigned = has_signed = False
+        for elem_width, elem_signed in (elem.shape() for elem in self._iter_as_values()):
+            if elem_signed:
+                has_signed = True
+                signed_width = max(signed_width, elem_width)
+            else:
+                has_unsigned = True
+                unsigned_width = max(unsigned_width, elem_width)
+        # The shape of the proxy must be such that it preserves the mathematical value of the array
+        # elements. I.e., shape-wise, an array proxy must be identical to an equivalent mux tree.
+        # To ensure this holds, if the array contains both signed and unsigned values, make sure
+        # that every unsigned value is zero-extended by at least one bit.
+        if has_signed and has_unsigned and unsigned_width >= signed_width:
+            # Array contains both signed and unsigned values, and at least one of the unsigned
+            # values won't be zero-extended otherwise.
+            return signed(unsigned_width + 1)
+        else:
+            # Array contains values of the same signedness, or else all of the unsigned values
+            # are zero-extended.
+            return Shape(max(unsigned_width, signed_width), has_signed)
 
     def _lhs_signals(self):
 
     def _lhs_signals(self):
-        signals = union((elem._lhs_signals() for elem in self._iter_as_values()), start=ValueSet())
+        signals = union((elem._lhs_signals() for elem in self._iter_as_values()),
+                        start=SignalSet())
         return signals
 
     def _rhs_signals(self):
         return signals
 
     def _rhs_signals(self):
-        signals = union((elem._rhs_signals() for elem in self._iter_as_values()), start=ValueSet())
+        signals = union((elem._rhs_signals() for elem in self._iter_as_values()),
+                        start=SignalSet())
         return self.index._rhs_signals() | signals
 
     def __repr__(self):
         return self.index._rhs_signals() | signals
 
     def __repr__(self):
@@ -885,7 +1257,7 @@ class UserValue(Value):
         * Indexing or iterating through individual bits;
         * Adding an assignment to the value to a ``Module`` using ``m.d.<domain> +=``.
     """
         * Indexing or iterating through individual bits;
         * Adding an assignment to the value to a ``Module`` using ``m.d.<domain> +=``.
     """
-    def __init__(self, src_loc_at=1):
+    def __init__(self, *, src_loc_at=0):
         super().__init__(src_loc_at=1 + src_loc_at)
         self.__lowered = None
 
         super().__init__(src_loc_at=1 + src_loc_at)
         self.__lowered = None
 
@@ -896,7 +1268,10 @@ class UserValue(Value):
 
     def _lazy_lower(self):
         if self.__lowered is None:
 
     def _lazy_lower(self):
         if self.__lowered is None:
-            self.__lowered = Value.wrap(self.lower())
+            lowered = self.lower()
+            if isinstance(lowered, UserValue):
+                lowered = lowered._lazy_lower()
+            self.__lowered = Value.cast(lowered)
         return self.__lowered
 
     def shape(self):
         return self.__lowered
 
     def shape(self):
@@ -909,6 +1284,51 @@ class UserValue(Value):
         return self._lazy_lower()._rhs_signals()
 
 
         return self._lazy_lower()._rhs_signals()
 
 
+class ValueCastable:
+    """Base class for classes which can be cast to Values.
+
+    A ``ValueCastable`` can be cast to ``Value``, meaning its precise representation does not have
+    to be immediately known. This is useful in certain metaprogramming scenarios. Instead of
+    providing fixed semantics upfront, it is kept abstract for as long as possible, only being
+    cast to a concrete nMigen value when required.
+
+    Note that it is necessary to ensure that nMigen's view of representation of all values stays 
+    internally consistent. The class deriving from ``ValueCastable`` must decorate the ``as_value``
+    method with the ``lowermethod`` decorator, which ensures that all calls to ``as_value``return the
+    same ``Value`` representation. If the class deriving from ``ValueCastable`` is mutable, it is
+    up to the user to ensure that it is not mutated in a way that changes its representation after
+    the first call to ``as_value``.
+    """
+    def __new__(cls, *args, **kwargs):
+        self = super().__new__(cls)
+        if not hasattr(self, "as_value"):
+            raise TypeError(f"Class '{cls.__name__}' deriving from `ValueCastable` must override the `as_value` method")
+
+        if not hasattr(self.as_value, "_ValueCastable__memoized"):
+            raise TypeError(f"Class '{cls.__name__}' deriving from `ValueCastable` must decorate the `as_value` "
+                            "method with the `ValueCastable.lowermethod` decorator")
+        return self
+
+    @staticmethod
+    def lowermethod(func):
+        """Decorator to memoize lowering methods.
+
+        Ensures the decorated method is called only once, with subsequent method calls returning the
+        object returned by the first first method call.
+
+        This decorator is required to decorate the ``as_value`` method of ``ValueCastable`` subclasses.
+        This is to ensure that nMigen's view of representation of all values stays internally
+        consistent.
+        """
+        @functools.wraps(func)
+        def wrapper_memoized(self, *args, **kwargs):
+            if not hasattr(self, "_ValueCastable__lowered_to"):
+                self.__lowered_to = func(self, *args, **kwargs)
+            return self.__lowered_to
+        wrapper_memoized.__memoized = True
+        return wrapper_memoized
+
+
 @final
 class Sample(Value):
     """Value from the past.
 @final
 class Sample(Value):
     """Value from the past.
@@ -917,23 +1337,26 @@ class Sample(Value):
     of the ``domain`` clock back. If that moment is before the beginning of time, it is equal
     to the value of the expression calculated as if each signal had its reset value.
     """
     of the ``domain`` clock back. If that moment is before the beginning of time, it is equal
     to the value of the expression calculated as if each signal had its reset value.
     """
-    def __init__(self, expr, clocks, domain):
-        super().__init__(src_loc_at=1)
-        self.value  = Value.wrap(expr)
+    def __init__(self, expr, clocks, domain, *, src_loc_at=0):
+        super().__init__(src_loc_at=1 + src_loc_at)
+        self.value  = Value.cast(expr)
         self.clocks = int(clocks)
         self.domain = domain
         self.clocks = int(clocks)
         self.domain = domain
-        if not isinstance(self.value, (Const, Signal, ClockSignal, ResetSignal)):
-            raise TypeError("Sampled value may only be a signal or a constant, not {!r}"
+        if not isinstance(self.value, (Const, Signal, ClockSignal, ResetSignal, Initial)):
+            raise TypeError("Sampled value must be a signal or a constant, not {!r}"
                             .format(self.value))
         if self.clocks < 0:
             raise ValueError("Cannot sample a value {} cycles in the future"
                              .format(-self.clocks))
                             .format(self.value))
         if self.clocks < 0:
             raise ValueError("Cannot sample a value {} cycles in the future"
                              .format(-self.clocks))
+        if not (self.domain is None or isinstance(self.domain, str)):
+            raise TypeError("Domain name must be a string or None, not {!r}"
+                            .format(self.domain))
 
     def shape(self):
         return self.value.shape()
 
     def _rhs_signals(self):
 
     def shape(self):
         return self.value.shape()
 
     def _rhs_signals(self):
-        return ValueSet((self,))
+        return SignalSet((self,))
 
     def __repr__(self):
         return "(sample {!r} @ {}[{}])".format(
 
     def __repr__(self):
         return "(sample {!r} @ {}[{}])".format(
@@ -956,30 +1379,51 @@ def Fell(expr, clocks=0, domain=None):
     return Sample(expr, clocks + 1, domain) & ~Sample(expr, clocks, domain)
 
 
     return Sample(expr, clocks + 1, domain) & ~Sample(expr, clocks, domain)
 
 
+@final
+class Initial(Value):
+    """Start indicator, for model checking.
+
+    An ``Initial`` signal is ``1`` at the first cycle of model checking, and ``0`` at any other.
+    """
+    def __init__(self, *, src_loc_at=0):
+        super().__init__(src_loc_at=src_loc_at)
+
+    def shape(self):
+        return Shape(1)
+
+    def _rhs_signals(self):
+        return SignalSet((self,))
+
+    def __repr__(self):
+        return "(initial)"
+
+
 class _StatementList(list):
     def __repr__(self):
         return "({})".format(" ".join(map(repr, self)))
 
 
 class Statement:
 class _StatementList(list):
     def __repr__(self):
         return "({})".format(" ".join(map(repr, self)))
 
 
 class Statement:
+    def __init__(self, *, src_loc_at=0):
+        self.src_loc = tracer.get_src_loc(1 + src_loc_at)
+
     @staticmethod
     @staticmethod
-    def wrap(obj):
+    def cast(obj):
         if isinstance(obj, Iterable):
         if isinstance(obj, Iterable):
-            return _StatementList(sum((Statement.wrap(e) for e in obj), []))
+            return _StatementList(sum((Statement.cast(e) for e in obj), []))
         else:
             if isinstance(obj, Statement):
                 return _StatementList([obj])
             else:
         else:
             if isinstance(obj, Statement):
                 return _StatementList([obj])
             else:
-                raise TypeError("Object '{!r}' is not an nMigen statement".format(obj))
+                raise TypeError("Object {!r} is not an nMigen statement".format(obj))
 
 
 @final
 class Assign(Statement):
 
 
 @final
 class Assign(Statement):
-    def __init__(self, lhs, rhs, src_loc_at=0):
-        self.src_loc = tracer.get_src_loc(src_loc_at)
-
-        self.lhs = Value.wrap(lhs)
-        self.rhs = Value.wrap(rhs)
+    def __init__(self, lhs, rhs, *, src_loc_at=0):
+        super().__init__(src_loc_at=src_loc_at)
+        self.lhs = Value.cast(lhs)
+        self.rhs = Value.cast(rhs)
 
     def _lhs_signals(self):
         return self.lhs._lhs_signals()
 
     def _lhs_signals(self):
         return self.lhs._lhs_signals()
@@ -991,24 +1435,27 @@ class Assign(Statement):
         return "(eq {!r} {!r})".format(self.lhs, self.rhs)
 
 
         return "(eq {!r} {!r})".format(self.lhs, self.rhs)
 
 
-class Property(Statement):
-    def __init__(self, test, _check=None, _en=None):
-        self.src_loc = tracer.get_src_loc()
+class UnusedProperty(UnusedMustUse):
+    pass
+
 
 
-        self.test = Value.wrap(test)
+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)
         self._check = _check
         self._check = _check
+        self._en    = _en
         if self._check is None:
             self._check = Signal(reset_less=True, name="${}$check".format(self._kind))
             self._check.src_loc = self.src_loc
         if self._check is None:
             self._check = Signal(reset_less=True, name="${}$check".format(self._kind))
             self._check.src_loc = self.src_loc
-
-        self._en = _en
         if _en is None:
             self._en = Signal(reset_less=True, name="${}$en".format(self._kind))
             self._en.src_loc = self.src_loc
 
     def _lhs_signals(self):
         if _en is None:
             self._en = Signal(reset_less=True, name="${}$en".format(self._kind))
             self._en.src_loc = self.src_loc
 
     def _lhs_signals(self):
-        return ValueSet((self._en, self._check))
+        return SignalSet((self._en, self._check))
 
     def _rhs_signals(self):
         return self.test._rhs_signals()
 
     def _rhs_signals(self):
         return self.test._rhs_signals()
@@ -1027,15 +1474,29 @@ class Assume(Property):
     _kind = "assume"
 
 
     _kind = "assume"
 
 
+@final
+class Cover(Property):
+    _kind = "cover"
+
+
 # @final
 class Switch(Statement):
 # @final
 class Switch(Statement):
-    def __init__(self, test, cases, src_loc_at=0):
-        self.src_loc = tracer.get_src_loc(src_loc_at)
-
-        self.test  = Value.wrap(test)
+    def __init__(self, test, cases, *, src_loc=None, src_loc_at=0, case_src_locs={}):
+        if src_loc is None:
+            super().__init__(src_loc_at=src_loc_at)
+        else:
+            # Switch is a bit special in terms of location tracking because it is usually created
+            # long after the control has left the statement that directly caused its creation.
+            self.src_loc = src_loc
+        # Switch is also a bit special in that its parts also have location information. It can't
+        # be automatically traced, so whatever constructs a Switch may optionally provide it.
+        self.case_src_locs = {}
+
+        self.test  = Value.cast(test)
         self.cases = OrderedDict()
         self.cases = OrderedDict()
-        for keys, stmts in cases.items():
+        for orig_keys, stmts in cases.items():
             # Map: None -> (); key -> (key,); (key...) -> (key...)
             # Map: None -> (); key -> (key,); (key...) -> (key...)
+            keys = orig_keys
             if keys is None:
                 keys = ()
             if not isinstance(keys, tuple):
             if keys is None:
                 keys = ()
             if not isinstance(keys, tuple):
@@ -1043,27 +1504,31 @@ class Switch(Statement):
             # Map: 2 -> "0010"; "0010" -> "0010"
             new_keys = ()
             for key in keys:
             # Map: 2 -> "0010"; "0010" -> "0010"
             new_keys = ()
             for key in keys:
-                if isinstance(key, (bool, int)):
-                    key = "{:0{}b}".format(key, len(self.test))
-                elif isinstance(key, str):
-                    pass
+                if isinstance(key, str):
+                    key = "".join(key.split()) # remove whitespace
+                elif isinstance(key, int):
+                    key = format(key, "b").rjust(len(self.test), "0")
+                elif isinstance(key, Enum):
+                    key = format(key.value, "b").rjust(len(self.test), "0")
                 else:
                 else:
-                    raise TypeError("Object '{!r}' cannot be used as a switch key"
+                    raise TypeError("Object {!r} cannot be used as a switch key"
                                     .format(key))
                 assert len(key) == len(self.test)
                 new_keys = (*new_keys, key)
             if not isinstance(stmts, Iterable):
                 stmts = [stmts]
                                     .format(key))
                 assert len(key) == len(self.test)
                 new_keys = (*new_keys, key)
             if not isinstance(stmts, Iterable):
                 stmts = [stmts]
-            self.cases[new_keys] = Statement.wrap(stmts)
+            self.cases[new_keys] = Statement.cast(stmts)
+            if orig_keys in case_src_locs:
+                self.case_src_locs[new_keys] = case_src_locs[orig_keys]
 
     def _lhs_signals(self):
         signals = union((s._lhs_signals() for ss in self.cases.values() for s in ss),
 
     def _lhs_signals(self):
         signals = union((s._lhs_signals() for ss in self.cases.values() for s in ss),
-                        start=ValueSet())
+                        start=SignalSet())
         return signals
 
     def _rhs_signals(self):
         signals = union((s._rhs_signals() for ss in self.cases.values() for s in ss),
         return signals
 
     def _rhs_signals(self):
         signals = union((s._rhs_signals() for ss in self.cases.values() for s in ss),
-                        start=ValueSet())
+                        start=SignalSet())
         return self.test._rhs_signals() | signals
 
     def __repr__(self):
         return self.test._rhs_signals() | signals
 
     def __repr__(self):
@@ -1079,42 +1544,6 @@ class Switch(Statement):
         return "(switch {!r} {})".format(self.test, " ".join(case_reprs))
 
 
         return "(switch {!r} {})".format(self.test, " ".join(case_reprs))
 
 
-@final
-class Delay(Statement):
-    def __init__(self, interval=None):
-        self.interval = None if interval is None else float(interval)
-
-    def _rhs_signals(self):
-        return ValueSet()
-
-    def __repr__(self):
-        if self.interval is None:
-            return "(delay ε)"
-        else:
-            return "(delay {:.3}us)".format(self.interval * 1e6)
-
-
-@final
-class Tick(Statement):
-    def __init__(self, domain="sync"):
-        self.domain = str(domain)
-
-    def _rhs_signals(self):
-        return ValueSet()
-
-    def __repr__(self):
-        return "(tick {})".format(self.domain)
-
-
-@final
-class Passive(Statement):
-    def _rhs_signals(self):
-        return ValueSet()
-
-    def __repr__(self):
-        return "(passive)"
-
-
 class _MappedKeyCollection(metaclass=ABCMeta):
     @abstractmethod
     def _map_key(self, key):
 class _MappedKeyCollection(metaclass=ABCMeta):
     @abstractmethod
     def _map_key(self, key):
@@ -1205,33 +1634,37 @@ class _MappedKeySet(MutableSet, _MappedKeyCollection):
 
 class ValueKey:
     def __init__(self, value):
 
 class ValueKey:
     def __init__(self, value):
-        self.value = Value.wrap(value)
-
-    def __hash__(self):
+        self.value = Value.cast(value)
         if isinstance(self.value, Const):
         if isinstance(self.value, Const):
-            return hash(self.value.value)
+            self._hash = hash(self.value.value)
         elif isinstance(self.value, (Signal, AnyValue)):
         elif isinstance(self.value, (Signal, AnyValue)):
-            return hash(self.value.duid)
+            self._hash = hash(self.value.duid)
         elif isinstance(self.value, (ClockSignal, ResetSignal)):
         elif isinstance(self.value, (ClockSignal, ResetSignal)):
-            return hash(self.value.domain)
+            self._hash = hash(self.value.domain)
         elif isinstance(self.value, Operator):
         elif isinstance(self.value, Operator):
-            return hash((self.value.op, tuple(ValueKey(o) for o in self.value.operands)))
+            self._hash = hash((self.value.operator,
+                               tuple(ValueKey(o) for o in self.value.operands)))
         elif isinstance(self.value, Slice):
         elif isinstance(self.value, Slice):
-            return hash((ValueKey(self.value.value), self.value.start, self.value.end))
+            self._hash = hash((ValueKey(self.value.value), self.value.start, self.value.stop))
         elif isinstance(self.value, Part):
         elif isinstance(self.value, Part):
-            return hash((ValueKey(self.value.value), ValueKey(self.value.offset),
-                         self.value.width))
+            self._hash = hash((ValueKey(self.value.value), ValueKey(self.value.offset),
+                              self.value.width, self.value.stride))
         elif isinstance(self.value, Cat):
         elif isinstance(self.value, Cat):
-            return hash(tuple(ValueKey(o) for o in self.value.parts))
+            self._hash = hash(tuple(ValueKey(o) for o in self.value.parts))
         elif isinstance(self.value, ArrayProxy):
         elif isinstance(self.value, ArrayProxy):
-            return hash((ValueKey(self.value.index),
-                         tuple(ValueKey(e) for e in self.value._iter_as_values())))
+            self._hash = hash((ValueKey(self.value.index),
+                              tuple(ValueKey(e) for e in self.value._iter_as_values())))
         elif isinstance(self.value, Sample):
         elif isinstance(self.value, Sample):
-            return hash((ValueKey(self.value.value), self.value.clocks, self.value.domain))
+            self._hash = hash((ValueKey(self.value.value), self.value.clocks, self.value.domain))
+        elif isinstance(self.value, Initial):
+            self._hash = 0
         else: # :nocov:
         else: # :nocov:
-            raise TypeError("Object '{!r}' cannot be used as a key in value collections"
+            raise TypeError("Object {!r} cannot be used as a key in value collections"
                             .format(self.value))
 
                             .format(self.value))
 
+    def __hash__(self):
+        return self._hash
+
     def __eq__(self, other):
         if type(other) is not ValueKey:
             return False
     def __eq__(self, other):
         if type(other) is not ValueKey:
             return False
@@ -1245,18 +1678,19 @@ class ValueKey:
         elif isinstance(self.value, (ClockSignal, ResetSignal)):
             return self.value.domain == other.value.domain
         elif isinstance(self.value, Operator):
         elif isinstance(self.value, (ClockSignal, ResetSignal)):
             return self.value.domain == other.value.domain
         elif isinstance(self.value, Operator):
-            return (self.value.op == other.value.op and
+            return (self.value.operator == other.value.operator and
                     len(self.value.operands) == len(other.value.operands) and
                     all(ValueKey(a) == ValueKey(b)
                         for a, b in zip(self.value.operands, other.value.operands)))
         elif isinstance(self.value, Slice):
             return (ValueKey(self.value.value) == ValueKey(other.value.value) and
                     self.value.start == other.value.start and
                     len(self.value.operands) == len(other.value.operands) and
                     all(ValueKey(a) == ValueKey(b)
                         for a, b in zip(self.value.operands, other.value.operands)))
         elif isinstance(self.value, Slice):
             return (ValueKey(self.value.value) == ValueKey(other.value.value) and
                     self.value.start == other.value.start and
-                    self.value.end == other.value.end)
+                    self.value.stop == other.value.stop)
         elif isinstance(self.value, Part):
             return (ValueKey(self.value.value) == ValueKey(other.value.value) and
                     ValueKey(self.value.offset) == ValueKey(other.value.offset) and
         elif isinstance(self.value, Part):
             return (ValueKey(self.value.value) == ValueKey(other.value.value) and
                     ValueKey(self.value.offset) == ValueKey(other.value.offset) and
-                    self.value.width == other.value.width)
+                    self.value.width == other.value.width and
+                    self.value.stride == other.value.stride)
         elif isinstance(self.value, Cat):
             return all(ValueKey(a) == ValueKey(b)
                         for a, b in zip(self.value.parts, other.value.parts))
         elif isinstance(self.value, Cat):
             return all(ValueKey(a) == ValueKey(b)
                         for a, b in zip(self.value.parts, other.value.parts))
@@ -1270,8 +1704,10 @@ class ValueKey:
             return (ValueKey(self.value.value) == ValueKey(other.value.value) and
                     self.value.clocks == other.value.clocks and
                     self.value.domain == self.value.domain)
             return (ValueKey(self.value.value) == ValueKey(other.value.value) and
                     self.value.clocks == other.value.clocks and
                     self.value.domain == self.value.domain)
+        elif isinstance(self.value, Initial):
+            return True
         else: # :nocov:
         else: # :nocov:
-            raise TypeError("Object '{!r}' cannot be used as a key in value collections"
+            raise TypeError("Object {!r} cannot be used as a key in value collections"
                             .format(self.value))
 
     def __lt__(self, other):
                             .format(self.value))
 
     def __lt__(self, other):
@@ -1289,7 +1725,7 @@ class ValueKey:
                     self.value.start < other.value.start and
                     self.value.end < other.value.end)
         else: # :nocov:
                     self.value.start < other.value.start and
                     self.value.end < other.value.end)
         else: # :nocov:
-            raise TypeError("Object '{!r}' cannot be used as a key in value collections")
+            raise TypeError("Object {!r} cannot be used as a key in value collections")
 
     def __repr__(self):
         return "<{}.ValueKey {!r}>".format(__name__, self.value)
 
     def __repr__(self):
         return "<{}.ValueKey {!r}>".format(__name__, self.value)
@@ -1307,15 +1743,15 @@ class ValueSet(_MappedKeySet):
 
 class SignalKey:
     def __init__(self, signal):
 
 class SignalKey:
     def __init__(self, signal):
-        if type(signal) is Signal:
+        self.signal = signal
+        if isinstance(signal, Signal):
             self._intern = (0, signal.duid)
         elif type(signal) is ClockSignal:
             self._intern = (1, signal.domain)
         elif type(signal) is ResetSignal:
             self._intern = (2, signal.domain)
         else:
             self._intern = (0, signal.duid)
         elif type(signal) is ClockSignal:
             self._intern = (1, signal.domain)
         elif type(signal) is ResetSignal:
             self._intern = (2, signal.domain)
         else:
-            raise TypeError("Object '{!r}' is not an nMigen signal".format(signal))
-        self.signal = signal
+            raise TypeError("Object {!r} is not an nMigen signal".format(signal))
 
     def __hash__(self):
         return hash(self._intern)
 
     def __hash__(self):
         return hash(self._intern)
@@ -1327,7 +1763,7 @@ class SignalKey:
 
     def __lt__(self, other):
         if type(other) is not SignalKey:
 
     def __lt__(self, other):
         if type(other) is not SignalKey:
-            raise TypeError("Object '{!r}' cannot be compared to a SignalKey".format(signal))
+            raise TypeError("Object {!r} cannot be compared to a SignalKey".format(signal))
         return self._intern < other._intern
 
     def __repr__(self):
         return self._intern < other._intern
 
     def __repr__(self):