diff --git a/amaranth/hdl/_ir.py b/amaranth/hdl/_ir.py index 7f8a998..b4c3801 100644 --- a/amaranth/hdl/_ir.py +++ b/amaranth/hdl/_ir.py @@ -9,8 +9,8 @@ from . import _ast, _cd, _ir, _nir __all__ = [ - "UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance", - "IOBufferInstance", "PortDirection", "Design", "build_netlist", + "UnusedElaboratable", "Elaboratable", "DuplicateElaboratable", "DriverConflict", "Fragment", + "Instance", "IOBufferInstance", "PortDirection", "Design", "build_netlist", ] @@ -30,6 +30,10 @@ class DriverConflict(UserWarning): pass +class DuplicateElaboratable(Exception): + pass + + class Fragment: @staticmethod def get(obj, platform): @@ -430,6 +434,7 @@ class Design: self.hierarchy = hierarchy self.fragments: dict[Fragment, DesignFragmentInfo] = {} self.signal_lca = _ast.SignalDict() + self.elaboratables: dict[Elaboratable, Fragment] = {} self._compute_fragment_depth_parent(fragment, None, 0) self._collect_used_signals(fragment) self._add_io_ports() @@ -598,6 +603,15 @@ class Design: frag_info = self.fragments[fragment] frag_info.name = hierarchy + if fragment.origins is not None: + for origin in fragment.origins: + if origin in self.elaboratables: + other_hierarchy = self.fragments[self.elaboratables[origin]].name + raise DuplicateElaboratable(f"Elaboratable {origin!r} is included twice " + f"in the hierarchy, as {'.'.join(other_hierarchy)} " + f"and {'.'.join(hierarchy)}") + self.elaboratables[origin] = fragment + if fragment is self.fragment: # Reserve names for top-level ports. If equal to the signal name, let the signal share it. for name, conn, _dir in self.ports: diff --git a/amaranth/hdl/_xfrm.py b/amaranth/hdl/_xfrm.py index dde48d5..ccc91ae 100644 --- a/amaranth/hdl/_xfrm.py +++ b/amaranth/hdl/_xfrm.py @@ -318,6 +318,7 @@ class FragmentTransformer: else: new_fragment = Fragment(src_loc=fragment.src_loc) new_fragment.attrs = OrderedDict(fragment.attrs) + new_fragment.origins = fragment.origins self.map_subfragments(fragment, new_fragment) self.map_domains(fragment, new_fragment) self.map_statements(fragment, new_fragment) diff --git a/tests/test_hdl_ir.py b/tests/test_hdl_ir.py index 8161bd2..f54bc08 100644 --- a/tests/test_hdl_ir.py +++ b/tests/test_hdl_ir.py @@ -77,6 +77,18 @@ class FragmentDriversTestCase(FHDLTestCase): self.assertEqual(list(f.iter_sync()), []) +class DuplicateElaboratableTestCase(FHDLTestCase): + def test_duplicate(self): + sub = Module() + m = Module() + m.submodules.a = sub + m.submodules.b = sub + with self.assertRaisesRegex(DuplicateElaboratable, + r"^Elaboratable .* is included twice in the hierarchy, as " + r"top\.a and top\.b$"): + Fragment.get(m, None).prepare() + + class FragmentPortsTestCase(FHDLTestCase): def setUp(self): self.s1 = Signal()