update docstrings
[ieee754fpu.git] / src / add / iocontrol.py
index e0a6e0cbf9d0ae55bb61d4371d133ccbe9ae33c5..3d82027e68878049a2ff95b0ea6c638afa5edfad 100644 (file)
@@ -9,6 +9,14 @@
 
     stage requires compliance with a strict API that may be
     implemented in several means, including as a static class.
+
+    Stage Blocks really must be combinatorial blocks.  It would be ok
+    to have input come in from sync'd sources (clock-driven) however by
+    doing so they would no longer be deterministic, and chaining such
+    blocks with such side-effects together could result in unexpected,
+    unpredictable, unreproduceable behaviour.
+    So generally to be avoided, then unless you know what you are doing.
+
     the methods of a stage instance must be as follows:
 
     * ispec() - Input data format specification
     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
@@ -59,7 +72,7 @@ from collections.abc import Sequence, Iterable
 from collections import OrderedDict
 import inspect
 
-from nmoperator import eq, cat, shape
+import nmoperator
 
 
 class Object:
@@ -121,7 +134,7 @@ class RecordObject(Record):
         elif isinstance(v, Value):
             newlayout = {k: (k, v.shape())}
         else:
-            newlayout = {k: (k, shape(v))}
+            newlayout = {k: (k, nmoperator.shape(v))}
         self.layout.fields.update(newlayout)
 
     def __iter__(self):
@@ -150,16 +163,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
@@ -178,7 +191,7 @@ class PrevControl(Elaboratable):
         data_i = fn(prev.data_i) if fn is not None else prev.data_i
         return [self.valid_i.eq(valid_i),
                 prev.ready_o.eq(self.ready_o),
-                eq(self.data_i, data_i),
+                nmoperator.eq(self.data_i, data_i),
                ]
 
     @property
@@ -227,7 +240,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
@@ -251,7 +264,7 @@ class NextControl(Elaboratable):
         """
         return [nxt.valid_i.eq(self.valid_o),
                 self.ready_i.eq(nxt.ready_o),
-                eq(nxt.data_i, self.data_o),
+                nmoperator.eq(nxt.data_i, self.data_o),
                ]
 
     def _connect_out(self, nxt, direct=False, fn=None):
@@ -262,7 +275,7 @@ class NextControl(Elaboratable):
         data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
         return [nxt.valid_o.eq(self.valid_o),
                 self.ready_i.eq(ready_i),
-                eq(data_o, self.data_o),
+                nmoperator.eq(data_o, self.data_o),
                ]
 
     def elaborate(self, platform):
@@ -339,10 +352,21 @@ class StageChain(StageCls):
         * 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
+        *really* important to appreciate that StageChain is pure
+        combinatorial and bypasses (does not involve, at all, ready/valid
+        signalling of any kind).
+
+        ControlBase.connect on the other hand respects, connects, and uses
+        ready/valid signalling.
+
         Arguments:
 
         * :chain: a chain of combinatorial blocks conforming to the Stage API
-                  NOTE: this may be an EMPTY list (or tuple, or iterable).
+                  NOTE: StageChain.ispec and ospect have to have something
+                  to return (beginning and end specs of the chain),
+                  therefore the chain argument must be non-zero length
+
         * :specallocate: if set, new input and output data will be allocated
                          and connected (eq'd) to each chained Stage.
                          in some cases if this is not done, the nmigen warning
@@ -354,13 +378,18 @@ class StageChain(StageCls):
         (inter-chain) dependencies, unless you really know what you are doing.
     """
     def __init__(self, chain, specallocate=False):
+        assert len(chain) > 0, "stage chain must be non-zero length"
         self.chain = chain
         self.specallocate = specallocate
 
     def ispec(self):
+        """ returns the ispec of the first of the chain
+        """
         return _spec(self.chain[0].ispec, "chainin")
 
     def ospec(self):
+        """ returns the ospec of the last of the chain
+        """
         return _spec(self.chain[-1].ospec, "chainout")
 
     def _specallocate_setup(self, m, i):
@@ -370,12 +399,12 @@ class StageChain(StageCls):
                 c.setup(m, i)               # stage may have some module stuff
             ofn = self.chain[idx].ospec     # last assignment survives
             o = _spec(ofn, 'chainin%d' % idx)
-            m.d.comb += eq(o, c.process(i)) # process input into "o"
+            m.d.comb += nmoperator.eq(o, c.process(i)) # process input into "o"
             if idx == len(self.chain)-1:
                 break
             ifn = self.chain[idx+1].ispec   # new input on next loop
             i = _spec(ifn, 'chainin%d' % (idx+1))
-            m.d.comb += eq(i, o)            # assign to next input
+            m.d.comb += nmoperator.eq(i, o) # assign to next input
         return o                            # last loop is the output
 
     def _noallocate_setup(self, m, i):
@@ -397,7 +426,9 @@ class StageChain(StageCls):
 
 
 class ControlBase(Elaboratable):
-    """ Common functions for Pipeline API
+    """ Common functions for Pipeline API.  Note: a "pipeline stage" only
+        exists (conceptually) when a ControlBase derivative is handed
+        a Stage (combinatorial block)
     """
     def __init__(self, stage=None, in_multi=None, stage_ctl=False):
         """ Base class containing ready/valid/data to previous and next stages
@@ -466,7 +497,18 @@ class ControlBase(Elaboratable):
             Thus it becomes possible to build up larger chains recursively.
             More complex chains (multi-input, multi-output) will have to be
             done manually.
+
+            Argument:
+
+            * :pipechain: - a sequence of ControlBase-derived classes
+                            (must be one or more in length)
+
+            Returns:
+
+            * a list of eq assignments that will need to be added in
+              an elaborate() to m.d.comb
         """
+        assert len(pipechain) > 0, "pipechain must be non-zero length"
         eqs = [] # collated list of assignment statements
 
         # connect inter-chain
@@ -496,7 +538,7 @@ class ControlBase(Elaboratable):
     def set_input(self, i):
         """ helper function to set the input data
         """
-        return eq(self.p.data_i, i)
+        return nmoperator.eq(self.p.data_i, i)
 
     def __iter__(self):
         yield from self.p
@@ -527,4 +569,3 @@ class ControlBase(Elaboratable):
 
         return m
 
-