Implement RFC 51: Add ShapeCastable.from_bits
and amaranth.lib.data.Const
.
Co-authored-by: Catherine <whitequark@whitequark.org>
This commit is contained in:
parent
598cf8db28
commit
d6bf47d549
|
@ -243,6 +243,10 @@ class ShapeCastable:
|
||||||
if cls.__call__ is ShapeCastable.__call__:
|
if cls.__call__ is ShapeCastable.__call__:
|
||||||
raise TypeError(f"Class '{cls.__name__}' deriving from 'ShapeCastable' must override "
|
raise TypeError(f"Class '{cls.__name__}' deriving from 'ShapeCastable' must override "
|
||||||
f"the '__call__' method")
|
f"the '__call__' method")
|
||||||
|
if cls.from_bits is ShapeCastable.from_bits:
|
||||||
|
warnings.warn(f"Class '{cls.__name__}' deriving from 'ShapeCastable' does not override "
|
||||||
|
f"the 'from_bits' method, which will be required in Amaranth 0.6",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
|
||||||
# The signatures and definitions of these methods are weird because they are present here for
|
# The signatures and definitions of these methods are weird because they are present here for
|
||||||
# documentation (and error checking above) purpose only and should not affect control flow.
|
# documentation (and error checking above) purpose only and should not affect control flow.
|
||||||
|
@ -308,6 +312,40 @@ class ShapeCastable:
|
||||||
"""
|
"""
|
||||||
return super().const(*args, **kwargs) # :nocov:
|
return super().const(*args, **kwargs) # :nocov:
|
||||||
|
|
||||||
|
def from_bits(self, raw):
|
||||||
|
"""Lift a bit pattern to a higher-level representation.
|
||||||
|
|
||||||
|
This method is called by the Amaranth language to lift :py:`raw`, which is an :class:`int`,
|
||||||
|
to a higher-level representation, which may be any object accepted by :meth:`const`.
|
||||||
|
Most importantly, the simulator calls this method when the value of a shape-castable
|
||||||
|
object is retrieved.
|
||||||
|
|
||||||
|
For any valid bit pattern :py:`raw`, the following condition must hold:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
Const.cast(self.const(self.from_bits(raw))).value == raw
|
||||||
|
|
||||||
|
While :meth:`const` will usually return an Amaranth value or a custom value-castable
|
||||||
|
object that is convenient to use while constructing the design, this method will usually
|
||||||
|
return a Python object that is convenient to use while simulating the design. While not
|
||||||
|
constrained here, these objects should have the same type whenever feasible.
|
||||||
|
|
||||||
|
This method may also be called by code that is not a part of the Amaranth language.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
unspecified type
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
Exception
|
||||||
|
When the bit pattern isn't valid. This exception must be propagated by callers,
|
||||||
|
either directly or as a cause of another exception. While not constrained here,
|
||||||
|
usually the exception class will be :exc:`ValueError`.
|
||||||
|
"""
|
||||||
|
return raw
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
"""__call__(obj)
|
"""__call__(obj)
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,17 @@ from abc import ABCMeta, abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
import warnings
|
import warnings
|
||||||
|
import operator
|
||||||
|
|
||||||
from amaranth._utils import final
|
from amaranth._utils import final
|
||||||
from amaranth.hdl import *
|
from amaranth.hdl import *
|
||||||
from amaranth.hdl._repr import *
|
from amaranth.hdl._repr import *
|
||||||
from amaranth.hdl._ast import ShapeCastable, ValueCastable
|
from amaranth import hdl
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Field", "Layout", "StructLayout", "UnionLayout", "ArrayLayout", "FlexibleLayout",
|
"Field", "Layout", "StructLayout", "UnionLayout", "ArrayLayout", "FlexibleLayout",
|
||||||
"View", "Struct", "Union",
|
"View", "Const", "Struct", "Union",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ class Field:
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
shape : :ref:`shape-like <lang-shapelike>`
|
shape : :class:`.ShapeLike`
|
||||||
Shape of the field. When initialized or assigned, the object is stored as-is.
|
Shape of the field. When initialized or assigned, the object is stored as-is.
|
||||||
offset : :class:`int`, >=0
|
offset : :class:`int`, >=0
|
||||||
Index of the least significant bit of the field.
|
Index of the least significant bit of the field.
|
||||||
|
@ -55,14 +56,14 @@ class Field:
|
||||||
def width(self):
|
def width(self):
|
||||||
"""Width of the field.
|
"""Width of the field.
|
||||||
|
|
||||||
This property should be used over ``self.shape.width`` because ``self.shape`` can be
|
This property should be used over :py:`self.shape.width` because :py:`self.shape` can be
|
||||||
an arbitrary :ref:`shape-like <lang-shapelike>` object, which may not have
|
an arbitrary :ref:`shape-like <lang-shapelike>` object, which may not have
|
||||||
a ``width`` property.
|
a :py:`width` property.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`int`
|
:class:`int`
|
||||||
``Shape.cast(self.shape).width``
|
:py:`Shape.cast(self.shape).width`
|
||||||
"""
|
"""
|
||||||
return Shape.cast(self.shape).width
|
return Shape.cast(self.shape).width
|
||||||
|
|
||||||
|
@ -98,15 +99,15 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
|
||||||
def cast(obj):
|
def cast(obj):
|
||||||
"""Cast a :ref:`shape-like <lang-shapelike>` object to a layout.
|
"""Cast a :ref:`shape-like <lang-shapelike>` object to a layout.
|
||||||
|
|
||||||
This method performs a subset of the operations done by :meth:`Shape.cast`; it will
|
This method performs a subset of the operations done by :meth:`.Shape.cast`; it will
|
||||||
recursively call ``.as_shape()``, but only until a layout is returned.
|
recursively call :py:`.as_shape()`, but only until a layout is returned.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
TypeError
|
TypeError
|
||||||
If ``obj`` cannot be converted to a :class:`Layout` instance.
|
If :py:`obj` cannot be converted to a :class:`Layout` instance.
|
||||||
RecursionError
|
RecursionError
|
||||||
If ``obj.as_shape()`` returns ``obj``.
|
If :py:`obj.as_shape()` returns :py:`obj`.
|
||||||
"""
|
"""
|
||||||
while isinstance(obj, ShapeCastable):
|
while isinstance(obj, ShapeCastable):
|
||||||
if isinstance(obj, Layout):
|
if isinstance(obj, Layout):
|
||||||
|
@ -138,12 +139,12 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`Field`
|
:class:`Field`
|
||||||
The field associated with ``key``.
|
The field associated with :py:`key`.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
KeyError
|
KeyError
|
||||||
If there is no field associated with ``key``.
|
If there is no field associated with :py:`key`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -162,8 +163,8 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`Shape`
|
:class:`.Shape`
|
||||||
``unsigned(self.size)``
|
:py:`unsigned(self.size)`
|
||||||
"""
|
"""
|
||||||
return unsigned(self.size)
|
return unsigned(self.size)
|
||||||
|
|
||||||
|
@ -191,21 +192,21 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`View`
|
:class:`View`
|
||||||
``View(self, target)``
|
:py:`View(self, target)`
|
||||||
"""
|
"""
|
||||||
return View(self, target)
|
return View(self, target)
|
||||||
|
|
||||||
def const(self, init):
|
def const(self, init):
|
||||||
"""Convert a constant initializer to a constant.
|
"""Convert a constant initializer to a constant.
|
||||||
|
|
||||||
Converts ``init``, which may be a sequence or a mapping of field values, to a constant.
|
Converts :py:`init`, which may be a sequence or a mapping of field values, to a constant.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`Const`
|
:class:`Const`
|
||||||
A constant that has the same value as a view with this layout that was initialized with
|
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
|
an all-zero value and had every field assigned to the corresponding value in the order
|
||||||
in which they appear in ``init``.
|
in which they appear in :py:`init`.
|
||||||
"""
|
"""
|
||||||
if init is None:
|
if init is None:
|
||||||
iterator = iter(())
|
iterator = iter(())
|
||||||
|
@ -222,18 +223,30 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
|
||||||
field = self[key]
|
field = self[key]
|
||||||
cast_field_shape = Shape.cast(field.shape)
|
cast_field_shape = Shape.cast(field.shape)
|
||||||
if isinstance(field.shape, ShapeCastable):
|
if isinstance(field.shape, ShapeCastable):
|
||||||
key_value = Const.cast(field.shape.const(key_value))
|
key_value = hdl.Const.cast(field.shape.const(key_value))
|
||||||
if key_value.shape() != cast_field_shape:
|
if key_value.shape() != cast_field_shape:
|
||||||
raise ValueError("Constant returned by {!r}.const() must have the shape that "
|
raise ValueError("Constant returned by {!r}.const() must have the shape that "
|
||||||
"it casts to, {!r}, and not {!r}"
|
"it casts to, {!r}, and not {!r}"
|
||||||
.format(field.shape, cast_field_shape,
|
.format(field.shape, cast_field_shape,
|
||||||
key_value.shape()))
|
key_value.shape()))
|
||||||
elif not isinstance(key_value, Const):
|
elif not isinstance(key_value, hdl.Const):
|
||||||
key_value = Const(key_value, cast_field_shape)
|
key_value = hdl.Const(key_value, cast_field_shape)
|
||||||
mask = ((1 << cast_field_shape.width) - 1) << field.offset
|
mask = ((1 << cast_field_shape.width) - 1) << field.offset
|
||||||
int_value &= ~mask
|
int_value &= ~mask
|
||||||
int_value |= (key_value.value << field.offset) & mask
|
int_value |= (key_value.value << field.offset) & mask
|
||||||
return View(self, Const(int_value, self.as_shape()))
|
return Const(self, int_value)
|
||||||
|
|
||||||
|
def from_bits(self, raw):
|
||||||
|
"""Convert a bit pattern to a constant.
|
||||||
|
|
||||||
|
Converts :py:`raw`, which is an :class:`int`, to a constant.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`Const`
|
||||||
|
:py:`Const(self, raw)`
|
||||||
|
"""
|
||||||
|
return Const(self, raw)
|
||||||
|
|
||||||
def _value_repr(self, value):
|
def _value_repr(self, value):
|
||||||
yield Repr(FormatInt(), value)
|
yield Repr(FormatInt(), value)
|
||||||
|
@ -279,7 +292,7 @@ class StructLayout(Layout):
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
members : mapping of :class:`str` to :ref:`shape-like <lang-shapelike>`
|
members : mapping of :class:`str` to :class:`.ShapeLike`
|
||||||
Dictionary of structure members.
|
Dictionary of structure members.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -350,7 +363,7 @@ class UnionLayout(Layout):
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
members : mapping of :class:`str` to :ref:`shape-like <lang-shapelike>`
|
members : mapping of :class:`str` to :class:`.ShapeLike`
|
||||||
Dictionary of union members.
|
Dictionary of union members.
|
||||||
"""
|
"""
|
||||||
def __init__(self, members):
|
def __init__(self, members):
|
||||||
|
@ -425,7 +438,7 @@ class ArrayLayout(Layout):
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
elem_shape : :ref:`shape-like <lang-shapelike>`
|
elem_shape : :class:`.ShapeLike`
|
||||||
Shape of an individual element.
|
Shape of an individual element.
|
||||||
length : :class:`int`
|
length : :class:`int`
|
||||||
Amount of elements.
|
Amount of elements.
|
||||||
|
@ -573,7 +586,10 @@ class View(ValueCastable):
|
||||||
Creating a view
|
Creating a view
|
||||||
###############
|
###############
|
||||||
|
|
||||||
A view must be created using an explicitly provided layout and target. To create a new :class:`Signal` that is wrapped in a :class:`View` with a given ``layout``, use ``Signal(layout, ...)``, which for a :class:`Layout` is equivalent to ``View(layout, Signal(...))``.
|
A view must be created using an explicitly provided layout and target. To create a new
|
||||||
|
:class:`Signal` that is wrapped in a :class:`View` with a given :py:`layout`, use
|
||||||
|
:py:`Signal(layout, ...)`, which for a :class:`Layout` is equivalent to
|
||||||
|
:py:`View(layout, Signal(...))`.
|
||||||
|
|
||||||
Accessing a view
|
Accessing a view
|
||||||
################
|
################
|
||||||
|
@ -582,46 +598,46 @@ class View(ValueCastable):
|
||||||
corresponding to the field with that index or name, which is itself either a value or
|
corresponding to the field with that index or name, which is itself either a value or
|
||||||
a value-castable object. If the shape of the field is a :class:`Layout`, it will be
|
a value-castable object. If the shape of the field is a :class:`Layout`, it will be
|
||||||
a :class:`View`; if it is a class deriving from :class:`Struct` or :class:`Union`, it
|
a :class:`View`; if it is a class deriving from :class:`Struct` or :class:`Union`, it
|
||||||
will be an instance of that data class; if it is another
|
will be an instance of that data class; if it is another :ref:`shape-like <lang-shapelike>`
|
||||||
:ref:`shape-like <lang-shapelike>` object implementing ``__call__``, it will be
|
object implementing :meth:`~.ShapeCastable.__call__`, it will be the result of calling that
|
||||||
the result of calling that method.
|
method.
|
||||||
|
|
||||||
Slicing a view whose layout is an :class:`ArrayLayout` can be done with an index that is
|
Slicing a view whose layout is an :class:`ArrayLayout` can be done with an index that is
|
||||||
an Amaranth value instead of a constant integer. The returned element is chosen dynamically
|
an Amaranth value rather than a constant integer. The returned element is chosen dynamically
|
||||||
in that case.
|
in that case.
|
||||||
|
|
||||||
A view can only be compared for equality with another view of the same layout,
|
A view can only be compared for equality with another view or constant with the same layout,
|
||||||
returning a single-bit value. No other operators are supported on views. If required,
|
returning a single-bit :class:`.Value`. No other operators are supported. A view can be
|
||||||
a view can be converted back to its underlying value via :meth:`as_value`.
|
lowered to a :class:`.Value` using :meth:`as_value`.
|
||||||
|
|
||||||
Custom view classes
|
Custom view classes
|
||||||
###################
|
###################
|
||||||
|
|
||||||
The :class:`View` class can be inherited from to define additional properties or methods on
|
The :class:`View` class can be inherited from to define additional properties or methods on
|
||||||
a view. The only two names that are reserved on instances of :class:`View` are :meth:`as_value`
|
a view. The only three names that are reserved on instances of :class:`View` and :class:`Const`
|
||||||
and :meth:`eq`, leaving the rest to the developer. The :class:`Struct` and :class:`Union`
|
are :meth:`as_value`, :meth:`Const.as_bits`, and :meth:`eq`, leaving the rest to the developer.
|
||||||
classes provided in this module are subclasses of :class:`View` that also provide a concise way
|
The :class:`Struct` and :class:`Union` classes provided in this module are subclasses of
|
||||||
to define a layout.
|
:class:`View` that also provide a concise way to define a layout.
|
||||||
"""
|
"""
|
||||||
def __init__(self, layout, target):
|
def __init__(self, layout, target):
|
||||||
try:
|
try:
|
||||||
cast_layout = Layout.cast(layout)
|
cast_layout = Layout.cast(layout)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise TypeError("View layout must be a layout, not {!r}"
|
raise TypeError("Layout of a view must be a Layout, not {!r}"
|
||||||
.format(layout)) from e
|
.format(layout)) from e
|
||||||
try:
|
try:
|
||||||
cast_target = Value.cast(target)
|
cast_target = Value.cast(target)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise TypeError("View target must be a value-castable object, not {!r}"
|
raise TypeError("Target of a view must be a value-castable object, not {!r}"
|
||||||
.format(target)) from e
|
.format(target)) from e
|
||||||
if len(cast_target) != cast_layout.size:
|
if len(cast_target) != cast_layout.size:
|
||||||
raise ValueError("View target is {} bit(s) wide, which is not compatible with "
|
raise ValueError("Target of a view is {} bit(s) wide, which is not compatible with "
|
||||||
"the {} bit(s) wide view layout"
|
"its {} bit(s) wide layout"
|
||||||
.format(len(cast_target), cast_layout.size))
|
.format(len(cast_target), cast_layout.size))
|
||||||
for name, field in cast_layout:
|
for name, field in cast_layout:
|
||||||
if isinstance(name, str) and name[0] != "_" and hasattr(type(self), name):
|
if isinstance(name, str) and name[0] != "_" and hasattr(type(self), name):
|
||||||
warnings.warn("View layout includes a field {!r} that will be shadowed by "
|
warnings.warn("Layout of a view includes a field {!r} that will be shadowed by "
|
||||||
"the view attribute '{}.{}.{}'"
|
"the attribute '{}.{}.{}'"
|
||||||
.format(name, type(self).__module__, type(self).__qualname__, name),
|
.format(name, type(self).__module__, type(self).__qualname__, name),
|
||||||
SyntaxWarning, stacklevel=2)
|
SyntaxWarning, stacklevel=2)
|
||||||
self.__orig_layout = layout
|
self.__orig_layout = layout
|
||||||
|
@ -634,7 +650,7 @@ class View(ValueCastable):
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`Layout`
|
:class:`Layout`
|
||||||
The ``layout`` provided when constructing the view.
|
The :py:`layout` provided when constructing the view.
|
||||||
"""
|
"""
|
||||||
return self.__orig_layout
|
return self.__orig_layout
|
||||||
|
|
||||||
|
@ -643,8 +659,8 @@ class View(ValueCastable):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`Value`
|
:class:`.Value`
|
||||||
The ``target`` provided when constructing the view, or the :class:`Signal` that
|
The :py:`target` provided when constructing the view, or the :class:`Signal` that
|
||||||
was created.
|
was created.
|
||||||
"""
|
"""
|
||||||
return self.__target
|
return self.__target
|
||||||
|
@ -654,52 +670,53 @@ class View(ValueCastable):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`Assign`
|
:class:`.Assign`
|
||||||
``self.as_value().eq(other)``
|
:py:`self.as_value().eq(other)`
|
||||||
"""
|
"""
|
||||||
return self.as_value().eq(other)
|
return self.as_value().eq(other)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
"""Slice the underlying value.
|
"""Slice the underlying value.
|
||||||
|
|
||||||
A field corresponding to ``key`` is looked up in the layout. If the field's shape is
|
A field corresponding to :py:`key` is looked up in the layout. If the field's shape is
|
||||||
a shape-castable object that has a ``__call__`` method, it is called and the result is
|
a shape-castable object that has a :meth:`~.ShapeCastable.__call__` method, it is called and
|
||||||
returned. Otherwise, ``as_shape`` is called repeatedly on the shape until either an object
|
the result is returned. Otherwise, :meth:`~.ShapeCastable.as_shape` is called repeatedly on
|
||||||
with a ``__call__`` method is reached, or a ``Shape`` is returned. In the latter case,
|
the shape until either an object with a :meth:`~.ShapeCastable.__call__` method is reached,
|
||||||
returns an unspecified Amaranth expression with the right shape.
|
or a :class:`.Shape` is returned. In the latter case, returns an unspecified Amaranth
|
||||||
|
expression with the right shape.
|
||||||
|
|
||||||
Arguments
|
Arguments
|
||||||
---------
|
---------
|
||||||
key : :class:`str` or :class:`int` or :class:`ValueCastable`
|
key : :class:`str` or :class:`int` or :class:`.ValueCastable`
|
||||||
Name or index of a field.
|
Name or index of a field.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
:class:`Value` or :class:`ValueCastable`, inout
|
:class:`.Value` or :class:`.ValueCastable`, :ref:`assignable <lang-assignable>`
|
||||||
A slice of the underlying value defined by the field.
|
A slice of the underlying value defined by the field.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
KeyError
|
:exc:`KeyError`
|
||||||
If the layout does not define a field corresponding to ``key``.
|
If the layout does not define a field corresponding to :py:`key`.
|
||||||
TypeError
|
:exc:`TypeError`
|
||||||
If ``key`` is a value-castable object, but the layout of the view is not
|
If :py:`key` is a value-castable object, but the layout of the view is not
|
||||||
a :class:`ArrayLayout`.
|
an :class:`ArrayLayout`.
|
||||||
TypeError
|
:exc:`TypeError`
|
||||||
If ``ShapeCastable.__call__`` does not return a value or a value-castable object.
|
If :meth:`.ShapeCastable.__call__` does not return a value or a value-castable object.
|
||||||
"""
|
"""
|
||||||
if isinstance(self.__layout, ArrayLayout):
|
if isinstance(self.__layout, ArrayLayout):
|
||||||
if not isinstance(key, (int, Value, ValueCastable)):
|
if not isinstance(key, (int, Value, ValueCastable)):
|
||||||
raise TypeError("Views with array layout may only be indexed with an integer "
|
raise TypeError(
|
||||||
"or a value, not {!r}"
|
f"View with array layout may only be indexed with an integer or a value, "
|
||||||
.format(key))
|
f"not {key!r}")
|
||||||
shape = self.__layout.elem_shape
|
shape = self.__layout.elem_shape
|
||||||
value = self.__target.word_select(key, Shape.cast(self.__layout.elem_shape).width)
|
value = self.__target.word_select(key, Shape.cast(self.__layout.elem_shape).width)
|
||||||
else:
|
else:
|
||||||
if isinstance(key, (Value, ValueCastable)):
|
if isinstance(key, (Value, ValueCastable)):
|
||||||
raise TypeError("Only views with array layout, not {!r}, may be indexed "
|
raise TypeError(
|
||||||
"with a value"
|
f"Only views with array layout, not {self.__layout!r}, may be indexed with "
|
||||||
.format(self.__layout))
|
f"a value")
|
||||||
field = self.__layout[key]
|
field = self.__layout[key]
|
||||||
shape = field.shape
|
shape = field.shape
|
||||||
value = self.__target[field.offset:field.offset + field.width]
|
value = self.__target[field.offset:field.offset + field.width]
|
||||||
|
@ -708,9 +725,9 @@ class View(ValueCastable):
|
||||||
if isinstance(shape, ShapeCastable):
|
if isinstance(shape, ShapeCastable):
|
||||||
value = shape(value)
|
value = shape(value)
|
||||||
if not isinstance(value, (Value, ValueCastable)):
|
if not isinstance(value, (Value, ValueCastable)):
|
||||||
raise TypeError("{!r}.__call__() must return a value or "
|
raise TypeError(
|
||||||
"a value-castable object, not {!r}"
|
f"{shape!r}.__call__() must return a value or a value-castable object, not "
|
||||||
.format(shape, value))
|
f"{value!r}")
|
||||||
return value
|
return value
|
||||||
if Shape.cast(shape).signed:
|
if Shape.cast(shape).signed:
|
||||||
return value.as_signed()
|
return value.as_signed()
|
||||||
|
@ -720,40 +737,48 @@ class View(ValueCastable):
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
"""Access a field of the underlying value.
|
"""Access a field of the underlying value.
|
||||||
|
|
||||||
Returns ``self[name]``.
|
Returns :py:`self[name]`.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
AttributeError
|
:exc:`AttributeError`
|
||||||
If the layout does not define a field called ``name``, or if ``name`` starts with
|
If the layout does not define a field called :py:`name`, or if :py:`name` starts with
|
||||||
an underscore.
|
an underscore.
|
||||||
"""
|
"""
|
||||||
if isinstance(self.__layout, ArrayLayout):
|
if isinstance(self.__layout, ArrayLayout):
|
||||||
raise AttributeError("View of {!r} with an array layout does not have fields"
|
raise AttributeError(
|
||||||
.format(self.__target))
|
f"View with an array layout does not have fields")
|
||||||
try:
|
try:
|
||||||
item = self[name]
|
item = self[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError("View of {!r} does not have a field {!r}; "
|
raise AttributeError(
|
||||||
"did you mean one of: {}?"
|
f"View with layout {self.__layout!r} does not have a field {name!r}; did you mean "
|
||||||
.format(self.__target, name,
|
f"one of: {', '.join(repr(name) for name, field in self.__layout)}?")
|
||||||
", ".join(repr(name)
|
|
||||||
for name, field in self.__layout)))
|
|
||||||
if name.startswith("_"):
|
if name.startswith("_"):
|
||||||
raise AttributeError("View of {!r} field {!r} has a reserved name and may only be "
|
raise AttributeError(
|
||||||
"accessed by indexing"
|
f"Field {name!r} of view with layout {self.__layout!r} has a reserved name and "
|
||||||
.format(self.__target, name))
|
f"may only be accessed by indexing")
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, View) or self.__layout != other.__layout:
|
if isinstance(other, View) and self.__layout == other.__layout:
|
||||||
raise TypeError(f"View of {self.__layout!r} can only be compared to another view of the same layout, not {other!r}")
|
return self.__target == other.__target
|
||||||
return self.__target == other.__target
|
elif isinstance(other, Const) and self.__layout == other._Const__layout:
|
||||||
|
return self.__target == other.as_value()
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
f"View with layout {self.__layout!r} can only be compared to another view or "
|
||||||
|
f"constant with the same layout, not {other!r}")
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
if not isinstance(other, View) or self.__layout != other.__layout:
|
if isinstance(other, View) and self.__layout == other.__layout:
|
||||||
raise TypeError(f"View of {self.__layout!r} can only be compared to another view of the same layout, not {other!r}")
|
return self.__target != other.__target
|
||||||
return self.__target != other.__target
|
elif isinstance(other, Const) and self.__layout == other._Const__layout:
|
||||||
|
return self.__target != other.as_value()
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
f"View with layout {self.__layout!r} can only be compared to another view or "
|
||||||
|
f"constant with the same layout, not {other!r}")
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
raise TypeError("Cannot perform arithmetic operations on a View")
|
raise TypeError("Cannot perform arithmetic operations on a View")
|
||||||
|
@ -789,6 +814,223 @@ class View(ValueCastable):
|
||||||
return f"{self.__class__.__name__}({self.__layout!r}, {self.__target!r})"
|
return f"{self.__class__.__name__}({self.__layout!r}, {self.__target!r})"
|
||||||
|
|
||||||
|
|
||||||
|
class Const(ValueCastable):
|
||||||
|
"""A constant value viewed through the lens of a layout.
|
||||||
|
|
||||||
|
The :class:`Const` class is similar to the :class:`View` class, except that its target is
|
||||||
|
a specific bit pattern and operations on it return constants.
|
||||||
|
|
||||||
|
Creating a constant
|
||||||
|
###################
|
||||||
|
|
||||||
|
A constant can be created from a :class:`dict` or :class:`list` of field values using
|
||||||
|
:meth:`Layout.const`, or from a bit pattern using :meth:`Layout.from_bits`.
|
||||||
|
|
||||||
|
Accessing a constant
|
||||||
|
####################
|
||||||
|
|
||||||
|
Slicing a constant or accessing its attributes returns a part of the underlying value
|
||||||
|
corresponding to the field with that index or name. If the shape of the field is
|
||||||
|
a :class:`Layout`, the returned value is a :class:`Const`; if it is a different
|
||||||
|
:ref:`shape-like <lang-shapelike>` object implementing :meth:`~.ShapeCastable.from_bits`,
|
||||||
|
it will be the result of calling that method; otherwise, it is an :class:`int`.
|
||||||
|
|
||||||
|
Slicing a constant whose layout is an :class:`ArrayLayout` can be done with an index that is
|
||||||
|
an Amaranth value rather than a constant integer. The returned element is chosen dynamically
|
||||||
|
in that case, and the resulting value will be a :class:`View` instead of a :class:`Const`.
|
||||||
|
|
||||||
|
A :class:`Const` can only be compared for equality with another constant or view that has
|
||||||
|
the same layout. When compared with another constant, the result will be a :class:`bool`.
|
||||||
|
When compared with a view, the result will be a single-bit :class:`.Value`. No other operators
|
||||||
|
are supported. A constant can be lowered to a :class:`.Value` using :meth:`as_value`, or to
|
||||||
|
its underlying bit pattern using :meth:`as_bits`.
|
||||||
|
"""
|
||||||
|
def __init__(self, layout, target):
|
||||||
|
try:
|
||||||
|
cast_layout = Layout.cast(layout)
|
||||||
|
except TypeError as e:
|
||||||
|
raise TypeError(f"Layout of a constant must be a Layout, not {layout!r}") from e
|
||||||
|
try:
|
||||||
|
target = operator.index(target)
|
||||||
|
except TypeError as e:
|
||||||
|
raise TypeError(f"Target of a constant must be an int, not {target!r}") from e
|
||||||
|
if target not in range(1 << cast_layout.size):
|
||||||
|
raise ValueError(f"Target of a constant does not fit in {cast_layout.size} bit(s)")
|
||||||
|
for name, field in cast_layout:
|
||||||
|
if isinstance(name, str) and name[0] != "_" and hasattr(type(self), name):
|
||||||
|
warnings.warn("Layout of a constant includes a field {!r} that will be shadowed by "
|
||||||
|
"the attribute '{}.{}.{}'"
|
||||||
|
.format(name, type(self).__module__, type(self).__qualname__, name),
|
||||||
|
SyntaxWarning, stacklevel=2)
|
||||||
|
self.__orig_layout = layout
|
||||||
|
self.__layout = cast_layout
|
||||||
|
self.__target = target
|
||||||
|
|
||||||
|
def shape(self):
|
||||||
|
"""Get layout of this constant.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`Layout`
|
||||||
|
The :py:`layout` provided when constructing the constant.
|
||||||
|
"""
|
||||||
|
return self.__orig_layout
|
||||||
|
|
||||||
|
def as_bits(self):
|
||||||
|
"""Get underlying bit pattern.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`int`
|
||||||
|
The :py:`target` provided when constructing the constant.
|
||||||
|
"""
|
||||||
|
return self.__target
|
||||||
|
|
||||||
|
def as_value(self):
|
||||||
|
"""Convert to a value.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`.Const`
|
||||||
|
The bit pattern of this constant, as a :class:`.Value`.
|
||||||
|
"""
|
||||||
|
return hdl.Const(self.__target, self.__layout.size)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""Slice the underlying value.
|
||||||
|
|
||||||
|
A field corresponding to :py:`key` is looked up in the layout. If the field's shape is
|
||||||
|
a shape-castable object that has a :meth:`~.ShapeCastable.from_bits` method, returns
|
||||||
|
the result of calling that method. Otherwise, returns an :class:`int`.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
key : :class:`str` or :class:`int` or :class:`.ValueCastable`
|
||||||
|
Name or index of a field.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
unspecified type or :class:`int`
|
||||||
|
A slice of the underlying value defined by the field.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
:exc:`KeyError`
|
||||||
|
If the layout does not define a field corresponding to :py:`key`.
|
||||||
|
:exc:`TypeError`
|
||||||
|
If :py:`key` is a value-castable object, but the layout of the constant is not
|
||||||
|
an :class:`ArrayLayout`.
|
||||||
|
:exc:`Exception`
|
||||||
|
If the bit pattern of the field is not valid according to
|
||||||
|
:meth:`.ShapeCastable.from_bits`. Usually this will be a :exc:`ValueError`.
|
||||||
|
"""
|
||||||
|
if isinstance(self.__layout, ArrayLayout):
|
||||||
|
if isinstance(key, (Value, ValueCastable)):
|
||||||
|
return View(self.__layout, self.as_value())[key]
|
||||||
|
if not isinstance(key, int):
|
||||||
|
raise TypeError(
|
||||||
|
f"Constant with array layout may only be indexed with an integer or a value, "
|
||||||
|
f"not {key!r}")
|
||||||
|
shape = self.__layout.elem_shape
|
||||||
|
elem_width = Shape.cast(self.__layout.elem_shape).width
|
||||||
|
value = (self.__target >> key * elem_width) & ((1 << elem_width) - 1)
|
||||||
|
else:
|
||||||
|
if isinstance(key, (Value, ValueCastable)):
|
||||||
|
raise TypeError(
|
||||||
|
f"Only constants with array layout, not {self.__layout!r}, may be indexed with "
|
||||||
|
f"a value")
|
||||||
|
field = self.__layout[key]
|
||||||
|
shape = field.shape
|
||||||
|
value = (self.__target >> field.offset) & ((1 << field.width) - 1)
|
||||||
|
# Field guarantees that the shape-castable object is well-formed, so there is no need
|
||||||
|
# to handle erroneous cases here.
|
||||||
|
if isinstance(shape, ShapeCastable):
|
||||||
|
return shape.from_bits(value)
|
||||||
|
return hdl.Const(value, Shape.cast(shape)).value
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
"""Access a field of the underlying value.
|
||||||
|
|
||||||
|
Returns :py:`self[name]`.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
:exc:`AttributeError`
|
||||||
|
If the layout does not define a field called :py:`name`, or if :py:`name` starts with
|
||||||
|
an underscore.
|
||||||
|
:exc:`Exception`
|
||||||
|
If the bit pattern of the field is not valid according to
|
||||||
|
:meth:`.ShapeCastable.from_bits`. Usually this will be a :exc:`ValueError`.
|
||||||
|
"""
|
||||||
|
if isinstance(self.__layout, ArrayLayout):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Constant with an array layout does not have fields")
|
||||||
|
try:
|
||||||
|
item = self[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(
|
||||||
|
f"Constant with layout {self.__layout!r} does not have a field {name!r}; did you mean "
|
||||||
|
f"one of: {', '.join(repr(name) for name, field in self.__layout)}?")
|
||||||
|
if name.startswith("_"):
|
||||||
|
raise AttributeError(
|
||||||
|
f"Field {name!r} of constant with layout {self.__layout!r} has a reserved name and "
|
||||||
|
f"may only be accessed by indexing")
|
||||||
|
return item
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, View) and self.__layout == other._View__layout:
|
||||||
|
return self.as_value() == other._View__target
|
||||||
|
elif isinstance(other, Const) and self.__layout == other.__layout:
|
||||||
|
return self.__target == other.__target
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
f"Constant with layout {self.__layout!r} can only be compared to another view or "
|
||||||
|
f"constant with the same layout, not {other!r}")
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if isinstance(other, View) and self.__layout == other._View__layout:
|
||||||
|
return self.as_value() != other._View__target
|
||||||
|
elif isinstance(other, Const) and self.__layout == other.__layout:
|
||||||
|
return self.__target != other.__target
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
f"Constant with layout {self.__layout!r} can only be compared to another view or "
|
||||||
|
f"constant with the same layout, not {other!r}")
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
raise TypeError("Cannot perform arithmetic operations on a lib.data.Const")
|
||||||
|
|
||||||
|
__radd__ = __add__
|
||||||
|
__sub__ = __add__
|
||||||
|
__rsub__ = __add__
|
||||||
|
__mul__ = __add__
|
||||||
|
__rmul__ = __add__
|
||||||
|
__floordiv__ = __add__
|
||||||
|
__rfloordiv__ = __add__
|
||||||
|
__mod__ = __add__
|
||||||
|
__rmod__ = __add__
|
||||||
|
__lshift__ = __add__
|
||||||
|
__rlshift__ = __add__
|
||||||
|
__rshift__ = __add__
|
||||||
|
__rrshift__ = __add__
|
||||||
|
__lt__ = __add__
|
||||||
|
__le__ = __add__
|
||||||
|
__gt__ = __add__
|
||||||
|
__ge__ = __add__
|
||||||
|
|
||||||
|
def __and__(self, other):
|
||||||
|
raise TypeError("Cannot perform bitwise operations on a lib.data.Const")
|
||||||
|
|
||||||
|
__rand__ = __and__
|
||||||
|
__or__ = __and__
|
||||||
|
__ror__ = __and__
|
||||||
|
__xor__ = __and__
|
||||||
|
__rxor__ = __and__
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}({self.__layout!r}, {self.__target!r})"
|
||||||
|
|
||||||
|
|
||||||
class _AggregateMeta(ShapeCastable, type):
|
class _AggregateMeta(ShapeCastable, type):
|
||||||
def __new__(metacls, name, bases, namespace):
|
def __new__(metacls, name, bases, namespace):
|
||||||
if "__annotations__" not in namespace:
|
if "__annotations__" not in namespace:
|
||||||
|
@ -847,6 +1089,9 @@ class _AggregateMeta(ShapeCastable, type):
|
||||||
fields.update(init or {})
|
fields.update(init or {})
|
||||||
return cls.as_shape().const(fields)
|
return cls.as_shape().const(fields)
|
||||||
|
|
||||||
|
def from_bits(cls, bits):
|
||||||
|
return cls.as_shape().from_bits(bits)
|
||||||
|
|
||||||
def _value_repr(cls, value):
|
def _value_repr(cls, value):
|
||||||
return cls.__layout._value_repr(value)
|
return cls.__layout._value_repr(value)
|
||||||
|
|
||||||
|
@ -881,7 +1126,7 @@ class Struct(View, metaclass=_AggregateMeta):
|
||||||
def is_subnormal(self):
|
def is_subnormal(self):
|
||||||
return self.exponent == 0
|
return self.exponent == 0
|
||||||
|
|
||||||
The ``IEEE754Single`` class itself can be used where a :ref:`shape <lang-shapes>` is expected:
|
The :py:`IEEE754Single` class itself can be used where a :ref:`shape <lang-shapes>` is expected:
|
||||||
|
|
||||||
.. doctest::
|
.. doctest::
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,9 @@ class EnumMeta(ShapeCastable, py_enum.EnumMeta):
|
||||||
member = cls(init)
|
member = cls(init)
|
||||||
return cls(Const(member.value, cls.as_shape()))
|
return cls(Const(member.value, cls.as_shape()))
|
||||||
|
|
||||||
|
def from_bits(cls, bits):
|
||||||
|
return cls(bits)
|
||||||
|
|
||||||
def _value_repr(cls, value):
|
def _value_repr(cls, value):
|
||||||
yield Repr(FormatEnum(cls), value)
|
yield Repr(FormatEnum(cls), value)
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ Implemented RFCs
|
||||||
.. _RFC 45: https://amaranth-lang.org/rfcs/0045-lib-memory.html
|
.. _RFC 45: https://amaranth-lang.org/rfcs/0045-lib-memory.html
|
||||||
.. _RFC 46: https://amaranth-lang.org/rfcs/0046-shape-range-1.html
|
.. _RFC 46: https://amaranth-lang.org/rfcs/0046-shape-range-1.html
|
||||||
.. _RFC 50: https://amaranth-lang.org/rfcs/0050-print.html
|
.. _RFC 50: https://amaranth-lang.org/rfcs/0050-print.html
|
||||||
|
.. _RFC 51: https://amaranth-lang.org/rfcs/0051-const-from-bits.html
|
||||||
.. _RFC 53: https://amaranth-lang.org/rfcs/0053-ioport.html
|
.. _RFC 53: https://amaranth-lang.org/rfcs/0053-ioport.html
|
||||||
.. _RFC 55: https://amaranth-lang.org/rfcs/0055-lib-io.html
|
.. _RFC 55: https://amaranth-lang.org/rfcs/0055-lib-io.html
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ Implemented RFCs
|
||||||
* `RFC 45`_: Move ``hdl.Memory`` to ``lib.Memory``
|
* `RFC 45`_: Move ``hdl.Memory`` to ``lib.Memory``
|
||||||
* `RFC 46`_: Change ``Shape.cast(range(1))`` to ``unsigned(0)``
|
* `RFC 46`_: Change ``Shape.cast(range(1))`` to ``unsigned(0)``
|
||||||
* `RFC 50`_: ``Print`` statement and string formatting
|
* `RFC 50`_: ``Print`` statement and string formatting
|
||||||
|
* `RFC 51`_: Add ``ShapeCastable.from_bits`` and ``amaranth.lib.data.Const``
|
||||||
* `RFC 53`_: Low-level I/O primitives
|
* `RFC 53`_: Low-level I/O primitives
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,6 +72,7 @@ Language changes
|
||||||
* Added: :class:`Slice` objects have been made const-castable.
|
* Added: :class:`Slice` objects have been made const-castable.
|
||||||
* Added: :func:`amaranth.utils.ceil_log2`, :func:`amaranth.utils.exact_log2`. (`RFC 17`_)
|
* Added: :func:`amaranth.utils.ceil_log2`, :func:`amaranth.utils.exact_log2`. (`RFC 17`_)
|
||||||
* Added: :class:`Format` objects, :class:`Print` statements, messages in :class:`Assert`, :class:`Assume` and :class:`Cover`. (`RFC 50`_)
|
* Added: :class:`Format` objects, :class:`Print` statements, messages in :class:`Assert`, :class:`Assume` and :class:`Cover`. (`RFC 50`_)
|
||||||
|
* Added: :meth:`ShapeCastable.from_bits` method. (`RFC 51`_)
|
||||||
* Added: IO values, :class:`IOPort` objects, :class:`IOBufferInstance` objects. (`RFC 53`_)
|
* Added: IO values, :class:`IOPort` objects, :class:`IOBufferInstance` objects. (`RFC 53`_)
|
||||||
* Changed: ``m.Case()`` with no patterns is never active instead of always active. (`RFC 39`_)
|
* Changed: ``m.Case()`` with no patterns is never active instead of always active. (`RFC 39`_)
|
||||||
* Changed: ``Value.matches()`` with no patterns is ``Const(0)`` instead of ``Const(1)``. (`RFC 39`_)
|
* Changed: ``Value.matches()`` with no patterns is ``Const(0)`` instead of ``Const(1)``. (`RFC 39`_)
|
||||||
|
@ -94,6 +97,8 @@ Standard library changes
|
||||||
.. currentmodule:: amaranth.lib
|
.. currentmodule:: amaranth.lib
|
||||||
|
|
||||||
* Added: :mod:`amaranth.lib.memory`. (`RFC 45`_)
|
* Added: :mod:`amaranth.lib.memory`. (`RFC 45`_)
|
||||||
|
* Added: :class:`amaranth.lib.data.Const` class. (`RFC 51`_)
|
||||||
|
* Changed: :meth:`amaranth.lib.data.Layout.const` returns a :class:`amaranth.lib.data.Const`, not a view (`RFC 51`_)
|
||||||
* Added: :class:`amaranth.lib.io.SingleEndedPort`, :class:`amaranth.lib.io.DifferentialPort`. (`RFC 55`_)
|
* Added: :class:`amaranth.lib.io.SingleEndedPort`, :class:`amaranth.lib.io.DifferentialPort`. (`RFC 55`_)
|
||||||
* Removed: (deprecated in 0.4) :mod:`amaranth.lib.scheduler`. (`RFC 19`_)
|
* Removed: (deprecated in 0.4) :mod:`amaranth.lib.scheduler`. (`RFC 19`_)
|
||||||
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.FIFOInterface` with ``fwft=False``. (`RFC 20`_)
|
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.FIFOInterface` with ``fwft=False``. (`RFC 20`_)
|
||||||
|
|
|
@ -221,7 +221,7 @@ Modeling structured data
|
||||||
========================
|
========================
|
||||||
|
|
||||||
.. autoclass:: Field
|
.. autoclass:: Field
|
||||||
.. autoclass:: Layout
|
.. autoclass:: Layout()
|
||||||
|
|
||||||
|
|
||||||
Common data layouts
|
Common data layouts
|
||||||
|
@ -237,6 +237,7 @@ Data views
|
||||||
==========
|
==========
|
||||||
|
|
||||||
.. autoclass:: View
|
.. autoclass:: View
|
||||||
|
.. autoclass:: Const
|
||||||
|
|
||||||
|
|
||||||
Data classes
|
Data classes
|
||||||
|
|
|
@ -178,6 +178,9 @@ class MockShapeCastable(ShapeCastable):
|
||||||
def const(self, init):
|
def const(self, init):
|
||||||
return Const(init, self.dest)
|
return Const(init, self.dest)
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return bits
|
||||||
|
|
||||||
|
|
||||||
class ShapeCastableTestCase(FHDLTestCase):
|
class ShapeCastableTestCase(FHDLTestCase):
|
||||||
def test_no_override(self):
|
def test_no_override(self):
|
||||||
|
@ -208,6 +211,25 @@ class ShapeCastableTestCase(FHDLTestCase):
|
||||||
r"^Can't instantiate abstract class ShapeCastable$"):
|
r"^Can't instantiate abstract class ShapeCastable$"):
|
||||||
ShapeCastable()
|
ShapeCastable()
|
||||||
|
|
||||||
|
def test_no_from_bits(self):
|
||||||
|
with self.assertWarnsRegex(DeprecationWarning,
|
||||||
|
r"^Class 'MockShapeCastableNoFromBits' deriving from 'ShapeCastable' does "
|
||||||
|
r"not override the 'from_bits' method, which will be required in Amaranth 0.6$"):
|
||||||
|
class MockShapeCastableNoFromBits(ShapeCastable):
|
||||||
|
def __init__(self, dest):
|
||||||
|
self.dest = dest
|
||||||
|
|
||||||
|
def as_shape(self):
|
||||||
|
return self.dest
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def const(self, init):
|
||||||
|
return Const(init, self.dest)
|
||||||
|
|
||||||
|
self.assertEqual(MockShapeCastableNoFromBits(unsigned(2)).from_bits(123), 123)
|
||||||
|
|
||||||
|
|
||||||
class ShapeLikeTestCase(FHDLTestCase):
|
class ShapeLikeTestCase(FHDLTestCase):
|
||||||
def test_construct(self):
|
def test_construct(self):
|
||||||
|
@ -514,6 +536,9 @@ class ConstTestCase(FHDLTestCase):
|
||||||
def const(self, init):
|
def const(self, init):
|
||||||
return MockConstValue(init)
|
return MockConstValue(init)
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return bits
|
||||||
|
|
||||||
s = Const(10, MockConstShape())
|
s = Const(10, MockConstShape())
|
||||||
self.assertIsInstance(s, MockConstValue)
|
self.assertIsInstance(s, MockConstValue)
|
||||||
self.assertEqual(s.value, 10)
|
self.assertEqual(s.value, 10)
|
||||||
|
@ -1186,6 +1211,9 @@ class SignalTestCase(FHDLTestCase):
|
||||||
def const(self, init):
|
def const(self, init):
|
||||||
return int(init, 16)
|
return int(init, 16)
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return bits
|
||||||
|
|
||||||
s1 = Signal(CastableFromHex(), init="aa")
|
s1 = Signal(CastableFromHex(), init="aa")
|
||||||
self.assertEqual(s1.init, 0xaa)
|
self.assertEqual(s1.init, 0xaa)
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,9 @@ class MockShapeCastable(ShapeCastable):
|
||||||
def const(self, init):
|
def const(self, init):
|
||||||
return Const(init, self.shape)
|
return Const(init, self.shape)
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return bits
|
||||||
|
|
||||||
|
|
||||||
class FieldTestCase(TestCase):
|
class FieldTestCase(TestCase):
|
||||||
def test_construct(self):
|
def test_construct(self):
|
||||||
|
@ -417,6 +420,9 @@ class LayoutTestCase(FHDLTestCase):
|
||||||
def const(self, init):
|
def const(self, init):
|
||||||
return int(init, 16)
|
return int(init, 16)
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return bits
|
||||||
|
|
||||||
sl = data.StructLayout({"f": CastableFromHex()})
|
sl = data.StructLayout({"f": CastableFromHex()})
|
||||||
self.assertRepr(sl.const({"f": "aa"}).as_value(), "(const 8'd170)")
|
self.assertRepr(sl.const({"f": "aa"}).as_value(), "(const 8'd170)")
|
||||||
|
|
||||||
|
@ -467,13 +473,13 @@ class ViewTestCase(FHDLTestCase):
|
||||||
|
|
||||||
def test_layout_wrong(self):
|
def test_layout_wrong(self):
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
r"^View layout must be a layout, not <.+?>$"):
|
r"^Layout of a view must be a Layout, not <.+?>$"):
|
||||||
data.View(object(), Signal(1))
|
data.View(object(), Signal(1))
|
||||||
|
|
||||||
def test_layout_conflict_with_attr(self):
|
def test_layout_conflict_with_attr(self):
|
||||||
with self.assertWarnsRegex(SyntaxWarning,
|
with self.assertWarnsRegex(SyntaxWarning,
|
||||||
r"^View layout includes a field 'as_value' that will be shadowed by the view "
|
r"^Layout of a view includes a field 'as_value' that will be shadowed by "
|
||||||
r"attribute 'amaranth\.lib\.data\.View\.as_value'$"):
|
r"the attribute 'amaranth\.lib\.data\.View\.as_value'$"):
|
||||||
data.View(data.StructLayout({"as_value": unsigned(1)}), Signal(1))
|
data.View(data.StructLayout({"as_value": unsigned(1)}), Signal(1))
|
||||||
|
|
||||||
def test_layout_conflict_with_attr_derived(self):
|
def test_layout_conflict_with_attr_derived(self):
|
||||||
|
@ -481,20 +487,20 @@ class ViewTestCase(FHDLTestCase):
|
||||||
def foo(self):
|
def foo(self):
|
||||||
pass
|
pass
|
||||||
with self.assertWarnsRegex(SyntaxWarning,
|
with self.assertWarnsRegex(SyntaxWarning,
|
||||||
r"^View layout includes a field 'foo' that will be shadowed by the view "
|
r"^Layout of a view includes a field 'foo' that will be shadowed by "
|
||||||
r"attribute 'tests\.test_lib_data\.ViewTestCase\."
|
r"the attribute 'tests\.test_lib_data\.ViewTestCase\."
|
||||||
r"test_layout_conflict_with_attr_derived\.<locals>.DerivedView\.foo'$"):
|
r"test_layout_conflict_with_attr_derived\.<locals>.DerivedView\.foo'$"):
|
||||||
DerivedView(data.StructLayout({"foo": unsigned(1)}), Signal(1))
|
DerivedView(data.StructLayout({"foo": unsigned(1)}), Signal(1))
|
||||||
|
|
||||||
def test_target_wrong_type(self):
|
def test_target_wrong_type(self):
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
r"^View target must be a value-castable object, not <.+?>$"):
|
r"^Target of a view must be a value-castable object, not <.+?>$"):
|
||||||
data.View(data.StructLayout({}), object())
|
data.View(data.StructLayout({}), object())
|
||||||
|
|
||||||
def test_target_wrong_size(self):
|
def test_target_wrong_size(self):
|
||||||
with self.assertRaisesRegex(ValueError,
|
with self.assertRaisesRegex(ValueError,
|
||||||
r"^View target is 2 bit\(s\) wide, which is not compatible with the 1 bit\(s\) "
|
r"^Target of a view is 2 bit\(s\) wide, which is not compatible with its 1 bit\(s\) "
|
||||||
r"wide view layout$"):
|
r"wide layout$"):
|
||||||
data.View(data.StructLayout({"a": unsigned(1)}), Signal(2))
|
data.View(data.StructLayout({"a": unsigned(1)}), Signal(2))
|
||||||
|
|
||||||
def test_getitem(self):
|
def test_getitem(self):
|
||||||
|
@ -540,6 +546,9 @@ class ViewTestCase(FHDLTestCase):
|
||||||
def const(self, init):
|
def const(self, init):
|
||||||
return Const(init, 2)
|
return Const(init, 2)
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return bits
|
||||||
|
|
||||||
v = Signal(data.StructLayout({
|
v = Signal(data.StructLayout({
|
||||||
"f": Reverser()
|
"f": Reverser()
|
||||||
}))
|
}))
|
||||||
|
@ -557,6 +566,9 @@ class ViewTestCase(FHDLTestCase):
|
||||||
def const(self, init):
|
def const(self, init):
|
||||||
return Const(init, 2)
|
return Const(init, 2)
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return bits
|
||||||
|
|
||||||
v = Signal(data.StructLayout({
|
v = Signal(data.StructLayout({
|
||||||
"f": WrongCastable()
|
"f": WrongCastable()
|
||||||
}))
|
}))
|
||||||
|
@ -606,14 +618,13 @@ class ViewTestCase(FHDLTestCase):
|
||||||
|
|
||||||
def test_attr_wrong_missing(self):
|
def test_attr_wrong_missing(self):
|
||||||
with self.assertRaisesRegex(AttributeError,
|
with self.assertRaisesRegex(AttributeError,
|
||||||
r"^View of \(sig \$signal\) does not have a field 'a'; "
|
r"^View with layout .* does not have a field 'a'; did you mean one of: 'b', 'c'\?$"):
|
||||||
r"did you mean one of: 'b', 'c'\?$"):
|
|
||||||
Signal(data.StructLayout({"b": unsigned(1), "c": signed(1)})).a
|
Signal(data.StructLayout({"b": unsigned(1), "c": signed(1)})).a
|
||||||
|
|
||||||
def test_attr_wrong_reserved(self):
|
def test_attr_wrong_reserved(self):
|
||||||
with self.assertRaisesRegex(AttributeError,
|
with self.assertRaisesRegex(AttributeError,
|
||||||
r"^View of \(sig \$signal\) field '_c' has a reserved name "
|
r"^Field '_c' of view with layout .* has a reserved name and may only be accessed "
|
||||||
r"and may only be accessed by indexing$"):
|
r"by indexing$"):
|
||||||
Signal(data.StructLayout({"_c": signed(1)}))._c
|
Signal(data.StructLayout({"_c": signed(1)}))._c
|
||||||
|
|
||||||
def test_signal_like(self):
|
def test_signal_like(self):
|
||||||
|
@ -623,13 +634,13 @@ class ViewTestCase(FHDLTestCase):
|
||||||
|
|
||||||
def test_bug_837_array_layout_getitem_str(self):
|
def test_bug_837_array_layout_getitem_str(self):
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
r"^Views with array layout may only be indexed with an integer or a value, "
|
r"^View with array layout may only be indexed with an integer or a value, "
|
||||||
r"not 'init'$"):
|
r"not 'init'$"):
|
||||||
Signal(data.ArrayLayout(unsigned(1), 1), init=[0])["init"]
|
Signal(data.ArrayLayout(unsigned(1), 1), init=[0])["init"]
|
||||||
|
|
||||||
def test_bug_837_array_layout_getattr(self):
|
def test_bug_837_array_layout_getattr(self):
|
||||||
with self.assertRaisesRegex(AttributeError,
|
with self.assertRaisesRegex(AttributeError,
|
||||||
r"^View of \(sig \$signal\) with an array layout does not have fields$"):
|
r"^View with an array layout does not have fields$"):
|
||||||
Signal(data.ArrayLayout(unsigned(1), 1), init=[0]).init
|
Signal(data.ArrayLayout(unsigned(1), 1), init=[0]).init
|
||||||
|
|
||||||
def test_eq(self):
|
def test_eq(self):
|
||||||
|
@ -639,16 +650,20 @@ class ViewTestCase(FHDLTestCase):
|
||||||
self.assertRepr(s1 == s2, "(== (sig s1) (sig s2))")
|
self.assertRepr(s1 == s2, "(== (sig s1) (sig s2))")
|
||||||
self.assertRepr(s1 != s2, "(!= (sig s1) (sig s2))")
|
self.assertRepr(s1 != s2, "(!= (sig s1) (sig s2))")
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
r"^View of .* can only be compared to another view of the same layout, not .*$"):
|
r"^View with layout .* can only be compared to another view or constant "
|
||||||
|
r"with the same layout, not .*$"):
|
||||||
s1 == s3
|
s1 == s3
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
r"^View of .* can only be compared to another view of the same layout, not .*$"):
|
r"^View with layout .* can only be compared to another view or constant "
|
||||||
|
r"with the same layout, not .*$"):
|
||||||
s1 != s3
|
s1 != s3
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
r"^View of .* can only be compared to another view of the same layout, not .*$"):
|
r"^View with layout .* can only be compared to another view or constant "
|
||||||
|
r"with the same layout, not .*$"):
|
||||||
s1 == Const(0, 2)
|
s1 == Const(0, 2)
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
r"^View of .* can only be compared to another view of the same layout, not .*$"):
|
r"^View with layout .* can only be compared to another view or constant "
|
||||||
|
r"with the same layout, not .*$"):
|
||||||
s1 != Const(0, 2)
|
s1 != Const(0, 2)
|
||||||
|
|
||||||
def test_operator(self):
|
def test_operator(self):
|
||||||
|
@ -690,6 +705,251 @@ class ViewTestCase(FHDLTestCase):
|
||||||
self.assertRepr(s1, "View(StructLayout({'a': unsigned(2)}), (sig s1))")
|
self.assertRepr(s1, "View(StructLayout({'a': unsigned(2)}), (sig s1))")
|
||||||
|
|
||||||
|
|
||||||
|
class ConstTestCase(FHDLTestCase):
|
||||||
|
def test_construct(self):
|
||||||
|
c = data.Const(data.StructLayout({"a": unsigned(1), "b": unsigned(2)}), 5)
|
||||||
|
self.assertRepr(Value.cast(c), "(const 3'd5)")
|
||||||
|
self.assertEqual(c.shape(), data.StructLayout({"a": unsigned(1), "b": unsigned(2)}))
|
||||||
|
self.assertEqual(c.as_bits(), 5)
|
||||||
|
self.assertEqual(c["a"], 1)
|
||||||
|
self.assertEqual(c["b"], 2)
|
||||||
|
|
||||||
|
def test_construct_const(self):
|
||||||
|
c = Const({"a": 1, "b": 2}, data.StructLayout({"a": unsigned(1), "b": unsigned(2)}))
|
||||||
|
self.assertRepr(Const.cast(c), "(const 3'd5)")
|
||||||
|
self.assertEqual(c.a, 1)
|
||||||
|
self.assertEqual(c.b, 2)
|
||||||
|
|
||||||
|
def test_layout_wrong(self):
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Layout of a constant must be a Layout, not <.+?>$"):
|
||||||
|
data.Const(object(), 1)
|
||||||
|
|
||||||
|
def test_layout_conflict_with_attr(self):
|
||||||
|
with self.assertWarnsRegex(SyntaxWarning,
|
||||||
|
r"^Layout of a constant includes a field 'as_value' that will be shadowed by "
|
||||||
|
r"the attribute 'amaranth\.lib\.data\.Const\.as_value'$"):
|
||||||
|
data.Const(data.StructLayout({"as_value": unsigned(1)}), 1)
|
||||||
|
|
||||||
|
def test_layout_conflict_with_attr_derived(self):
|
||||||
|
class DerivedConst(data.Const):
|
||||||
|
def foo(self):
|
||||||
|
pass
|
||||||
|
with self.assertWarnsRegex(SyntaxWarning,
|
||||||
|
r"^Layout of a constant includes a field 'foo' that will be shadowed by "
|
||||||
|
r"the attribute 'tests\.test_lib_data\.ConstTestCase\."
|
||||||
|
r"test_layout_conflict_with_attr_derived\.<locals>.DerivedConst\.foo'$"):
|
||||||
|
DerivedConst(data.StructLayout({"foo": unsigned(1)}), 1)
|
||||||
|
|
||||||
|
def test_target_wrong_type(self):
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Target of a constant must be an int, not <.+?>$"):
|
||||||
|
data.Const(data.StructLayout({}), object())
|
||||||
|
|
||||||
|
def test_target_wrong_value(self):
|
||||||
|
with self.assertRaisesRegex(ValueError,
|
||||||
|
r"^Target of a constant does not fit in 1 bit\(s\)$"):
|
||||||
|
data.Const(data.StructLayout({"a": unsigned(1)}), 2)
|
||||||
|
|
||||||
|
def test_getitem(self):
|
||||||
|
l = data.StructLayout({
|
||||||
|
"u": unsigned(1),
|
||||||
|
"v": unsigned(1)
|
||||||
|
})
|
||||||
|
v = data.Const(data.StructLayout({
|
||||||
|
"a": unsigned(2),
|
||||||
|
"s": data.StructLayout({
|
||||||
|
"b": unsigned(1),
|
||||||
|
"c": unsigned(3)
|
||||||
|
}),
|
||||||
|
"p": 1,
|
||||||
|
"q": signed(1),
|
||||||
|
"r": data.ArrayLayout(unsigned(2), 2),
|
||||||
|
"t": data.ArrayLayout(data.StructLayout({
|
||||||
|
"u": unsigned(1),
|
||||||
|
"v": unsigned(1)
|
||||||
|
}), 2),
|
||||||
|
}), 0xabcd)
|
||||||
|
cv = Value.cast(v)
|
||||||
|
i = Signal(1)
|
||||||
|
self.assertEqual(cv.shape(), unsigned(16))
|
||||||
|
self.assertEqual(v["a"], 1)
|
||||||
|
self.assertEqual(v["s"]["b"], 1)
|
||||||
|
self.assertEqual(v["s"]["c"], 1)
|
||||||
|
self.assertEqual(v["p"], 1)
|
||||||
|
self.assertEqual(v["q"], -1)
|
||||||
|
self.assertEqual(v["r"][0], 3)
|
||||||
|
self.assertEqual(v["r"][1], 2)
|
||||||
|
self.assertRepr(v["r"][i], "(part (const 4'd11) (sig i) 2 2)")
|
||||||
|
self.assertEqual(v["t"][0], data.Const(l, 2))
|
||||||
|
self.assertEqual(v["t"][1], data.Const(l, 2))
|
||||||
|
self.assertEqual(v["t"][0]["u"], 0)
|
||||||
|
self.assertEqual(v["t"][1]["v"], 1)
|
||||||
|
|
||||||
|
def test_getitem_custom_call(self):
|
||||||
|
class Reverser(ShapeCastable):
|
||||||
|
def as_shape(self):
|
||||||
|
return unsigned(2)
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def const(self, init):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def from_bits(self, bits):
|
||||||
|
return float(bits) / 2
|
||||||
|
|
||||||
|
v = data.Const(data.StructLayout({
|
||||||
|
"f": Reverser()
|
||||||
|
}), 3)
|
||||||
|
self.assertEqual(v.f, 1.5)
|
||||||
|
|
||||||
|
def test_index_wrong_missing(self):
|
||||||
|
with self.assertRaisesRegex(KeyError,
|
||||||
|
r"^'a'$"):
|
||||||
|
data.Const(data.StructLayout({}), 0)["a"]
|
||||||
|
|
||||||
|
def test_index_wrong_struct_dynamic(self):
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Only constants with array layout, not StructLayout\(\{\}\), may be indexed "
|
||||||
|
r"with a value$"):
|
||||||
|
data.Const(data.StructLayout({}), 0)[Signal(1)]
|
||||||
|
|
||||||
|
def test_getattr(self):
|
||||||
|
v = data.Const(data.UnionLayout({
|
||||||
|
"a": unsigned(2),
|
||||||
|
"s": data.StructLayout({
|
||||||
|
"b": unsigned(1),
|
||||||
|
"c": unsigned(3)
|
||||||
|
}),
|
||||||
|
"p": 1,
|
||||||
|
"q": signed(1),
|
||||||
|
}), 13)
|
||||||
|
cv = Const.cast(v)
|
||||||
|
i = Signal(1)
|
||||||
|
self.assertEqual(cv.shape(), unsigned(4))
|
||||||
|
self.assertEqual(v.a, 1)
|
||||||
|
self.assertEqual(v.s.b, 1)
|
||||||
|
self.assertEqual(v.s.c, 6)
|
||||||
|
self.assertEqual(v.p, 1)
|
||||||
|
self.assertEqual(v.q, -1)
|
||||||
|
|
||||||
|
def test_getattr_reserved(self):
|
||||||
|
v = data.Const(data.UnionLayout({
|
||||||
|
"_a": unsigned(2)
|
||||||
|
}), 2)
|
||||||
|
self.assertEqual(v["_a"], 2)
|
||||||
|
|
||||||
|
def test_attr_wrong_missing(self):
|
||||||
|
with self.assertRaisesRegex(AttributeError,
|
||||||
|
r"^Constant with layout .* does not have a field 'a'; did you mean one of: "
|
||||||
|
r"'b', 'c'\?$"):
|
||||||
|
data.Const(data.StructLayout({"b": unsigned(1), "c": signed(1)}), 0).a
|
||||||
|
|
||||||
|
def test_attr_wrong_reserved(self):
|
||||||
|
with self.assertRaisesRegex(AttributeError,
|
||||||
|
r"^Field '_c' of constant with layout .* has a reserved name and may only be "
|
||||||
|
r"accessed by indexing$"):
|
||||||
|
data.Const(data.StructLayout({"_c": signed(1)}), 0)._c
|
||||||
|
|
||||||
|
def test_bug_837_array_layout_getitem_str(self):
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Constant with array layout may only be indexed with an integer or a value, "
|
||||||
|
r"not 'init'$"):
|
||||||
|
data.Const(data.ArrayLayout(unsigned(1), 1), 0)["init"]
|
||||||
|
|
||||||
|
def test_bug_837_array_layout_getattr(self):
|
||||||
|
with self.assertRaisesRegex(AttributeError,
|
||||||
|
r"^Constant with an array layout does not have fields$"):
|
||||||
|
data.Const(data.ArrayLayout(unsigned(1), 1), 0).init
|
||||||
|
|
||||||
|
def test_eq(self):
|
||||||
|
c1 = data.Const(data.StructLayout({"a": unsigned(2)}), 1)
|
||||||
|
c2 = data.Const(data.StructLayout({"a": unsigned(2)}), 1)
|
||||||
|
c3 = data.Const(data.StructLayout({"a": unsigned(2)}), 2)
|
||||||
|
c4 = data.Const(data.StructLayout({"a": unsigned(1), "b": unsigned(1)}), 2)
|
||||||
|
s1 = Signal(data.StructLayout({"a": unsigned(2)}))
|
||||||
|
self.assertTrue(c1 == c2)
|
||||||
|
self.assertFalse(c1 != c2)
|
||||||
|
self.assertFalse(c1 == c3)
|
||||||
|
self.assertTrue(c1 != c3)
|
||||||
|
self.assertRepr(c1 == s1, "(== (const 2'd1) (sig s1))")
|
||||||
|
self.assertRepr(c1 != s1, "(!= (const 2'd1) (sig s1))")
|
||||||
|
self.assertRepr(s1 == c1, "(== (sig s1) (const 2'd1))")
|
||||||
|
self.assertRepr(s1 != c1, "(!= (sig s1) (const 2'd1))")
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Constant with layout .* can only be compared to another view or constant with "
|
||||||
|
r"the same layout, not .*$"):
|
||||||
|
c1 == c4
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Constant with layout .* can only be compared to another view or constant with "
|
||||||
|
r"the same layout, not .*$"):
|
||||||
|
c1 != c4
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^View with layout .* can only be compared to another view or constant with "
|
||||||
|
r"the same layout, not .*$"):
|
||||||
|
s1 == c4
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^View with layout .* can only be compared to another view or constant with "
|
||||||
|
r"the same layout, not .*$"):
|
||||||
|
s1 != c4
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Constant with layout .* can only be compared to another view or constant with "
|
||||||
|
r"the same layout, not .*$"):
|
||||||
|
c4 == s1
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Constant with layout .* can only be compared to another view or constant with "
|
||||||
|
r"the same layout, not .*$"):
|
||||||
|
c4 != s1
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Constant with layout .* can only be compared to another view or constant with "
|
||||||
|
r"the same layout, not .*$"):
|
||||||
|
c1 == Const(0, 2)
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Constant with layout .* can only be compared to another view or constant with "
|
||||||
|
r"the same layout, not .*$"):
|
||||||
|
c1 != Const(0, 2)
|
||||||
|
|
||||||
|
def test_operator(self):
|
||||||
|
s1 = data.Const(data.StructLayout({"a": unsigned(2)}), 2)
|
||||||
|
s2 = Signal(unsigned(2))
|
||||||
|
for op in [
|
||||||
|
operator.__add__,
|
||||||
|
operator.__sub__,
|
||||||
|
operator.__mul__,
|
||||||
|
operator.__floordiv__,
|
||||||
|
operator.__mod__,
|
||||||
|
operator.__lshift__,
|
||||||
|
operator.__rshift__,
|
||||||
|
operator.__lt__,
|
||||||
|
operator.__le__,
|
||||||
|
operator.__gt__,
|
||||||
|
operator.__ge__,
|
||||||
|
]:
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Cannot perform arithmetic operations on a lib.data.Const$"):
|
||||||
|
op(s1, s2)
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Cannot perform arithmetic operations on a lib.data.Const$"):
|
||||||
|
op(s2, s1)
|
||||||
|
for op in [
|
||||||
|
operator.__and__,
|
||||||
|
operator.__or__,
|
||||||
|
operator.__xor__,
|
||||||
|
]:
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Cannot perform bitwise operations on a lib.data.Const$"):
|
||||||
|
op(s1, s2)
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Cannot perform bitwise operations on a lib.data.Const$"):
|
||||||
|
op(s2, s1)
|
||||||
|
|
||||||
|
def test_repr(self):
|
||||||
|
s1 = data.Const(data.StructLayout({"a": unsigned(2)}), 2)
|
||||||
|
self.assertRepr(s1, "Const(StructLayout({'a': unsigned(2)}), 2)")
|
||||||
|
|
||||||
|
|
||||||
class StructTestCase(FHDLTestCase):
|
class StructTestCase(FHDLTestCase):
|
||||||
def test_construct(self):
|
def test_construct(self):
|
||||||
class S(data.Struct):
|
class S(data.Struct):
|
||||||
|
@ -815,6 +1075,13 @@ class StructTestCase(FHDLTestCase):
|
||||||
s2 = Signal.like(s1)
|
s2 = Signal.like(s1)
|
||||||
self.assertEqual(s2.shape(), S)
|
self.assertEqual(s2.shape(), S)
|
||||||
|
|
||||||
|
def test_from_bits(self):
|
||||||
|
class S(data.Struct):
|
||||||
|
a: 1
|
||||||
|
c = S.from_bits(1)
|
||||||
|
self.assertIsInstance(c, data.Const)
|
||||||
|
self.assertEqual(c.a, 1)
|
||||||
|
|
||||||
|
|
||||||
class UnionTestCase(FHDLTestCase):
|
class UnionTestCase(FHDLTestCase):
|
||||||
def test_construct(self):
|
def test_construct(self):
|
||||||
|
|
|
@ -110,6 +110,15 @@ class EnumTestCase(FHDLTestCase):
|
||||||
self.assertRepr(EnumA.const(10), "EnumView(EnumA, (const 8'd10))")
|
self.assertRepr(EnumA.const(10), "EnumView(EnumA, (const 8'd10))")
|
||||||
self.assertRepr(EnumA.const(EnumA.A), "EnumView(EnumA, (const 8'd10))")
|
self.assertRepr(EnumA.const(EnumA.A), "EnumView(EnumA, (const 8'd10))")
|
||||||
|
|
||||||
|
def test_from_bits(self):
|
||||||
|
class EnumA(Enum, shape=2):
|
||||||
|
A = 0
|
||||||
|
B = 1
|
||||||
|
C = 2
|
||||||
|
self.assertIs(EnumA.from_bits(2), EnumA.C)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
EnumA.from_bits(3)
|
||||||
|
|
||||||
def test_shape_implicit_wrong_in_concat(self):
|
def test_shape_implicit_wrong_in_concat(self):
|
||||||
class EnumA(Enum):
|
class EnumA(Enum):
|
||||||
A = 0
|
A = 0
|
||||||
|
|
Loading…
Reference in a new issue