Skip to content

Commit 0e4c2de

Browse files
wanda-phiwhitequark
authored andcommitted
Implement RFC 59: Get rid of upwards propagation of clock domains
1 parent 09d5540 commit 0e4c2de

File tree

4 files changed

+62
-4
lines changed

4 files changed

+62
-4
lines changed

amaranth/hdl/_ir.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def __init__(self, *, src_loc=None):
6767
self.generated = OrderedDict()
6868
self.src_loc = src_loc
6969
self.origins = None
70+
self.domains_propagated_up = {}
7071

7172
def add_driver(self, signal, domain="comb"):
7273
assert isinstance(domain, str)
@@ -179,22 +180,37 @@ def _propagate_domains_up(self, hierarchy=("top",)):
179180
self.subfragments[i] = (DomainRenamer(domain_name_map)(subfrag), name, src_loc)
180181

181182
# Finally, collect the (now unique) subfragment domains, and merge them into our domains.
182-
for subfrag, name, src_loc in self.subfragments:
183+
for i, (subfrag, name, src_loc) in enumerate(self.subfragments):
184+
hier_name = name
185+
if hier_name is None:
186+
hier_name = f"<unnamed #{i}>"
183187
for domain_name, domain in subfrag.domains.items():
184188
if domain.local:
185189
continue
186190
self.add_domains(domain)
191+
if domain_name in subfrag.domains_propagated_up:
192+
_used_in, defined_in = subfrag.domains_propagated_up[domain_name]
193+
else:
194+
defined_in = (*hierarchy, hier_name)
195+
self.domains_propagated_up[domain_name] = (hierarchy, defined_in)
187196

188-
def _propagate_domains_down(self):
197+
def _propagate_domains_down(self, hierarchy=("top",)):
189198
# For each domain defined in this fragment, ensure it also exists in all subfragments.
190-
for subfrag, name, src_loc in self.subfragments:
199+
for i, (subfrag, name, src_loc) in enumerate(self.subfragments):
200+
hier_name = name
201+
if hier_name is None:
202+
hier_name = f"<unnamed #{i}>"
203+
191204
for domain in self.iter_domains():
192205
if domain in subfrag.domains:
193206
assert self.domains[domain] is subfrag.domains[domain]
194207
else:
195208
subfrag.add_domains(self.domains[domain])
209+
if domain in self.domains_propagated_up:
210+
_used_in, defined_in = self.domains_propagated_up[domain]
211+
subfrag.domains_propagated_up[domain] = (hierarchy + (hier_name,)), defined_in
196212

197-
subfrag._propagate_domains_down()
213+
subfrag._propagate_domains_down(hierarchy + (hier_name,))
198214

199215
def _create_missing_domains(self, missing_domain, *, platform=None):
200216
from ._xfrm import DomainCollector

amaranth/hdl/_xfrm.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import warnings
12
from abc import ABCMeta, abstractmethod
23
from collections import OrderedDict
34
from collections.abc import Iterable
@@ -539,11 +540,26 @@ def map_memory_ports(self, fragment, new_fragment):
539540
class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
540541
def __init__(self, domains=None):
541542
self.domains = domains
543+
self.domains_propagated_up = {}
544+
545+
def _warn_on_propagation_up(self, domain, src_loc):
546+
if domain in self.domains_propagated_up:
547+
used_in, defined_in = self.domains_propagated_up[domain]
548+
common_prefix = []
549+
for u, d in zip(used_in, defined_in):
550+
if u == d:
551+
common_prefix.append(u)
552+
warnings.warn_explicit(f"Domain '{domain}' is used in '{'.'.join(used_in)}', but "
553+
f"defined in '{'.'.join(defined_in)}', which will not be "
554+
f"supported in Amaranth 0.6; define the domain in "
555+
f"'{'.'.join(common_prefix)}' or one of its parents",
556+
DeprecationWarning, filename=src_loc[0], lineno=src_loc[1])
542557

543558
def _resolve(self, domain, context):
544559
if domain not in self.domains:
545560
raise DomainError("Signal {!r} refers to nonexistent domain '{}'"
546561
.format(context, domain))
562+
self._warn_on_propagation_up(domain, context.src_loc)
547563
return self.domains[domain]
548564

549565
def map_drivers(self, fragment, new_fragment):
@@ -569,6 +585,14 @@ def on_ResetSignal(self, value):
569585

570586
def on_fragment(self, fragment):
571587
self.domains = fragment.domains
588+
self.domains_propagated_up = fragment.domains_propagated_up
589+
for domain, statements in fragment.statements.items():
590+
self._warn_on_propagation_up(domain, statements[0].src_loc)
591+
if isinstance(fragment, MemoryInstance):
592+
for port in fragment._read_ports:
593+
self._warn_on_propagation_up(port._domain, fragment.src_loc)
594+
for port in fragment._write_ports:
595+
self._warn_on_propagation_up(port._domain, fragment.src_loc)
572596
return super().on_fragment(fragment)
573597

574598

docs/changes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Apply the following changes to code written against Amaranth 0.4 to migrate it t
3838
* Replace imports of ``amaranth.asserts.{Assert, Assume, Cover}`` with imports from ``amaranth.hdl``
3939
* Remove any usage of ``name=`` with assertions, possibly replacing them with custom messages
4040
* Ensure all elaboratables are subclasses of :class:`Elaboratable`
41+
* 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
4142

4243

4344
Implemented RFCs
@@ -53,6 +54,7 @@ Implemented RFCs
5354
.. _RFC 51: https://amaranth-lang.org/rfcs/0051-const-from-bits.html
5455
.. _RFC 53: https://amaranth-lang.org/rfcs/0053-ioport.html
5556
.. _RFC 55: https://amaranth-lang.org/rfcs/0055-lib-io.html
57+
.. _RFC 59: https://amaranth-lang.org/rfcs/0059-no-domain-upwards-propagation.html
5658
.. _RFC 62: https://amaranth-lang.org/rfcs/0062-memory-data.html
5759

5860
* `RFC 17`_: Remove ``log2_int``
@@ -64,6 +66,7 @@ Implemented RFCs
6466
* `RFC 50`_: ``Print`` statement and string formatting
6567
* `RFC 51`_: Add ``ShapeCastable.from_bits`` and ``amaranth.lib.data.Const``
6668
* `RFC 53`_: Low-level I/O primitives
69+
* `RFC 59`_: Get rid of upwards propagation of clock domains
6770

6871

6972
Language changes
@@ -88,6 +91,7 @@ Language changes
8891
* Changed: :class:`Instance` IO ports now accept only IO values, not plain values. (`RFC 53`_)
8992
* Deprecated: :func:`amaranth.utils.log2_int`. (`RFC 17`_)
9093
* Deprecated: :class:`amaranth.hdl.Memory`. (`RFC 45`_)
94+
* Deprecated: upwards propagation of clock domains. (`RFC 59`_)
9195
* Removed: (deprecated in 0.4) :meth:`Const.normalize`. (`RFC 5`_)
9296
* Removed: (deprecated in 0.4) :class:`Repl`. (`RFC 10`_)
9397
* Removed: (deprecated in 0.4) :class:`ast.Sample`, :class:`ast.Past`, :class:`ast.Stable`, :class:`ast.Rose`, :class:`ast.Fell`.

tests/test_hdl_ir.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,20 @@ def test_propagate_create_missing_fragment_wrong(self):
668668
r"domain 'sync' \(defines 'foo'\)\.$")):
669669
f1._propagate_domains(missing_domain=lambda name: f2)
670670

671+
def test_propagate_up_use(self):
672+
a = Signal()
673+
m = Module()
674+
m1 = Module()
675+
m2 = Module()
676+
m.submodules.m1 = m1
677+
m.submodules.m2 = m2
678+
m1.d.test += a.eq(1)
679+
m2.domains.test = ClockDomain()
680+
with self.assertWarnsRegex(DeprecationWarning,
681+
r"^Domain 'test' is used in 'top.m1', but defined in 'top.m2', which will not be "
682+
r"supported in Amaranth 0.6; define the domain in 'top' or one of its parents$"):
683+
Fragment.get(m, None).prepare()
684+
671685

672686
class FragmentHierarchyConflictTestCase(FHDLTestCase):
673687
def test_no_conflict_local_domains(self):

0 commit comments

Comments
 (0)