1602 lines
54 KiB
Python
1602 lines
54 KiB
Python
from abc import ABCMeta, abstractmethod
|
|
import traceback
|
|
import warnings
|
|
import typing
|
|
from collections import OrderedDict
|
|
from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
|
|
from enum import Enum
|
|
|
|
from .. import tracer
|
|
from .._utils import *
|
|
from .._unused import *
|
|
|
|
|
|
__all__ = [
|
|
"Shape", "signed", "unsigned",
|
|
"Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
|
|
"Array", "ArrayProxy",
|
|
"Signal", "ClockSignal", "ResetSignal",
|
|
"UserValue",
|
|
"Sample", "Past", "Stable", "Rose", "Fell", "Initial",
|
|
"Statement", "Switch",
|
|
"Property", "Assign", "Assert", "Assume", "Cover",
|
|
"ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", "SignalSet",
|
|
]
|
|
|
|
|
|
class DUID:
|
|
"""Deterministic Unique IDentifier."""
|
|
__next_uid = 0
|
|
def __init__(self):
|
|
self.duid = DUID.__next_uid
|
|
DUID.__next_uid += 1
|
|
|
|
|
|
class Shape(typing.NamedTuple):
|
|
"""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.
|
|
"""
|
|
width: int = 1
|
|
signed: bool = False
|
|
|
|
@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))
|
|
|
|
|
|
# TODO: use dataclasses instead of this hack
|
|
def _Shape___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))
|
|
Shape.__init__ = _Shape___init__
|
|
|
|
|
|
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 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, int):
|
|
return Const(obj)
|
|
if isinstance(obj, Enum):
|
|
return Const(obj.value, Shape.cast(type(obj)))
|
|
raise TypeError("Object {!r} cannot be converted to an nMigen value".format(obj))
|
|
|
|
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")
|
|
|
|
def __invert__(self):
|
|
return Operator("~", [self])
|
|
def __neg__(self):
|
|
return Operator("-", [self])
|
|
|
|
def __add__(self, other):
|
|
return Operator("+", [self, other])
|
|
def __radd__(self, other):
|
|
return Operator("+", [other, self])
|
|
def __sub__(self, other):
|
|
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 __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 NotImplementedError("Shift by a signed value is not supported")
|
|
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):
|
|
return Operator("&", [other, self])
|
|
def __xor__(self, other):
|
|
return Operator("^", [self, other])
|
|
def __rxor__(self, other):
|
|
return Operator("^", [other, self])
|
|
def __or__(self, other):
|
|
return Operator("|", [self, other])
|
|
def __ror__(self, other):
|
|
return Operator("|", [other, self])
|
|
|
|
def __eq__(self, other):
|
|
return Operator("==", [self, other])
|
|
def __ne__(self, other):
|
|
return Operator("!=", [self, other])
|
|
def __lt__(self, other):
|
|
return Operator("<", [self, other])
|
|
def __le__(self, other):
|
|
return Operator("<=", [self, other])
|
|
def __gt__(self, other):
|
|
return Operator(">", [self, other])
|
|
def __ge__(self, other):
|
|
return Operator(">=", [self, other])
|
|
|
|
def __len__(self):
|
|
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))
|
|
if key < 0:
|
|
key += n
|
|
return Slice(self, key, key + 1)
|
|
elif isinstance(key, slice):
|
|
start, stop, step = key.indices(n)
|
|
if step != 1:
|
|
return Cat(self[i] for i in range(start, stop, step))
|
|
return Slice(self, start, stop)
|
|
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
|
|
``1`` if any bits are set, ``0`` otherwise.
|
|
"""
|
|
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.
|
|
|
|
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``, such that successive
|
|
parts overlap by all but 1 bit.
|
|
|
|
Parameters
|
|
----------
|
|
offset : Value, in
|
|
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``, such that successive
|
|
parts do not overlap.
|
|
|
|
Parameters
|
|
----------
|
|
offset : Value, in
|
|
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 eq(self, value):
|
|
"""Assignment.
|
|
|
|
Parameters
|
|
----------
|
|
value : Value, in
|
|
Value to be assigned.
|
|
|
|
Returns
|
|
-------
|
|
Assign
|
|
Assignment statement that can be used in combinatorial or synchronous context.
|
|
"""
|
|
return Assign(self, value, src_loc_at=1)
|
|
|
|
@abstractmethod
|
|
def shape(self):
|
|
"""Bit width and signedness of a value.
|
|
|
|
Returns
|
|
-------
|
|
Shape
|
|
See :class:`Shape`.
|
|
|
|
Examples
|
|
--------
|
|
>>> Signal(8).shape()
|
|
Shape(width=8, signed=False)
|
|
>>> Const(0xaa).shape()
|
|
Shape(width=8, signed=False)
|
|
"""
|
|
pass # :nocov:
|
|
|
|
def _lhs_signals(self):
|
|
raise TypeError("Value {!r} cannot be used in assignments".format(self))
|
|
|
|
@abstractmethod
|
|
def _rhs_signals(self):
|
|
pass # :nocov:
|
|
|
|
def _as_const(self):
|
|
raise TypeError("Value {!r} cannot be evaluated as constant".format(self))
|
|
|
|
__hash__ = None
|
|
|
|
|
|
@final
|
|
class Const(Value):
|
|
"""A constant, literal integer value.
|
|
|
|
Parameters
|
|
----------
|
|
value : int
|
|
shape : int or tuple or None
|
|
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
|
|
----------
|
|
width : int
|
|
signed : bool
|
|
"""
|
|
src_loc = None
|
|
|
|
@staticmethod
|
|
def normalize(value, shape):
|
|
width, signed = shape
|
|
mask = (1 << width) - 1
|
|
value &= mask
|
|
if signed and value >> (width - 1):
|
|
value |= ~mask
|
|
return value
|
|
|
|
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 = 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 Shape(self.width, self.signed)
|
|
|
|
def _rhs_signals(self):
|
|
return ValueSet()
|
|
|
|
def _as_const(self):
|
|
return self.value
|
|
|
|
def __repr__(self):
|
|
return "(const {}'{}d{})".format(self.width, "s" if self.signed else "", self.value)
|
|
|
|
|
|
C = Const # shorthand
|
|
|
|
|
|
class AnyValue(Value, DUID):
|
|
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 Shape(self.width, self.signed)
|
|
|
|
def _rhs_signals(self):
|
|
return ValueSet()
|
|
|
|
|
|
@final
|
|
class AnyConst(AnyValue):
|
|
def __repr__(self):
|
|
return "(anyconst {}'{})".format(self.width, "s" if self.signed else "")
|
|
|
|
|
|
@final
|
|
class AnySeq(AnyValue):
|
|
def __repr__(self):
|
|
return "(anyseq {}'{})".format(self.width, "s" if self.signed else "")
|
|
|
|
|
|
@final
|
|
class Operator(Value):
|
|
def __init__(self, operator, operands, *, src_loc_at=0):
|
|
super().__init__(src_loc_at=1 + src_loc_at)
|
|
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_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_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_width) - 1
|
|
return Shape(a_width + extra, a_signed)
|
|
if self.operator == ">>":
|
|
if b_signed:
|
|
extra = 2 ** (b_width - 1)
|
|
else:
|
|
extra = 0
|
|
return Shape(a_width + extra, a_signed)
|
|
elif len(op_shapes) == 3:
|
|
if self.operator == "m":
|
|
s_shape, a_shape, b_shape = op_shapes
|
|
return _bitwise_binary_shape(a_shape, b_shape)
|
|
raise NotImplementedError("Operator {}/{} not implemented"
|
|
.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.operator, " ".join(map(repr, self.operands)))
|
|
|
|
|
|
def Mux(sel, val1, val0):
|
|
"""Choose between two values.
|
|
|
|
Parameters
|
|
----------
|
|
sel : Value, in
|
|
Selector.
|
|
val1 : Value, in
|
|
val0 : Value, in
|
|
Input values.
|
|
|
|
Returns
|
|
-------
|
|
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):
|
|
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(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 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__(src_loc_at=src_loc_at)
|
|
self.value = Value.cast(value)
|
|
self.start = start
|
|
self.stop = stop
|
|
|
|
def shape(self):
|
|
return Shape(self.stop - self.start)
|
|
|
|
def _lhs_signals(self):
|
|
return self.value._lhs_signals()
|
|
|
|
def _rhs_signals(self):
|
|
return self.value._rhs_signals()
|
|
|
|
def __repr__(self):
|
|
return "(slice {} {}:{})".format(repr(self.value), self.start, self.stop)
|
|
|
|
|
|
@final
|
|
class Part(Value):
|
|
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))
|
|
if not isinstance(stride, int) or stride <= 0:
|
|
raise TypeError("Part stride must be a positive integer, not {!r}".format(stride))
|
|
|
|
super().__init__(src_loc_at=src_loc_at)
|
|
self.value = value
|
|
self.offset = Value.cast(offset)
|
|
self.width = width
|
|
self.stride = stride
|
|
|
|
def shape(self):
|
|
return Shape(self.width)
|
|
|
|
def _lhs_signals(self):
|
|
return self.value._lhs_signals()
|
|
|
|
def _rhs_signals(self):
|
|
return self.value._rhs_signals() | self.offset._rhs_signals()
|
|
|
|
def __repr__(self):
|
|
return "(part {} {} {} {})".format(repr(self.value), repr(self.offset),
|
|
self.width, self.stride)
|
|
|
|
|
|
@final
|
|
class Cat(Value):
|
|
"""Concatenate values.
|
|
|
|
Form a compound ``Value`` from several smaller ones by concatenation.
|
|
The first argument occupies the lower bits of the result.
|
|
The return value can be used on either side of an assignment, that
|
|
is, the concatenated value can be used as an argument on the RHS or
|
|
as a target on the LHS. If it is used on the LHS, it must solely
|
|
consist of ``Signal`` s, slices of ``Signal`` s, and other concatenations
|
|
meeting these properties. The bit length of the return value is the sum of
|
|
the bit lengths of the arguments::
|
|
|
|
len(Cat(args)) == sum(len(arg) for arg in args)
|
|
|
|
Parameters
|
|
----------
|
|
*args : Values or iterables of Values, inout
|
|
``Value`` s to be concatenated.
|
|
|
|
Returns
|
|
-------
|
|
Value, inout
|
|
Resulting ``Value`` obtained by concatentation.
|
|
"""
|
|
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 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())
|
|
|
|
def _rhs_signals(self):
|
|
return union((part._rhs_signals() for part in self.parts), start=ValueSet())
|
|
|
|
def _as_const(self):
|
|
value = 0
|
|
for part in reversed(self.parts):
|
|
value <<= len(part)
|
|
value |= part._as_const()
|
|
return value
|
|
|
|
def __repr__(self):
|
|
return "(cat {})".format(" ".join(map(repr, self.parts)))
|
|
|
|
|
|
@final
|
|
class Repl(Value):
|
|
"""Replicate a value
|
|
|
|
An input value is replicated (repeated) several times
|
|
to be used on the RHS of assignments::
|
|
|
|
len(Repl(s, n)) == len(s) * n
|
|
|
|
Parameters
|
|
----------
|
|
value : Value, in
|
|
Input value to be replicated.
|
|
count : int
|
|
Number of replications.
|
|
|
|
Returns
|
|
-------
|
|
Repl, out
|
|
Replicated value.
|
|
"""
|
|
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}"
|
|
.format(count))
|
|
|
|
super().__init__(src_loc_at=src_loc_at)
|
|
self.value = Value.cast(value)
|
|
self.count = count
|
|
|
|
def shape(self):
|
|
return Shape(len(self.value) * self.count)
|
|
|
|
def _rhs_signals(self):
|
|
return self.value._rhs_signals()
|
|
|
|
def __repr__(self):
|
|
return "(repl {!r} {})".format(self.value, self.count)
|
|
|
|
|
|
# @final
|
|
class Signal(Value, DUID):
|
|
"""A varying integer value.
|
|
|
|
Parameters
|
|
----------
|
|
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.
|
|
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
|
|
in combinatorial context (due to conditional assignments not being taken), the ``Signal``
|
|
assumes its ``reset`` value. Defaults to 0.
|
|
reset_less : bool
|
|
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``.
|
|
attrs : dict
|
|
Dictionary of synthesis attributes.
|
|
decoder : function or Enum
|
|
A function converting integer signal values to human-readable strings (e.g. FSM state
|
|
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
|
|
----------
|
|
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,
|
|
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))
|
|
self.name = name or tracer.get_var_name(depth=2 + src_loc_at, default="$signal")
|
|
|
|
if shape is None:
|
|
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)
|
|
|
|
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
|
|
else:
|
|
self.decoder = decoder
|
|
|
|
# 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 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 Signal(**kw, src_loc_at=1 + src_loc_at)
|
|
|
|
def shape(self):
|
|
return Shape(self.width, self.signed)
|
|
|
|
def _lhs_signals(self):
|
|
return ValueSet((self,))
|
|
|
|
def _rhs_signals(self):
|
|
return ValueSet((self,))
|
|
|
|
def __repr__(self):
|
|
return "(sig {})".format(self.name)
|
|
|
|
|
|
@final
|
|
class ClockSignal(Value):
|
|
"""Clock signal for a clock domain.
|
|
|
|
Any ``ClockSignal`` is equivalent to ``cd.clk`` for a clock domain with the corresponding name.
|
|
All of these signals ultimately refer to the same signal, but they can be manipulated
|
|
independently of the clock domain, even before the clock domain is created.
|
|
|
|
Parameters
|
|
----------
|
|
domain : str
|
|
Clock domain to obtain a clock signal for. Defaults to ``"sync"``.
|
|
"""
|
|
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))
|
|
if domain == "comb":
|
|
raise ValueError("Domain '{}' does not have a clock".format(domain))
|
|
self.domain = domain
|
|
|
|
def shape(self):
|
|
return Shape(1)
|
|
|
|
def _lhs_signals(self):
|
|
return ValueSet((self,))
|
|
|
|
def _rhs_signals(self):
|
|
raise NotImplementedError("ClockSignal must be lowered to a concrete signal") # :nocov:
|
|
|
|
def __repr__(self):
|
|
return "(clk {})".format(self.domain)
|
|
|
|
|
|
@final
|
|
class ResetSignal(Value):
|
|
"""Reset signal for a clock domain.
|
|
|
|
Any ``ResetSignal`` is equivalent to ``cd.rst`` for a clock domain with the corresponding name.
|
|
All of these signals ultimately refer to the same signal, but they can be manipulated
|
|
independently of the clock domain, even before the clock domain is created.
|
|
|
|
Parameters
|
|
----------
|
|
domain : str
|
|
Clock domain to obtain a reset signal for. Defaults to ``"sync"``.
|
|
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, *, 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))
|
|
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 Shape(1)
|
|
|
|
def _lhs_signals(self):
|
|
return ValueSet((self,))
|
|
|
|
def _rhs_signals(self):
|
|
raise NotImplementedError("ResetSignal must be lowered to a concrete signal") # :nocov:
|
|
|
|
def __repr__(self):
|
|
return "(rst {})".format(self.domain)
|
|
|
|
|
|
class Array(MutableSequence):
|
|
"""Addressable multiplexer.
|
|
|
|
An array is similar to a ``list`` that can also be indexed by ``Value``s; indexing by an integer or a slice works the same as for Python lists, but indexing by a ``Value`` results
|
|
in a proxy.
|
|
|
|
The array proxy can be used as an ordinary ``Value``, i.e. participate in calculations and
|
|
assignments, provided that all elements of the array are values. The array proxy also supports
|
|
attribute access and further indexing, each returning another array proxy; this means that
|
|
the results of indexing into arrays, arrays of records, and arrays of arrays can all
|
|
be used as first-class values.
|
|
|
|
It is an error to change an array or any of its elements after an array proxy was created.
|
|
Changing the array directly will raise an exception. However, it is not possible to detect
|
|
the elements being modified; if an element's attribute or element is modified after the proxy
|
|
for it has been created, the proxy will refer to stale data.
|
|
|
|
Examples
|
|
--------
|
|
|
|
Simple array::
|
|
|
|
gpios = Array(Signal() for _ in range(10))
|
|
with m.If(bus.we):
|
|
m.d.sync += gpios[bus.addr].eq(bus.w_data)
|
|
with m.Else():
|
|
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.range(10)
|
|
b = Signal.range(10)
|
|
r = Signal(8)
|
|
m.d.comb += r.eq(mult[a][b])
|
|
|
|
Array of records::
|
|
|
|
layout = [
|
|
("r_data", 16),
|
|
("r_en", 1),
|
|
]
|
|
buses = Array(Record(layout) for busno in range(4))
|
|
master = Record(layout)
|
|
m.d.comb += [
|
|
buses[sel].r_en.eq(master.r_en),
|
|
master.r_data.eq(buses[sel].r_data),
|
|
]
|
|
"""
|
|
def __init__(self, iterable=()):
|
|
self._inner = list(iterable)
|
|
self._proxy_at = None
|
|
self._mutable = True
|
|
|
|
def __getitem__(self, index):
|
|
if isinstance(index, Value):
|
|
if self._mutable:
|
|
self._proxy_at = tracer.get_src_loc()
|
|
self._mutable = False
|
|
return ArrayProxy(self, index)
|
|
else:
|
|
return self._inner[index]
|
|
|
|
def __len__(self):
|
|
return len(self._inner)
|
|
|
|
def _check_mutability(self):
|
|
if not self._mutable:
|
|
raise ValueError("Array can no longer be mutated after it was indexed with a value "
|
|
"at {}:{}".format(*self._proxy_at))
|
|
|
|
def __setitem__(self, index, value):
|
|
self._check_mutability()
|
|
self._inner[index] = value
|
|
|
|
def __delitem__(self, index):
|
|
self._check_mutability()
|
|
del self._inner[index]
|
|
|
|
def insert(self, index, value):
|
|
self._check_mutability()
|
|
self._inner.insert(index, value)
|
|
|
|
def __repr__(self):
|
|
return "(array{} [{}])".format(" mutable" if self._mutable else "",
|
|
", ".join(map(repr, self._inner)))
|
|
|
|
|
|
@final
|
|
class ArrayProxy(Value):
|
|
def __init__(self, elems, index, *, src_loc_at=0):
|
|
super().__init__(src_loc_at=1 + src_loc_at)
|
|
self.elems = elems
|
|
self.index = Value.cast(index)
|
|
|
|
def __getattr__(self, attr):
|
|
return ArrayProxy([getattr(elem, attr) for elem in self.elems], self.index)
|
|
|
|
def __getitem__(self, index):
|
|
return ArrayProxy([ elem[index] for elem in self.elems], self.index)
|
|
|
|
def _iter_as_values(self):
|
|
return (Value.cast(elem) for elem in self.elems)
|
|
|
|
def shape(self):
|
|
width, signed = 0, False
|
|
for elem_width, elem_signed in (elem.shape() for elem in self._iter_as_values()):
|
|
width = max(width, elem_width + elem_signed)
|
|
signed = max(signed, elem_signed)
|
|
return Shape(width, signed)
|
|
|
|
def _lhs_signals(self):
|
|
signals = union((elem._lhs_signals() for elem in self._iter_as_values()), start=ValueSet())
|
|
return signals
|
|
|
|
def _rhs_signals(self):
|
|
signals = union((elem._rhs_signals() for elem in self._iter_as_values()), start=ValueSet())
|
|
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:
|
|
self.__lowered = Value.cast(self.lower())
|
|
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()
|
|
|
|
|
|
@final
|
|
class Sample(Value):
|
|
"""Value from the past.
|
|
|
|
A ``Sample`` of an expression is equal to the value of the expression ``clocks`` clock edges
|
|
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, *, 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, 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,))
|
|
|
|
def __repr__(self):
|
|
return "(sample {!r} @ {}[{}])".format(
|
|
self.value, "<default>" if self.domain is None else self.domain, self.clocks)
|
|
|
|
|
|
def Past(expr, clocks=1, domain=None):
|
|
return Sample(expr, clocks, domain)
|
|
|
|
|
|
def Stable(expr, clocks=0, domain=None):
|
|
return Sample(expr, clocks + 1, domain) == Sample(expr, clocks, domain)
|
|
|
|
|
|
def Rose(expr, clocks=0, domain=None):
|
|
return ~Sample(expr, clocks + 1, domain) & Sample(expr, clocks, domain)
|
|
|
|
|
|
def Fell(expr, clocks=0, domain=None):
|
|
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 ValueSet((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 cast(obj):
|
|
if isinstance(obj, Iterable):
|
|
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))
|
|
|
|
|
|
@final
|
|
class Assign(Statement):
|
|
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 _rhs_signals(self):
|
|
return self.lhs._rhs_signals() | self.rhs._rhs_signals()
|
|
|
|
def __repr__(self):
|
|
return "(eq {!r} {!r})".format(self.lhs, self.rhs)
|
|
|
|
|
|
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="${}$check".format(self._kind))
|
|
self._check.src_loc = self.src_loc
|
|
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))
|
|
|
|
def _rhs_signals(self):
|
|
return self.test._rhs_signals()
|
|
|
|
def __repr__(self):
|
|
return "({} {!r})".format(self._kind, self.test)
|
|
|
|
|
|
@final
|
|
class Assert(Property):
|
|
_kind = "assert"
|
|
|
|
|
|
@final
|
|
class Assume(Property):
|
|
_kind = "assume"
|
|
|
|
|
|
@final
|
|
class Cover(Property):
|
|
_kind = "cover"
|
|
|
|
|
|
# @final
|
|
class Switch(Statement):
|
|
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 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)
|
|
new_keys = (*new_keys, key)
|
|
if not isinstance(stmts, Iterable):
|
|
stmts = [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())
|
|
return signals
|
|
|
|
def _rhs_signals(self):
|
|
signals = union((s._rhs_signals() for ss in self.cases.values() for s in ss),
|
|
start=ValueSet())
|
|
return self.test._rhs_signals() | signals
|
|
|
|
def __repr__(self):
|
|
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):
|
|
@abstractmethod
|
|
def _map_key(self, key):
|
|
pass # :nocov:
|
|
|
|
@abstractmethod
|
|
def _unmap_key(self, key):
|
|
pass # :nocov:
|
|
|
|
|
|
class _MappedKeyDict(MutableMapping, _MappedKeyCollection):
|
|
def __init__(self, pairs=()):
|
|
self._storage = OrderedDict()
|
|
for key, value in pairs:
|
|
self[key] = value
|
|
|
|
def __getitem__(self, key):
|
|
key = None if key is None else self._map_key(key)
|
|
return self._storage[key]
|
|
|
|
def __setitem__(self, key, value):
|
|
key = None if key is None else self._map_key(key)
|
|
self._storage[key] = value
|
|
|
|
def __delitem__(self, key):
|
|
key = None if key is None else self._map_key(key)
|
|
del self._storage[key]
|
|
|
|
def __iter__(self):
|
|
for key in self._storage:
|
|
if key is None:
|
|
yield None
|
|
else:
|
|
yield self._unmap_key(key)
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, type(self)):
|
|
return False
|
|
if len(self) != len(other):
|
|
return False
|
|
for ak, bk in zip(sorted(self._storage), sorted(other._storage)):
|
|
if ak != bk:
|
|
return False
|
|
if self._storage[ak] != other._storage[bk]:
|
|
return False
|
|
return True
|
|
|
|
def __len__(self):
|
|
return len(self._storage)
|
|
|
|
def __repr__(self):
|
|
pairs = ["({!r}, {!r})".format(k, v) for k, v in self.items()]
|
|
return "{}.{}([{}])".format(type(self).__module__, type(self).__name__,
|
|
", ".join(pairs))
|
|
|
|
|
|
class _MappedKeySet(MutableSet, _MappedKeyCollection):
|
|
def __init__(self, elements=()):
|
|
self._storage = OrderedDict()
|
|
for elem in elements:
|
|
self.add(elem)
|
|
|
|
def add(self, value):
|
|
self._storage[self._map_key(value)] = None
|
|
|
|
def update(self, values):
|
|
for value in values:
|
|
self.add(value)
|
|
|
|
def discard(self, value):
|
|
if value in self:
|
|
del self._storage[self._map_key(value)]
|
|
|
|
def __contains__(self, value):
|
|
return self._map_key(value) in self._storage
|
|
|
|
def __iter__(self):
|
|
for key in [k for k in self._storage]:
|
|
yield self._unmap_key(key)
|
|
|
|
def __len__(self):
|
|
return len(self._storage)
|
|
|
|
def __repr__(self):
|
|
return "{}.{}({})".format(type(self).__module__, type(self).__name__,
|
|
", ".join(repr(x) for x in self))
|
|
|
|
|
|
class ValueKey:
|
|
def __init__(self, value):
|
|
self.value = Value.cast(value)
|
|
if isinstance(self.value, Const):
|
|
self._hash = hash(self.value.value)
|
|
elif isinstance(self.value, (Signal, AnyValue)):
|
|
self._hash = hash(self.value.duid)
|
|
elif isinstance(self.value, (ClockSignal, ResetSignal)):
|
|
self._hash = hash(self.value.domain)
|
|
elif isinstance(self.value, Operator):
|
|
self._hash = hash((self.value.operator,
|
|
tuple(ValueKey(o) for o in self.value.operands)))
|
|
elif isinstance(self.value, Slice):
|
|
self._hash = hash((ValueKey(self.value.value), self.value.start, self.value.stop))
|
|
elif isinstance(self.value, Part):
|
|
self._hash = hash((ValueKey(self.value.value), ValueKey(self.value.offset),
|
|
self.value.width, self.value.stride))
|
|
elif isinstance(self.value, Cat):
|
|
self._hash = hash(tuple(ValueKey(o) for o in self.value.parts))
|
|
elif isinstance(self.value, ArrayProxy):
|
|
self._hash = hash((ValueKey(self.value.index),
|
|
tuple(ValueKey(e) for e in self.value._iter_as_values())))
|
|
elif isinstance(self.value, Sample):
|
|
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"
|
|
.format(self.value))
|
|
|
|
def __hash__(self):
|
|
return self._hash
|
|
|
|
def __eq__(self, other):
|
|
if type(other) is not ValueKey:
|
|
return False
|
|
if type(self.value) is not type(other.value):
|
|
return False
|
|
|
|
if isinstance(self.value, Const):
|
|
return self.value.value == other.value.value
|
|
elif isinstance(self.value, (Signal, AnyValue)):
|
|
return self.value is other.value
|
|
elif isinstance(self.value, (ClockSignal, ResetSignal)):
|
|
return self.value.domain == other.value.domain
|
|
elif isinstance(self.value, Operator):
|
|
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.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 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, ArrayProxy):
|
|
return (ValueKey(self.value.index) == ValueKey(other.value.index) and
|
|
len(self.value.elems) == len(other.value.elems) and
|
|
all(ValueKey(a) == ValueKey(b)
|
|
for a, b in zip(self.value._iter_as_values(),
|
|
other.value._iter_as_values())))
|
|
elif isinstance(self.value, Sample):
|
|
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"
|
|
.format(self.value))
|
|
|
|
def __lt__(self, other):
|
|
if not isinstance(other, ValueKey):
|
|
return False
|
|
if type(self.value) != type(other.value):
|
|
return False
|
|
|
|
if isinstance(self.value, Const):
|
|
return self.value < other.value
|
|
elif isinstance(self.value, (Signal, AnyValue)):
|
|
return self.value.duid < other.value.duid
|
|
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)
|
|
else: # :nocov:
|
|
raise TypeError("Object {!r} cannot be used as a key in value collections")
|
|
|
|
def __repr__(self):
|
|
return "<{}.ValueKey {!r}>".format(__name__, self.value)
|
|
|
|
|
|
class ValueDict(_MappedKeyDict):
|
|
_map_key = ValueKey
|
|
_unmap_key = lambda self, key: key.value
|
|
|
|
|
|
class ValueSet(_MappedKeySet):
|
|
_map_key = ValueKey
|
|
_unmap_key = lambda self, key: key.value
|
|
|
|
|
|
class SignalKey:
|
|
def __init__(self, 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))
|
|
|
|
def __hash__(self):
|
|
return hash(self._intern)
|
|
|
|
def __eq__(self, other):
|
|
if type(other) is not SignalKey:
|
|
return False
|
|
return self._intern == other._intern
|
|
|
|
def __lt__(self, other):
|
|
if type(other) is not SignalKey:
|
|
raise TypeError("Object {!r} cannot be compared to a SignalKey".format(signal))
|
|
return self._intern < other._intern
|
|
|
|
def __repr__(self):
|
|
return "<{}.SignalKey {!r}>".format(__name__, self.signal)
|
|
|
|
|
|
class SignalDict(_MappedKeyDict):
|
|
_map_key = SignalKey
|
|
_unmap_key = lambda self, key: key.signal
|
|
|
|
|
|
class SignalSet(_MappedKeySet):
|
|
_map_key = SignalKey
|
|
_unmap_key = lambda self, key: key.signal
|