From e19eeb30cb8ede0674f581e028c06d0cb22796a8 Mon Sep 17 00:00:00 2001 From: Cesar Strauss Date: Sat, 30 Apr 2022 08:08:27 -0300 Subject: [PATCH] Implement transparent read port option on the XOR wrapper SRAM 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 | 67 +++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/soc/regfile/sram_wrapper.py b/src/soc/regfile/sram_wrapper.py index f33088f0..e4223f54 100644 --- a/src/soc/regfile/sram_wrapper.py +++ b/src/soc/regfile/sram_wrapper.py @@ -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() -- 2.30.2