add comments to help experimentation
[ieee754fpu.git] / src / add / singlepipe.py
index f88907e10877c620fa85f0b1c8a692948542eb82..f9e5ec8f49a063b6da383efeca50fc78930f5788 100644 (file)
@@ -188,12 +188,13 @@ class PrevControl:
         """ 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
@@ -219,25 +220,16 @@ class NextControl:
     """
     def __init__(self, stage_ctl=False):
         self.stage_ctl = stage_ctl
-        self._o_valid = Signal(name="n_o_valid") # self out>>  next
+        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
-
-    @property
-    def o_valid(self):
-        """ public-facing API: indicates (externally) that data is valid
-        """
         if self.stage_ctl:
-            return self.s_o_valid
-        return self._o_valid
+            self.d_valid = Signal(reset=1) # INTERNAL (data valid)
 
-    def i_ready_logic(self):
-        """ public-facing API: receives indication that transmit is possible
-        """
+    @property
+    def i_ready_test(self):
         if self.stage_ctl:
-            return self.i_ready & self.s_o_valid
+            return self.i_ready & self.d_valid
         return self.i_ready
 
     def connect_to_next(self, nxt):
@@ -255,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),
                ]
 
@@ -534,23 +526,15 @@ class ControlBase:
         """ handles case where stage has dynamic ready/valid functions
         """
         m = Module()
-        if not self.n.stage_ctl:
+        if not self.p.stage_ctl:
             return m
 
-        # when the pipeline (buffered or otherwise) says "ready",
-        # test the *stage* "ready".
+        # 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)
 
-        with m.If(self.p._o_ready):
-            m.d.comb += self.p.s_o_ready.eq(self.stage.p_o_ready)
-        with m.Else():
-            m.d.comb += self.p.s_o_ready.eq(0)
+        # 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)
 
-        # when the pipeline (buffered or otherwise) says "valid",
-        # test the *stage* "valid".
-        with m.If(self.n._o_valid):
-            m.d.comb += self.n.s_o_valid.eq(self.stage.n_o_valid)
-        with m.Else():
-            m.d.comb += self.n.s_o_valid.eq(0)
         return m
 
 
@@ -605,7 +589,7 @@ class BufferedPipeline(ControlBase):
         o_n_validn = Signal(reset_less=True)
         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),
         ]
@@ -617,15 +601,15 @@ class BufferedPipeline(ControlBase):
         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
+        with self.m.If(self.n.i_ready_test): # 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),
+                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),     # reg empty
+                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
                             ]
@@ -633,7 +617,7 @@ class BufferedPipeline(ControlBase):
 
         # (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.m.d.sync += [self.n.o_valid.eq(p_i_valid),
                               self.p._o_ready.eq(1), # Keep the buffer empty
                               eq(self.n.o_data, result), # set output data
                         ]
@@ -702,19 +686,87 @@ class UnbufferedPipeline(ControlBase):
         # 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.n.o_valid.eq(data_valid)
+        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.