Repository: amaranth-lang/amaranth
Pull request #1207 (2024-03-15 21:28:49)

hdl._ir: Fix fallout from #1190, add more tests.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1207?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 89.48%. Comparing base [(`83701d7`)](https://app.codecov.io/gh/amaranth-lang/amaranth/commit/83701d74cfca7d684f1137a3eb5d277d1ef572ce?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) to head [(`27dddf4`)](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1207?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1207      +/-   ##
==========================================
+ Coverage   89.44%   89.48%   +0.04%     
==========================================
  Files          43       43              
  Lines        9067     9067              
  Branches     2150     2150              
==========================================
+ Hits         8110     8114       +4     
+ Misses        781      777       -4     
  Partials      176      176              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1207?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).


Patch:
diff --git a/amaranth/hdl/_ir.py b/amaranth/hdl/_ir.py
index 5c6ace6..762683b 100644
--- a/amaranth/hdl/_ir.py
+++ b/amaranth/hdl/_ir.py
@@ -772,7 +772,7 @@ class NetlistEmitter:
                 signal, bit = self.late_net_to_signal[left]
                 other_src_loc = self.connect_src_loc[left]
                 raise _ir.DriverConflict(f"Bit {bit} of signal {signal!r} has multiple drivers: "
-                                         f"{other_src_loc} and {src_loc}")
+                                         f"{other_src_loc[0]}:{other_src_loc[1]} and {src_loc[0]}:{src_loc[1]}")
             self.netlist.connections[left] = right
             self.connect_src_loc[left] = src_loc
 
@@ -785,15 +785,15 @@ class NetlistEmitter:
                     domain_name = cd.name if cd is not None else "comb"
                     other_domain_name = driver.domain.name if driver.domain is not None else "comb"
                     raise _ir.DriverConflict(
-                        f"Signal {lhs} driven from domain {domain_name} at {src_loc} and domain "
-                        f"{other_domain_name} at {driver.src_loc}")
+                        f"Signal {lhs!r} driven from domain {domain_name} at {src_loc[0]}:{src_loc[1]} and domain "
+                        f"{other_domain_name} at {driver.src_loc[0]}:{driver.src_loc[1]}")
                 if driver.module_idx != module_idx:
                     mod_name = ".".join(self.netlist.modules[module_idx].name or ("<toplevel>",))
                     other_mod_name = \
                         ".".join(self.netlist.modules[driver.module_idx].name or ("<toplevel>",))
                     raise _ir.DriverConflict(
-                        f"Signal {lhs} driven from module {mod_name} at {src_loc} and "
-                        f"module {other_mod_name} at {driver.src_loc}")
+                        f"Signal {lhs!r} driven from module {mod_name} at {src_loc[0]}:{src_loc[1]} and "
+                        f"module {other_mod_name} at {driver.src_loc[0]}:{driver.src_loc[1]}")
             else:
                 driver = NetlistDriver(module_idx, lhs, domain=cd, src_loc=src_loc)
                 self.drivers[lhs] = driver
diff --git a/amaranth/lib/memory.py b/amaranth/lib/memory.py
index 802671e..dc7aed3 100644
--- a/amaranth/lib/memory.py
+++ b/amaranth/lib/memory.py
@@ -98,6 +98,9 @@ class WritePort:
                 "en": wiring.In(en_width),
             })
 
+        def create(self, *, path=None, src_loc_at=0):
+            return WritePort(self, memory=None, domain="sync", path=path, src_loc_at=1 + src_loc_at)
+
         @property
         def addr_width(self):
             return self._addr_width
@@ -121,7 +124,7 @@ class WritePort:
             return f"WritePort.Signature(addr_width={self.addr_width}, shape={self.shape}{granularity})"
 
 
-    def __init__(self, signature, *, memory, domain):
+    def __init__(self, signature, *, memory, domain, path=None, src_loc_at=0):
         if not isinstance(signature, WritePort.Signature):
             raise TypeError(f"Expected `WritePort.Signature`, not {signature!r}")
         if memory is not None:
@@ -138,7 +141,7 @@ class WritePort:
         self._signature = signature
         self._memory = memory
         self._domain = domain
-        self.__dict__.update(signature.members.create())
+        self.__dict__.update(signature.members.create(path=path, src_loc_at=1 + src_loc_at))
         if memory is not None:
             memory._w_ports.append(self)
 
@@ -211,6 +214,9 @@ class ReadPort:
                 "en": wiring.In(1, init=1),
             })
 
+        def create(self, *, path=None, src_loc_at=0):
+            return ReadPort(self, memory=None, domain="sync", path=path, src_loc_at=1 + src_loc_at)
+
         @property
         def addr_width(self):
             return self._addr_width
@@ -228,7 +234,7 @@ class ReadPort:
             return f"ReadPort.Signature(addr_width={self.addr_width}, shape={self.shape})"
 
 
-    def __init__(self, signature, *, memory, domain, transparent_for=()):
+    def __init__(self, signature, *, memory, domain, transparent_for=(), path=None, src_loc_at=0):
         if not isinstance(signature, ReadPort.Signature):
             raise TypeError(f"Expected `ReadPort.Signature`, not {signature!r}")
         if memory is not None:
@@ -252,7 +258,7 @@ class ReadPort:
         self._memory = memory
         self._domain = domain
         self._transparent_for = transparent_for
-        self.__dict__.update(signature.members.create())
+        self.__dict__.update(signature.members.create(path=path, src_loc_at=1 + src_loc_at))
         if domain == "comb":
             self.en = Const(1)
         if memory is not None:


Test patch:
diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
index 5cc6c2a..18653a5 100644
--- a/tests/test_hdl_ast.py
+++ b/tests/test_hdl_ast.py
@@ -1,4 +1,4 @@
-# amaranth: UnusedPrint=no, UnusedProperty
+# amaranth: UnusedPrint=no, UnusedProperty=no
 
 import warnings
 from enum import Enum, EnumMeta
diff --git a/tests/test_hdl_ir.py b/tests/test_hdl_ir.py
index f01a773..f37e05a 100644
--- a/tests/test_hdl_ir.py
+++ b/tests/test_hdl_ir.py
@@ -3142,3 +3142,40 @@ class SwitchTestCase(FHDLTestCase):
             (cell 21 0 (cover 0.6 20.0 neg 0.12 ('c')))
         )
         """)
+
+class ConflictTestCase(FHDLTestCase):
+    def test_domain_conflict(self):
+        s = Signal()
+        m = Module()
+        m.d.sync += s.eq(1)
+        m1 = Module()
+        m1.d.comb += s.eq(2)
+        m.submodules.m1 = m1
+        with self.assertRaisesRegex(DriverConflict,
+                r"^Signal \(sig s\) driven from domain comb at "
+                r".*test_hdl_ir.py:\d+ and domain sync at "
+                r".*test_hdl_ir.py:\d+$"):
+            build_netlist(Fragment.get(m, None), [])
+
+    def test_module_conflict(self):
+        s = Signal()
+        m = Module()
+        m.d.sync += s.eq(1)
+        m1 = Module()
+        m1.d.sync += s.eq(2)
+        m.submodules.m1 = m1
+        with self.assertRaisesRegex(DriverConflict,
+                r"^Signal \(sig s\) driven from module top\.m1 at "
+                r".*test_hdl_ir.py:\d+ and module top at "
+                r".*test_hdl_ir.py:\d+$"):
+            build_netlist(Fragment.get(m, None), [])
+
+    def test_instance_conflict(self):
+        s = Signal()
+        m = Module()
+        m.d.sync += s.eq(1)
+        m.submodules.t = Instance("tt", o_s=s)
+        with self.assertRaisesRegex(DriverConflict,
+                r"^Bit 0 of signal \(sig s\) has multiple drivers: "
+                r".*test_hdl_ir.py:\d+ and .*test_hdl_ir.py:\d+$"):
+            build_netlist(Fragment.get(m, None), [])
diff --git a/tests/test_lib_memory.py b/tests/test_lib_memory.py
index 357d90a..401b4bb 100644
--- a/tests/test_lib_memory.py
+++ b/tests/test_lib_memory.py
@@ -122,6 +122,15 @@ class WritePortTestCase(FHDLTestCase):
         self.assertIs(port.memory, m)
         self.assertEqual(m.w_ports, (port,))
 
+        signature = memory.WritePort.Signature(shape=MyStruct, addr_width=4)
+        port = signature.create()
+        self.assertEqual(port.signature, signature)
+        self.assertIsNone(port.memory)
+        self.assertEqual(port.domain, "sync")
+        self.assertRepr(port.addr, "(sig port__addr)")
+        port = signature.create(path=("abc",))
+        self.assertRepr(port.addr, "(sig abc__addr)")
+
     def test_constructor_wrong(self):
         signature = memory.ReadPort.Signature(shape=8, addr_width=4)
         with self.assertRaisesRegex(TypeError,
@@ -224,6 +233,15 @@ class ReadPortTestCase(FHDLTestCase):
         self.assertIs(port.memory, m)
         self.assertEqual(port.transparent_for, (write_port,))
 
+        signature = memory.ReadPort.Signature(shape=MyStruct, addr_width=4)
+        port = signature.create()
+        self.assertEqual(port.signature, signature)
+        self.assertIsNone(port.memory)
+        self.assertEqual(port.domain, "sync")
+        self.assertRepr(port.addr, "(sig port__addr)")
+        port = signature.create(path=("abc",))
+        self.assertRepr(port.addr, "(sig abc__addr)")
+
     def test_constructor_wrong(self):
         signature = memory.WritePort.Signature(shape=8, addr_width=4)
         with self.assertRaisesRegex(TypeError,