reduce number of wait states in ReservationStations2 by detecting
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Sun, 7 Nov 2021 13:14:05 +0000 (13:14 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Sun, 7 Nov 2021 13:14:05 +0000 (13:14 +0000)
opportunities for sending (and receiving) data immediately.

the previous version was a 4-cycle FSM.  however it is perfectly
fine to detect, in the very first phase (as part of ACCEPTANCE),
if the ALU is already ready to accept.  effectively this combines
phase 1 and phase 2.  if the ALU was *not* ready then and only
then will a given FSM move to phase 2 (after buffering the data)

likewise, when data comes out of the ALU, there is an opportunity
to signal to the RS output that the data is in fact ready... *if*
the RS output was in fact waiting for it already.  again, this
combines phase 3 and phase 4.  again: if the RS output was not
ready, then a given FSM will move to phase 4 (again, after
buffering the data)

src/nmutil/concurrentunit.py

index 25822b1ab66b9851ad4d879cee91413040278c26..b2b7650823f5cf95df8f398db22e3d43bddc4d98 100644 (file)
@@ -172,7 +172,8 @@ class ReservationStations(Elaboratable):
 
 
 class ReservationStations2(Elaboratable):
-    """ Reservation-Station pipeline
+    """ Reservation-Station pipeline.  Manages an ALU and makes it look like
+        there are multiple of them, presenting the same ready/valid API
 
         Input:
 
@@ -186,6 +187,7 @@ class ReservationStations2(Elaboratable):
         It is the responsibility of the USER of the ReservationStations
         class to correctly set that muxid in each data packet to the
         correct constant.  this could change in future.
+
     """
     def __init__(self, alu, num_rows):
         self.num_rows = nr = num_rows
@@ -264,12 +266,25 @@ class ReservationStations2(Elaboratable):
             i_buf, o_buf = self.alu.new_specs("buf%d" % i) # buffers
             with m.FSM():
                 # indicate ready to accept data, and accept it if incoming
+                # BUT, if there is an opportunity to send on immediately
+                # to the ALU, take it early (combinatorial)
                 with m.State("ACCEPTING%d" % i):
                     m.d.comb += self.p[i].o_ready.eq(1) # ready indicator
                     with m.If(self.p[i].i_valid):  # valid data incoming
                         m.d.sync += rsvd[i].eq(1)  # now reserved
-                        m.d.sync += nmoperator.eq(i_buf, self.p[i].i_data)
-                        m.next = "ACCEPTED%d" % i # move to "accepted"
+                        # a unique opportunity: the ALU happens to be free
+                        with m.If(mid == i): # picker selected us
+                            with m.If(self.alu.p.o_ready):  # ALU can accept
+                                m.d.comb += self.alu.p.i_valid.eq(1) # transfer
+                                m.d.comb += nmoperator.eq(self.alu.p.i_data,
+                                                         self.p[i].i_data)
+                                m.d.sync += sent[i].eq(1) # now reserved
+                                m.next = "WAITOUT%d" % i # move to "wait output"
+                        with m.Else():
+                            # nope. ALU wasn't free. try next cycle(s)
+                            m.d.sync += nmoperator.eq(i_buf, self.p[i].i_data)
+                            m.next = "ACCEPTED%d" % i # move to "accepted"
+
                 # now try to deliver to the ALU, but only if we are "picked"
                 with m.State("ACCEPTED%d" % i):
                     with m.If(mid == i): # picker selected us
@@ -278,13 +293,29 @@ class ReservationStations2(Elaboratable):
                             m.d.comb += nmoperator.eq(self.alu.p.i_data, i_buf)
                             m.d.sync += sent[i].eq(1) # now reserved
                             m.next = "WAITOUT%d" % i # move to "wait output"
+
                 # waiting for output to appear on the ALU, take a copy
+                # BUT, again, if there is an opportunity to send on
+                # immediately, take it (combinatorial)
                 with m.State("WAITOUT%d" % i):
                     with m.If(o_muxid == i): # when ALU output matches our RS
                         with m.If(self.alu.n.o_valid):  # ALU can accept
-                            m.d.sync += nmoperator.eq(o_buf, self.alu.n.o_data)
-                            m.d.sync += wait[i].eq(1) # now waiting
-                            m.next = "SENDON%d" % i # move to "send data on"
+                            # second unique opportunity: the RS is ready
+                            with m.If(self.n[i].i_ready): # ready to receive
+                                m.d.comb += self.n[i].o_valid.eq(1) # valid
+                                m.d.comb += nmoperator.eq(self.n[i].o_data,
+                                                          self.alu.n.o_data)
+                                m.d.sync += wait[i].eq(0) # clear waiting
+                                m.d.sync += sent[i].eq(0) # and sending
+                                m.d.sync += rsvd[i].eq(0) # and reserved
+                                m.next = "ACCEPTING%d" % i # back to "accepting"
+                            with m.Else():
+                                # nope. RS wasn't ready. try next cycles
+                                m.d.sync += wait[i].eq(1) # now waiting
+                                m.d.sync += nmoperator.eq(o_buf,
+                                                          self.alu.n.o_data)
+                                m.next = "SENDON%d" % i # move to "send data on"
+
                 # waiting for "valid" indicator on RS output: deliver it
                 with m.State("SENDON%d" % i):
                     with m.If(self.n[i].i_ready): # user is ready to receive