Implement transparent read port option on the XOR wrapper SRAM
authorCesar Strauss <cestrauss@gmail.com>
Sat, 30 Apr 2022 11:08:27 +0000 (08:08 -0300)
committerCesar Strauss <cestrauss@gmail.com>
Sat, 30 Apr 2022 11:10:41 +0000 (08:10 -0300)
Add a bypass path when simultaneously reading and writing to the same
address.
There needs to be an independent Mux for each write lane, since we may not be
writing on all lanes, necessarily.

src/soc/regfile/sram_wrapper.py

index f33088f0c94b9c95a844b7ec132428426d73c2a5..e4223f548359ddde592aa5805ea522169447d08f 100644 (file)
@@ -1268,12 +1268,16 @@ class DualPortXorRegfile(Elaboratable):
     :param addr_width: width of the address bus
     :param data_width: width of the data bus
     :param we_width: number of write enable lines
+    :param transparent: whether a simultaneous read and write returns the
+                        new value (True) or the old value (False) on the full
+                        read port
     """
 
-    def __init__(self, addr_width, data_width, we_width):
+    def __init__(self, addr_width, data_width, we_width, transparent):
         self.addr_width = addr_width
         self.data_width = data_width
         self.we_width = we_width
+        self.transparent = transparent
         # interface signals
         self.wr_addr_i = Signal(addr_width); """write port address"""
         self.wr_data_i = Signal(data_width); """write port data"""
@@ -1297,27 +1301,48 @@ class DualPortXorRegfile(Elaboratable):
             mem0.phase.eq(phase),
             mem1.phase.eq(phase),
         ]
+        # store the write information for the next cycle
+        last_addr = Signal(self.addr_width)
+        last_we = Signal(self.we_width)
+        last_data = Signal(self.data_width)
+        m.d.sync += [
+            last_addr.eq(self.wr_addr_i),
+            last_we.eq(self.wr_we_i),
+            last_data.eq(self.wr_data_i),
+        ]
+        # read path
         # wire read address to memories, and XOR their output
+        xor_data = Signal(self.data_width)
         m.d.comb += [
             mem0.rd_addr_i.eq(self.rd_addr_i),
             mem1.rd_addr_i.eq(self.rd_addr_i),
-            self.rd_data_o.eq(mem0.rd_data_o ^ mem1.rd_data_o),
+            xor_data.eq(mem0.rd_data_o ^ mem1.rd_data_o),
         ]
+        if self.transparent:
+            # do the read and write addresses coincide?
+            same_read_write = Signal()
+            m.d.sync += same_read_write.eq(self.rd_addr_i == self.wr_addr_i)
+            gran = self.data_width // self.we_width
+            for i in range(self.we_width):
+                # when simultaneously reading and writing to the same location
+                # and write lane, bypass the memory, and output the write
+                # holding register instead
+                with m.If(same_read_write & last_we[i]):
+                    m.d.comb += self.rd_data_o.word_select(i, gran).eq(
+                        last_data.word_select(i, gran))
+                # otherwise, output the xor data
+                with m.Else():
+                    m.d.comb += self.rd_data_o.word_select(i, gran).eq(
+                        xor_data.word_select(i, gran))
+        # when not transparent, just output the memory contents (xor data)
+        else:
+            m.d.comb += self.rd_data_o.eq(xor_data)
         # write path
         # 1) read the memory location which is about to be written
         m.d.comb += [
             mem0.rdp_addr_i.eq(self.wr_addr_i),
             mem1.rdp_addr_i.eq(self.wr_addr_i),
         ]
-        # store the write information for the next cycle
-        last_addr = Signal(self.addr_width)
-        last_we = Signal(self.we_width)
-        last_data = Signal(self.data_width)
-        m.d.sync += [
-            last_addr.eq(self.wr_addr_i),
-            last_we.eq(self.wr_we_i),
-            last_data.eq(self.wr_data_i),
-        ]
         # 2) write the XOR of the other memory data, and the desired value
         m.d.comb += [
             mem0.wr_addr_i.eq(last_addr),
@@ -1332,12 +1357,12 @@ class DualPortXorRegfile(Elaboratable):
 
 class DualPortXorRegfileTestCase(FHDLTestCase):
 
-    def test_case(self):
+    def do_test_case(self, transparent):
         """
         Simulate some read/write/modify operations on the dual port register
         file
         """
-        dut = DualPortXorRegfile(7, 32, 4)
+        dut = DualPortXorRegfile(7, 32, 4, transparent)
         sim = Simulator(dut)
         sim.add_clock(1e-6)
 
@@ -1400,8 +1425,12 @@ class DualPortXorRegfileTestCase(FHDLTestCase):
             yield from write(0, 0, 0)
             yield
             # test simultaneous read and write
-            # non-transparent read: returns the old value
-            yield from read(0x42, 0x78345612)
+            if transparent:
+                # transparent reads, returns the new value
+                yield from read(0x42, 0x78AA5666)
+            else:
+                # non-transparent read: returns the old value
+                yield from read(0x42, 0x78345612)
             yield from write(0x42, 0b0101, 0x55AA9966)
             yield
             # after a cycle, returns the new value
@@ -1417,6 +1446,8 @@ class DualPortXorRegfileTestCase(FHDLTestCase):
 
         sim.add_sync_process(process)
         debug_file = 'test_dual_port_xor_regfile'
+        if transparent:
+            debug_file += '_transparent'
         traces = ['clk', 'phase',
                   {'comment': 'write port'},
                   'wr_addr_i[6:0]', 'wr_we_i[3:0]', 'wr_data_i[31:0]',
@@ -1430,6 +1461,12 @@ class DualPortXorRegfileTestCase(FHDLTestCase):
         with sim_writer:
             sim.run()
 
+    def test_case(self):
+        with self.subTest("non-transparent reads"):
+            self.do_test_case(False)
+        with self.subTest("transparent reads"):
+            self.do_test_case(True)
+
 
 if __name__ == "__main__":
     unittest.main()