diff --git a/amaranth/hdl/ast.py b/amaranth/hdl/ast.py index 7fe1d56..33feba7 100644 --- a/amaranth/hdl/ast.py +++ b/amaranth/hdl/ast.py @@ -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 diff --git a/amaranth/lib/data.py b/amaranth/lib/data.py index 981ca82..51aeb8d 100644 --- a/amaranth/lib/data.py +++ b/amaranth/lib/data.py @@ -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,33 +616,21 @@ 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: - raise TypeError("View target must be a value-castable object, not {!r}" - .format(target)) from e - if len(cast_target) != cast_layout.size: - 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) + try: + cast_target = Value.cast(target) + except TypeError as e: + raise TypeError("View target must be a value-castable object, not {!r}" + .format(target)) from e + if len(cast_target) != cast_layout.size: + 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)) self.__orig_layout = layout self.__layout = cast_layout self.__target = cast_target @@ -752,8 +747,8 @@ class _AggregateMeta(ShapeCastable, type): elif all(not hasattr(base, "_AggregateMeta__layout") for base in bases): # 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() + layout = 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()))) - cls.__layout = cls.__layout_cls(layout) - cls.__reset = reset + .format(", ".join(default.keys()))) + cls.__layout = cls.__layout_cls(layout) + 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 ` 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 diff --git a/docs/stdlib/data.rst b/docs/stdlib/data.rst index 1714a3d..e063550 100644 --- a/docs/stdlib/data.rst +++ b/docs/stdlib/data.rst @@ -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 ` and can be used anywhere a shape is accepted: +Here, the ``RGBLayout`` class itself is :ref:`shape-castable ` 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), diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py index 88209bb..bfec166 100644 --- a/tests/test_hdl_ast.py +++ b/tests/test_hdl_ast.py @@ -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) diff --git a/tests/test_lib_data.py b/tests/test_lib_data.py index af54527..24b4c3c 100644 --- a/tests/test_lib_data.py +++ b/tests/test_lib_data.py @@ -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)}), - reset={"a": 0b1, "b": 0b10}) + 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}}) + reset={"a": 0b1, "b": {"x": 0b0, "y": 0b1}}) self.assertEqual(Value.cast(v2).reset, 0b101) - v3 = View(ArrayLayout(unsigned(2), 2), - reset=[0b01, 0b10]) + 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 $") 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))