Skip to content

Commit 563f869

Browse files
wanda-phiwhitequark
authored andcommitted
Implement RFC 59: Get rid of upwards propagation of clock domains.
1 parent 2ef3388 commit 563f869

File tree

7 files changed

+30
-215
lines changed

7 files changed

+30
-215
lines changed

amaranth/hdl/_cd.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
13
from .. import tracer
24
from ._ast import Signal
35

@@ -27,9 +29,6 @@ class ClockDomain:
2729
If ``True``, the domain uses an asynchronous reset, and registers within this domain
2830
are initialized to their reset state when reset level changes. Otherwise, registers
2931
are initialized to reset state at the next clock cycle when reset is asserted.
30-
local : bool
31-
If ``True``, the domain will propagate only downwards in the design hierarchy. Otherwise,
32-
the domain will propagate everywhere.
3332
3433
Attributes
3534
----------
@@ -48,7 +47,7 @@ def _name_for(domain_name, signal_name):
4847
return f"{domain_name}_{signal_name}"
4948

5049
def __init__(self, name=None, *, clk_edge="pos", reset_less=False, async_reset=False,
51-
local=False):
50+
local=None):
5251
if name is None:
5352
try:
5453
name = tracer.get_var_name()
@@ -75,7 +74,17 @@ def __init__(self, name=None, *, clk_edge="pos", reset_less=False, async_reset=F
7574

7675
self.async_reset = async_reset
7776

78-
self.local = local
77+
# TODO(amaranth-0.7): remove
78+
if local is not None:
79+
warnings.warn(f"`local` is deprecated and has no effect",
80+
DeprecationWarning, stacklevel=2)
81+
82+
@property
83+
def local(self):
84+
# TODO(amaranth-0.7): remove
85+
warnings.warn(f"`local` is deprecated and has no effect",
86+
DeprecationWarning, stacklevel=2)
87+
return True
7988

8089
def rename(self, new_name):
8190
self.name = new_name

amaranth/hdl/_ir.py

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ def __init__(self, *, src_loc=None):
8282
self.generated = OrderedDict()
8383
self.src_loc = src_loc
8484
self.origins = None
85-
self.domains_propagated_up = {}
8685
self.domain_renames = {}
8786

8887
def add_domains(self, *domains):
@@ -127,67 +126,6 @@ def find_generated(self, *path):
127126
def elaborate(self, platform):
128127
return self
129128

130-
def _propagate_domains_up(self, hierarchy=("top",)):
131-
from ._xfrm import DomainRenamer
132-
133-
domain_subfrags = defaultdict(set)
134-
135-
# For each domain defined by a subfragment, determine which subfragments define it.
136-
for i, (subfrag, name, src_loc) in enumerate(self.subfragments):
137-
# First, recurse into subfragments and let them propagate domains up as well.
138-
hier_name = name
139-
if hier_name is None:
140-
hier_name = f"<unnamed #{i}>"
141-
subfrag._propagate_domains_up(hierarchy + (hier_name,))
142-
143-
# Second, classify subfragments by domains they define.
144-
for domain_name, domain in subfrag.domains.items():
145-
if domain.local:
146-
continue
147-
domain_subfrags[domain_name].add((subfrag, name, src_loc, i))
148-
149-
# For each domain defined by more than one subfragment, rename the domain in each
150-
# of the subfragments such that they no longer conflict.
151-
for domain_name, subfrags in domain_subfrags.items():
152-
if len(subfrags) == 1:
153-
continue
154-
155-
names = [n for f, n, s, i in subfrags]
156-
if not all(names):
157-
names = sorted(f"<unnamed #{i}>" if n is None else f"'{n}'"
158-
for f, n, s, i in subfrags)
159-
raise _cd.DomainError(
160-
"Domain '{}' is defined by subfragments {} of fragment '{}'; it is necessary "
161-
"to either rename subfragment domains explicitly, or give names to subfragments"
162-
.format(domain_name, ", ".join(names), ".".join(hierarchy)))
163-
164-
if len(names) != len(set(names)):
165-
names = sorted(f"#{i}" for f, n, s, i in subfrags)
166-
raise _cd.DomainError(
167-
"Domain '{}' is defined by subfragments {} of fragment '{}', some of which "
168-
"have identical names; it is necessary to either rename subfragment domains "
169-
"explicitly, or give distinct names to subfragments"
170-
.format(domain_name, ", ".join(names), ".".join(hierarchy)))
171-
172-
for subfrag, name, src_loc, i in subfrags:
173-
domain_name_map = {domain_name: f"{name}_{domain_name}"}
174-
self.subfragments[i] = (DomainRenamer(domain_name_map)(subfrag), name, src_loc)
175-
176-
# Finally, collect the (now unique) subfragment domains, and merge them into our domains.
177-
for i, (subfrag, name, src_loc) in enumerate(self.subfragments):
178-
hier_name = name
179-
if hier_name is None:
180-
hier_name = f"<unnamed #{i}>"
181-
for domain_name, domain in subfrag.domains.items():
182-
if domain.local:
183-
continue
184-
self.add_domains(domain)
185-
if domain_name in subfrag.domains_propagated_up:
186-
_used_in, defined_in = subfrag.domains_propagated_up[domain_name]
187-
else:
188-
defined_in = (*hierarchy, hier_name)
189-
self.domains_propagated_up[domain_name] = (hierarchy, defined_in)
190-
191129
def _propagate_domains_down(self, hierarchy=("top",)):
192130
# For each domain defined in this fragment, ensure it also exists in all subfragments.
193131
for i, (subfrag, name, src_loc) in enumerate(self.subfragments):
@@ -196,13 +134,8 @@ def _propagate_domains_down(self, hierarchy=("top",)):
196134
hier_name = f"<unnamed #{i}>"
197135

198136
for domain in self.iter_domains():
199-
if domain in subfrag.domains:
200-
assert self.domains[domain] is subfrag.domains[domain]
201-
else:
137+
if domain not in subfrag.domains:
202138
subfrag.add_domains(self.domains[domain])
203-
if domain in self.domains_propagated_up:
204-
_used_in, defined_in = self.domains_propagated_up[domain]
205-
subfrag.domains_propagated_up[domain] = (hierarchy + (hier_name,)), defined_in
206139

207140
subfrag._propagate_domains_down(hierarchy + (hier_name,))
208141

@@ -237,7 +170,6 @@ def _create_missing_domains(self, missing_domain, *, platform=None):
237170
return new_domains
238171

239172
def _propagate_domains(self, missing_domain, *, platform=None):
240-
self._propagate_domains_up()
241173
self._propagate_domains_down()
242174
new_domains = self._create_missing_domains(missing_domain, platform=platform)
243175
self._propagate_domains_down()

amaranth/hdl/_xfrm.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -464,10 +464,7 @@ def on_fragment(self, fragment):
464464

465465
old_local_domains, self._local_domains = self._local_domains, set(self._local_domains)
466466
for domain_name, domain in fragment.domains.items():
467-
if domain.local:
468-
self._local_domains.add(domain_name)
469-
else:
470-
self.defined_domains.add(domain_name)
467+
self._local_domains.add(domain_name)
471468

472469
for domain_name, statements in fragment.statements.items():
473470
self._add_used_domain(domain_name)
@@ -549,26 +546,11 @@ def on_fragment(self, fragment):
549546
class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
550547
def __init__(self, domains=None):
551548
self.domains = domains
552-
self.domains_propagated_up = {}
553-
554-
def _warn_on_propagation_up(self, domain, src_loc):
555-
if domain in self.domains_propagated_up:
556-
used_in, defined_in = self.domains_propagated_up[domain]
557-
common_prefix = []
558-
for u, d in zip(used_in, defined_in):
559-
if u == d:
560-
common_prefix.append(u)
561-
warnings.warn_explicit(f"Domain '{domain}' is used in '{'.'.join(used_in)}', but "
562-
f"defined in '{'.'.join(defined_in)}', which will not be "
563-
f"supported in Amaranth 0.6; define the domain in "
564-
f"'{'.'.join(common_prefix)}' or one of its parents",
565-
DeprecationWarning, filename=src_loc[0], lineno=src_loc[1])
566549

567550
def _resolve(self, domain, context):
568551
if domain not in self.domains:
569552
raise DomainError("Signal {!r} refers to nonexistent domain '{}'"
570553
.format(context, domain))
571-
self._warn_on_propagation_up(domain, context.src_loc)
572554
return self.domains[domain]
573555

574556
def replace_value_src_loc(self, value, new_value):
@@ -590,14 +572,6 @@ def on_ResetSignal(self, value):
590572

591573
def on_fragment(self, fragment):
592574
self.domains = fragment.domains
593-
self.domains_propagated_up = fragment.domains_propagated_up
594-
for domain, statements in fragment.statements.items():
595-
self._warn_on_propagation_up(domain, statements[0].src_loc)
596-
if isinstance(fragment, MemoryInstance):
597-
for port in fragment._read_ports:
598-
self._warn_on_propagation_up(port._domain, fragment.src_loc)
599-
for port in fragment._write_ports:
600-
self._warn_on_propagation_up(port._domain, fragment.src_loc)
601575
return super().on_fragment(fragment)
602576

603577

amaranth/lib/cdc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def elaborate(self, platform):
173173
.format(type(platform).__name__))
174174

175175
m = Module()
176-
m.domains += ClockDomain("async_ff", async_reset=True, local=True)
176+
m.domains += ClockDomain("async_ff", async_reset=True)
177177
flops = [Signal(1, name=f"stage{index}", init=1)
178178
for index in range(self._stages)]
179179
for i, o in zip((0, *flops), flops):

docs/changes.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ Language changes
2929
.. currentmodule:: amaranth.hdl
3030

3131
* Changed: overriding :meth:`ValueCastable.from_bits` is now mandatory. (`RFC 51`_)
32+
* Deprecated: the :py:`local=` argument to :class:`ClockDomain`. (`RFC 59`_)
3233
* Removed: (deprecated in 0.4) :class:`Record`.
3334
* Removed: (deprecated in 0.5) :class:`Memory` (`RFC 45`_)
3435
* Removed: (deprecated in 0.5) public submodules of :mod:`amaranth.hdl`.
3536
* Removed: (deprecated in 0.5) :meth:`Value.implies`.
36-
* Removed: (deprecated in 0.5) :meth:`Const.width`, :meth:`Const.signed`, :meth:`Signal.width`, :meth:`Signal.signed`
37+
* Removed: (deprecated in 0.5) :meth:`Const.width`, :meth:`Const.signed`, :meth:`Signal.width`, :meth:`Signal.signed`.
38+
* Removed: (deprecated in 0.5) upwards propagation of clock domains. (`RFC 59`_)
3739

3840

3941
Standard library changes

tests/test_hdl_cd.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ def test_name(self):
99
self.assertEqual(sync.name, "sync")
1010
self.assertEqual(sync.clk.name, "clk")
1111
self.assertEqual(sync.rst.name, "rst")
12-
self.assertEqual(sync.local, False)
1312
pix = ClockDomain()
1413
self.assertEqual(pix.name, "pix")
1514
self.assertEqual(pix.clk.name, "pix_clk")
@@ -21,8 +20,6 @@ def test_name(self):
2120
with self.assertRaisesRegex(ValueError,
2221
r"^Clock domain name must be specified explicitly$"):
2322
ClockDomain()
24-
cd_reset = ClockDomain(local=True)
25-
self.assertEqual(cd_reset.local, True)
2623

2724
def test_edge(self):
2825
sync = ClockDomain()
@@ -77,3 +74,11 @@ def test_wrong_name_comb(self):
7774
with self.assertRaisesRegex(ValueError,
7875
r"^Domain 'comb' may not be clocked$"):
7976
comb = ClockDomain()
77+
78+
def test_local(self):
79+
with self.assertWarnsRegex(DeprecationWarning,
80+
r"^`local` is deprecated and has no effect$"):
81+
sync = ClockDomain(local=False)
82+
with self.assertWarnsRegex(DeprecationWarning,
83+
r"^`local` is deprecated and has no effect$"):
84+
self.assertTrue(sync.local)

tests/test_hdl_ir.py

Lines changed: 2 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -456,99 +456,6 @@ def test_port_not_iterable(self):
456456

457457

458458
class FragmentDomainsTestCase(FHDLTestCase):
459-
def test_propagate_up(self):
460-
cd = ClockDomain()
461-
462-
f1 = Fragment()
463-
f2 = Fragment()
464-
f1.add_subfragment(f2)
465-
f2.add_domains(cd)
466-
467-
f1._propagate_domains_up()
468-
self.assertEqual(f1.domains, {"cd": cd})
469-
470-
def test_propagate_up_local(self):
471-
cd = ClockDomain(local=True)
472-
473-
f1 = Fragment()
474-
f2 = Fragment()
475-
f1.add_subfragment(f2)
476-
f2.add_domains(cd)
477-
478-
f1._propagate_domains_up()
479-
self.assertEqual(f1.domains, {})
480-
481-
def test_domain_conflict(self):
482-
cda = ClockDomain("sync")
483-
cdb = ClockDomain("sync")
484-
485-
fa = Fragment()
486-
fa.add_domains(cda)
487-
fb = Fragment()
488-
fb.add_domains(cdb)
489-
f = Fragment()
490-
f.add_subfragment(fa, "a")
491-
f.add_subfragment(fb, "b")
492-
493-
f._propagate_domains_up()
494-
self.assertEqual(f.domains, {"a_sync": cda, "b_sync": cdb})
495-
(fa, _, _), (fb, _, _) = f.subfragments
496-
self.assertEqual(fa.domains, {"a_sync": cda})
497-
self.assertEqual(fb.domains, {"b_sync": cdb})
498-
499-
def test_domain_conflict_anon(self):
500-
cda = ClockDomain("sync")
501-
cdb = ClockDomain("sync")
502-
503-
fa = Fragment()
504-
fa.add_domains(cda)
505-
fb = Fragment()
506-
fb.add_domains(cdb)
507-
f = Fragment()
508-
f.add_subfragment(fa, "a")
509-
f.add_subfragment(fb)
510-
511-
with self.assertRaisesRegex(DomainError,
512-
(r"^Domain 'sync' is defined by subfragments 'a', <unnamed #1> of fragment "
513-
r"'top'; it is necessary to either rename subfragment domains explicitly, "
514-
r"or give names to subfragments$")):
515-
f._propagate_domains_up()
516-
517-
def test_domain_conflict_name(self):
518-
cda = ClockDomain("sync")
519-
cdb = ClockDomain("sync")
520-
521-
fa = Fragment()
522-
fa.add_domains(cda)
523-
fb = Fragment()
524-
fb.add_domains(cdb)
525-
f = Fragment()
526-
f.add_subfragment(fa, "x")
527-
f.add_subfragment(fb, "x")
528-
529-
with self.assertRaisesRegex(DomainError,
530-
(r"^Domain 'sync' is defined by subfragments #0, #1 of fragment 'top', some "
531-
r"of which have identical names; it is necessary to either rename subfragment "
532-
r"domains explicitly, or give distinct names to subfragments$")):
533-
f._propagate_domains_up()
534-
535-
def test_domain_conflict_rename_drivers(self):
536-
cda = ClockDomain("sync")
537-
cdb = ClockDomain("sync")
538-
539-
fa = Fragment()
540-
fa.add_domains(cda)
541-
fb = Fragment()
542-
fb.add_domains(cdb)
543-
fb.add_statements("comb", ResetSignal("sync").eq(1))
544-
f = Fragment()
545-
f.add_subfragment(fa, "a")
546-
f.add_subfragment(fb, "b")
547-
548-
f._propagate_domains_up()
549-
fb_new, _, _ = f.subfragments[1]
550-
self.assertRepr(fb_new.statements["comb"], "((eq (rst b_sync) (const 1'd1)))")
551-
552459
def test_domain_conflict_rename_drivers_before_creating_missing(self):
553460
cda = ClockDomain("sync")
554461
cdb = ClockDomain("sync")
@@ -673,29 +580,15 @@ def test_propagate_create_missing_fragment_wrong(self):
673580
r"domain 'sync' \(defines 'foo'\)\.$")):
674581
f1._propagate_domains(missing_domain=lambda name: f2)
675582

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

691584
class FragmentHierarchyConflictTestCase(FHDLTestCase):
692585
def test_no_conflict_local_domains(self):
693586
f1 = Fragment()
694-
cd1 = ClockDomain("d", local=True)
587+
cd1 = ClockDomain("d")
695588
f1.add_domains(cd1)
696589
f1.add_statements("comb", ClockSignal("d").eq(1))
697590
f2 = Fragment()
698-
cd2 = ClockDomain("d", local=True)
591+
cd2 = ClockDomain("d")
699592
f2.add_domains(cd2)
700593
f2.add_statements("comb", ClockSignal("d").eq(1))
701594
f3 = Fragment()

0 commit comments

Comments
 (0)