parent
0ef01b1282
commit
06c734992f
|
@ -1,7 +1,9 @@
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import traceback
|
import traceback
|
||||||
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
import typing
|
import typing
|
||||||
|
import functools
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
|
from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
@ -16,7 +18,7 @@ __all__ = [
|
||||||
"Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
|
"Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
|
||||||
"Array", "ArrayProxy",
|
"Array", "ArrayProxy",
|
||||||
"Signal", "ClockSignal", "ResetSignal",
|
"Signal", "ClockSignal", "ResetSignal",
|
||||||
"UserValue",
|
"UserValue", "ValueCastable",
|
||||||
"Sample", "Past", "Stable", "Rose", "Fell", "Initial",
|
"Sample", "Past", "Stable", "Rose", "Fell", "Initial",
|
||||||
"Statement", "Switch",
|
"Statement", "Switch",
|
||||||
"Property", "Assign", "Assert", "Assume", "Cover",
|
"Property", "Assign", "Assert", "Assume", "Cover",
|
||||||
|
@ -142,6 +144,8 @@ class Value(metaclass=ABCMeta):
|
||||||
return Const(obj)
|
return Const(obj)
|
||||||
if isinstance(obj, Enum):
|
if isinstance(obj, Enum):
|
||||||
return Const(obj.value, Shape.cast(type(obj)))
|
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))
|
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):
|
||||||
|
@ -1280,6 +1284,51 @@ class UserValue(Value):
|
||||||
return self._lazy_lower()._rhs_signals()
|
return self._lazy_lower()._rhs_signals()
|
||||||
|
|
||||||
|
|
||||||
|
class ValueCastable:
|
||||||
|
"""Base class for classes which can be cast to Values.
|
||||||
|
|
||||||
|
A ``ValueCastable`` can be cast to ``Value``, meaning its precise representation does not have
|
||||||
|
to be immediately known. This is useful in certain metaprogramming scenarios. Instead of
|
||||||
|
providing fixed semantics upfront, it is kept abstract for as long as possible, only being
|
||||||
|
cast to a concrete nMigen value when required.
|
||||||
|
|
||||||
|
Note that it is necessary to ensure that nMigen's view of representation of all values stays
|
||||||
|
internally consistent. The class deriving from ``ValueCastable`` must decorate the ``as_value``
|
||||||
|
method with the ``lowermethod`` decorator, which ensures that all calls to ``as_value``return the
|
||||||
|
same ``Value`` representation. If the class deriving from ``ValueCastable`` is mutable, it is
|
||||||
|
up to the user to ensure that it is not mutated in a way that changes its representation after
|
||||||
|
the first call to ``as_value``.
|
||||||
|
"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
self = super().__new__(cls)
|
||||||
|
if not hasattr(self, "as_value"):
|
||||||
|
raise TypeError(f"Class '{cls.__name__}' deriving from `ValueCastable` must override the `as_value` method")
|
||||||
|
|
||||||
|
if not hasattr(self.as_value, "_ValueCastable__memoized"):
|
||||||
|
raise TypeError(f"Class '{cls.__name__}' deriving from `ValueCastable` must decorate the `as_value` "
|
||||||
|
"method with the `ValueCastable.lowermethod` decorator")
|
||||||
|
return self
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def lowermethod(func):
|
||||||
|
"""Decorator to memoize lowering methods.
|
||||||
|
|
||||||
|
Ensures the decorated method is called only once, with subsequent method calls returning the
|
||||||
|
object returned by the first first method call.
|
||||||
|
|
||||||
|
This decorator is required to decorate the ``as_value`` method of ``ValueCastable`` subclasses.
|
||||||
|
This is to ensure that nMigen's view of representation of all values stays internally
|
||||||
|
consistent.
|
||||||
|
"""
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper_memoized(self, *args, **kwargs):
|
||||||
|
if not hasattr(self, "_ValueCastable__lowered_to"):
|
||||||
|
self.__lowered_to = func(self, *args, **kwargs)
|
||||||
|
return self.__lowered_to
|
||||||
|
wrapper_memoized.__memoized = True
|
||||||
|
return wrapper_memoized
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class Sample(Value):
|
class Sample(Value):
|
||||||
"""Value from the past.
|
"""Value from the past.
|
||||||
|
|
|
@ -1025,6 +1025,52 @@ class UserValueTestCase(FHDLTestCase):
|
||||||
self.assertEqual(uv.lower_count, 1)
|
self.assertEqual(uv.lower_count, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class MockValueCastableChanges(ValueCastable):
|
||||||
|
def __init__(self, width=0):
|
||||||
|
self.width = width
|
||||||
|
|
||||||
|
@ValueCastable.lowermethod
|
||||||
|
def as_value(self):
|
||||||
|
return Signal(self.width)
|
||||||
|
|
||||||
|
|
||||||
|
class MockValueCastableNotDecorated(ValueCastable):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def as_value(self):
|
||||||
|
return Signal()
|
||||||
|
|
||||||
|
|
||||||
|
class MockValueCastableNoOverride(ValueCastable):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ValueCastableTestCase(FHDLTestCase):
|
||||||
|
def test_not_decorated(self):
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Class 'MockValueCastableNotDecorated' deriving from `ValueCastable` must decorate the `as_value` "
|
||||||
|
r"method with the `ValueCastable.lowermethod` decorator$"):
|
||||||
|
vc = MockValueCastableNotDecorated()
|
||||||
|
|
||||||
|
def test_no_override(self):
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Class 'MockValueCastableNoOverride' deriving from `ValueCastable` must override the `as_value` "
|
||||||
|
r"method$"):
|
||||||
|
vc = MockValueCastableNoOverride()
|
||||||
|
|
||||||
|
def test_memoized(self):
|
||||||
|
vc = MockValueCastableChanges(1)
|
||||||
|
sig1 = vc.as_value()
|
||||||
|
vc.width = 2
|
||||||
|
sig2 = vc.as_value()
|
||||||
|
self.assertIs(sig1, sig2)
|
||||||
|
vc.width = 3
|
||||||
|
sig3 = Value.cast(vc)
|
||||||
|
self.assertIs(sig1, sig3)
|
||||||
|
|
||||||
|
|
||||||
class SampleTestCase(FHDLTestCase):
|
class SampleTestCase(FHDLTestCase):
|
||||||
def test_const(self):
|
def test_const(self):
|
||||||
s = Sample(1, 1, "sync")
|
s = Sample(1, 1, "sync")
|
||||||
|
|
Loading…
Reference in a new issue