Allow the formal engine to perform a same-cycle result in the ALU
[soc.git] / src / soc / experiment / l0_cache.py
index 474e9ae7d531a33cc0657733dc4391ebfddbfc32..42ef061072d6b6b1511fa9e16061286744b27153 100644 (file)
@@ -23,68 +23,48 @@ from nmigen.utils import log2_int
 from nmigen.hdl.rec import Record, Layout
 
 from nmutil.latch import SRLatch, latchregister
-from soc.decoder.power_decoder2 import Data
-from soc.decoder.power_enums import InternalOp
+from openpower.decoder.power_decoder2 import Data
+from openpower.decoder.power_enums import MicrOp
 from soc.regfile.regfile import ortreereduce
 from nmutil.util import treereduce
 
-from soc.decoder.power_decoder2 import Data
+from openpower.decoder.power_decoder2 import Data
 #from nmutil.picker import PriorityPicker
 from nmigen.lib.coding import PriorityEncoder
 from soc.scoreboard.addr_split import LDSTSplitter
 from soc.scoreboard.addr_match import LenExpand
 
 # for testing purposes
-from soc.experiment.testmem import TestMemory # TODO: replace with TMLSUI
-# TODO: from soc.experiment.testmem import TestMemoryLoadStoreUnit
-from soc.experiment.pimem import PortInterface, TestMemoryPortInterface
-
+from soc.config.test.test_loadstore import TestMemPspec
+from soc.config.loadstore import ConfigMemoryPortInterface
+from soc.experiment.pimem import PortInterface
+from soc.config.test.test_pi2ls import pi_ld, pi_st, pi_ldst
 import unittest
 
+class L0CacheBuffer2(Elaboratable):
+    """L0CacheBuffer2"""
+    def __init__(self, n_units=8, regwid=64, addrwid=64):
+        self.n_units = n_units
+        self.regwid = regwid
+        self.addrwid = addrwid
+        ul = []
+        for i in range(self.n_units):
+            ul += [PortInterface()]
+        self.dports = Array(ul)
 
-class DualPortSplitter(Elaboratable):
-    """DualPortSplitter
-
-    * one incoming PortInterface
-    * two *OUTGOING* PortInterfaces
-    * uses LDSTSplitter to do it
-
-    (actually, thinking about it LDSTSplitter could simply be
-     modified to conform to PortInterface: one in, two out)
-
-    once that is done each pair of ports may be wired directly
-    to the dual ports of L0CacheBuffer
+    def elaborate(self, platform):
+        m = Module()
+        comb, sync = m.d.comb, m.d.sync
 
-    The split is carried out so that, regardless of alignment or
-    mis-alignment, outgoing PortInterface[0] takes bit 4 == 0
-    of the address, whilst outgoing PortInterface[1] takes
-    bit 4 == 1.
+        # connect the ports as modules
 
-    PortInterface *may* need to be changed so that the length is
-    a binary number (accepting values 1-16).
-    """
-    def __init__(self):
-        self.outp = [PortInterface(name="outp_0"),
-                     PortInterface(name="outp_1")]
-        self.inp  = PortInterface(name="inp")
-        print(self.outp)
+        for i in range(self.n_units):
+            d = LDSTSplitter(64, 64, 4, self.dports[i])
+            setattr(m.submodules, "ldst_splitter%d" % i, d)
 
-    def elaborate(self, platform):
-        m = Module()
-        comb = m.d.comb
-        m.submodules.splitter = splitter = LDSTSplitter(64, 48, 4)
-        comb += splitter.addr_i.eq(self.inp.addr) #XXX
-        #comb += splitter.len_i.eq()
-        #comb += splitter.valid_i.eq()
-        comb += splitter.is_ld_i.eq(self.inp.is_ld_i)
-        comb += splitter.is_st_i.eq(self.inp.is_st_i)
-        #comb += splitter.st_data_i.eq()
-        #comb += splitter.sld_valid_i.eq()
-        #comb += splitter.sld_data_i.eq()
-        #comb += splitter.sst_valid_i.eq()
+        # state-machine latches TODO
         return m
 
-
 class DataMergerRecord(Record):
     """
     {data: 128 bit, byte_enable: 16 bit}
@@ -98,6 +78,26 @@ class DataMergerRecord(Record):
         self.data.reset_less = True
         self.en.reset_less = True
 
+class CacheRecord(Record):
+    def __init__(self, name=None):
+        layout = (('addr', 37),
+                  ('a_even', 7),
+                  ('bytemask_even', 16),
+                  ('data_even', 128),
+                  ('a_odd', 7),
+                  ('bytemask_odd', 16),
+                  ('data_odd', 128))
+        Record.__init__(self, Layout(layout), name=name)
+
+        self.addr.reset_less = True
+        self.a_even.reset_less = True
+        self.bytemask_even.reset_less = True
+        self.data_even.reset_less = True
+        self.a_odd.reset_less = True
+        self.bytemask_odd.reset_less = True
+        self.data_odd.reset_less = True
+
+
 
 # TODO: formal verification
 class DataMerger(Elaboratable):
@@ -126,8 +126,8 @@ class DataMerger(Elaboratable):
         :addr_array_i: an NxN Array of Signals with bits set indicating address
                        match.  bits across the diagonal (addr_array_i[x][x])
                        will always be set, to indicate "active".
-        :data_i: an Nx Array of Records {data: 128 bit, byte_enable: 16 bit}
-        :data_o: an Output Record of same type
+        :i_data: an Nx Array of Records {data: 128 bit, byte_enable: 16 bit}
+        :o_data: an Output Record of same type
                  {data: 128 bit, byte_enable: 16 bit}
         """
         self.array_size = array_size
@@ -141,30 +141,71 @@ class DataMerger(Elaboratable):
         ul = []
         for i in range(array_size):
             ul.append(DataMergerRecord())
-        self.data_i = Array(ul)
-        self.data_o = DataMergerRecord()
+        self.i_data = Array(ul)
+        self.o_data = DataMergerRecord()
 
     def elaborate(self, platform):
         m = Module()
         comb = m.d.comb
-        #(1) pick a row
+        # (1) pick a row
         m.submodules.pick = pick = PriorityEncoder(self.array_size)
         for j in range(self.array_size):
             comb += pick.i[j].eq(self.addr_array_i[j].bool())
         valid = ~pick.n
         idx = pick.o
-        #(2) merge
+        # (2) merge
         with m.If(valid):
             l = []
             for j in range(self.array_size):
                 select = self.addr_array_i[idx][j]
                 r = DataMergerRecord()
                 with m.If(select):
-                    comb += r.eq(self.data_i[j])
+                    comb += r.eq(self.i_data[j])
                 l.append(r)
-            comb += self.data_o.data.eq(ortreereduce(l,"data"))
-            comb += self.data_o.en.eq(ortreereduce(l,"en"))
+            comb += self.o_data.data.eq(ortreereduce(l, "data"))
+            comb += self.o_data.en.eq(ortreereduce(l, "en"))
+
+        return m
+
+class TstDataMerger2(Elaboratable):
+    def __init__(self):
+        self.data_odd = Signal(128,reset_less=True)
+        self.data_even = Signal(128,reset_less=True)
+        self.n_units = 8
+        ul = []
+        for i in range(self.n_units):
+            ul.append(CacheRecord())
+        self.input_array = Array(ul)
 
+    def addr_match(self,j,addr):
+        ret = []
+        for k in range(self.n_units):
+            ret += [(addr[j] == addr[k])]
+        return Cat(*ret)
+
+    def elaborate(self, platform):
+        m = Module()
+        m.submodules.dm_odd = dm_odd = DataMerger(self.n_units)
+        m.submodules.dm_even = dm_even = DataMerger(self.n_units)
+
+        addr_even = []
+        addr_odd = []
+        for j in range(self.n_units):
+            inp = self.input_array[j]
+            addr_even += [Cat(inp.addr,inp.a_even)]
+            addr_odd +=  [Cat(inp.addr,inp.a_odd)]
+
+        for j in range(self.n_units):
+            inp = self.input_array[j]
+            m.d.comb += dm_even.i_data[j].en.eq(inp.bytemask_even)
+            m.d.comb += dm_odd.i_data[j].en.eq(inp.bytemask_odd)
+            m.d.comb += dm_even.i_data[j].data.eq(inp.data_even)
+            m.d.comb += dm_odd.i_data[j].data.eq(inp.data_odd)
+            m.d.comb += dm_even.addr_array_i[j].eq(self.addr_match(j,addr_even))
+            m.d.comb += dm_odd.addr_array_i[j].eq(self.addr_match(j,addr_odd))
+
+        m.d.comb += self.data_odd.eq(dm_odd.o_data.data)
+        m.d.comb += self.data_even.eq(dm_even.o_data.data)
         return m
 
 
@@ -187,7 +228,7 @@ class L0CacheBuffer(Elaboratable):
     by this class.  That task is taken care of by LDSTCompUnit.
     """
 
-    def __init__(self, n_units, pimem, regwid=64, addrwid=48):
+    def __init__(self, n_units, pimem, regwid=64, addrwid=64):
         self.n_units = n_units
         self.pimem = pimem
         self.regwid = regwid
@@ -202,7 +243,7 @@ class L0CacheBuffer(Elaboratable):
         comb, sync = m.d.comb, m.d.sync
 
         # connect the ports as modules
-        #for i in range(self.n_units):
+        # for i in range(self.n_units):
         #    setattr(m.submodules, "port%d" % i, self.dports[i])
 
         # state-machine latches
@@ -218,8 +259,8 @@ class L0CacheBuffer(Elaboratable):
         ldsti = []
         for i in range(self.n_units):
             pi = self.dports[i]
-            busy = (pi.is_ld_i | pi.is_st_i)# & pi.busy_o
-            ldsti.append(busy) # accumulate ld/st-req
+            busy = (pi.is_ld_i | pi.is_st_i)  # & pi.busy_o
+            ldsti.append(busy)  # accumulate ld/st-req
         # put the requests into the priority-picker
         comb += pick.i.eq(Cat(*ldsti))
 
@@ -244,8 +285,8 @@ class L0CacheBuffer(Elaboratable):
 
         with m.If(idx_l.q):
             comb += self.pimem.connect_port(port)
-            with m.If(~self.pimem.pi.pi.busy_o):
-                comb += reset_l.s.eq(1) # reset when no longer busy
+            with m.If(~self.pimem.pi.busy_o):
+                comb += reset_l.s.eq(1)  # reset when no longer busy
 
         # ugly hack, due to simultaneous addr req-go acknowledge
         reset_delay = Signal(reset_less=True)
@@ -258,26 +299,46 @@ class L0CacheBuffer(Elaboratable):
 
         return m
 
-    def ports(self):
+    def __iter__(self):
         for p in self.dports:
             yield from p.ports()
 
+    def ports(self):
+        return list(self)
+
 
 class TstL0CacheBuffer(Elaboratable):
-    def __init__(self, n_units=3, regwid=16, addrwid=4):
-        self.pimem = TestMemoryPortInterface(regwid, addrwid<<1)
-        self.l0 = L0CacheBuffer(n_units, self.pimem, regwid, addrwid<<1)
+    def __init__(self, pspec, n_units=3):
+        self.pspec = pspec
+        regwid = pspec.reg_wid
+        addrwid = pspec.addr_wid
+        self.cmpi = ConfigMemoryPortInterface(pspec)
+        self.pimem = self.cmpi.pi
+        self.l0 = L0CacheBuffer(n_units, self.pimem, regwid, addrwid << 1)
 
     def elaborate(self, platform):
         m = Module()
         m.submodules.pimem = self.pimem
         m.submodules.l0 = self.l0
 
+        if not hasattr(self.cmpi, 'lsmem'):
+            return m
+
+        # really bad hack, the LoadStore1 classes already have the
+        # lsi (LoadStoreInterface) as a submodule.
+        if self.pspec.ldst_ifacetype in ['mmu_cache_wb', 'test_mmu_cache_wb']:
+            return m
+
+        # hmmm not happy about this - should not be digging down and
+        # putting modules in
+        m.submodules.lsmem = self.cmpi.lsmem.lsi
+
         return m
 
     def ports(self):
+        yield from self.cmpi.ports()
         yield from self.l0.ports()
-        yield from self.pimem
+        yield from self.pimem.ports()
 
 
 def wait_busy(port, no=False):
@@ -308,141 +369,103 @@ def wait_ldok(port):
 
 
 def l0_cache_st(dut, addr, data, datalen):
-    l0 = dut.l0
-    mem = dut.pimem
-    port0 = l0.dports[0]
-    port1 = l0.dports[1]
-
-    # have to wait until not busy
-    yield from wait_busy(port1, no=False)    # wait until not busy
-
-    # set up a ST on the port.  address first:
-    yield port1.is_st_i.eq(1)  # indicate ST
-    yield port1.data_len.eq(datalen)  # ST length (1/2/4/8)
-
-    yield port1.addr.data.eq(addr)  # set address
-    yield port1.addr.ok.eq(1)  # set ok
-    yield from wait_addr(port1)             # wait until addr ok
-    # yield # not needed, just for checking
-    # yield # not needed, just for checking
-    # assert "ST" for one cycle (required by the API)
-    yield port1.st.data.eq(data)
-    yield port1.st.ok.eq(1)
-    yield
-    yield port1.st.ok.eq(0)
-
-    # can go straight to reset.
-    yield port1.is_st_i.eq(0)  # end
-    yield port1.addr.ok.eq(0)  # set !ok
-    # yield from wait_busy(port1, False)    # wait until not busy
+    return pi_st(dut.l0, addr, datalen)
 
 
 def l0_cache_ld(dut, addr, datalen, expected):
-
-    l0 = dut.l0
-    mem = dut.pimem
-    port1 = l0.dports[0]
-    port2 = l0.dports[2]
-
-    # have to wait until not busy
-    yield from wait_busy(port1, no=False)    # wait until not busy
-
-    # set up a LD on the port.  address first:
-    yield port1.is_ld_i.eq(1)  # indicate LD
-    yield port1.data_len.eq(datalen)  # LD length (1/2/4/8)
-
-    yield port1.addr.data.eq(addr)  # set address
-    yield port1.addr.ok.eq(1)  # set ok
-    yield from wait_addr(port1)             # wait until addr ok
-
-    yield from wait_ldok(port1)             # wait until ld ok
-    data = yield port1.ld.data
-
-    # cleanup
-    yield port1.is_ld_i.eq(0)  # end
-    yield port1.addr.ok.eq(0)  # set !ok
-    # yield from wait_busy(port1, no=False)    # wait until not busy
-
-    return data
+    return pi_ld(dut.l0, addr, datalen)
 
 
 def l0_cache_ldst(arg, dut):
-    yield
-    addr = 0x2
-    data = 0xbeef
-    data2 = 0xf00f
-    #data = 0x4
-    yield from l0_cache_st(dut, 0x2, data, 2)
-    yield from l0_cache_st(dut, 0x4, data2, 2)
-    result = yield from l0_cache_ld(dut, 0x2, 2, data)
-    result2 = yield from l0_cache_ld(dut, 0x4, 2, data2)
-    yield
-    arg.assertEqual(data, result, "data %x != %x" % (result, data))
-    arg.assertEqual(data2, result2, "data2 %x != %x" % (result2, data2))
+    port0 = dut.l0.dports[0]
+    return pi_ldst(arg, port0)
 
 
 def data_merger_merge(dut):
-    print("data_merger")
-    #starting with all inputs zero
+    # starting with all inputs zero
     yield Settle()
-    en = yield dut.data_o.en
-    data = yield dut.data_o.data
+    en = yield dut.o_data.en
+    data = yield dut.o_data.data
     assert en == 0, "en must be zero"
     assert data == 0, "data must be zero"
     yield
 
     yield dut.addr_array_i[0].eq(0xFF)
     for j in range(dut.array_size):
-        yield dut.data_i[j].en.eq(1 << j)
-        yield dut.data_i[j].data.eq(0xFF << (16*j))
+        yield dut.i_data[j].en.eq(1 << j)
+        yield dut.i_data[j].data.eq(0xFF << (16*j))
     yield Settle()
 
-    en = yield dut.data_o.en
-    data = yield dut.data_o.data
+    en = yield dut.o_data.en
+    data = yield dut.o_data.data
     assert data == 0xff00ff00ff00ff00ff00ff00ff00ff
     assert en == 0xff
     yield
 
+def data_merger_test2(dut):
+    # starting with all inputs zero
+    yield Settle()
+    yield
+    yield
+
 
 class TestL0Cache(unittest.TestCase):
 
-    def test_l0_cache(self):
+    def test_l0_cache_test_bare_wb(self):
 
-        dut = TstL0CacheBuffer(regwid=64)
-        #vl = rtlil.convert(dut, ports=dut.ports())
-        #with open("test_basic_l0_cache.il", "w") as f:
-        #    f.write(vl)
+        pspec = TestMemPspec(ldst_ifacetype='test_bare_wb',
+                             addr_wid=64,
+                             mask_wid=8,
+                             reg_wid=64)
+        dut = TstL0CacheBuffer(pspec)
+        vl = rtlil.convert(dut, ports=[])  # TODOdut.ports())
+        with open("test_basic_l0_cache_bare_wb.il", "w") as f:
+            f.write(vl)
+
+        run_simulation(dut, l0_cache_ldst(self, dut),
+                       vcd_name='test_l0_cache_basic_bare_wb.vcd')
+
+    def test_l0_cache_testpi(self):
+
+        pspec = TestMemPspec(ldst_ifacetype='testpi',
+                             addr_wid=64,
+                             mask_wid=8,
+                             reg_wid=64)
+        dut = TstL0CacheBuffer(pspec)
+        vl = rtlil.convert(dut, ports=[])  # TODOdut.ports())
+        with open("test_basic_l0_cache.il", "w") as f:
+            f.write(vl)
 
         run_simulation(dut, l0_cache_ldst(self, dut),
-                       vcd_name='test_l0_cache_basic.vcd')
+                       vcd_name='test_l0_cache_basic_testpi.vcd')
 
 
 class TestDataMerger(unittest.TestCase):
 
     def test_data_merger(self):
 
-        dut = DataMerger(8)
+        dut = TstDataMerger2()
         #vl = rtlil.convert(dut, ports=dut.ports())
-        #with open("test_data_merger.il", "w") as f:
+        # with open("test_data_merger.il", "w") as f:
         #    f.write(vl)
 
-        run_simulation(dut, data_merger_merge(dut),
+        run_simulation(dut, data_merger_test2(dut),
                        vcd_name='test_data_merger.vcd')
 
 
+
 class TestDualPortSplitter(unittest.TestCase):
 
     def test_dual_port_splitter(self):
 
         dut = DualPortSplitter()
         #vl = rtlil.convert(dut, ports=dut.ports())
-        #with open("test_data_merger.il", "w") as f:
+        # with open("test_data_merger.il", "w") as f:
         #    f.write(vl)
 
-        #run_simulation(dut, data_merger_merge(dut),
+        # run_simulation(dut, data_merger_merge(dut),
         #               vcd_name='test_dual_port_splitter.vcd')
 
 
 if __name__ == '__main__':
-    unittest.main(exit=False)
-
+    unittest.main()