Implement RFC 15: Lifting shape-castable objects.
See amaranth-lang/rfcs#15 and #784. Note that this RFC breaks the existing syntax for initializing a view with a new signal. Instances of `View(layout)` *must* be changed to `Signal(layout)`.
This commit is contained in:
parent
e9975587bf
commit
7d99981d57
|
@ -1,4 +1,5 @@
|
|||
from abc import ABCMeta, abstractmethod
|
||||
import inspect
|
||||
import warnings
|
||||
import functools
|
||||
from collections import OrderedDict
|
||||
|
@ -45,6 +46,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, "__call__") and inspect.isfunction(cls.__call__)):
|
||||
raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
|
||||
f"the `__call__` method")
|
||||
if not hasattr(cls, "const"):
|
||||
raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
|
||||
f"the `const` method")
|
||||
|
@ -949,8 +953,16 @@ class Repl(Value):
|
|||
return "(repl {!r} {})".format(self.value, self.count)
|
||||
|
||||
|
||||
class _SignalMeta(ABCMeta):
|
||||
def __call__(cls, shape=None, src_loc_at=0, **kwargs):
|
||||
signal = super().__call__(shape, **kwargs, src_loc_at=src_loc_at + 1)
|
||||
if isinstance(shape, ShapeCastable):
|
||||
return shape(signal)
|
||||
return signal
|
||||
|
||||
|
||||
# @final
|
||||
class Signal(Value, DUID):
|
||||
class Signal(Value, DUID, metaclass=_SignalMeta):
|
||||
"""A varying integer value.
|
||||
|
||||
Parameters
|
||||
|
|
|
@ -390,6 +390,13 @@ class UnionLayout(Layout):
|
|||
"""
|
||||
return max((field.width for field in self._fields.values()), default=0)
|
||||
|
||||
def const(self, init):
|
||||
if init is not None and len(init) > 1:
|
||||
raise ValueError("Initializer for at most one field can be provided for "
|
||||
"a union layout (specified: {})"
|
||||
.format(", ".join(init.keys())))
|
||||
return super().const(init)
|
||||
|
||||
def __repr__(self):
|
||||
return f"UnionLayout({self.members!r})"
|
||||
|
||||
|
@ -609,19 +616,12 @@ class View(ValueCastable):
|
|||
classes provided in this module are subclasses of :class:`View` that also provide a concise way
|
||||
to define a layout.
|
||||
"""
|
||||
def __init__(self, layout, target=None, *, name=None, reset=None, reset_less=None,
|
||||
attrs=None, decoder=None, src_loc_at=0):
|
||||
def __init__(self, layout, target):
|
||||
try:
|
||||
cast_layout = Layout.cast(layout)
|
||||
except TypeError as e:
|
||||
raise TypeError("View layout must be a layout, not {!r}"
|
||||
.format(layout)) from e
|
||||
if target is not None:
|
||||
if (name is not None or reset is not None or reset_less is not None or
|
||||
attrs is not None or decoder is not None):
|
||||
raise ValueError("View target cannot be provided at the same time as any of "
|
||||
"the Signal constructor arguments (name, reset, reset_less, "
|
||||
"attrs, decoder)")
|
||||
try:
|
||||
cast_target = Value.cast(target)
|
||||
except TypeError as e:
|
||||
|
@ -631,11 +631,6 @@ class View(ValueCastable):
|
|||
raise ValueError("View target is {} bit(s) wide, which is not compatible with "
|
||||
"the {} bit(s) wide view layout"
|
||||
.format(len(cast_target), cast_layout.size))
|
||||
else:
|
||||
if reset_less is None:
|
||||
reset_less = False
|
||||
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
|
||||
self.__target = cast_target
|
||||
|
@ -753,7 +748,7 @@ class _AggregateMeta(ShapeCastable, type):
|
|||
# This is a leaf class with its own layout. It is shape-castable and can
|
||||
# be instantiated. It can also be subclassed, and used to share layout and behavior.
|
||||
layout = dict()
|
||||
reset = dict()
|
||||
default = dict()
|
||||
for name in {**namespace["__annotations__"]}:
|
||||
try:
|
||||
Shape.cast(namespace["__annotations__"][name])
|
||||
|
@ -762,15 +757,15 @@ class _AggregateMeta(ShapeCastable, type):
|
|||
continue
|
||||
layout[name] = namespace["__annotations__"].pop(name)
|
||||
if name in namespace:
|
||||
reset[name] = namespace.pop(name)
|
||||
default[name] = namespace.pop(name)
|
||||
cls = type.__new__(metacls, name, bases, namespace)
|
||||
if cls.__layout_cls is UnionLayout:
|
||||
if len(reset) > 1:
|
||||
if len(default) > 1:
|
||||
raise ValueError("Reset value for at most one field can be provided for "
|
||||
"a union class (specified: {})"
|
||||
.format(", ".join(reset.keys())))
|
||||
.format(", ".join(default.keys())))
|
||||
cls.__layout = cls.__layout_cls(layout)
|
||||
cls.__reset = reset
|
||||
cls.__default = default
|
||||
return cls
|
||||
else:
|
||||
# This is a class that has a base class with a layout and annotations. Such a class
|
||||
|
@ -785,28 +780,24 @@ class _AggregateMeta(ShapeCastable, type):
|
|||
.format(cls.__module__, cls.__qualname__))
|
||||
return cls.__layout
|
||||
|
||||
def __call__(cls, target):
|
||||
# This method exists to pass the override check done by ShapeCastable.
|
||||
return super().__call__(cls, target)
|
||||
|
||||
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,
|
||||
attrs=None, decoder=None, src_loc_at=0):
|
||||
if self.__class__._AggregateMeta__layout_cls is UnionLayout:
|
||||
if reset is not None and len(reset) > 1:
|
||||
raise ValueError("Reset value for at most one field can be provided for "
|
||||
if cls.__layout_cls is UnionLayout:
|
||||
if init is not None and len(init) > 1:
|
||||
raise ValueError("Initializer for at most one field can be provided for "
|
||||
"a union class (specified: {})"
|
||||
.format(", ".join(reset.keys())))
|
||||
if target is None and hasattr(self.__class__, "_AggregateMeta__reset"):
|
||||
if reset is None:
|
||||
reset = self.__class__._AggregateMeta__reset
|
||||
elif self.__class__._AggregateMeta__layout_cls is not UnionLayout:
|
||||
reset = {**self.__class__._AggregateMeta__reset, **reset}
|
||||
super().__init__(self.__class__, target, name=name, reset=reset, reset_less=reset_less,
|
||||
attrs=attrs, decoder=decoder, src_loc_at=src_loc_at + 1)
|
||||
.format(", ".join(init.keys())))
|
||||
return cls.as_shape().const(init or cls.__default)
|
||||
else:
|
||||
fields = cls.__default.copy()
|
||||
fields.update(init or {})
|
||||
return cls.as_shape().const(fields)
|
||||
|
||||
|
||||
class Struct(_Aggregate):
|
||||
class Struct(View, metaclass=_AggregateMeta):
|
||||
"""Structures defined with annotations.
|
||||
|
||||
The :class:`Struct` base class is a subclass of :class:`View` that provides a concise way
|
||||
|
@ -842,14 +833,14 @@ class Struct(_Aggregate):
|
|||
|
||||
>>> IEEE754Single.as_shape()
|
||||
StructLayout({'fraction': 23, 'exponent': 8, 'sign': 1})
|
||||
>>> Signal(IEEE754Single).width
|
||||
>>> Signal(IEEE754Single).as_value().width
|
||||
32
|
||||
|
||||
Instances of this class can be used where :ref:`values <lang-values>` are expected:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> flt = IEEE754Single()
|
||||
>>> flt = Signal(IEEE754Single)
|
||||
>>> Signal(32).eq(flt)
|
||||
(eq (sig $signal) (sig flt))
|
||||
|
||||
|
@ -866,11 +857,11 @@ class Struct(_Aggregate):
|
|||
|
||||
.. doctest::
|
||||
|
||||
>>> hex(IEEE754Single().as_value().reset)
|
||||
>>> hex(Signal(IEEE754Single).as_value().reset)
|
||||
'0x3f800000'
|
||||
>>> hex(IEEE754Single(reset={'sign': 1}).as_value().reset)
|
||||
>>> hex(Signal(IEEE754Single, reset={'sign': 1}).as_value().reset)
|
||||
'0xbf800000'
|
||||
>>> hex(IEEE754Single(reset={'exponent': 0}).as_value().reset)
|
||||
>>> hex(Signal(IEEE754Single, reset={'exponent': 0}).as_value().reset)
|
||||
'0x0'
|
||||
|
||||
Classes inheriting from :class:`Struct` can be used as base classes. The only restrictions
|
||||
|
@ -903,15 +894,15 @@ class Struct(_Aggregate):
|
|||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Aggregate class 'HasChecksum' does not have a defined shape
|
||||
>>> bare = BareHeader(); bare.checksum()
|
||||
>>> bare = Signal(BareHeader); bare.checksum()
|
||||
(+ (+ (+ (const 1'd0) (slice (sig bare) 0:8)) (slice (sig bare) 8:16)) (slice (sig bare) 16:24))
|
||||
>>> param = HeaderWithParam(); param.checksum()
|
||||
>>> param = Signal(HeaderWithParam); param.checksum()
|
||||
(+ (+ (+ (+ (const 1'd0) (slice (sig param) 0:8)) (slice (sig param) 8:16)) (slice (sig param) 16:24)) (slice (sig param) 24:32))
|
||||
"""
|
||||
_AggregateMeta__layout_cls = StructLayout
|
||||
|
||||
|
||||
class Union(_Aggregate):
|
||||
class Union(View, metaclass=_AggregateMeta):
|
||||
"""Unions defined with annotations.
|
||||
|
||||
The :class:`Union` base class is a subclass of :class:`View` that provides a concise way
|
||||
|
@ -931,9 +922,9 @@ class Union(_Aggregate):
|
|||
|
||||
.. doctest::
|
||||
|
||||
>>> VarInt().as_value().reset
|
||||
>>> Signal(VarInt).as_value().reset
|
||||
256
|
||||
>>> VarInt(reset={'int8': 10}).as_value().reset
|
||||
>>> Signal(VarInt, reset={'int8': 10}).as_value().reset
|
||||
10
|
||||
"""
|
||||
_AggregateMeta__layout_cls = UnionLayout
|
||||
|
|
|
@ -56,7 +56,7 @@ While this implementation works, it is repetitive, error-prone, hard to read, an
|
|||
"blue": 5
|
||||
})
|
||||
|
||||
i_color = data.View(rgb565_layout)
|
||||
i_color = Signal(rgb565_layout)
|
||||
o_gray = Signal(8)
|
||||
|
||||
m.d.comb += o_gray.eq((i_color.red + i_color.green + i_color.blue) << 1)
|
||||
|
@ -82,7 +82,7 @@ For example, consider a module that processes RGB pixels in groups of up to four
|
|||
"valid": 4
|
||||
})
|
||||
|
||||
i_stream = data.View(input_layout)
|
||||
i_stream = Signal(input_layout)
|
||||
r_accum = Signal(32)
|
||||
|
||||
m.d.sync += r_accum.eq(
|
||||
|
@ -120,7 +120,7 @@ In case the data has related operations or transformations, :class:`View` can be
|
|||
|
||||
.. testcode::
|
||||
|
||||
class RGBPixelLayout(data.StructLayout):
|
||||
class RGBLayout(data.StructLayout):
|
||||
def __init__(self, r_bits, g_bits, b_bits):
|
||||
super().__init__({
|
||||
"red": unsigned(r_bits),
|
||||
|
@ -129,20 +129,20 @@ In case the data has related operations or transformations, :class:`View` can be
|
|||
})
|
||||
|
||||
def __call__(self, value):
|
||||
return RGBPixelView(self, value)
|
||||
return RGBView(self, value)
|
||||
|
||||
class RGBPixelView(data.View):
|
||||
class RGBView(data.View):
|
||||
def brightness(self):
|
||||
return (self.red + self.green + self.blue)[-8:]
|
||||
|
||||
Here, the ``RGBLayout`` class itself is :ref:`shape-castable <lang-shapecasting>` and can be used anywhere a shape is accepted:
|
||||
Here, the ``RGBLayout`` class itself is :ref:`shape-castable <lang-shapecasting>` and can be used anywhere a shape is accepted. When a :class:`Signal` is constructed with this layout, the returned value is wrapped in an ``RGBView``:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> pixel = Signal(RGBPixelLayout(5, 6, 5))
|
||||
>>> len(pixel)
|
||||
>>> pixel = Signal(RGBLayout(5, 6, 5))
|
||||
>>> len(pixel.as_value())
|
||||
16
|
||||
>>> RGBPixelView(RGBPixelLayout(5, 6, 5), pixel).red
|
||||
>>> pixel.red
|
||||
(slice (sig pixel) 0:5)
|
||||
|
||||
In case the data format is static, :class:`Struct` (or :class:`Union`) can be subclassed instead of :class:`View`, to reduce the amount of boilerplate needed:
|
||||
|
@ -189,7 +189,7 @@ One module could submit a command with:
|
|||
|
||||
.. testcode::
|
||||
|
||||
cmd = Command()
|
||||
cmd = Signal(Command)
|
||||
|
||||
m.d.comb += [
|
||||
cmd.valid.eq(1),
|
||||
|
|
|
@ -145,8 +145,11 @@ class MockShapeCastable(ShapeCastable):
|
|||
def as_shape(self):
|
||||
return self.dest
|
||||
|
||||
def const(self, obj):
|
||||
return Const(obj, self.dest)
|
||||
def __call__(self, value):
|
||||
return value
|
||||
|
||||
def const(self, init):
|
||||
return Const(init, self.dest)
|
||||
|
||||
|
||||
class ShapeCastableTestCase(FHDLTestCase):
|
||||
|
@ -1004,6 +1007,9 @@ class SignalTestCase(FHDLTestCase):
|
|||
def as_shape(self):
|
||||
return unsigned(8)
|
||||
|
||||
def __call__(self, value):
|
||||
return value
|
||||
|
||||
def const(self, init):
|
||||
return int(init, 16)
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@ class MockShapeCastable(ShapeCastable):
|
|||
def as_shape(self):
|
||||
return self.shape
|
||||
|
||||
def __call__(self, value):
|
||||
return value
|
||||
|
||||
def const(self, init):
|
||||
return Const(init, self.shape)
|
||||
|
||||
|
@ -172,6 +175,12 @@ class UnionLayoutTestCase(TestCase):
|
|||
r"^Union layout member shape must be a shape-castable object, not 1\.0$"):
|
||||
UnionLayout({"a": 1.0})
|
||||
|
||||
def test_const_two_members_wrong(self):
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Initializer for at most one field can be provided for a union layout "
|
||||
r"\(specified: a, b\)$"):
|
||||
UnionLayout({"a": 1, "b": 2}).const(dict(a=1, b=2))
|
||||
|
||||
|
||||
class ArrayLayoutTestCase(TestCase):
|
||||
def test_construct(self):
|
||||
|
@ -382,13 +391,13 @@ class LayoutTestCase(FHDLTestCase):
|
|||
self.assertRepr(sl.const(None), "(const 3'd0)")
|
||||
self.assertRepr(sl.const({"a": 0b1, "b": 0b10}), "(const 3'd5)")
|
||||
|
||||
ul = UnionLayout({
|
||||
"a": unsigned(1),
|
||||
"b": unsigned(2)
|
||||
fl = FlexibleLayout(2, {
|
||||
"a": Field(unsigned(1), 0),
|
||||
"b": Field(unsigned(2), 0)
|
||||
})
|
||||
self.assertRepr(ul.const({"a": 0b11}), "(const 2'd1)")
|
||||
self.assertRepr(ul.const({"b": 0b10}), "(const 2'd2)")
|
||||
self.assertRepr(ul.const({"a": 0b1, "b": 0b10}), "(const 2'd2)")
|
||||
self.assertRepr(fl.const({"a": 0b11}), "(const 2'd1)")
|
||||
self.assertRepr(fl.const({"b": 0b10}), "(const 2'd2)")
|
||||
self.assertRepr(fl.const({"a": 0b1, "b": 0b10}), "(const 2'd2)")
|
||||
|
||||
def test_const_wrong(self):
|
||||
sl = StructLayout({"f": unsigned(1)})
|
||||
|
@ -402,6 +411,9 @@ class LayoutTestCase(FHDLTestCase):
|
|||
def as_shape(self):
|
||||
return unsigned(8)
|
||||
|
||||
def __call__(self, value):
|
||||
return value
|
||||
|
||||
def const(self, init):
|
||||
return int(init, 16)
|
||||
|
||||
|
@ -418,8 +430,8 @@ class LayoutTestCase(FHDLTestCase):
|
|||
"a": unsigned(1),
|
||||
"b": unsigned(2)
|
||||
})
|
||||
self.assertEqual(Signal(sl).reset, 0)
|
||||
self.assertEqual(Signal(sl, reset={"a": 0b1, "b": 0b10}).reset, 5)
|
||||
self.assertEqual(Signal(sl).as_value().reset, 0)
|
||||
self.assertEqual(Signal(sl, reset={"a": 0b1, "b": 0b10}).as_value().reset, 5)
|
||||
|
||||
|
||||
class ViewTestCase(FHDLTestCase):
|
||||
|
@ -431,41 +443,24 @@ class ViewTestCase(FHDLTestCase):
|
|||
self.assertRepr(v["b"], "(slice (sig s) 1:3)")
|
||||
|
||||
def test_construct_signal(self):
|
||||
v = View(StructLayout({"a": unsigned(1), "b": unsigned(2)}))
|
||||
v = Signal(StructLayout({"a": unsigned(1), "b": unsigned(2)}))
|
||||
cv = Value.cast(v)
|
||||
self.assertIsInstance(cv, Signal)
|
||||
self.assertEqual(cv.shape(), unsigned(3))
|
||||
self.assertEqual(cv.name, "v")
|
||||
|
||||
def test_construct_signal_name(self):
|
||||
v = View(StructLayout({"a": unsigned(1), "b": unsigned(2)}), name="named")
|
||||
self.assertEqual(Value.cast(v).name, "named")
|
||||
|
||||
def test_construct_signal_reset(self):
|
||||
v1 = View(StructLayout({"a": unsigned(1), "b": unsigned(2)}),
|
||||
v1 = Signal(StructLayout({"a": unsigned(1), "b": unsigned(2)}),
|
||||
reset={"a": 0b1, "b": 0b10})
|
||||
self.assertEqual(Value.cast(v1).reset, 0b101)
|
||||
v2 = View(StructLayout({"a": unsigned(1),
|
||||
v2 = Signal(StructLayout({"a": unsigned(1),
|
||||
"b": StructLayout({"x": unsigned(1), "y": unsigned(1)})}),
|
||||
reset={"a": 0b1, "b": {"x": 0b0, "y": 0b1}})
|
||||
self.assertEqual(Value.cast(v2).reset, 0b101)
|
||||
v3 = View(ArrayLayout(unsigned(2), 2),
|
||||
v3 = Signal(ArrayLayout(unsigned(2), 2),
|
||||
reset=[0b01, 0b10])
|
||||
self.assertEqual(Value.cast(v3).reset, 0b1001)
|
||||
|
||||
def test_construct_signal_reset_less(self):
|
||||
v = View(StructLayout({"a": unsigned(1), "b": unsigned(2)}), reset_less=True)
|
||||
self.assertEqual(Value.cast(v).reset_less, True)
|
||||
|
||||
def test_construct_signal_attrs(self):
|
||||
v = View(StructLayout({"a": unsigned(1), "b": unsigned(2)}), attrs={"debug": 1})
|
||||
self.assertEqual(Value.cast(v).attrs, {"debug": 1})
|
||||
|
||||
def test_construct_signal_decoder(self):
|
||||
decoder = lambda x: f"{x}"
|
||||
v = View(StructLayout({"a": unsigned(1), "b": unsigned(2)}), decoder=decoder)
|
||||
self.assertEqual(Value.cast(v).decoder, decoder)
|
||||
|
||||
def test_layout_wrong(self):
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^View layout must be a layout, not <.+?>$"):
|
||||
|
@ -482,19 +477,8 @@ class ViewTestCase(FHDLTestCase):
|
|||
r"wide view layout$"):
|
||||
View(StructLayout({"a": unsigned(1)}), Signal(2))
|
||||
|
||||
def test_signal_reset_wrong(self):
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Reset value must be a constant initializer of StructLayout\({}\)$"):
|
||||
View(StructLayout({}), reset=0b1)
|
||||
|
||||
def test_target_signal_wrong(self):
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^View target cannot be provided at the same time as any of the Signal "
|
||||
r"constructor arguments \(name, reset, reset_less, attrs, decoder\)$"):
|
||||
View(StructLayout({}), Signal(), reset=0b1)
|
||||
|
||||
def test_getitem(self):
|
||||
v = View(UnionLayout({
|
||||
v = Signal(UnionLayout({
|
||||
"a": unsigned(2),
|
||||
"s": StructLayout({
|
||||
"b": unsigned(1),
|
||||
|
@ -536,7 +520,7 @@ class ViewTestCase(FHDLTestCase):
|
|||
def const(self, init):
|
||||
return Const(init, 2)
|
||||
|
||||
v = View(StructLayout({
|
||||
v = Signal(StructLayout({
|
||||
"f": Reverser()
|
||||
}))
|
||||
self.assertRepr(v.f, "(cat (slice (slice (sig v) 0:2) 1:2) "
|
||||
|
@ -553,7 +537,7 @@ class ViewTestCase(FHDLTestCase):
|
|||
def const(self, init):
|
||||
return Const(init, 2)
|
||||
|
||||
v = View(StructLayout({
|
||||
v = Signal(StructLayout({
|
||||
"f": WrongCastable()
|
||||
}))
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
|
@ -564,16 +548,16 @@ class ViewTestCase(FHDLTestCase):
|
|||
def test_index_wrong_missing(self):
|
||||
with self.assertRaisesRegex(KeyError,
|
||||
r"^'a'$"):
|
||||
View(StructLayout({}))["a"]
|
||||
Signal(StructLayout({}))["a"]
|
||||
|
||||
def test_index_wrong_struct_dynamic(self):
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Only views with array layout, not StructLayout\(\{\}\), may be indexed "
|
||||
r"with a value$"):
|
||||
View(StructLayout({}))[Signal(1)]
|
||||
Signal(StructLayout({}))[Signal(1)]
|
||||
|
||||
def test_getattr(self):
|
||||
v = View(UnionLayout({
|
||||
v = Signal(UnionLayout({
|
||||
"a": unsigned(2),
|
||||
"s": StructLayout({
|
||||
"b": unsigned(1),
|
||||
|
@ -595,7 +579,7 @@ class ViewTestCase(FHDLTestCase):
|
|||
self.assertEqual(v.q.shape(), signed(1))
|
||||
|
||||
def test_getattr_reserved(self):
|
||||
v = View(UnionLayout({
|
||||
v = Signal(UnionLayout({
|
||||
"_a": unsigned(2)
|
||||
}))
|
||||
self.assertRepr(v["_a"], "(slice (sig v) 0:2)")
|
||||
|
@ -604,13 +588,13 @@ class ViewTestCase(FHDLTestCase):
|
|||
with self.assertRaisesRegex(AttributeError,
|
||||
r"^View of \(sig \$signal\) does not have a field 'a'; "
|
||||
r"did you mean one of: 'b', 'c'\?$"):
|
||||
View(StructLayout({"b": unsigned(1), "c": signed(1)})).a
|
||||
Signal(StructLayout({"b": unsigned(1), "c": signed(1)})).a
|
||||
|
||||
def test_attr_wrong_reserved(self):
|
||||
with self.assertRaisesRegex(AttributeError,
|
||||
r"^View of \(sig \$signal\) field '_c' has a reserved name "
|
||||
r"and may only be accessed by indexing$"):
|
||||
View(StructLayout({"_c": signed(1)}))._c
|
||||
Signal(StructLayout({"_c": signed(1)}))._c
|
||||
|
||||
|
||||
class StructTestCase(FHDLTestCase):
|
||||
|
@ -625,7 +609,7 @@ class StructTestCase(FHDLTestCase):
|
|||
"b": signed(3)
|
||||
}))
|
||||
|
||||
v = S()
|
||||
v = Signal(S)
|
||||
self.assertEqual(Layout.of(v), S)
|
||||
self.assertEqual(Value.cast(v).shape(), Shape.cast(S))
|
||||
self.assertEqual(Value.cast(v).name, "v")
|
||||
|
@ -645,7 +629,7 @@ class StructTestCase(FHDLTestCase):
|
|||
|
||||
self.assertEqual(Shape.cast(S), unsigned(9))
|
||||
|
||||
v = S()
|
||||
v = Signal(S)
|
||||
self.assertIs(Layout.of(v), S)
|
||||
self.assertIsInstance(v, S)
|
||||
self.assertIs(Layout.of(v.b), R)
|
||||
|
@ -657,17 +641,6 @@ class StructTestCase(FHDLTestCase):
|
|||
self.assertRepr(v.b.q.r, "(s (slice (slice (slice (sig v) 1:9) 4:8) 0:2))")
|
||||
self.assertRepr(v.b.q.s, "(s (slice (slice (slice (sig v) 1:9) 4:8) 2:4))")
|
||||
|
||||
def test_construct_signal_kwargs(self):
|
||||
decoder = lambda x: f"{x}"
|
||||
v = View(StructLayout({"a": unsigned(1), "b": unsigned(2)}),
|
||||
name="named", reset={"b": 0b1}, reset_less=True, attrs={"debug": 1}, decoder=decoder)
|
||||
s = Value.cast(v)
|
||||
self.assertEqual(s.name, "named")
|
||||
self.assertEqual(s.reset, 0b010)
|
||||
self.assertEqual(s.reset_less, True)
|
||||
self.assertEqual(s.attrs, {"debug": 1})
|
||||
self.assertEqual(s.decoder, decoder)
|
||||
|
||||
def test_construct_reset(self):
|
||||
class S(Struct):
|
||||
p: 4
|
||||
|
@ -676,11 +649,11 @@ class StructTestCase(FHDLTestCase):
|
|||
with self.assertRaises(AttributeError):
|
||||
S.q
|
||||
|
||||
v1 = S()
|
||||
v1 = Signal(S)
|
||||
self.assertEqual(v1.as_value().reset, 0b010000)
|
||||
v2 = S(reset=dict(p=0b0011))
|
||||
v2 = Signal(S, reset=dict(p=0b0011))
|
||||
self.assertEqual(v2.as_value().reset, 0b010011)
|
||||
v3 = S(reset=dict(p=0b0011, q=0b00))
|
||||
v3 = Signal(S, reset=dict(p=0b0011, q=0b00))
|
||||
self.assertEqual(v3.as_value().reset, 0b000011)
|
||||
|
||||
def test_shape_undefined_wrong(self):
|
||||
|
@ -704,8 +677,8 @@ class StructTestCase(FHDLTestCase):
|
|||
a: 2
|
||||
b: 2
|
||||
|
||||
self.assertEqual(Sb1().add().shape(), unsigned(2))
|
||||
self.assertEqual(Sb2().add().shape(), unsigned(3))
|
||||
self.assertEqual(Signal(Sb1).add().shape(), unsigned(2))
|
||||
self.assertEqual(Signal(Sb2).add().shape(), unsigned(3))
|
||||
|
||||
def test_base_class_2(self):
|
||||
class Sb(Struct):
|
||||
|
@ -720,8 +693,8 @@ class StructTestCase(FHDLTestCase):
|
|||
def do(self):
|
||||
return self.a + self.b
|
||||
|
||||
self.assertEqual(Sb1().do().shape(), unsigned(4))
|
||||
self.assertEqual(Sb2().do().shape(), unsigned(3))
|
||||
self.assertEqual(Signal(Sb1).do().shape(), unsigned(4))
|
||||
self.assertEqual(Signal(Sb2).do().shape(), unsigned(3))
|
||||
|
||||
def test_layout_redefined_wrong(self):
|
||||
class Sb(Struct):
|
||||
|
@ -738,7 +711,7 @@ class StructTestCase(FHDLTestCase):
|
|||
b: int
|
||||
c: str = "x"
|
||||
|
||||
self.assertEqual(Layout.of(S()), StructLayout({"a": unsigned(1)}))
|
||||
self.assertEqual(Layout.of(Signal(S)), StructLayout({"a": unsigned(1)}))
|
||||
self.assertEqual(S.__annotations__, {"b": int, "c": str})
|
||||
self.assertEqual(S.c, "x")
|
||||
|
||||
|
@ -755,23 +728,12 @@ class UnionTestCase(FHDLTestCase):
|
|||
"b": signed(3)
|
||||
}))
|
||||
|
||||
v = U()
|
||||
v = Signal(U)
|
||||
self.assertEqual(Layout.of(v), U)
|
||||
self.assertEqual(Value.cast(v).shape(), Shape.cast(U))
|
||||
self.assertRepr(v.a, "(slice (sig v) 0:1)")
|
||||
self.assertRepr(v.b, "(s (slice (sig v) 0:3))")
|
||||
|
||||
def test_construct_signal_kwargs(self):
|
||||
decoder = lambda x: f"{x}"
|
||||
v = View(UnionLayout({"a": unsigned(1), "b": unsigned(2)}),
|
||||
name="named", reset={"b": 0b1}, reset_less=True, attrs={"debug": 1}, decoder=decoder)
|
||||
s = Value.cast(v)
|
||||
self.assertEqual(s.name, "named")
|
||||
self.assertEqual(s.reset, 0b01)
|
||||
self.assertEqual(s.reset_less, True)
|
||||
self.assertEqual(s.attrs, {"debug": 1})
|
||||
self.assertEqual(s.decoder, decoder)
|
||||
|
||||
def test_define_reset_two_wrong(self):
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Reset value for at most one field can be provided for a union class "
|
||||
|
@ -785,18 +747,20 @@ class UnionTestCase(FHDLTestCase):
|
|||
a: unsigned(1)
|
||||
b: unsigned(2)
|
||||
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Reset value for at most one field can be provided for a union class "
|
||||
r"\(specified: a, b\)$"):
|
||||
U(reset=dict(a=1, b=2))
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Reset value must be a constant initializer of <class '.+?\.U'>$") as cm:
|
||||
Signal(U, reset=dict(a=1, b=2))
|
||||
self.assertRegex(cm.exception.__cause__.message,
|
||||
r"^Initializer for at most one field can be provided for a union "
|
||||
r"class \(specified: a, b\)$")
|
||||
|
||||
def test_construct_reset_override(self):
|
||||
class U(Union):
|
||||
a: unsigned(1) = 1
|
||||
b: unsigned(2)
|
||||
|
||||
self.assertEqual(U().as_value().reset, 0b01)
|
||||
self.assertEqual(U(reset=dict(b=0b10)).as_value().reset, 0b10)
|
||||
self.assertEqual(Signal(U).as_value().reset, 0b01)
|
||||
self.assertEqual(Signal(U, reset=dict(b=0b10)).as_value().reset, 0b10)
|
||||
|
||||
|
||||
# Examples from https://github.com/amaranth-lang/amaranth/issues/693
|
||||
|
@ -817,7 +781,7 @@ class RFCExamplesTestCase(TestCase):
|
|||
|
||||
self.assertEqual(Float32.as_shape().size, 32)
|
||||
|
||||
flt_a = Float32()
|
||||
flt_a = Float32(Signal(32))
|
||||
flt_b = Float32(Const(0b00111110001000000000000000000000, 32))
|
||||
|
||||
m1 = Module()
|
||||
|
@ -835,7 +799,7 @@ class RFCExamplesTestCase(TestCase):
|
|||
float: Float32
|
||||
int: signed(32)
|
||||
|
||||
f_or_i = FloatOrInt32()
|
||||
f_or_i = Signal(FloatOrInt32)
|
||||
is_gt_1 = Signal()
|
||||
m2 = Module()
|
||||
m2.d.comb += [
|
||||
|
@ -857,10 +821,9 @@ class RFCExamplesTestCase(TestCase):
|
|||
"b": Float32
|
||||
})
|
||||
|
||||
adder_op_storage = Signal(adder_op_layout)
|
||||
self.assertEqual(len(adder_op_storage), 65)
|
||||
adder_op = Signal(adder_op_layout)
|
||||
self.assertEqual(len(adder_op.as_value()), 65)
|
||||
|
||||
adder_op = View(adder_op_layout, adder_op_storage)
|
||||
m3 = Module()
|
||||
m3.d.comb += [
|
||||
adder_op.eq(Op.SUB),
|
||||
|
@ -886,15 +849,10 @@ class RFCExamplesTestCase(TestCase):
|
|||
})
|
||||
self.assertEqual(layout1.size, 3)
|
||||
|
||||
sig1 = Signal(layout1)
|
||||
self.assertEqual(sig1.shape(), unsigned(3))
|
||||
|
||||
view1 = View(layout1, sig1)
|
||||
self.assertIs(Value.cast(view1), sig1)
|
||||
|
||||
view2 = View(layout1)
|
||||
self.assertIsInstance(Value.cast(view2), Signal)
|
||||
self.assertEqual(Value.cast(view2).shape(), unsigned(3))
|
||||
view1 = Signal(layout1)
|
||||
self.assertIsInstance(view1, View)
|
||||
self.assertEqual(Layout.of(view1), layout1)
|
||||
self.assertEqual(view1.as_value().shape(), unsigned(3))
|
||||
|
||||
m1 = Module()
|
||||
m1.d.comb += [
|
||||
|
@ -916,23 +874,20 @@ class RFCExamplesTestCase(TestCase):
|
|||
|
||||
self.assertEqual(Shape.cast(SomeVariant), unsigned(3))
|
||||
|
||||
view3 = SomeVariant()
|
||||
self.assertIsInstance(Value.cast(view3), Signal)
|
||||
self.assertEqual(Value.cast(view3).shape(), unsigned(3))
|
||||
view2 = Signal(SomeVariant)
|
||||
self.assertIsInstance(Value.cast(view2), Signal)
|
||||
self.assertEqual(Value.cast(view2).shape(), unsigned(3))
|
||||
|
||||
m2 = Module()
|
||||
m2.submodules += m1
|
||||
m2.d.comb += [
|
||||
view3.kind.eq(Kind.ONE_SIGNED),
|
||||
view3.value.eq(view1.value)
|
||||
view2.kind.eq(Kind.ONE_SIGNED),
|
||||
view2.value.eq(view1.value)
|
||||
]
|
||||
|
||||
@self.simulate(m2)
|
||||
def check_m2():
|
||||
self.assertEqual((yield view3.as_value()), 0b010)
|
||||
|
||||
sig2 = Signal(SomeVariant)
|
||||
self.assertEqual(sig2.shape(), unsigned(3))
|
||||
self.assertEqual((yield view2.as_value()), 0b010)
|
||||
|
||||
layout2 = StructLayout({
|
||||
"ready": unsigned(1),
|
||||
|
@ -942,18 +897,4 @@ class RFCExamplesTestCase(TestCase):
|
|||
|
||||
self.assertEqual(layout1, Layout.cast(SomeVariant))
|
||||
|
||||
self.assertIs(SomeVariant, Layout.of(view3))
|
||||
|
||||
def test_rfc_example_3(self):
|
||||
class Stream8b10b(View):
|
||||
data: Signal
|
||||
ctrl: Signal
|
||||
|
||||
def __init__(self, value=None, *, width: int):
|
||||
super().__init__(StructLayout({
|
||||
"data": unsigned(8 * width),
|
||||
"ctrl": unsigned(width)
|
||||
}), value)
|
||||
|
||||
self.assertEqual(len(Stream8b10b(width=1).data), 8)
|
||||
self.assertEqual(len(Stream8b10b(width=4).data), 32)
|
||||
self.assertIs(SomeVariant, Layout.of(view2))
|
||||
|
|
Loading…
Reference in a new issue