From cea92e95314e882059b8380c8253f34c46c9a8b0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 3 Aug 2019 15:44:02 +0000 Subject: [PATCH] hdl.ir: allow returning elaboratables from missing domain callback. This allows e.g. injecting a clock/reset generator in platform build code on demand (i.e. if the domain is not instantiated manually). See #57. --- nmigen/hdl/ir.py | 18 ++++++++++++++++-- nmigen/test/test_hdl_ir.py | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/nmigen/hdl/ir.py b/nmigen/hdl/ir.py index 578509e..db5693f 100644 --- a/nmigen/hdl/ir.py +++ b/nmigen/hdl/ir.py @@ -359,9 +359,23 @@ class Fragment: if domain_name is None: continue if domain_name not in self.domains: - domain = missing_domain(domain_name) - if domain is None: + value = missing_domain(domain_name) + if value is None: raise DomainError("Domain '{}' is used but not defined".format(domain_name)) + if type(value) is ClockDomain: + domain = value + else: + new_fragment = Fragment.get(value, platform=None) + if new_fragment.domains.keys() != {domain_name}: + raise DomainError( + "Fragment returned by missing domain callback should define exactly " + "one domain '{}', but defines domain(s) {}." + .format(domain_name, + ", ".join("'{}'".format(n) + for n in new_fragment.domains.keys()))) + new_fragment.flatten = True + self.add_subfragment(new_fragment) + domain = new_fragment.domains[domain_name] self.add_domains(domain) new_domains.append(domain) return new_domains diff --git a/nmigen/test/test_hdl_ir.py b/nmigen/test/test_hdl_ir.py index 60c0189..5763380 100644 --- a/nmigen/test/test_hdl_ir.py +++ b/nmigen/test/test_hdl_ir.py @@ -376,9 +376,10 @@ class FragmentDomainsTestCase(FHDLTestCase): f1.add_domains(cd) f1.add_subfragment(f2) - f1._propagate_domains(missing_domain=lambda name: None) + new_domains = f1._propagate_domains(missing_domain=lambda name: None) self.assertEqual(f1.domains, {"cd": cd}) self.assertEqual(f2.domains, {"cd": cd}) + self.assertEqual(new_domains, []) def test_propagate_missing(self): s1 = Signal() @@ -396,10 +397,42 @@ class FragmentDomainsTestCase(FHDLTestCase): f2 = Fragment() f1.add_subfragment(f2) - f1._propagate_domains(missing_domain=lambda name: ClockDomain(name)) + new_domains = f1._propagate_domains(missing_domain=lambda name: ClockDomain(name)) self.assertEqual(f1.domains.keys(), {"sync"}) self.assertEqual(f2.domains.keys(), {"sync"}) self.assertEqual(f1.domains["sync"], f2.domains["sync"]) + self.assertEqual(new_domains, [f1.domains["sync"]]) + + def test_propagate_create_missing_fragment(self): + s1 = Signal() + f1 = Fragment() + f1.add_driver(s1, "sync") + + cd = ClockDomain("sync") + f2 = Fragment() + f2.add_domains(cd) + + new_domains = f1._propagate_domains(missing_domain=lambda name: f2) + self.assertEqual(f1.domains.keys(), {"sync"}) + self.assertEqual(f1.domains["sync"], f2.domains["sync"]) + self.assertEqual(new_domains, [f1.domains["sync"]]) + self.assertEqual(f1.subfragments, [ + (f2, None) + ]) + self.assertTrue(f2.flatten) + + def test_propagate_create_missing_fragment_wrong(self): + s1 = Signal() + f1 = Fragment() + f1.add_driver(s1, "sync") + + f2 = Fragment() + f2.add_domains(ClockDomain("foo")) + + with self.assertRaises(DomainError, + msg="Fragment returned by missing domain callback should define exactly " + "one domain 'sync', but defines domain(s) 'foo'."): + f1._propagate_domains(missing_domain=lambda name: f2) class FragmentHierarchyConflictTestCase(FHDLTestCase): -- 2.30.2