Implement RFC 59: Get rid of upwards propagation of clock domains
This commit is contained in:
parent
09d5540430
commit
0e4c2de725
|
@ -67,6 +67,7 @@ class Fragment:
|
||||||
self.generated = OrderedDict()
|
self.generated = OrderedDict()
|
||||||
self.src_loc = src_loc
|
self.src_loc = src_loc
|
||||||
self.origins = None
|
self.origins = None
|
||||||
|
self.domains_propagated_up = {}
|
||||||
|
|
||||||
def add_driver(self, signal, domain="comb"):
|
def add_driver(self, signal, domain="comb"):
|
||||||
assert isinstance(domain, str)
|
assert isinstance(domain, str)
|
||||||
|
@ -179,22 +180,37 @@ class Fragment:
|
||||||
self.subfragments[i] = (DomainRenamer(domain_name_map)(subfrag), name, src_loc)
|
self.subfragments[i] = (DomainRenamer(domain_name_map)(subfrag), name, src_loc)
|
||||||
|
|
||||||
# Finally, collect the (now unique) subfragment domains, and merge them into our domains.
|
# Finally, collect the (now unique) subfragment domains, and merge them into our domains.
|
||||||
for subfrag, name, src_loc in self.subfragments:
|
for i, (subfrag, name, src_loc) in enumerate(self.subfragments):
|
||||||
|
hier_name = name
|
||||||
|
if hier_name is None:
|
||||||
|
hier_name = f"<unnamed #{i}>"
|
||||||
for domain_name, domain in subfrag.domains.items():
|
for domain_name, domain in subfrag.domains.items():
|
||||||
if domain.local:
|
if domain.local:
|
||||||
continue
|
continue
|
||||||
self.add_domains(domain)
|
self.add_domains(domain)
|
||||||
|
if domain_name in subfrag.domains_propagated_up:
|
||||||
|
_used_in, defined_in = subfrag.domains_propagated_up[domain_name]
|
||||||
|
else:
|
||||||
|
defined_in = (*hierarchy, hier_name)
|
||||||
|
self.domains_propagated_up[domain_name] = (hierarchy, defined_in)
|
||||||
|
|
||||||
def _propagate_domains_down(self):
|
def _propagate_domains_down(self, hierarchy=("top",)):
|
||||||
# For each domain defined in this fragment, ensure it also exists in all subfragments.
|
# For each domain defined in this fragment, ensure it also exists in all subfragments.
|
||||||
for subfrag, name, src_loc in self.subfragments:
|
for i, (subfrag, name, src_loc) in enumerate(self.subfragments):
|
||||||
|
hier_name = name
|
||||||
|
if hier_name is None:
|
||||||
|
hier_name = f"<unnamed #{i}>"
|
||||||
|
|
||||||
for domain in self.iter_domains():
|
for domain in self.iter_domains():
|
||||||
if domain in subfrag.domains:
|
if domain in subfrag.domains:
|
||||||
assert self.domains[domain] is subfrag.domains[domain]
|
assert self.domains[domain] is subfrag.domains[domain]
|
||||||
else:
|
else:
|
||||||
subfrag.add_domains(self.domains[domain])
|
subfrag.add_domains(self.domains[domain])
|
||||||
|
if domain in self.domains_propagated_up:
|
||||||
|
_used_in, defined_in = self.domains_propagated_up[domain]
|
||||||
|
subfrag.domains_propagated_up[domain] = (hierarchy + (hier_name,)), defined_in
|
||||||
|
|
||||||
subfrag._propagate_domains_down()
|
subfrag._propagate_domains_down(hierarchy + (hier_name,))
|
||||||
|
|
||||||
def _create_missing_domains(self, missing_domain, *, platform=None):
|
def _create_missing_domains(self, missing_domain, *, platform=None):
|
||||||
from ._xfrm import DomainCollector
|
from ._xfrm import DomainCollector
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import warnings
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
@ -539,11 +540,26 @@ class DomainRenamer(FragmentTransformer, ValueTransformer, StatementTransformer)
|
||||||
class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
|
class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
|
||||||
def __init__(self, domains=None):
|
def __init__(self, domains=None):
|
||||||
self.domains = domains
|
self.domains = domains
|
||||||
|
self.domains_propagated_up = {}
|
||||||
|
|
||||||
|
def _warn_on_propagation_up(self, domain, src_loc):
|
||||||
|
if domain in self.domains_propagated_up:
|
||||||
|
used_in, defined_in = self.domains_propagated_up[domain]
|
||||||
|
common_prefix = []
|
||||||
|
for u, d in zip(used_in, defined_in):
|
||||||
|
if u == d:
|
||||||
|
common_prefix.append(u)
|
||||||
|
warnings.warn_explicit(f"Domain '{domain}' is used in '{'.'.join(used_in)}', but "
|
||||||
|
f"defined in '{'.'.join(defined_in)}', which will not be "
|
||||||
|
f"supported in Amaranth 0.6; define the domain in "
|
||||||
|
f"'{'.'.join(common_prefix)}' or one of its parents",
|
||||||
|
DeprecationWarning, filename=src_loc[0], lineno=src_loc[1])
|
||||||
|
|
||||||
def _resolve(self, domain, context):
|
def _resolve(self, domain, context):
|
||||||
if domain not in self.domains:
|
if domain not in self.domains:
|
||||||
raise DomainError("Signal {!r} refers to nonexistent domain '{}'"
|
raise DomainError("Signal {!r} refers to nonexistent domain '{}'"
|
||||||
.format(context, domain))
|
.format(context, domain))
|
||||||
|
self._warn_on_propagation_up(domain, context.src_loc)
|
||||||
return self.domains[domain]
|
return self.domains[domain]
|
||||||
|
|
||||||
def map_drivers(self, fragment, new_fragment):
|
def map_drivers(self, fragment, new_fragment):
|
||||||
|
@ -569,6 +585,14 @@ class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer)
|
||||||
|
|
||||||
def on_fragment(self, fragment):
|
def on_fragment(self, fragment):
|
||||||
self.domains = fragment.domains
|
self.domains = fragment.domains
|
||||||
|
self.domains_propagated_up = fragment.domains_propagated_up
|
||||||
|
for domain, statements in fragment.statements.items():
|
||||||
|
self._warn_on_propagation_up(domain, statements[0].src_loc)
|
||||||
|
if isinstance(fragment, MemoryInstance):
|
||||||
|
for port in fragment._read_ports:
|
||||||
|
self._warn_on_propagation_up(port._domain, fragment.src_loc)
|
||||||
|
for port in fragment._write_ports:
|
||||||
|
self._warn_on_propagation_up(port._domain, fragment.src_loc)
|
||||||
return super().on_fragment(fragment)
|
return super().on_fragment(fragment)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ Apply the following changes to code written against Amaranth 0.4 to migrate it t
|
||||||
* Replace imports of ``amaranth.asserts.{Assert, Assume, Cover}`` with imports from ``amaranth.hdl``
|
* Replace imports of ``amaranth.asserts.{Assert, Assume, Cover}`` with imports from ``amaranth.hdl``
|
||||||
* Remove any usage of ``name=`` with assertions, possibly replacing them with custom messages
|
* Remove any usage of ``name=`` with assertions, possibly replacing them with custom messages
|
||||||
* Ensure all elaboratables are subclasses of :class:`Elaboratable`
|
* Ensure all elaboratables are subclasses of :class:`Elaboratable`
|
||||||
|
* Ensure clock domains aren't used outside the module that defines them, or its submodules; move clock domain definitions upwards in the hierarchy as necessary
|
||||||
|
|
||||||
|
|
||||||
Implemented RFCs
|
Implemented RFCs
|
||||||
|
@ -53,6 +54,7 @@ Implemented RFCs
|
||||||
.. _RFC 51: https://amaranth-lang.org/rfcs/0051-const-from-bits.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
|
||||||
|
.. _RFC 59: https://amaranth-lang.org/rfcs/0059-no-domain-upwards-propagation.html
|
||||||
.. _RFC 62: https://amaranth-lang.org/rfcs/0062-memory-data.html
|
.. _RFC 62: https://amaranth-lang.org/rfcs/0062-memory-data.html
|
||||||
|
|
||||||
* `RFC 17`_: Remove ``log2_int``
|
* `RFC 17`_: Remove ``log2_int``
|
||||||
|
@ -64,6 +66,7 @@ Implemented RFCs
|
||||||
* `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 51`_: Add ``ShapeCastable.from_bits`` and ``amaranth.lib.data.Const``
|
||||||
* `RFC 53`_: Low-level I/O primitives
|
* `RFC 53`_: Low-level I/O primitives
|
||||||
|
* `RFC 59`_: Get rid of upwards propagation of clock domains
|
||||||
|
|
||||||
|
|
||||||
Language changes
|
Language changes
|
||||||
|
@ -88,6 +91,7 @@ Language changes
|
||||||
* Changed: :class:`Instance` IO ports now accept only IO values, not plain values. (`RFC 53`_)
|
* Changed: :class:`Instance` IO ports now accept only IO values, not plain values. (`RFC 53`_)
|
||||||
* Deprecated: :func:`amaranth.utils.log2_int`. (`RFC 17`_)
|
* Deprecated: :func:`amaranth.utils.log2_int`. (`RFC 17`_)
|
||||||
* Deprecated: :class:`amaranth.hdl.Memory`. (`RFC 45`_)
|
* Deprecated: :class:`amaranth.hdl.Memory`. (`RFC 45`_)
|
||||||
|
* Deprecated: upwards propagation of clock domains. (`RFC 59`_)
|
||||||
* Removed: (deprecated in 0.4) :meth:`Const.normalize`. (`RFC 5`_)
|
* Removed: (deprecated in 0.4) :meth:`Const.normalize`. (`RFC 5`_)
|
||||||
* Removed: (deprecated in 0.4) :class:`Repl`. (`RFC 10`_)
|
* Removed: (deprecated in 0.4) :class:`Repl`. (`RFC 10`_)
|
||||||
* Removed: (deprecated in 0.4) :class:`ast.Sample`, :class:`ast.Past`, :class:`ast.Stable`, :class:`ast.Rose`, :class:`ast.Fell`.
|
* Removed: (deprecated in 0.4) :class:`ast.Sample`, :class:`ast.Past`, :class:`ast.Stable`, :class:`ast.Rose`, :class:`ast.Fell`.
|
||||||
|
|
|
@ -668,6 +668,20 @@ class FragmentDomainsTestCase(FHDLTestCase):
|
||||||
r"domain 'sync' \(defines 'foo'\)\.$")):
|
r"domain 'sync' \(defines 'foo'\)\.$")):
|
||||||
f1._propagate_domains(missing_domain=lambda name: f2)
|
f1._propagate_domains(missing_domain=lambda name: f2)
|
||||||
|
|
||||||
|
def test_propagate_up_use(self):
|
||||||
|
a = Signal()
|
||||||
|
m = Module()
|
||||||
|
m1 = Module()
|
||||||
|
m2 = Module()
|
||||||
|
m.submodules.m1 = m1
|
||||||
|
m.submodules.m2 = m2
|
||||||
|
m1.d.test += a.eq(1)
|
||||||
|
m2.domains.test = ClockDomain()
|
||||||
|
with self.assertWarnsRegex(DeprecationWarning,
|
||||||
|
r"^Domain 'test' is used in 'top.m1', but defined in 'top.m2', which will not be "
|
||||||
|
r"supported in Amaranth 0.6; define the domain in 'top' or one of its parents$"):
|
||||||
|
Fragment.get(m, None).prepare()
|
||||||
|
|
||||||
|
|
||||||
class FragmentHierarchyConflictTestCase(FHDLTestCase):
|
class FragmentHierarchyConflictTestCase(FHDLTestCase):
|
||||||
def test_no_conflict_local_domains(self):
|
def test_no_conflict_local_domains(self):
|
||||||
|
|
Loading…
Reference in a new issue