Implement RFC 9: Constant initialization for shape-castable objects.
See amaranth-lang/rfcs#9 and #771.
This commit is contained in:
parent
ea5a150155
commit
54d5c4c047
7 changed files with 181 additions and 42 deletions
|
|
@ -45,6 +45,9 @@ class ShapeCastable:
|
|||
if not hasattr(cls, "as_shape"):
|
||||
raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
|
||||
f"the `as_shape` method")
|
||||
if not hasattr(cls, "const"):
|
||||
raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
|
||||
f"the `const` method")
|
||||
|
||||
|
||||
class Shape:
|
||||
|
|
@ -988,7 +991,7 @@ class Signal(Value, DUID):
|
|||
decoder : function
|
||||
"""
|
||||
|
||||
def __init__(self, shape=None, *, name=None, reset=0, reset_less=False,
|
||||
def __init__(self, shape=None, *, name=None, reset=None, reset_less=False,
|
||||
attrs=None, decoder=None, src_loc_at=0):
|
||||
super().__init__(src_loc_at=src_loc_at)
|
||||
|
||||
|
|
@ -1005,12 +1008,24 @@ class Signal(Value, DUID):
|
|||
self.signed = shape.signed
|
||||
|
||||
orig_reset = reset
|
||||
try:
|
||||
reset = Const.cast(reset)
|
||||
except TypeError:
|
||||
raise TypeError("Reset value must be a constant-castable expression, not {!r}"
|
||||
.format(orig_reset))
|
||||
if orig_reset not in (0, -1): # Avoid false positives for all-zeroes and all-ones
|
||||
if isinstance(orig_shape, ShapeCastable):
|
||||
try:
|
||||
reset = Const.cast(orig_shape.const(reset))
|
||||
except Exception:
|
||||
raise TypeError("Reset value must be a constant initializer of {!r}"
|
||||
.format(orig_shape))
|
||||
if reset.shape() != Shape.cast(orig_shape):
|
||||
raise ValueError("Constant returned by {!r}.const() must have the shape that "
|
||||
"it casts to, {!r}, and not {!r}"
|
||||
.format(orig_shape, Shape.cast(orig_shape),
|
||||
reset.shape()))
|
||||
else:
|
||||
try:
|
||||
reset = Const.cast(reset or 0)
|
||||
except TypeError:
|
||||
raise TypeError("Reset value must be a constant-castable expression, not {!r}"
|
||||
.format(orig_reset))
|
||||
if orig_reset not in (None, 0, -1): # Avoid false positives for all-zeroes and all-ones
|
||||
if reset.shape().signed and not self.signed:
|
||||
warnings.warn(
|
||||
message="Reset value {!r} is signed, but the signal shape is {!r}"
|
||||
|
|
|
|||
|
|
@ -201,29 +201,44 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
|
|||
"""
|
||||
return View(self, target)
|
||||
|
||||
def _convert_to_int(self, value):
|
||||
"""Convert ``value``, which may be a dict or an array of field values, to an integer using
|
||||
the representation defined by this layout.
|
||||
def const(self, init):
|
||||
"""Convert a constant initializer to a constant.
|
||||
|
||||
This method is private because Amaranth does not currently have a concept of
|
||||
a constant initializer; this requires an RFC. It will be renamed or removed
|
||||
in a future version.
|
||||
Converts ``init``, which may be a sequence or a mapping of field values, to a constant.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`Const`
|
||||
A constant that has the same value as a view with this layout that was initialized with
|
||||
an all-zero value and had every field assigned to the corresponding value in the order
|
||||
in which they appear in ``init``.
|
||||
"""
|
||||
if isinstance(value, Mapping):
|
||||
iterator = value.items()
|
||||
elif isinstance(value, Sequence):
|
||||
iterator = enumerate(value)
|
||||
if init is None:
|
||||
iterator = iter(())
|
||||
elif isinstance(init, Mapping):
|
||||
iterator = init.items()
|
||||
elif isinstance(init, Sequence):
|
||||
iterator = enumerate(init)
|
||||
else:
|
||||
raise TypeError("Layout initializer must be a mapping or a sequence, not {!r}"
|
||||
.format(value))
|
||||
raise TypeError("Layout constant initializer must be a mapping or a sequence, not {!r}"
|
||||
.format(init))
|
||||
|
||||
int_value = 0
|
||||
for key, key_value in iterator:
|
||||
field = self[key]
|
||||
if isinstance(field.shape, Layout):
|
||||
key_value = field.shape._convert_to_int(key_value)
|
||||
int_value |= Const(key_value, Shape.cast(field.shape)).value << field.offset
|
||||
return int_value
|
||||
cast_field_shape = Shape.cast(field.shape)
|
||||
if isinstance(field.shape, ShapeCastable):
|
||||
key_value = Const.cast(field.shape.const(key_value))
|
||||
if key_value.shape() != cast_field_shape:
|
||||
raise ValueError("Constant returned by {!r}.const() must have the shape that "
|
||||
"it casts to, {!r}, and not {!r}"
|
||||
.format(field.shape, cast_field_shape,
|
||||
key_value.shape()))
|
||||
else:
|
||||
key_value = Const(key_value, cast_field_shape)
|
||||
int_value &= ~(((1 << cast_field_shape.width) - 1) << field.offset)
|
||||
int_value |= key_value.value << field.offset
|
||||
return Const(int_value, self.as_shape())
|
||||
|
||||
|
||||
class StructLayout(Layout):
|
||||
|
|
@ -617,13 +632,9 @@ class View(ValueCastable):
|
|||
"the {} bit(s) wide view layout"
|
||||
.format(len(cast_target), cast_layout.size))
|
||||
else:
|
||||
if reset is None:
|
||||
reset = 0
|
||||
else:
|
||||
reset = cast_layout._convert_to_int(reset)
|
||||
if reset_less is None:
|
||||
reset_less = False
|
||||
cast_target = Signal(cast_layout, name=name, reset=reset, reset_less=reset_less,
|
||||
cast_target = Signal(layout, name=name, reset=reset, reset_less=reset_less,
|
||||
attrs=attrs, decoder=decoder, src_loc_at=src_loc_at + 1)
|
||||
self.__orig_layout = layout
|
||||
self.__layout = cast_layout
|
||||
|
|
@ -774,6 +785,9 @@ class _AggregateMeta(ShapeCastable, type):
|
|||
.format(cls.__module__, cls.__qualname__))
|
||||
return cls.__layout
|
||||
|
||||
def const(cls, init):
|
||||
return cls.as_shape().const(init)
|
||||
|
||||
|
||||
class _Aggregate(View, metaclass=_AggregateMeta):
|
||||
def __init__(self, target=None, *, name=None, reset=None, reset_less=None,
|
||||
|
|
|
|||
|
|
@ -137,6 +137,17 @@ class EnumMeta(ShapeCastable, py_enum.EnumMeta):
|
|||
return value
|
||||
return super().__call__(value)
|
||||
|
||||
def const(cls, init):
|
||||
# Same considerations apply as above.
|
||||
if init is None:
|
||||
# Signal with unspecified reset value passes ``None`` to :meth:`const`.
|
||||
# Before RFC 9 was implemented, the unspecified reset value was 0, so this keeps
|
||||
# the old behavior intact.
|
||||
member = cls(0)
|
||||
else:
|
||||
member = cls(init)
|
||||
return Const(member.value, cls.as_shape())
|
||||
|
||||
|
||||
class Enum(py_enum.Enum, metaclass=EnumMeta):
|
||||
"""Subclass of the standard :class:`enum.Enum` that has :class:`EnumMeta` as
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue