Revert "move wrapping of stage into StageHandler"
[ieee754fpu.git] / src / add / iocontrol.py
index a4450c80e7af3ad2855fa21ad413a6f358df65f0..dfa04797ec52fe44934d1054a8d32251086f5b95 100644 (file)
 
     the methods of a stage instance must be as follows:
 
-    * ispec() - Input data format specification
-                returns an object or a list or tuple of objects, or
-                a Record, each object having an "eq" function which
-                takes responsibility for copying by assignment all
-                sub-objects
-    * ospec() - Output data format specification
-                requirements as for ospec
-    * process(m, i) - Processes an ispec-formatted object
+    * ispec() - Input data format specification.  Takes a bit of explaining.
+                The requirements are: something that eventually derives from
+                nmigen Value must be returned *OR* an iterator or iterable
+                or sequence (list, tuple etc.) or generator must *yield*
+                thing(s) that (eventually) derive from the nmigen Value
+                class.  Complex to state, very simple in practice:
+                see test_buf_pipe.py for over 25 working examples.
+
+    * ospec() - Output data format specification.
+                requirements identical to ispec
+
+    * process(m, i) - Processes an ispec-formatted object/sequence
                 returns a combinatorial block of a result that
-                may be assigned to the output, by way of the "eq"
-                function
+                may be assigned to the output, by way of the "nmoperator.eq"
+                function.  Note that what is returned here can be
+                extremely flexible.  Even a dictionary can be returned
+                as long as it has fields that match precisely with the
+                Record into which its values is intended to be assigned.
+                Again: see example unit tests for details.
+
     * setup(m, i) - Optional function for setting up submodules
                 may be used for more complex stages, to link
                 the input (i) to submodules.  must take responsibility
@@ -40,7 +49,8 @@
     Both StageCls (for use with non-static classes) and Stage (for use
     by static classes) are abstract classes from which, for convenience
     and as a courtesy to other developers, anything conforming to the
-    Stage API may *choose* to derive.
+    Stage API may *choose* to derive.  See Liskov Substitution Principle:
+    https://en.wikipedia.org/wiki/Liskov_substitution_principle
 
     StageChain:
     ----------
     Also has an extremely useful "connect" function that can be used to
     connect a chain of pipelines and present the exact same prev/next
     ready/valid/data API.
+
+    Note: pipelines basically do not become pipelines as such until
+    handed to a derivative of ControlBase.  ControlBase itself is *not*
+    strictly considered a pipeline class.  Wishbone and AXI4 (master or
+    slave) could be derived from ControlBase, for example.
 """
 
 from nmigen import Signal, Cat, Const, Mux, Module, Value, Elaboratable
@@ -90,7 +105,7 @@ class Object:
             raise AttributeError(e)
 
     def __iter__(self):
-        for x in self.fields.values():
+        for x in self.fields.values():  # OrderedDict so order is preserved
             if isinstance(x, Iterable):
                 yield from x
             else:
@@ -109,7 +124,7 @@ class Object:
         print (res)
         return res
 
-    def ports(self):
+    def ports(self): # being called "keys" would be much better
         return list(self)
 
 
@@ -133,13 +148,13 @@ class RecordObject(Record):
         self.layout.fields.update(newlayout)
 
     def __iter__(self):
-        for x in self.fields.values():
+        for x in self.fields.values(): # remember: fields is an OrderedDict
             if isinstance(x, Iterable):
-                yield from x
+                yield from x           # a bit like flatten (nmigen.tools)
             else:
                 yield x
 
-    def ports(self):
+    def ports(self): # would be better being called "keys"
         return list(self)
 
 
@@ -158,16 +173,16 @@ class PrevControl(Elaboratable):
                    may be a multi-bit signal, where all bits are required
                    to be asserted to indicate "valid".
         * ready_o: output to next stage indicating readiness to accept data
-        * data_i : an input - added by the user of this class
+        * data_i : an input - MUST be added by the USER of this class
     """
 
     def __init__(self, i_width=1, stage_ctl=False):
         self.stage_ctl = stage_ctl
         self.valid_i = Signal(i_width, name="p_valid_i") # prev   >>in  self
-        self._ready_o = Signal(name="p_ready_o") # prev   <<out self
+        self._ready_o = Signal(name="p_ready_o")         # prev   <<out self
         self.data_i = None # XXX MUST BE ADDED BY USER
         if stage_ctl:
-            self.s_ready_o = Signal(name="p_s_o_rdy") # prev   <<out self
+            self.s_ready_o = Signal(name="p_s_o_rdy")    # prev   <<out self
         self.trigger = Signal(reset_less=True)
 
     @property
@@ -235,7 +250,7 @@ class NextControl(Elaboratable):
     """ contains the signals that go *to* the next stage (both in and out)
         * valid_o: output indicating to next stage that data is valid
         * ready_i: input from next stage indicating that it can accept data
-        * data_o : an output - added by the user of this class
+        * data_o : an output - MUST be added by the USER of this class
     """
     def __init__(self, stage_ctl=False):
         self.stage_ctl = stage_ctl
@@ -338,13 +353,14 @@ class Stage(metaclass=ABCMeta):
 class StageChain(StageCls):
     """ pass in a list of stages, and they will automatically be
         chained together via their input and output specs into a
-        combinatorial chain.
+        combinatorial chain, to create one giant combinatorial block.
 
         the end result basically conforms to the exact same Stage API.
 
         * input to this class will be the input of the first stage
         * output of first stage goes into input of second
-        * output of second goes into input into third (etc. etc.)
+        * output of second goes into input into third
+        * ... (etc. etc.)
         * the output of this class will be the output of the last stage
 
         NOTE: whilst this is very similar to ControlBase.connect(), it is
@@ -420,7 +436,55 @@ class StageChain(StageCls):
         return self.o # conform to Stage API: return last-loop output
 
 
-class ControlBase(Elaboratable):
+class StageHandler(Elaboratable):
+    """ Stage handling class
+    """
+    def __init__(self, ctrl, stage):
+        """
+        """
+        if stage is not None:
+            self.new_data(self, self, "data")
+
+    @property
+    def data_r(self):
+        return self.stage.process(self.p.data_i)
+
+    def _postprocess(self, i): # XXX DISABLED
+        return i # RETURNS INPUT
+        if hasattr(self.stage, "postprocess"):
+            return self.stage.postprocess(i)
+        return i
+
+    def new_data(self, p, n, name):
+        """ allocates new data_i and data_o
+        """
+        self.p.data_i = _spec(p.stage.ispec, "%s_i" % name)
+        self.n.data_o = _spec(n.stage.ospec, "%s_o" % name)
+
+    def elaborate(self, platform):
+        """ handles case where stage has dynamic ready/valid functions
+        """
+        m = Module()
+        m.submodules.p = self.p
+        m.submodules.n = self.n
+
+        if self.stage is not None and hasattr(self.stage, "setup"):
+            self.stage.setup(m, self.p.data_i)
+
+        if not self.p.stage_ctl:
+            return m
+
+        # intercept the previous (outgoing) "ready", combine with stage ready
+        m.d.comb += self.p.s_ready_o.eq(self.p._ready_o & self.stage.d_ready)
+
+        # intercept the next (incoming) "ready" and combine it with data valid
+        sdv = self.stage.d_valid(self.n.ready_i)
+        m.d.comb += self.n.d_valid.eq(self.n.ready_i & sdv)
+
+        return m
+
+
+class ControlBase(StageHandler):
     """ Common functions for Pipeline API.  Note: a "pipeline stage" only
         exists (conceptually) when a ControlBase derivative is handed
         a Stage (combinatorial block)
@@ -441,10 +505,7 @@ class ControlBase(Elaboratable):
         self.p = PrevControl(in_multi, stage_ctl)
         self.n = NextControl(stage_ctl)
 
-        # set up the input and output data
-        if stage is not None:
-            self.p.data_i = _spec(stage.ispec, "data_i") # input type
-            self.n.data_o = _spec(stage.ospec, "data_o") # output type
+        StageHandler.__init__(self, self, stage)
 
     def connect_to_next(self, nxt):
         """ helper function to connect to the next stage data/valid/ready.
@@ -512,24 +573,15 @@ class ControlBase(Elaboratable):
             pipe2 = pipechain[i+1]
             eqs += pipe1.connect_to_next(pipe2)
 
-        # connect front of chain to ourselves
+        # connect front and back of chain to ourselves
         front = pipechain[0]
-        self.p.data_i = _spec(front.stage.ispec, "chainin")
-        eqs += front._connect_in(self)
-
-        # connect end of chain to ourselves
         end = pipechain[-1]
-        self.n.data_o = _spec(end.stage.ospec, "chainout")
+        self.new_data(front, end, "chain") # NOTE: REPLACES existing data
+        eqs += front._connect_in(self)
         eqs += end._connect_out(self)
 
         return eqs
 
-    def _postprocess(self, i): # XXX DISABLED
-        return i # RETURNS INPUT
-        if hasattr(self.stage, "postprocess"):
-            return self.stage.postprocess(i)
-        return i
-
     def set_input(self, i):
         """ helper function to set the input data
         """
@@ -542,25 +594,3 @@ class ControlBase(Elaboratable):
     def ports(self):
         return list(self)
 
-    def elaborate(self, platform):
-        """ handles case where stage has dynamic ready/valid functions
-        """
-        m = Module()
-        m.submodules.p = self.p
-        m.submodules.n = self.n
-
-        if self.stage is not None and hasattr(self.stage, "setup"):
-            self.stage.setup(m, self.p.data_i)
-
-        if not self.p.stage_ctl:
-            return m
-
-        # intercept the previous (outgoing) "ready", combine with stage ready
-        m.d.comb += self.p.s_ready_o.eq(self.p._ready_o & self.stage.d_ready)
-
-        # intercept the next (incoming) "ready" and combine it with data valid
-        sdv = self.stage.d_valid(self.n.ready_i)
-        m.d.comb += self.n.d_valid.eq(self.n.ready_i & sdv)
-
-        return m
-