move core hazard set/clear to separate function, for clarity
[soc.git] / src / soc / simple / core.py
index 67337460b407834b53e4249e6675d6d7889c1e57..e0ad6763a54cb376d24d415193795f31965ba2d1 100644 (file)
@@ -33,15 +33,13 @@ from nmutil.singlepipe import ControlBase
 
 from soc.fu.compunits.compunits import AllFunctionUnits
 from soc.regfile.regfiles import RegFiles
-from openpower.decoder.decode2execute1 import Decode2ToExecute1Type
-from openpower.decoder.decode2execute1 import IssuerDecode2ToOperand
 from openpower.decoder.power_decoder2 import get_rdflags
-from openpower.decoder.decode2execute1 import Data
 from soc.experiment.l0_cache import TstL0CacheBuffer  # test only
 from soc.config.test.test_loadstore import TestMemPspec
-from openpower.decoder.power_enums import MicrOp
-from soc.config.state import CoreState
+from openpower.decoder.power_enums import MicrOp, Function
+from soc.simple.core_data import CoreInput, CoreOutput
 
+from collections import defaultdict
 import operator
 
 from nmutil.util import rising_edge
@@ -69,57 +67,6 @@ def sort_fuspecs(fuspecs):
     return res  # enumerate(res)
 
 
-class CoreInput:
-    def __init__(self, pspec, svp64_en, regreduce_en):
-        self.pspec = pspec
-        self.svp64_en = svp64_en
-        self.e = Decode2ToExecute1Type("core", opkls=IssuerDecode2ToOperand,
-                                regreduce_en=regreduce_en)
-
-        # SVP64 RA_OR_ZERO needs to know if the relevant EXTRA2/3 field is zero
-        self.sv_a_nz = Signal()
-
-        # state and raw instruction (and SVP64 ReMap fields)
-        self.state = CoreState("core")
-        self.raw_insn_i = Signal(32) # raw instruction
-        self.bigendian_i = Signal() # bigendian - TODO, set by MSR.BE
-        if svp64_en:
-            self.sv_rm = SVP64Rec(name="core_svp64_rm") # SVP64 RM field
-            self.is_svp64_mode = Signal() # set if SVP64 mode is enabled
-            self.use_svp64_ldst_dec = Signal() # use alternative LDST decoder
-            self.sv_pred_sm = Signal() # TODO: SIMD width
-            self.sv_pred_dm = Signal() # TODO: SIMD width
-
-        # issue/valid/busy signalling
-        self.ivalid_i = Signal(reset_less=True) # instruction is valid
-        self.issue_i = Signal(reset_less=True)
-
-    def eq(self, i):
-        self.e.eq(i.e)
-        self.sv_a_nz.eq(i.sv_a_nz)
-        self.state.eq(i.state)
-        self.raw_insn_i.eq(i.raw_insn_i)
-        self.bigendian_i.eq(i.bigendian_i)
-        if not self.svp64_en:
-            return
-        self.sv_rm.eq(i.sv_rm)
-        self.is_svp64_mode.eq(i.is_svp64_mode)
-        self.use_svp64_ldst_dec.eq(i.use_svp64_ldst_dec)
-        self.sv_pred_sm.eq(i.sv_pred_sm)
-        self.sv_pred_dm.eq(i.sv_pred_dm)
-
-
-class CoreOutput:
-    def __init__(self):
-        self.busy_o = Signal(name="corebusy_o", reset_less=True)
-        # start/stop and terminated signalling
-        self.core_terminate_o = Signal(reset=0)  # indicates stopped
-
-    def eq(self, i):
-        self.busy_o.eq(i.busy_o)
-        self.core_terminate_o.eq(i.core_terminate_o)
-
-
 # derive from ControlBase rather than have a separate Stage instance,
 # this is simpler to do
 class NonProductionCore(ControlBase):
@@ -133,6 +80,12 @@ class NonProductionCore(ControlBase):
         self.regreduce_en = (hasattr(pspec, "regreduce") and
                              (pspec.regreduce == True))
 
+        # test core type
+        self.make_hazard_vecs = True
+        self.core_type = "fsm"
+        if hasattr(pspec, "core_type"):
+            self.core_type = pspec.core_type
+
         super().__init__(stage=self)
 
         # single LD/ST funnel for memory access
@@ -147,12 +100,12 @@ class NonProductionCore(ControlBase):
         mmu = self.fus.get_fu('mmu0')
         print ("core pspec", pspec.ldst_ifacetype)
         print ("core mmu", mmu)
-        print ("core lsmem.lsi", l0.cmpi.lsmem.lsi)
         if mmu is not None:
+            print ("core lsmem.lsi", l0.cmpi.lsmem.lsi)
             mmu.alu.set_ldst_interface(l0.cmpi.lsmem.lsi)
 
         # register files (yes plural)
-        self.regs = RegFiles(pspec)
+        self.regs = RegFiles(pspec, make_hazard_vecs=self.make_hazard_vecs)
 
         # set up input and output: unusual requirement to set data directly
         # (due to the way that the core is set up in a different domain,
@@ -160,7 +113,12 @@ class NonProductionCore(ControlBase):
         self.i, self.o = self.new_specs(None)
         self.i, self.o = self.p.i_data, self.n.o_data
 
-        # create per-FU instruction decoders (subsetted)
+        # create per-FU instruction decoders (subsetted).  these "satellite"
+        # decoders reduce wire fan-out from the one (main) PowerDecoder2
+        # (used directly by the trap unit) to the *twelve* (or more)
+        # Function Units.  we can either have 32 wires (the instruction)
+        # to each, or we can have well over a 200 wire fan-out (to 12
+        # ALUs). it's an easy choice to make.
         self.decoders = {}
         self.des = {}
 
@@ -179,9 +137,11 @@ class NonProductionCore(ControlBase):
                                             regreduce_en=self.regreduce_en)
             self.des[funame] = self.decoders[funame].do
 
+        # share the SPR decoder with the MMU if it exists
         if "mmu0" in self.decoders:
             self.decoders["mmu0"].mmu0_spr_dec = self.decoders["spr0"]
 
+    # next 3 functions are Stage API Compliance
     def setup(self, m, i):
         pass
 
@@ -191,6 +151,7 @@ class NonProductionCore(ControlBase):
     def ospec(self):
         return CoreOutput()
 
+    # elaborate function to create HDL
     def elaborate(self, platform):
         m = super().elaborate(platform)
 
@@ -208,6 +169,28 @@ class NonProductionCore(ControlBase):
         fus = self.fus.fus
 
         # connect decoders
+        self.connect_satellite_decoders(m)
+
+        # ssh, cheat: trap uses the main decoder because of the rewriting
+        self.des[self.trapunit] = self.i.e.do
+
+        # connect up Function Units, then read/write ports
+        fu_bitdict, fu_selected = self.connect_instruction(m)
+        self.connect_rdports(m, fu_selected)
+        self.connect_wrports(m, fu_selected)
+
+        # note if an exception happened.  in a pipelined or OoO design
+        # this needs to be accompanied by "shadowing" (or stalling)
+        el = []
+        for exc in self.fus.excs.values():
+            el.append(exc.happened)
+        if len(el) > 0: # at least one exception
+            comb += self.o.exc_happened.eq(Cat(*el).bool())
+
+        return m
+
+    def connect_satellite_decoders(self, m):
+        comb = m.d.comb
         for k, v in self.decoders.items():
             # connect each satellite decoder and give it the instruction.
             # as subset decoders this massively reduces wire fanout given
@@ -230,16 +213,6 @@ class NonProductionCore(ControlBase):
                         comb += v.use_svp64_ldst_dec.eq(
                                         self.i.use_svp64_ldst_dec)
 
-        # ssh, cheat: trap uses the main decoder because of the rewriting
-        self.des[self.trapunit] = self.i.e.do
-
-        # connect up Function Units, then read/write ports
-        fu_bitdict = self.connect_instruction(m)
-        self.connect_rdports(m, fu_bitdict)
-        self.connect_wrports(m, fu_bitdict)
-
-        return m
-
     def connect_instruction(self, m):
         """connect_instruction
 
@@ -255,36 +228,80 @@ class NonProductionCore(ControlBase):
         comb, sync = m.d.comb, m.d.sync
         fus = self.fus.fus
 
-        # enable-signals for each FU, get one bit for each FU (by name)
+        # indicate if core is busy
+        busy_o = self.o.busy_o
+
+        # enable/busy-signals for each FU, get one bit for each FU (by name)
         fu_enable = Signal(len(fus), reset_less=True)
+        fu_busy = Signal(len(fus), reset_less=True)
         fu_bitdict = {}
+        fu_selected = {}
         for i, funame in enumerate(fus.keys()):
             fu_bitdict[funame] = fu_enable[i]
-
-        # enable the required Function Unit based on the opcode decode
-        # note: this *only* works correctly for simple core when one and
-        # *only* one FU is allocated per instruction
-        for funame, fu in fus.items():
-            fnunit = fu.fnunit.value
-            enable = Signal(name="en_%s" % funame, reset_less=True)
-            comb += enable.eq((self.i.e.do.fn_unit & fnunit).bool())
-            comb += fu_bitdict[funame].eq(enable)
+            fu_selected[funame] = fu_busy[i]
+
+        # identify function units and create a list by fnunit so that
+        # PriorityPickers can be created for selecting one of them that
+        # isn't busy at the time the incoming instruction needs passing on
+        by_fnunit = defaultdict(list)
+        for fname, member in Function.__members__.items():
+            for funame, fu in fus.items():
+                fnunit = fu.fnunit.value
+                if member.value & fnunit: # this FU handles this type of op
+                    by_fnunit[fname].append((funame, fu)) # add by Function
+
+        # ok now just print out the list of FUs by Function, because we can
+        for fname, fu_list in by_fnunit.items():
+            print ("FUs by type", fname, fu_list)
+
+        # now create a PriorityPicker per FU-type such that only one
+        # non-busy FU will be picked
+        issue_pps = {}
+        fu_found = Signal() # take a note if no Function Unit was available
+        for fname, fu_list in by_fnunit.items():
+            i_pp = PriorityPicker(len(fu_list))
+            m.submodules['i_pp_%s' % fname] = i_pp
+            i_l = []
+            for i, (funame, fu) in enumerate(fu_list):
+                # match the decoded instruction (e.do.fn_unit) against the
+                # "capability" of this FU, gate that by whether that FU is
+                # busy, and drop that into the PriorityPicker.
+                # this will give us an output of the first available *non-busy*
+                # Function Unit (Reservation Statio) capable of handling this
+                # instruction.
+                fnunit = fu.fnunit.value
+                en_req = Signal(name="issue_en_%s" % funame, reset_less=True)
+                fnmatch = (self.i.e.do.fn_unit & fnunit).bool()
+                comb += en_req.eq(fnmatch & ~fu.busy_o & self.p.i_valid)
+                i_l.append(en_req) # store in list for doing the Cat-trick
+                # picker output, gated by enable: store in fu_bitdict
+                po = Signal(name="o_issue_pick_"+funame) # picker output
+                comb += po.eq(i_pp.o[i] & i_pp.en_o)
+                comb += fu_bitdict[funame].eq(po)
+                comb += fu_selected[funame].eq(fu.busy_o | po)
+                # if we don't do this, then when there are no FUs available,
+                # the "p.o_ready" signal will go back "ok we accepted this
+                # instruction" which of course isn't true.
+                comb += fu_found.eq(~fnmatch | i_pp.en_o)
+            # for each input, Cat them together and drop them into the picker
+            comb += i_pp.i.eq(Cat(*i_l))
 
         # sigh - need a NOP counter
         counter = Signal(2)
         with m.If(counter != 0):
             sync += counter.eq(counter - 1)
-            comb += self.o.busy_o.eq(1)
+            comb += busy_o.eq(1)
 
-        with m.If(self.i.ivalid_i): # run only when valid
+        with m.If(self.p.i_valid): # run only when valid
             with m.Switch(self.i.e.do.insn_type):
                 # check for ATTN: halt if true
                 with m.Case(MicrOp.OP_ATTN):
                     m.d.sync += self.o.core_terminate_o.eq(1)
 
+                # fake NOP - this isn't really used (Issuer detects NOP)
                 with m.Case(MicrOp.OP_NOP):
                     sync += counter.eq(2)
-                    comb += self.o.busy_o.eq(1)
+                    comb += busy_o.eq(1)
 
                 with m.Default():
                     # connect up instructions.  only one enabled at a time
@@ -297,15 +314,29 @@ class NonProductionCore(ControlBase):
                         with m.If(enable):
                             # operand comes from the *local*  decoder
                             comb += fu.oper_i.eq_from(do)
-                            #comb += fu.oper_i.eq_from_execute1(e)
-                            comb += fu.issue_i.eq(self.i.issue_i)
-                            comb += self.o.busy_o.eq(fu.busy_o)
+                            comb += fu.issue_i.eq(1) # issue when input valid
                             # rdmask, which is for registers, needs to come
                             # from the *main* decoder
                             rdmask = get_rdflags(self.i.e, fu)
                             comb += fu.rdmaskn.eq(~rdmask)
 
-        return fu_bitdict
+        # if instruction is busy, set busy output for core.
+        busys = map(lambda fu: fu.busy_o, fus.values())
+        comb += busy_o.eq(Cat(*busys).bool())
+
+        # ready/valid signalling.  if busy, means refuse incoming issue.
+        # (this is a global signal, TODO, change to one which allows
+        # overlapping instructions)
+        # also, if there was no fu found we must not send back a valid
+        # indicator.  BUT, of course, when there is no instruction
+        # we must ignore the fu_found flag, otherwise o_ready will never
+        # be set when everything is idle
+        comb += self.p.o_ready.eq(fu_found | ~self.p.i_valid)
+
+        # return both the function unit "enable" dict as well as the "busy".
+        # the "busy-or-issued" can be passed in to the Read/Write port
+        # connecters to give them permission to request access to regfiles
+        return fu_bitdict, fu_selected
 
     def connect_rdport(self, m, fu_bitdict, rdpickers, regfile, regname, fspec):
         comb, sync = m.d.comb, m.d.sync
@@ -330,7 +361,7 @@ class NonProductionCore(ControlBase):
         ppoffs = []
         for i, fspec in enumerate(fspecs):
             # get the regfile specs for this regfile port
-            (rf, read, write, wid, fuspec) = fspec
+            (rf, wf, read, write, wid, fuspec) = fspec
             print ("fpsec", i, fspec, len(fuspec))
             ppoffs.append(pplen) # record offset for picker
             pplen += len(fuspec)
@@ -348,8 +379,10 @@ class NonProductionCore(ControlBase):
 
         rens = []
         addrs = []
+        wvens = []
+
         for i, fspec in enumerate(fspecs):
-            (rf, read, write, wid, fuspec) = fspec
+            (rf, wf, read, write, wid, fuspec) = fspec
             # connect up the FU req/go signals, and the reg-read to the FU
             # and create a Read Broadcast Bus
             for pi, (funame, fu, idx) in enumerate(fuspec):
@@ -443,19 +476,73 @@ class NonProductionCore(ControlBase):
                 self.connect_rdport(m, fu_bitdict, rdpickers, regfile,
                                        regname, fspec)
 
+    def make_hazards(self, m, regfile, rfile, wvclr, wvset,
+                    funame, regname, idx,
+                    addr_en, wp, fu, fu_active, wrflag, write):
+        """make_hazards: a setter and a clearer for the regfile write ports
+
+        setter is at issue time (using PowerDecoder2 regfile write numbers)
+        clearer is at regfile write time (when FU has said what to write to)
+
+        there is *one* unusual case here which has to be dealt with:
+        when the Function Unit does *NOT* request a write to the regfile
+        (has its data.ok bit CLEARED).  this is perfectly legitimate.
+        and a royal pain.
+        """
+        comb = m.d.comb
+        name = "%s_%s_%d" % (funame, regname, idx)
+
+        # deal with write vector clear: this kicks in when the regfile
+        # is written to, and clears the corresponding bitvector entry
+        print ("write vector", regfile, wvclr)
+        wvaddr_en = Signal(len(wvclr.wen), name="wvaddr_en_"+name)
+        if rfile.unary:
+            comb += wvaddr_en.eq(addr_en)
+        else:
+            with m.If(wp):
+                comb += wvaddr_en.eq(1<<addr_en)
+
+        # now connect up the bitvector write hazard.  unlike the
+        # regfile writeports, a ONE must be written to the corresponding
+        # bit of the hazard bitvector (to indicate the existence of
+        # the hazard)
+
+        # the detection of what shall be written to is based
+        # on *issue*
+        print ("write vector (for regread)", regfile, wvset)
+        wviaddr_en = Signal(len(wvset.wen), name="wv_issue_addr_en_"+name)
+        issue_active = Signal(name="iactive_"+name)
+        comb += issue_active.eq(fu.issue_i & fu_active & wrflag)
+        with m.If(issue_active):
+            if rfile.unary:
+                comb += wviaddr_en.eq(write)
+            else:
+                comb += wviaddr_en.eq(1<<write)
+
+        return wvaddr_en, wviaddr_en
+
     def connect_wrport(self, m, fu_bitdict, wrpickers, regfile, regname, fspec):
         comb, sync = m.d.comb, m.d.sync
         fus = self.fus.fus
         regs = self.regs
 
-        print("connect wr", regname, fspec)
         rpidx = regname
 
         # select the required write port.  these are pre-defined sizes
-        print(regfile, regs.rf.keys())
         rfile = regs.rf[regfile.lower()]
         wport = rfile.w_ports[rpidx]
 
+        print("connect wr", regname, "unary", rfile.unary, fspec)
+        print(regfile, regs.rf.keys())
+
+        # select the write-protection hazard vector.  note that this still
+        # requires to WRITE to the hazard bitvector!  read-requests need
+        # to RAISE the bitvector (set it to 1), which, duh, requires a WRITE
+        if self.make_hazard_vecs:
+            wv = regs.wv[regfile.lower()]
+            wvset = wv.w_ports["set"] # write-vec bit-level hazard ctrl
+            wvclr = wv.w_ports["clr"] # write-vec bit-level hazard ctrl
+
         fspecs = fspec
         if not isinstance(fspecs, list):
             fspecs = [fspecs]
@@ -463,24 +550,46 @@ class NonProductionCore(ControlBase):
         pplen = 0
         writes = []
         ppoffs = []
+        rdflags = []
+        wrflags = []
         for i, fspec in enumerate(fspecs):
             # get the regfile specs for this regfile port
-            (rf, read, write, wid, fuspec) = fspec
-            print ("fpsec", i, fspec, len(fuspec))
+            (rf, wf, read, write, wid, fuspec) = fspec
+            print ("fpsec", i, "wrflag", wf, fspec, len(fuspec))
             ppoffs.append(pplen) # record offset for picker
             pplen += len(fuspec)
 
+            name = "%s_%s_%d" % (regfile, regname, i)
+            rdflag = Signal(name="rd_flag_"+name)
+            wrflag = Signal(name="wr_flag_"+name)
+            if rf is not None:
+                comb += rdflag.eq(rf)
+            else:
+                comb += rdflag.eq(0)
+            if wf is not None:
+                comb += wrflag.eq(wf)
+            else:
+                comb += wrflag.eq(0)
+            rdflags.append(rdflag)
+            wrflags.append(wrflag)
+
         # create a priority picker to manage this port
         wrpickers[regfile][rpidx] = wrpick = PriorityPicker(pplen)
         setattr(m.submodules, "wrpick_%s_%s" % (regfile, rpidx), wrpick)
 
         wsigs = []
         wens = []
+        wvsets = []
+        wvseten = []
+        wvclren = []
         addrs = []
         for i, fspec in enumerate(fspecs):
             # connect up the FU req/go signals and the reg-read to the FU
             # these are arbitrated by Data.ok signals
-            (rf, read, write, wid, fuspec) = fspec
+            (rf, wf, read, _write, wid, fuspec) = fspec
+            wrname = "write_%s_%s_%d" % (regfile, regname, i)
+            write = Signal.like(_write, name=wrname)
+            comb += write.eq(_write)
             for pi, (funame, fu, idx) in enumerate(fuspec):
                 pi += ppoffs[i]
 
@@ -503,7 +612,8 @@ class NonProductionCore(ControlBase):
                 # connect the regspec write "reg select" number to this port
                 # only if one FU actually requests (and is granted) the port
                 # will the write-enable be activated
-                addr_en = Signal.like(write)
+                wname = "waddr_en_%s_%s_%d" % (funame, regname, idx)
+                addr_en = Signal.like(write, name=wname)
                 wp = Signal()
                 comb += wp.eq(wr_pick & wrpick.en_o)
                 comb += addr_en.eq(Mux(wp, write, 0))
@@ -519,6 +629,18 @@ class NonProductionCore(ControlBase):
                       dest.shape(), wport.i_data.shape())
                 wsigs.append(fu_dest_latch)
 
+                # now connect up the bitvector write hazard
+                if not self.make_hazard_vecs:
+                    continue
+                res = self.make_hazards(m, regfile, rfile, wvclr, wvset,
+                                        funame, regname, idx,
+                                        addr_en, wp, fu, fu_active,
+                                        wrflags[i], write)
+                wvaddr_en, wv_issue_en = res
+                wvclren.append(wvaddr_en)   # set only: no data => clear bit
+                wvseten.append(wv_issue_en) # set data same as enable
+                wvsets.append(wv_issue_en)  # because enable needs a 1
+
         # here is where we create the Write Broadcast Bus. simple, eh?
         comb += wport.i_data.eq(ortreereduce_sig(wsigs))
         if rfile.unary:
@@ -529,6 +651,11 @@ class NonProductionCore(ControlBase):
             comb += wport.addr.eq(ortreereduce_sig(addrs))
             comb += wport.wen.eq(ortreereduce_sig(wens))
 
+        # for write-vectors
+        comb += wvclr.wen.eq(ortreereduce_sig(wvclren)) # clear (regfile write)
+        comb += wvset.wen.eq(ortreereduce_sig(wvseten)) # set (issue time)
+        comb += wvset.i_data.eq(ortreereduce_sig(wvsets))
+
     def connect_wrports(self, m, fu_bitdict):
         """connect write ports
 
@@ -589,7 +716,7 @@ class NonProductionCore(ControlBase):
                 print("    %d %s %s %s" % (idx, regfile, regname, str(wid)))
                 if readmode:
                     rdflag, read = regspec_decode_read(e, regfile, regname)
-                    write = None
+                    wrport, write = None, None
                 else:
                     rdflag, read = None, None
                     wrport, write = regspec_decode_write(e, regfile, regname)
@@ -598,25 +725,25 @@ class NonProductionCore(ControlBase):
                     byregfiles_spec[regfile] = {}
                 if regname not in byregfiles_spec[regfile]:
                     byregfiles_spec[regfile][regname] = \
-                        (rdflag, read, write, wid, [])
+                        (rdflag, wrport, read, write, wid, [])
                 # here we start to create "lanes"
                 if idx not in byregfiles[regfile]:
                     byregfiles[regfile][idx] = []
                 fuspec = (funame, fu, idx)
                 byregfiles[regfile][idx].append(fuspec)
-                byregfiles_spec[regfile][regname][4].append(fuspec)
+                byregfiles_spec[regfile][regname][5].append(fuspec)
 
         # ok just print that out, for convenience
         for regfile, spec in byregfiles.items():
             print("regfile %s ports:" % mode, regfile)
             fuspecs = byregfiles_spec[regfile]
             for regname, fspec in fuspecs.items():
-                [rdflag, read, write, wid, fuspec] = fspec
+                [rdflag, wrflag, read, write, wid, fuspec] = fspec
                 print("  rf %s port %s lane: %s" % (mode, regfile, regname))
-                print("  %s" % regname, wid, read, write, rdflag)
+                print("  %s" % regname, wid, read, write, rdflag, wrflag)
                 for (funame, fu, idx) in fuspec:
                     fusig = fu.src_i[idx] if readmode else fu.dest[idx]
-                    print("    ", funame, fu, idx, fusig)
+                    print("    ", funame, fu.__class__.__name__, idx, fusig)
                     print()
 
         return byregfiles, byregfiles_spec