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):
 
 
 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:
 
 
         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.
         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
     """
     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
             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
                 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
                 # 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"
                             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
                 # 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
                 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
                 # 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