hdl.ir: allow returning elaboratables from missing domain callback.
authorwhitequark <cz@m-labs.hk>
Sat, 3 Aug 2019 15:44:02 +0000 (15:44 +0000)
committerwhitequark <cz@m-labs.hk>
Sat, 3 Aug 2019 15:44:02 +0000 (15:44 +0000)
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
nmigen/test/test_hdl_ir.py

index 578509e92987a62a7baeb562a5361ac84f42e5af..db5693f8c10e8ddb0d97b11e944491e99f263327 100644 (file)
@@ -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
index 60c018943656f2cece58bd9e0ca77ae14631791d..576338057b4cd1f2ebf60b4ca7b61b01e93e3c98 100644 (file)
@@ -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):