from abc import ABCMeta, abstractmethod
-import builtins
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 ..tools import *
+from .._utils import *
+from .._unused import *
__all__ = [
+ "Shape", "signed", "unsigned",
"Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
"Array", "ArrayProxy",
- "Sample", "Past", "Stable", "Rose", "Fell",
"Signal", "ClockSignal", "ResetSignal",
- "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:
- """Deterministic Unique IDentifier"""
+ """Deterministic Unique IDentifier."""
__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
- 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
- elif isinstance(obj, (bool, int)):
+ if isinstance(obj, int):
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):
- 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])
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 __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):
+ other = Value.cast(other)
+ other.__check_divisor()
return Operator("%", [self, other])
def __rmod__(self, other):
+ self.__check_divisor()
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):
+ other = Value.cast(other)
+ other.__check_shamt()
return Operator("<<", [self, other])
def __rlshift__(self, other):
+ self.__check_shamt()
return Operator("<<", [other, self])
def __rshift__(self, other):
+ other = Value.cast(other)
+ other.__check_shamt()
return Operator(">>", [self, other])
def __rrshift__(self, other):
+ self.__check_shamt()
return Operator(">>", [other, self])
+
def __and__(self, other):
return Operator("&", [self, other])
def __rand__(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):
- 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):
- 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)
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
- Output ``Value``. If any bits are set, returns ``1``, else ``0``.
+ ``1`` if any bits are set, ``0`` otherwise.
"""
return Operator("b", [self])
- def part(self, offset, width):
- """Indexed part-select.
+ 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.
+
+ Returns
+ -------
+ Value, out
+ ``0`` if ``premise`` is true and ``conclusion`` is not, ``1`` otherwise.
+ """
+ return ~premise | conclusion
+
+ def bit_select(self, offset, width):
+ """Part-select with bit 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 overlap by all but 1 bit.
Parameters
----------
- offset : Value, in
- start point of the selected bits
+ offset : Value, int
+ Index of first selected bit.
width : int
- number of selected bits
+ Number of selected bits.
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: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``, such that successive
+ parts do not overlap.
+
+ Parameters
+ ----------
+ offset : Value, int
+ Index of first selected word.
+ 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 * 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.
Assign
Assignment statement that can be used in combinatorial or synchronous context.
"""
- return Assign(self, value)
+ return Assign(self, value, src_loc_at=1)
@abstractmethod
def shape(self):
- """Bit length and signedness of a value.
+ """Bit width and signedness of a value.
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
--------
- >>> 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:
__hash__ = None
+@final
class Const(Value):
"""A constant, literal integer value.
----------
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
----------
- nbits : int
+ width : int
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
- if signed and value >> (nbits - 1):
+ if signed and value >> (width - 1):
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:
- 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}'", 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):
- return self.nbits, self.signed
+ return Shape(self.width, self.signed)
def _rhs_signals(self):
- return ValueSet()
+ return SignalSet()
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):
- 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}'", 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):
- return self.nbits, self.signed
+ return Shape(self.width, self.signed)
def _rhs_signals(self):
- return ValueSet()
+ return SignalSet()
+@final
class AnyConst(AnyValue):
def __repr__(self):
- return "(anyconst {}'{})".format(self.nbits, "s" if self.signed else "")
+ return "(anyconst {}'{})".format(self.width, "s" if self.signed else "")
+@final
class AnySeq(AnyValue):
def __repr__(self):
- return "(anyseq {}'{})".format(self.nbits, "s" if self.signed else "")
+ return "(anyseq {}'{})".format(self.width, "s" if self.signed else "")
+@final
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)
- 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 _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:
- (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:
- (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 == "*":
- if not a_sign and not b_sign:
- # both operands unsigned
- return a_bits + b_bits, False
- if a_sign and b_sign:
- # both operands signed
- return a_bits + b_bits - 1, True
- # one operand signed, the other unsigned (add sign bit)
- return a_bits + b_bits + 1 - 1, True
- 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:
- 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
- return a_bits + extra, a_sign
+ return Shape(a_width + extra, a_signed)
elif len(op_shapes) == 3:
- if self.op == "m":
+ if self.operator == "m":
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"
- .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):
- return "({} {})".format(self.op, " ".join(map(repr, self.operands)))
+ return "({} {})".format(self.operator, " ".join(map(repr, self.operands)))
def Mux(sel, val1, val0):
Value, out
Output ``Value``. If ``sel`` is asserted, the Mux returns ``val1``, else ``val0``.
"""
- return Operator("m", [sel, val1, val0], src_loc_at=1)
+ sel = Value.cast(sel)
+ if len(sel) != 1:
+ sel = sel.bool()
+ 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):
- 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
- 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.end = end
+ self.stop = stop
def shape(self):
- return self.end - self.start, False
+ return Shape(self.stop - self.start)
def _lhs_signals(self):
return self.value._lhs_signals()
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):
- 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:
- 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.offset = Value.wrap(offset)
+ self.offset = Value.cast(offset)
self.width = width
+ self.stride = stride
def shape(self):
- return self.width, False
+ return Shape(self.width)
def _lhs_signals(self):
return self.value._lhs_signals()
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
class Cat(Value):
"""Concatenate values.
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):
- return sum(len(part) for part in self.parts), False
+ return Shape(sum(len(part) for part in self.parts))
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):
- 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
return "(cat {})".format(" ".join(map(repr, self.parts)))
+@final
class Repl(Value):
"""Replicate a 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:
- 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))
- 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):
- return len(self.value) * self.count, False
+ return Shape(len(self.value) * self.count)
def _rhs_signals(self):
return self.value._rhs_signals()
return "(repl {!r} {})".format(self.value, self.count)
+# @final
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 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
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
+ decoder : function or Enum
A function converting integer signal values to human-readable strings (e.g. FSM state
- names).
+ names). If an ``Enum`` subclass is passed, it is concisely decoded using format string
+ ``"{0.name:}/{0.value:}"``, or a number if the signal value is not a member of
+ the enumeration.
Attributes
----------
- nbits : int
+ width : int
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 None:
- try:
- name = tracer.get_var_name(depth=2 + src_loc_at)
- except tracer.NameNotFound:
- name = "$signal"
- self.name = name
+ if name is not None and not isinstance(name, str):
+ 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:
- if min is None:
- min = 0
- if max is None:
- max = 2
- max -= 1 # make both bounds inclusive
- if not min < max:
- raise ValueError("Lower bound {} should be less than higher bound {}"
- .format(min, max))
- self.signed = min < 0 or max < 0
- 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.decoder = decoder
- @classmethod
- def like(cls, other, name=None, src_loc_at=0, **kwargs):
+ 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:
+ return "{0.name:}/{0.value:}".format(decoder(value))
+ except ValueError:
+ return str(value)
+ self.decoder = enum_decoder
+ self._enum_class = decoder
+ else:
+ self.decoder = decoder
+ self._enum_class = None
+
+ # 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
other : Value
Object to base this Signal on.
"""
- if name is None:
- try:
- name = tracer.get_var_name(depth=2 + src_loc_at)
- except tracer.NameNotFound:
- name = "$like"
- kw = dict(shape=cls.wrap(other).shape(), name=name)
- if isinstance(other, cls):
+ if name is not None:
+ new_name = str(name)
+ elif name_suffix is not None:
+ new_name = other.name + str(name_suffix)
+ else:
+ new_name = tracer.get_var_name(depth=2 + src_loc_at, default="$like")
+ 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)
- return cls(**kw, src_loc_at=1 + src_loc_at)
+ return Signal(**kw, src_loc_at=1 + src_loc_at)
def shape(self):
- return self.nbits, self.signed
+ return Shape(self.width, self.signed)
def _lhs_signals(self):
- return ValueSet((self,))
+ return SignalSet((self,))
def _rhs_signals(self):
- return ValueSet((self,))
+ return SignalSet((self,))
def __repr__(self):
return "(sig {})".format(self.name)
+@final
class ClockSignal(Value):
"""Clock signal for a clock domain.
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):
- 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):
- return 1, False
+ return Shape(1)
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:
return "(clk {})".format(self.domain)
+@final
class ResetSignal(Value):
"""Reset signal for a clock domain.
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):
- 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):
- return 1, False
+ return Shape(1)
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:
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():
- 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))
- 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 = [
- ("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[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=()):
", ".join(map(repr, self._inner)))
+@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.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)
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):
- 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):
- 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):
- 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 "(proxy (array [{}]) {!r})".format(", ".join(map(repr, self.elems)), self.index)
+class UserValue(Value):
+ """Value with custom lowering.
+
+ A ``UserValue`` is a value whose precise representation does not have to be immediately known,
+ which is useful in certain metaprogramming scenarios. Instead of providing fixed semantics
+ upfront, it is kept abstract for as long as possible, only being lowered to a concrete nMigen
+ value when required.
+
+ Note that the ``lower`` method will only be called once; this is necessary to ensure that
+ nMigen's view of representation of all values stays internally consistent. If the class
+ deriving from ``UserValue`` is mutable, then it must ensure that after ``lower`` is called,
+ it is not mutated in a way that changes its representation.
+
+ The following is an incomplete list of actions that, when applied to an ``UserValue`` directly
+ or indirectly, will cause it to be lowered, provided as an illustrative reference:
+ * Querying the shape using ``.shape()`` or ``len()``;
+ * Creating a similarly shaped signal using ``Signal.like``;
+ * 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=0):
+ super().__init__(src_loc_at=1 + src_loc_at)
+ self.__lowered = None
+
+ @abstractmethod
+ def lower(self):
+ """Conversion to a concrete representation."""
+ pass # :nocov:
+
+ def _lazy_lower(self):
+ if self.__lowered is None:
+ lowered = self.lower()
+ if isinstance(lowered, UserValue):
+ lowered = lowered._lazy_lower()
+ self.__lowered = Value.cast(lowered)
+ return self.__lowered
+
+ def shape(self):
+ return self._lazy_lower().shape()
+
+ def _lhs_signals(self):
+ return self._lazy_lower()._lhs_signals()
+
+ def _rhs_signals(self):
+ 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.
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
- 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))
+ 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):
- return ValueSet((self,))
+ return SignalSet((self,))
def __repr__(self):
return "(sample {!r} @ {}[{}])".format(
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:
+ def __init__(self, *, src_loc_at=0):
+ self.src_loc = tracer.get_src_loc(1 + src_loc_at)
+
@staticmethod
- def wrap(obj):
+ def cast(obj):
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:
- 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):
- def __init__(self, lhs, rhs):
- 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()
return "(eq {!r} {!r})".format(self.lhs, self.rhs)
-class Assert(Statement):
- def __init__(self, test, _check=None, _en=None):
- self.test = Value.wrap(test)
+class UnusedProperty(UnusedMustUse):
+ pass
+
+class Property(Statement, MustUse):
+ _MustUse__warning = UnusedProperty
+
+ def __init__(self, test, *, _check=None, _en=None, src_loc_at=0):
+ super().__init__(src_loc_at=src_loc_at)
+ self.test = Value.cast(test)
self._check = _check
+ self._en = _en
if self._check is None:
- self._check = Signal(reset_less=True, name="$assert$check")
- self._check.src_loc = self.test.src_loc
-
- self._en = _en
+ self._check = Signal(reset_less=True, name="${}$check".format(self._kind))
+ self._check.src_loc = self.src_loc
if _en is None:
- self._en = Signal(reset_less=True, name="$assert$en")
- self._en.src_loc = self.test.src_loc
+ 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 __repr__(self):
- return "(assert {!r})".format(self.test)
+ return "({} {!r})".format(self._kind, self.test)
-class Assume(Statement):
- def __init__(self, test, _check=None, _en=None):
- self.test = Value.wrap(test)
+@final
+class Assert(Property):
+ _kind = "assert"
- self._check = _check
- if self._check is None:
- self._check = Signal(reset_less=True, name="$assume$check")
- self._check.src_loc = self.test.src_loc
-
- self._en = _en
- if self._en is None:
- self._en = Signal(reset_less=True, name="$assume$en")
- self._en.src_loc = self.test.src_loc
- def _lhs_signals(self):
- return ValueSet((self._en, self._check))
+@final
+class Assume(Property):
+ _kind = "assume"
- def _rhs_signals(self):
- return self.test._rhs_signals()
- def __repr__(self):
- return "(assume {!r})".format(self.test)
+@final
+class Cover(Property):
+ _kind = "cover"
+# @final
class Switch(Statement):
- def __init__(self, test, cases):
- 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()
- for key, stmts in cases.items():
- if isinstance(key, (bool, int)):
- key = "{:0{}b}".format(key, len(self.test))
- assert len(key) <= len(self.test)
- elif isinstance(key, str):
+ for orig_keys, stmts in cases.items():
+ # Map: None -> (); key -> (key,); (key...) -> (key...)
+ keys = orig_keys
+ if keys is None:
+ keys = ()
+ if not isinstance(keys, tuple):
+ keys = (keys,)
+ # Map: 2 -> "0010"; "0010" -> "0010"
+ new_keys = ()
+ for key in keys:
+ 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:
+ raise TypeError("Object {!r} cannot be used as a switch key"
+ .format(key))
assert len(key) == len(self.test)
- else:
- raise TypeError("Object '{!r}' cannot be used as a switch key"
- .format(key))
+ new_keys = (*new_keys, key)
if not isinstance(stmts, Iterable):
stmts = [stmts]
- self.cases[key] = 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),
- 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),
- start=ValueSet())
+ start=SignalSet())
return self.test._rhs_signals() | signals
def __repr__(self):
- cases = ["(case {} {})".format(key, " ".join(map(repr, stmts)))
- for key, stmts in self.cases.items()]
- return "(switch {!r} {})".format(self.test, " ".join(cases))
-
-
-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 * 10e6)
-
-
-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)
-
-
-class Passive(Statement):
- def _rhs_signals(self):
- return ValueSet()
-
- def __repr__(self):
- return "(passive)"
+ def case_repr(keys, stmts):
+ stmts_repr = " ".join(map(repr, stmts))
+ if keys == ():
+ return "(default {})".format(stmts_repr)
+ elif len(keys) == 1:
+ return "(case {} {})".format(keys[0], stmts_repr)
+ else:
+ return "(case ({}) {})".format(" ".join(keys), stmts_repr)
+ case_reprs = [case_repr(keys, stmts) for keys, stmts in self.cases.items()]
+ return "(switch {!r} {})".format(self.test, " ".join(case_reprs))
class _MappedKeyCollection(metaclass=ABCMeta):
class ValueKey:
def __init__(self, value):
- self.value = Value.wrap(value)
-
- def __hash__(self):
+ self.value = Value.cast(value)
if isinstance(self.value, Const):
- return hash(self.value.value)
+ self._hash = hash(self.value.value)
elif isinstance(self.value, (Signal, AnyValue)):
- return hash(self.value.duid)
+ self._hash = hash(self.value.duid)
elif isinstance(self.value, (ClockSignal, ResetSignal)):
- return hash(self.value.domain)
+ self._hash = hash(self.value.domain)
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):
- 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):
- 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):
- return hash(tuple(ValueKey(o) for o in self.value.operands))
+ self._hash = hash(tuple(ValueKey(o) for o in self.value.parts))
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):
- 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:
- 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 __hash__(self):
+ return self._hash
+
def __eq__(self, other):
if type(other) is not ValueKey:
return False
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
- 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
- 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.operands, other.value.operands))
+ for a, b in zip(self.value.parts, other.value.parts))
elif isinstance(self.value, ArrayProxy):
return (ValueKey(self.value.index) == ValueKey(other.value.index) and
len(self.value.elems) == len(other.value.elems) and
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:
- 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):
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)
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:
- 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 __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):