parent
6ad0d21cc9
commit
8e6ae9e6e0
|
@ -806,43 +806,41 @@ def connect(m, *args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
class Component(Elaboratable):
|
class Component(Elaboratable):
|
||||||
def __init__(self):
|
def __init__(self, signature=None):
|
||||||
for name in self.signature.members:
|
|
||||||
if hasattr(self, name):
|
|
||||||
raise NameError(f"Cannot initialize attribute for signature member {name!r} "
|
|
||||||
f"because an attribute with the same name already exists")
|
|
||||||
self.__dict__.update(self.signature.members.create(path=()))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def signature(self):
|
|
||||||
cls = type(self)
|
cls = type(self)
|
||||||
members = {}
|
members = {}
|
||||||
for base in reversed(cls.mro()[:cls.mro().index(Component)]):
|
for base in reversed(cls.mro()[:cls.mro().index(Component)]):
|
||||||
for name, annot in base.__dict__.get("__annotations__", {}).items():
|
for name, annot in base.__dict__.get("__annotations__", {}).items():
|
||||||
if name.startswith("_"):
|
if name.startswith("_"):
|
||||||
continue
|
continue
|
||||||
if (annot is Value or annot is Signal or annot is Const or
|
if type(annot) is Member:
|
||||||
(isinstance(annot, type) and issubclass(annot, ValueCastable)) or
|
|
||||||
isinstance(annot, Signature)):
|
|
||||||
if isinstance(annot, type):
|
|
||||||
annot_repr = annot.__name__
|
|
||||||
else:
|
|
||||||
annot_repr = repr(annot)
|
|
||||||
# To suppress this warning in the rare cases where it is necessary (and naming
|
|
||||||
# the field with a leading underscore is infeasible), override the property.
|
|
||||||
warnings.warn(
|
|
||||||
message=f"Component '{cls.__module__}.{cls.__qualname__}' has "
|
|
||||||
f"an annotation '{name}: {annot_repr}', which is not "
|
|
||||||
f"a signature member; did you mean '{name}: In({annot_repr})' "
|
|
||||||
f"or '{name}: Out({annot_repr})'?",
|
|
||||||
category=SyntaxWarning,
|
|
||||||
stacklevel=2)
|
|
||||||
elif type(annot) is Member:
|
|
||||||
if name in members:
|
if name in members:
|
||||||
raise SignatureError(f"Member '{name}' is redefined in {base.__module__}.{base.__qualname__}")
|
raise SignatureError(f"Member '{name}' is redefined in {base.__module__}.{base.__qualname__}")
|
||||||
members[name] = annot
|
members[name] = annot
|
||||||
if not members:
|
if not members:
|
||||||
raise NotImplementedError(
|
if signature is None:
|
||||||
f"Component '{cls.__module__}.{cls.__qualname__}' does not have signature member "
|
raise NotImplementedError(
|
||||||
f"annotations")
|
f"Component '{cls.__module__}.{cls.__qualname__}' does not have signature "
|
||||||
return Signature(members)
|
f"member annotations")
|
||||||
|
if isinstance(signature, dict):
|
||||||
|
signature = Signature(signature)
|
||||||
|
elif not isinstance(signature, Signature):
|
||||||
|
raise TypeError(f"Object {signature!r} is not a signature nor a dict")
|
||||||
|
else:
|
||||||
|
if signature is not None:
|
||||||
|
raise TypeError(
|
||||||
|
f"Signature was passed as an argument, but component "
|
||||||
|
f"'{cls.__module__}.{cls.__qualname__}' already has signature "
|
||||||
|
f"member annotations")
|
||||||
|
signature = Signature(members)
|
||||||
|
|
||||||
|
self.__signature = signature
|
||||||
|
for name in signature.members:
|
||||||
|
if hasattr(self, name):
|
||||||
|
raise NameError(f"Cannot initialize attribute for signature member {name!r} "
|
||||||
|
f"because an attribute with the same name already exists")
|
||||||
|
self.__dict__.update(signature.members.create(path=()))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def signature(self):
|
||||||
|
return self.__signature
|
||||||
|
|
|
@ -64,6 +64,7 @@ Implemented RFCs
|
||||||
.. _RFC 34: https://amaranth-lang.org/rfcs/0034-interface-rename.html
|
.. _RFC 34: https://amaranth-lang.org/rfcs/0034-interface-rename.html
|
||||||
.. _RFC 35: https://amaranth-lang.org/rfcs/0035-shapelike-valuelike.html
|
.. _RFC 35: https://amaranth-lang.org/rfcs/0035-shapelike-valuelike.html
|
||||||
.. _RFC 37: https://amaranth-lang.org/rfcs/0037-make-signature-immutable.html
|
.. _RFC 37: https://amaranth-lang.org/rfcs/0037-make-signature-immutable.html
|
||||||
|
.. _RFC 38: https://amaranth-lang.org/rfcs/0038-component-signature-immutability.html
|
||||||
|
|
||||||
|
|
||||||
* `RFC 1`_: Aggregate data structure library
|
* `RFC 1`_: Aggregate data structure library
|
||||||
|
@ -85,6 +86,7 @@ Implemented RFCs
|
||||||
* `RFC 34`_: Rename ``amaranth.lib.wiring.Interface`` to ``PureInterface``
|
* `RFC 34`_: Rename ``amaranth.lib.wiring.Interface`` to ``PureInterface``
|
||||||
* `RFC 35`_: Add ``ShapeLike``, ``ValueLike``
|
* `RFC 35`_: Add ``ShapeLike``, ``ValueLike``
|
||||||
* `RFC 37`_: Make ``Signature`` immutable
|
* `RFC 37`_: Make ``Signature`` immutable
|
||||||
|
* `RFC 38`_: ``Component.signature`` immutability
|
||||||
|
|
||||||
|
|
||||||
Language changes
|
Language changes
|
||||||
|
|
|
@ -711,17 +711,15 @@ class FlippedInterfaceTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_propagate_flipped(self):
|
def test_propagate_flipped(self):
|
||||||
class InterfaceWithFlippedSub(Component):
|
class InterfaceWithFlippedSub(Component):
|
||||||
signature = Signature({
|
a: In(Signature({
|
||||||
"a": In(Signature({
|
"b": Out(Signature({
|
||||||
"b": Out(Signature({
|
"c": Out(1)
|
||||||
"c": Out(1)
|
})),
|
||||||
})),
|
"d": In(Signature({
|
||||||
"d": In(Signature({
|
"e": Out(1)
|
||||||
"e": Out(1)
|
})),
|
||||||
})),
|
"f": Out(1)
|
||||||
"f": Out(1)
|
}))
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -984,53 +982,6 @@ class ComponentTestCase(unittest.TestCase):
|
||||||
r"with the same name already exists$"):
|
r"with the same name already exists$"):
|
||||||
C()
|
C()
|
||||||
|
|
||||||
def test_missing_in_out_warning(self):
|
|
||||||
class C1(Component):
|
|
||||||
prt1 : In(1)
|
|
||||||
sig2 : Signal
|
|
||||||
|
|
||||||
with self.assertWarnsRegex(SyntaxWarning,
|
|
||||||
r"^Component '.+\.C1' has an annotation 'sig2: Signal', which is not a signature "
|
|
||||||
r"member; did you mean 'sig2: In\(Signal\)' or 'sig2: Out\(Signal\)'\?$"):
|
|
||||||
C1().signature
|
|
||||||
|
|
||||||
class C2(Component):
|
|
||||||
prt1 : In(1)
|
|
||||||
sig2 : Signature({})
|
|
||||||
|
|
||||||
with self.assertWarnsRegex(SyntaxWarning,
|
|
||||||
r"^Component '.+\.C2' has an annotation 'sig2: Signature\({}\)', which is not "
|
|
||||||
r"a signature member; did you mean 'sig2: In\(Signature\({}\)\)' or "
|
|
||||||
r"'sig2: Out\(Signature\({}\)\)'\?$"):
|
|
||||||
C2().signature
|
|
||||||
|
|
||||||
class MockValueCastable(ValueCastable):
|
|
||||||
def shape(self): pass
|
|
||||||
@ValueCastable.lowermethod
|
|
||||||
def as_value(self): pass
|
|
||||||
|
|
||||||
class C3(Component):
|
|
||||||
prt1 : In(1)
|
|
||||||
val2 : MockValueCastable
|
|
||||||
|
|
||||||
with self.assertWarnsRegex(SyntaxWarning,
|
|
||||||
r"^Component '.+\.C3' has an annotation 'val2: MockValueCastable', which is not "
|
|
||||||
r"a signature member; did you mean 'val2: In\(MockValueCastable\)' or "
|
|
||||||
r"'val2: Out\(MockValueCastable\)'\?$"):
|
|
||||||
C3().signature
|
|
||||||
|
|
||||||
def test_bug_882(self):
|
|
||||||
class PageBuffer(Component):
|
|
||||||
rand: Signature({}).flip()
|
|
||||||
other: Out(1)
|
|
||||||
|
|
||||||
with self.assertWarnsRegex(SyntaxWarning,
|
|
||||||
r"^Component '.+\.PageBuffer' has an annotation 'rand: Signature\({}\)\.flip\(\)', "
|
|
||||||
r"which is not a signature member; did you mean "
|
|
||||||
r"'rand: In\(Signature\({}\)\.flip\(\)\)' or "
|
|
||||||
r"'rand: Out\(Signature\({}\)\.flip\(\)\)'\?$"):
|
|
||||||
PageBuffer()
|
|
||||||
|
|
||||||
def test_inherit(self):
|
def test_inherit(self):
|
||||||
class A(Component):
|
class A(Component):
|
||||||
clk: In(1)
|
clk: In(1)
|
||||||
|
@ -1054,3 +1005,49 @@ class ComponentTestCase(unittest.TestCase):
|
||||||
with self.assertRaisesRegex(SignatureError,
|
with self.assertRaisesRegex(SignatureError,
|
||||||
r"^Member 'a' is redefined in .*<locals>.B$"):
|
r"^Member 'a' is redefined in .*<locals>.B$"):
|
||||||
B()
|
B()
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
class C(Component):
|
||||||
|
def __init__(self, width):
|
||||||
|
super().__init__(Signature({
|
||||||
|
"a": In(width)
|
||||||
|
}))
|
||||||
|
|
||||||
|
c = C(2)
|
||||||
|
self.assertEqual(c.signature, Signature({"a": In(2)}))
|
||||||
|
self.assertIsInstance(c.a, Signal)
|
||||||
|
self.assertEqual(c.a.shape(), unsigned(2))
|
||||||
|
|
||||||
|
def test_create_dict(self):
|
||||||
|
class C(Component):
|
||||||
|
def __init__(self, width):
|
||||||
|
super().__init__({
|
||||||
|
"a": In(width)
|
||||||
|
})
|
||||||
|
|
||||||
|
c = C(2)
|
||||||
|
self.assertEqual(c.signature, Signature({"a": In(2)}))
|
||||||
|
self.assertIsInstance(c.a, Signal)
|
||||||
|
self.assertEqual(c.a.shape(), unsigned(2))
|
||||||
|
|
||||||
|
def test_create_wrong(self):
|
||||||
|
class C(Component):
|
||||||
|
a: In(2)
|
||||||
|
|
||||||
|
def __init__(self, width):
|
||||||
|
super().__init__(Signature({
|
||||||
|
"a": In(width)
|
||||||
|
}))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Signature was passed as an argument, but component '.*.C' already has signature member annotations$"):
|
||||||
|
C(2)
|
||||||
|
|
||||||
|
def test_create_wrong_type(self):
|
||||||
|
class C(Component):
|
||||||
|
def __init__(self, width):
|
||||||
|
super().__init__(4)
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
r"^Object 4 is not a signature nor a dict$"):
|
||||||
|
C(2)
|
||||||
|
|
Loading…
Reference in a new issue