hdl.mem: ensure transparent read port model has correct latency.
authorwhitequark <cz@m-labs.hk>
Fri, 21 Dec 2018 13:01:08 +0000 (13:01 +0000)
committerwhitequark <cz@m-labs.hk>
Fri, 21 Dec 2018 13:01:08 +0000 (13:01 +0000)
nmigen/hdl/mem.py
nmigen/test/test_sim.py

index 99ed6e1f78bae59e761d8999cff3bba9a4f7cb8e..3467a2b8379a4fae9c4c7c9ccba47e9b8c208a5d 100644 (file)
@@ -88,23 +88,38 @@ class ReadPort:
             i_ADDR=self.addr,
             o_DATA=self.data,
         )
-        read_data = self.data.eq(self.memory._array[self.addr])
         if self.synchronous and not self.transparent:
             # Synchronous, read-before-write port
-            f.add_statements(Switch(self.en, { 1: read_data }))
+            f.add_statements(
+                Switch(self.en, {
+                    1: self.data.eq(self.memory._array[self.addr])
+                })
+            )
             f.add_driver(self.data, self.domain)
         elif self.synchronous:
             # Synchronous, write-through port
             # This model is a bit unconventional. We model transparent ports as asynchronous ports
             # that are latched when the clock is high. This isn't exactly correct, but it is very
             # close to the correct behavior of a transparent port, and the difference should only
-            # be observable in pathological cases of clock gating.
-            f.add_statements(Switch(ClockSignal(self.domain),
-                { 1: self.data.eq(self.data), 0: read_data }))
+            # be observable in pathological cases of clock gating. A register is injected to
+            # the address input to achieve the correct address-to-data latency. Also, the reset
+            # value of the data output is forcibly set to the 0th initial value, if any--note that
+            # many FPGAs do not guarantee this behavior!
+            if len(self.memory.init) > 0:
+                self.data.reset = self.memory.init[0]
+            latch_addr = Signal.like(self.addr)
+            f.add_statements(
+                latch_addr.eq(self.addr),
+                Switch(ClockSignal(self.domain), {
+                    0: self.data.eq(self.data),
+                    1: self.data.eq(self.memory._array[latch_addr]),
+                }),
+            )
+            f.add_driver(latch_addr, self.domain)
             f.add_driver(self.data)
         else:
             # Asynchronous port
-            f.add_statements(read_data)
+            f.add_statements(self.data.eq(self.memory._array[self.addr]))
             f.add_driver(self.data)
         return f
 
index 070fcdb0115cd802ac085789f446d28ce485c4de..4dd7b5b16e7e16d89b977d097cc6d5757b227067 100644 (file)
@@ -419,13 +419,14 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
         self.setUp_memory()
         with self.assertSimulation(self.m) as sim:
             def process():
-                yield
                 self.assertEqual((yield self.rdport.data), 0xaa)
                 yield self.rdport.addr.eq(1)
                 yield
+                yield
                 self.assertEqual((yield self.rdport.data), 0x55)
                 yield self.rdport.addr.eq(2)
                 yield
+                yield
                 self.assertEqual((yield self.rdport.data), 0x00)
             sim.add_clock(1e-6)
             sim.add_sync_process(process)
@@ -493,6 +494,10 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
                 self.assertEqual((yield self.rdport.data), 0xaa)
                 yield Delay(1e-6) # let comb propagate
                 self.assertEqual((yield self.rdport.data), 0x33)
+                yield
+                yield self.rdport.addr.eq(1)
+                yield Delay(1e-6) # let comb propagate
+                self.assertEqual((yield self.rdport.data), 0x33)
             sim.add_clock(1e-6)
             sim.add_sync_process(process)