Implement RFC 9: Constant initialization for shape-castable objects.

See amaranth-lang/rfcs#9 and #771.
This commit is contained in:
Catherine 2023-05-12 22:04:35 +00:00
parent ea5a150155
commit 54d5c4c047
7 changed files with 181 additions and 42 deletions

View file

@ -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}"

View file

@ -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,

View file

@ -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