save to correct files, unit test 15
[ieee754fpu.git] / src / add / singlepipe.py
index 50eba9dc67167aae04355ebc78237c7275ef3706..590773a891872d7de63f917dc998002f5990a846 100644 (file)
@@ -171,27 +171,45 @@ class PrevControl:
     def __init__(self, i_width=1, stage_ctl=False):
         self.stage_ctl = stage_ctl
         self.i_valid = Signal(i_width, name="p_i_valid") # prev   >>in  self
-        self.o_ready = Signal(name="p_o_ready") # prev   <<out self
+        self._o_ready = Signal(name="p_o_ready") # prev   <<out self
         self.i_data = None # XXX MUST BE ADDED BY USER
         if stage_ctl:
             self.s_o_ready = Signal(name="p_s_o_rdy") # prev   <<out self
 
+    @property
+    def o_ready(self):
+        """ public-facing API: indicates (externally) that stage is ready
+        """
+        if self.stage_ctl:
+            return self.s_o_ready # set dynamically by stage
+        return self._o_ready      # return this when not under dynamic control
+
     def _connect_in(self, prev):
         """ internal helper function to connect stage to an input source.
             do not use to connect stage-to-stage!
         """
-        return [self.i_valid.eq(prev.i_valid),
+        return [self.i_valid.eq(prev.i_valid_test),
                 prev.o_ready.eq(self.o_ready),
                 eq(self.i_data, prev.i_data),
                ]
 
-    def i_valid_logic(self):
+    @property
+    def i_valid_test(self):
         vlen = len(self.i_valid)
-        if vlen > 1: # multi-bit case: valid only when i_valid is all 1s
+        if vlen > 1:
+            # multi-bit case: valid only when i_valid is all 1s
             all1s = Const(-1, (len(self.i_valid), False))
-            return self.i_valid == all1s
-        # single-bit i_valid case
-        return self.i_valid
+            i_valid = (self.i_valid == all1s)
+        else:
+            # single-bit i_valid case
+            i_valid = self.i_valid
+
+        # when stage indicates not ready, incoming data
+        # must "appear" to be not ready too
+        if self.stage_ctl:
+            i_valid = i_valid & self.s_o_ready
+
+        return i_valid
 
 
 class NextControl:
@@ -205,8 +223,14 @@ class NextControl:
         self.o_valid = Signal(name="n_o_valid") # self out>>  next
         self.i_ready = Signal(name="n_i_ready") # self <<in   next
         self.o_data = None # XXX MUST BE ADDED BY USER
-        if stage_ctl:
-            self.s_o_valid = Signal(name="n_s_o_vld") # self out>>  next
+        #if self.stage_ctl:
+        self.d_valid = Signal(reset=1) # INTERNAL (data valid)
+
+    @property
+    def i_ready_test(self):
+        if self.stage_ctl:
+            return self.i_ready & self.d_valid
+        return self.i_ready
 
     def connect_to_next(self, nxt):
         """ helper function to connect to the next stage data/valid/ready.
@@ -223,7 +247,7 @@ class NextControl:
             do not use to connect stage-to-stage!
         """
         return [nxt.o_valid.eq(self.o_valid),
-                self.i_ready.eq(nxt.i_ready),
+                self.i_ready.eq(nxt.i_ready_test),
                 eq(nxt.o_data, self.o_data),
                ]
 
@@ -498,6 +522,21 @@ class ControlBase:
             res += self.n.o_data
         return res
 
+    def _elaborate(self, platform):
+        """ handles case where stage has dynamic ready/valid functions
+        """
+        m = Module()
+        if not self.p.stage_ctl:
+            return m
+
+        # intercept the previous (outgoing) "ready", combine with stage ready
+        m.d.comb += self.p.s_o_ready.eq(self.p._o_ready & self.stage.d_ready)
+
+        # intercept the next (incoming) "ready" and combine it with data valid
+        m.d.comb += self.n.d_valid.eq(self.n.i_ready & self.stage.d_valid)
+
+        return m
+
 
 class BufferedPipeline(ControlBase):
     """ buffered pipeline stage.  data and strobe signals travel in sync.
@@ -529,9 +568,10 @@ class BufferedPipeline(ControlBase):
         input may begin to be processed and transferred directly to output.
 
     """
-    def __init__(self, stage):
-        ControlBase.__init__(self)
+    def __init__(self, stage, stage_ctl=False, buffermode=True):
+        ControlBase.__init__(self, stage_ctl=stage_ctl)
         self.stage = stage
+        self.buffermode = buffermode
 
         # set up the input and output data
         self.p.i_data = stage.ispec() # input type
@@ -539,54 +579,59 @@ class BufferedPipeline(ControlBase):
 
     def elaborate(self, platform):
 
-        self.m = Module()
+        self.m = ControlBase._elaborate(self, platform)
 
         result = self.stage.ospec()
-        r_data = self.stage.ospec()
+        if self.buffermode:
+            r_data = self.stage.ospec()
         if hasattr(self.stage, "setup"):
             self.stage.setup(self.m, self.p.i_data)
 
         # establish some combinatorial temporaries
         o_n_validn = Signal(reset_less=True)
+        n_i_ready = Signal(reset_less=True, name="n_i_rdy_data")
         i_p_valid_o_p_ready = Signal(reset_less=True)
         p_i_valid = Signal(reset_less=True)
-        self.m.d.comb += [p_i_valid.eq(self.p.i_valid_logic()),
+        self.m.d.comb += [p_i_valid.eq(self.p.i_valid_test),
                      o_n_validn.eq(~self.n.o_valid),
                      i_p_valid_o_p_ready.eq(p_i_valid & self.p.o_ready),
+                     n_i_ready.eq(self.n.i_ready_test),
         ]
 
         # store result of processing in combinatorial temporary
         self.m.d.comb += eq(result, self.stage.process(self.p.i_data))
 
-        # if not in stall condition, update the temporary register
-        with self.m.If(self.p.o_ready): # not stalled
-            self.m.d.sync += eq(r_data, result) # update buffer
-
-        with self.m.If(self.n.i_ready): # next stage is ready
+        if self.buffermode:
+            # if not in stall condition, update the temporary register
             with self.m.If(self.p.o_ready): # not stalled
+                self.m.d.sync += eq(r_data, result) # update buffer
+
+        with self.m.If(n_i_ready): # next stage is ready
+            with self.m.If(self.p._o_ready): # not stalled
                 # nothing in buffer: send (processed) input direct to output
                 self.m.d.sync += [self.n.o_valid.eq(p_i_valid),
                                   eq(self.n.o_data, result), # update output
                             ]
-            with self.m.Else(): # p.o_ready is false, and something in buffer
-                # Flush the [already processed] buffer to the output port.
-                self.m.d.sync += [self.n.o_valid.eq(1),      # declare reg empty
+            if self.buffermode:
+                with self.m.Else(): # p.o_ready is false, and data in buffer
+                    # Flush the [already processed] buffer to the output port.
+                    self.m.d.sync += [self.n.o_valid.eq(1),  # reg empty
                                   eq(self.n.o_data, r_data), # flush buffer
-                                  self.p.o_ready.eq(1),      # clear stall
+                                  self.p._o_ready.eq(1),     # clear stall
                             ]
                 # ignore input, since p.o_ready is also false.
 
         # (n.i_ready) is false here: next stage is ready
         with self.m.Elif(o_n_validn): # next stage being told "ready"
             self.m.d.sync += [self.n.o_valid.eq(p_i_valid),
-                              self.p.o_ready.eq(1), # Keep the buffer empty
+                              self.p._o_ready.eq(1), # Keep the buffer empty
                               eq(self.n.o_data, result), # set output data
                         ]
 
         # (n.i_ready) false and (n.o_valid) true:
         with self.m.Elif(i_p_valid_o_p_ready):
             # If next stage *is* ready, and not stalled yet, accept input
-            self.m.d.sync += self.p.o_ready.eq(~(p_i_valid & self.n.o_valid))
+            self.m.d.sync += self.p._o_ready.eq(~(p_i_valid & self.n.o_valid))
 
         return self.m
 
@@ -628,8 +673,8 @@ class UnbufferedPipeline(ControlBase):
             COMBINATORIALLY (no clock dependence).
     """
 
-    def __init__(self, stage):
-        ControlBase.__init__(self)
+    def __init__(self, stage, stage_ctl=False):
+        ControlBase.__init__(self, stage_ctl=stage_ctl)
         self.stage = stage
 
         # set up the input and output data
@@ -637,29 +682,97 @@ class UnbufferedPipeline(ControlBase):
         self.n.o_data = stage.ospec() # output type
 
     def elaborate(self, platform):
-        self.m = Module()
+        self.m = ControlBase._elaborate(self, platform)
 
         data_valid = Signal() # is data valid or not
         r_data = self.stage.ispec() # input type
         if hasattr(self.stage, "setup"):
             self.stage.setup(self.m, r_data)
 
-        # some temporarie
+        # some temporaries
         p_i_valid = Signal(reset_less=True)
         pv = Signal(reset_less=True)
-        self.m.d.comb += p_i_valid.eq(self.p.i_valid_logic())
+        self.m.d.comb += p_i_valid.eq(self.p.i_valid_test)
         self.m.d.comb += pv.eq(self.p.i_valid & self.p.o_ready)
 
         self.m.d.comb += self.n.o_valid.eq(data_valid)
-        self.m.d.comb += self.p.o_ready.eq(~data_valid | self.n.i_ready)
+        self.m.d.comb += self.p._o_ready.eq(~data_valid | self.n.i_ready_test)
         self.m.d.sync += data_valid.eq(p_i_valid | \
-                                        (~self.n.i_ready & data_valid))
+                                        (~self.n.i_ready_test & data_valid))
         with self.m.If(pv):
             self.m.d.sync += eq(r_data, self.p.i_data)
         self.m.d.comb += eq(self.n.o_data, self.stage.process(r_data))
         return self.m
 
 
+class UnbufferedPipeline2(ControlBase):
+    """ A simple pipeline stage with single-clock synchronisation
+        and two-way valid/ready synchronised signalling.
+
+        Note that a stall in one stage will result in the entire pipeline
+        chain stalling.
+
+        Also that unlike BufferedPipeline, the valid/ready signalling does NOT
+        travel synchronously with the data: the valid/ready signalling
+        combines in a *combinatorial* fashion.  Therefore, a long pipeline
+        chain will lengthen propagation delays.
+
+        Argument: stage.  see Stage API, above
+
+        stage-1   p.i_valid >>in   stage   n.o_valid out>>   stage+1
+        stage-1   p.o_ready <<out  stage   n.i_ready <<in    stage+1
+        stage-1   p.i_data  >>in   stage   n.o_data  out>>   stage+1
+                              |             |
+                            r_data        result
+                              |             |
+                              +--process ->-+
+
+        Attributes:
+        -----------
+        p.i_data : StageInput, shaped according to ispec
+            The pipeline input
+        p.o_data : StageOutput, shaped according to ospec
+            The pipeline output
+        buf : output_shape according to ospec
+            A temporary (buffered) copy of a valid output
+            This is HELD if the output is not ready.  It is updated
+            SYNCHRONOUSLY.
+    """
+
+    def __init__(self, stage, stage_ctl=False):
+        ControlBase.__init__(self, stage_ctl=stage_ctl)
+        self.stage = stage
+
+        # set up the input and output data
+        self.p.i_data = stage.ispec() # input type
+        self.n.o_data = stage.ospec() # output type
+
+    def elaborate(self, platform):
+        self.m = ControlBase._elaborate(self, platform)
+
+        buf_full = Signal() # is data valid or not
+        buf = self.stage.ospec() # output type
+        if hasattr(self.stage, "setup"):
+            self.stage.setup(self.m, self.p.i_data)
+
+        # some temporaries
+        p_i_valid = Signal(reset_less=True)
+        self.m.d.comb += p_i_valid.eq(self.p.i_valid_test)
+
+        self.m.d.comb += self.n.o_valid.eq(buf_full | p_i_valid)
+        self.m.d.comb += self.p._o_ready.eq(~buf_full)
+        self.m.d.sync += buf_full.eq(~self.n.i_ready_test & \
+                                        (p_i_valid | buf_full))
+        with self.m.If(buf_full):
+            self.m.d.comb += eq(self.n.o_data, buf)
+        with self.m.Else():
+            self.m.d.comb += eq(self.n.o_data,
+                                self.stage.process(self.p.i_data))
+        self.m.d.sync += eq(buf, self.n.o_data)
+
+        return self.m
+
+
 class PassThroughStage(StageCls):
     """ a pass-through stage which has its input data spec equal to its output,
         and "passes through" its data from input to output.