Repository: amaranth-lang/amaranth (python)

Issue:
Issue with `Memory` simulation
Hi,

we are currently updating Amaranth version in Coreblocks (https://github.com/kuznia-rdzeni/coreblocks/pull/461) and unexpectedly some unit tests started to fail. Part of failing unit tests check the whole core on assembler programs. Similar tests using Cocotb and Verilator passed successfully, so I assume some problems with simulation inside Amaranth (as unit tests use amaranth tests framework). After some investigation it looks like the issue is related #893. Here is a reproduction example:

```py
class MemoryReproduce(TestCaseWithSimulator):
    class DUT(Elaboratable):
        def __init__(self):
            pass

        def elaborate(self, platform):
            m = Module()
            self.mem = Memory(width=8, depth=8)
            m.submodules.read_port = self.read_port = self.mem.read_port()
            m.submodules.write_port = self.write_port = self.mem.write_port()
            return m

    def test_minimal(self):
        m = self.DUT()

        def process():
            yield m.read_port.addr.eq(1)
            yield m.read_port.en.eq(1)
            yield
            print((yield m.read_port.data))
            yield
            yield m.write_port.addr.eq(2)
            yield m.write_port.data.eq(14)
            yield m.write_port.en.eq(1)
            print((yield m.read_port.data))
            yield
            yield m.write_port.en.eq(0)
            print((yield m.read_port.data))
            yield
            print((yield m.read_port.data))
            yield
            print((yield m.read_port.data))

        with self.run_simulation(m) as sim:
            sim.add_sync_process(process)
```

Output for this test case:
```
0
0
0
14
0
```

![obraz](https://github.com/amaranth-lang/amaranth/assets/34948061/6ec43920-0bf3-4cec-a369-fac6a60c6450)


Idea is that we try to read memory from the one address and write to the other one. So we expect that writing has no impact on reading. But as you can see that is not a case in Amaranth simulation. Write cause that on read data output port there is visible written value for one cycle.

Pull request:
hdl.mem: fix transparent read handling for simple write ports.
Fixes #922.

Patch:
diff --git a/amaranth/hdl/mem.py b/amaranth/hdl/mem.py
index dd402ed..1f14847 100644
--- a/amaranth/hdl/mem.py
+++ b/amaranth/hdl/mem.py
@@ -187,7 +187,8 @@ class Memory(Elaboratable):
                                 parts.append(Mux(cond, write_port.data[bits], data[bits]))
                             data = Cat(parts)
                         else:
-                            data = Mux(write_port.en, write_port.data, data)
+                            cond = write_port.en & (port.addr == write_port.addr)
+                            data = Mux(cond, write_port.data, data)
                 f.add_statements(
                     Switch(port.en, {
                         1: port.data.eq(data)


Test patch:
diff --git a/tests/test_sim.py b/tests/test_sim.py
index facd8ec..1421609 100644
--- a/tests/test_sim.py
+++ b/tests/test_sim.py
@@ -815,7 +815,52 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
             sim.add_clock(1e-6)
             sim.add_sync_process(process)
 
-    def test_memory_transparency(self):
+    def test_memory_transparency_simple(self):
+        m = Module()
+        init = [0x11, 0x22, 0x33, 0x44]
+        m.submodules.memory = memory = Memory(width=8, depth=4, init=init)
+        rdport = memory.read_port()
+        wrport = memory.write_port(granularity=8)
+        with self.assertSimulation(m) as sim:
+            def process():
+                yield rdport.addr.eq(0)
+                yield
+                yield Settle()
+                self.assertEqual((yield rdport.data), 0x11)
+                yield rdport.addr.eq(1)
+                yield
+                yield Settle()
+                self.assertEqual((yield rdport.data), 0x22)
+                yield wrport.addr.eq(0)
+                yield wrport.data.eq(0x44444444)
+                yield wrport.en.eq(1)
+                yield
+                yield Settle()
+                self.assertEqual((yield rdport.data), 0x22)
+                yield wrport.addr.eq(1)
+                yield wrport.data.eq(0x55)
+                yield wrport.en.eq(1)
+                yield
+                yield Settle()
+                self.assertEqual((yield rdport.data), 0x55)
+                yield wrport.addr.eq(1)
+                yield wrport.data.eq(0x66)
+                yield wrport.en.eq(1)
+                yield rdport.en.eq(0)
+                yield
+                yield Settle()
+                self.assertEqual((yield rdport.data), 0x55)
+                yield wrport.addr.eq(2)
+                yield wrport.data.eq(0x77)
+                yield wrport.en.eq(1)
+                yield rdport.en.eq(1)
+                yield
+                yield Settle()
+                self.assertEqual((yield rdport.data), 0x66)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+
+    def test_memory_transparency_multibit(self):
         m = Module()
         init = [0x11111111, 0x22222222, 0x33333333, 0x44444444]
         m.submodules.memory = memory = Memory(width=32, depth=4, init=init)