fix name bug in specallocate
[ieee754fpu.git] / src / add / singlepipe.py
index e8d16ef521c1104660d0f8b7d0c9a3de43230030..4222e4694c8994ff43755e7c8eadf4d35f16dea5 100644 (file)
@@ -1,6 +1,10 @@
 """ Pipeline and BufferedHandshake implementation, conforming to the same API.
     For multi-input and multi-output variants, see multipipe.
 
+    Associated development bugs:
+    * http://bugs.libre-riscv.org/show_bug.cgi?id=64
+    * http://bugs.libre-riscv.org/show_bug.cgi?id=57
+
     eq:
     --
 
 
     input acceptance conditions are when:
         * incoming previous-stage strobe (p.i_valid) is HIGH
-        * outgoing previous-stage ready   (p.o_ready) is LOW
+        * outgoing previous-stage ready   (p.ready_o) is LOW
 
     output transmission conditions are when:
         * outgoing next-stage strobe (n.o_valid) is HIGH
     https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
 """
 
-from nmigen import Signal, Cat, Const, Mux, Module, Value
+from nmigen import Signal, Cat, Const, Mux, Module, Value, Elaboratable
 from nmigen.cli import verilog, rtlil
-from nmigen.lib.fifo import SyncFIFO
+from nmigen.lib.fifo import SyncFIFO, SyncFIFOBuffered
 from nmigen.hdl.ast import ArrayProxy
 from nmigen.hdl.rec import Record, Layout
 
 from abc import ABCMeta, abstractmethod
-from collections.abc import Sequence
+from collections.abc import Sequence, Iterable
+from collections import OrderedDict
+from queue import Queue
+import inspect
+
+
+class Object:
+    def __init__(self):
+        self.fields = OrderedDict()
+
+    def __setattr__(self, k, v):
+        print ("kv", k, v)
+        if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
+           k in dir(Object) or "fields" not in self.__dict__):
+            return object.__setattr__(self, k, v)
+        self.fields[k] = v
+
+    def __getattr__(self, k):
+        if k in self.__dict__:
+            return object.__getattr__(self, k)
+        try:
+            return self.fields[k]
+        except KeyError as e:
+            raise AttributeError(e)
+
+    def __iter__(self):
+        for x in self.fields.values():
+            if isinstance(x, Iterable):
+                yield from x
+            else:
+                yield x
+
+    def eq(self, inp):
+        res = []
+        for (k, o) in self.fields.items():
+            i = getattr(inp, k)
+            print ("eq", o, i)
+            rres = o.eq(i)
+            if isinstance(rres, Sequence):
+                res += rres
+            else:
+                res.append(rres)
+        print (res)
+        return res
+
+    def ports(self):
+        return list(self)
 
 
 class RecordObject(Record):
@@ -179,53 +229,75 @@ class RecordObject(Record):
         Record.__init__(self, layout=layout or [], name=None)
 
     def __setattr__(self, k, v):
-        if k in dir(Record) or "fields" not in self.__dict__:
+        #print (dir(Record))
+        if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
+           k in dir(Record) or "fields" not in self.__dict__):
             return object.__setattr__(self, k, v)
         self.fields[k] = v
+        #print ("RecordObject setattr", k, v)
         if isinstance(v, Record):
             newlayout = {k: (k, v.layout)}
-        else:
+        elif isinstance(v, Value):
             newlayout = {k: (k, v.shape())}
+        else:
+            newlayout = {k: (k, shape(v))}
         self.layout.fields.update(newlayout)
 
+    def __iter__(self):
+        for x in self.fields.values():
+            if isinstance(x, Iterable):
+                yield from x
+            else:
+                yield x
+
+    def ports(self):
+        return list(self)
+
 
+def _spec(fn, name=None):
+    if name is None:
+        return fn()
+    varnames = dict(inspect.getmembers(fn.__code__))['co_varnames']
+    if 'name' in varnames:
+        return fn(name=name)
+    return fn()
 
-class PrevControl:
+
+class PrevControl(Elaboratable):
     """ contains signals that come *from* the previous stage (both in and out)
         * i_valid: previous stage indicating all incoming data is valid.
                    may be a multi-bit signal, where all bits are required
                    to be asserted to indicate "valid".
-        * o_ready: output to next stage indicating readiness to accept data
+        * ready_o: output to next stage indicating readiness to accept data
         * i_data : an input - added by the user of this class
     """
 
     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._ready_o = Signal(name="p_ready_o") # 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
+            self.s_ready_o = Signal(name="p_s_o_rdy") # prev   <<out self
+        self.trigger = Signal(reset_less=True)
 
     @property
-    def o_ready(self):
+    def ready_o(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
+            return self.s_ready_o # set dynamically by stage
+        return self._ready_o      # return this when not under dynamic control
 
-    def _connect_in(self, prev, direct=False):
+    def _connect_in(self, prev, direct=False, fn=None):
         """ internal helper function to connect stage to an input source.
             do not use to connect stage-to-stage!
         """
-        if direct:
-            i_valid = prev.i_valid
-        else:
-            i_valid = prev.i_valid_test
+        i_valid = prev.i_valid if direct else prev.i_valid_test
+        i_data = fn(prev.i_data) if fn is not None else prev.i_data
         return [self.i_valid.eq(i_valid),
-                prev.o_ready.eq(self.o_ready),
-                eq(self.i_data, prev.i_data),
+                prev.ready_o.eq(self.ready_o),
+                eq(self.i_data, i_data),
                ]
 
     @property
@@ -242,12 +314,35 @@ class PrevControl:
         # 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
+            i_valid = i_valid & self.s_ready_o
 
         return i_valid
 
+    def elaborate(self, platform):
+        m = Module()
+        m.d.comb += self.trigger.eq(self.i_valid_test & self.ready_o)
+        return m
 
-class NextControl:
+    def eq(self, i):
+        return [self.i_data.eq(i.i_data),
+                self.ready_o.eq(i.ready_o),
+                self.i_valid.eq(i.i_valid)]
+
+    def __iter__(self):
+        yield self.i_valid
+        yield self.ready_o
+        if hasattr(self.i_data, "ports"):
+            yield from self.i_data.ports()
+        elif isinstance(self.i_data, Sequence):
+            yield from self.i_data
+        else:
+            yield self.i_data
+
+    def ports(self):
+        return list(self)
+
+
+class NextControl(Elaboratable):
     """ contains the signals that go *to* the next stage (both in and out)
         * o_valid: output indicating to next stage that data is valid
         * i_ready: input from next stage indicating that it can accept data
@@ -260,6 +355,7 @@ class NextControl:
         self.o_data = None # XXX MUST BE ADDED BY USER
         #if self.stage_ctl:
         self.d_valid = Signal(reset=1) # INTERNAL (data valid)
+        self.trigger = Signal(reset_less=True)
 
     @property
     def i_ready_test(self):
@@ -273,30 +369,42 @@ class NextControl:
             use this when connecting stage-to-stage
         """
         return [nxt.i_valid.eq(self.o_valid),
-                self.i_ready.eq(nxt.o_ready),
+                self.i_ready.eq(nxt.ready_o),
                 eq(nxt.i_data, self.o_data),
                ]
 
-    def _connect_out(self, nxt, direct=False):
+    def _connect_out(self, nxt, direct=False, fn=None):
         """ internal helper function to connect stage to an output source.
             do not use to connect stage-to-stage!
         """
-        if direct:
-            i_ready = nxt.i_ready
-        else:
-            i_ready = nxt.i_ready_test
+        i_ready = nxt.i_ready if direct else nxt.i_ready_test
+        o_data = fn(nxt.o_data) if fn is not None else nxt.o_data
         return [nxt.o_valid.eq(self.o_valid),
                 self.i_ready.eq(i_ready),
-                eq(nxt.o_data, self.o_data),
+                eq(o_data, self.o_data),
                ]
 
+    def elaborate(self, platform):
+        m = Module()
+        m.d.comb += self.trigger.eq(self.i_ready_test & self.o_valid)
+        return m
+
+    def __iter__(self):
+        yield self.i_ready
+        yield self.o_valid
+        if hasattr(self.o_data, "ports"):
+            yield from self.o_data.ports()
+        elif isinstance(self.o_data, Sequence):
+            yield from self.o_data
+        else:
+            yield self.o_data
+
+    def ports(self):
+        return list(self)
 
-class Visitor:
-    """ a helper routine which identifies if it is being passed a list
-        (or tuple) of objects, or signals, or Records, and calls
-        a visitor function.
 
-        the visiting fn is called when an object is identified.
+class Visitor2:
+    """ a helper class for iterating twin-argument compound data structures.
 
         Record is a special (unusual, recursive) case, where the input may be
         specified as a dictionary (which may contain further dictionaries,
@@ -313,33 +421,35 @@ class Visitor:
         python object, enumerate them, find out the list of Signals that way,
         and assign them.
     """
-    def visit(self, o, i, act):
+    def iterator2(self, o, i):
         if isinstance(o, dict):
-            return self.dict_visit(o, i, act)
+            yield from self.dict_iter2(o, i)
 
-        res = act.prepare()
         if not isinstance(o, Sequence):
             o, i = [o], [i]
         for (ao, ai) in zip(o, i):
             #print ("visit", fn, ao, ai)
             if isinstance(ao, Record):
-                rres = self.record_visit(ao, ai, act)
+                yield from self.record_iter2(ao, ai)
             elif isinstance(ao, ArrayProxy) and not isinstance(ai, Value):
-                rres = self.arrayproxy_visit(ao, ai, act)
+                yield from self.arrayproxy_iter2(ao, ai)
             else:
-                rres = act.fn(ao, ai)
-            res += rres
-        return res
+                yield (ao, ai)
 
-    def dict_visit(self, o, i, act):
-        res = act.prepare()
+    def dict_iter2(self, o, i):
         for (k, v) in o.items():
-            print ("d-eq", v, i[k])
-            res.append(act.fn(v, i[k]))
+            print ("d-iter", v, i[k])
+            yield (v, i[k])
         return res
 
-    def record_visit(self, ao, ai, act):
-        res = act.prepare()
+    def _not_quite_working_with_all_unit_tests_record_iter2(self, ao, ai):
+        print ("record_iter2", ao, ai, type(ao), type(ai))
+        if isinstance(ai, Value):
+            if isinstance(ao, Sequence):
+                ao, ai = [ao], [ai]
+            for o, i in zip(ao, ai):
+                yield (o, i)
+            return
         for idx, (field_name, field_shape, _) in enumerate(ao.layout):
             if isinstance(field_shape, Layout):
                 val = ai.fields
@@ -349,34 +459,62 @@ class Visitor:
                 val = getattr(val, field_name)
             else:
                 val = val[field_name] # dictionary-style specification
-            val = self.visit(ao.fields[field_name], val, act)
-            if isinstance(val, Sequence):
-                res += val
+            yield from self.iterator2(ao.fields[field_name], val)
+
+    def record_iter2(self, ao, ai):
+        for idx, (field_name, field_shape, _) in enumerate(ao.layout):
+            if isinstance(field_shape, Layout):
+                val = ai.fields
             else:
-                res.append(val)
-        return res
+                val = ai
+            if hasattr(val, field_name): # check for attribute
+                val = getattr(val, field_name)
+            else:
+                val = val[field_name] # dictionary-style specification
+            yield from self.iterator2(ao.fields[field_name], val)
 
-    def arrayproxy_visit(self, ao, ai, act):
-        res = act.prepare()
+    def arrayproxy_iter2(self, ao, ai):
         for p in ai.ports():
             op = getattr(ao, p.name)
-            #print (op, p, p.name)
-            res.append(fn(op, p))
-        return res
+            print ("arrayproxy - p", p, p.name)
+            yield from self.iterator2(op, p)
 
 
-class Eq(Visitor):
-    def __init__(self):
-        self.res = []
-    def prepare(self):
-        return []
-    def fn(self, o, i):
-        rres = o.eq(i)
-        if not isinstance(rres, Sequence):
-            rres = [rres]
-        return rres
-    def __call__(self, o, i):
-        return self.visit(o, i, self)
+class Visitor:
+    """ a helper class for iterating single-argument compound data structures.
+        similar to Visitor2.
+    """
+    def iterate(self, i):
+        """ iterate a compound structure recursively using yield
+        """
+        if not isinstance(i, Sequence):
+            i = [i]
+        for ai in i:
+            #print ("iterate", ai)
+            if isinstance(ai, Record):
+                #print ("record", list(ai.layout))
+                yield from self.record_iter(ai)
+            elif isinstance(ai, ArrayProxy) and not isinstance(ai, Value):
+                yield from self.array_iter(ai)
+            else:
+                yield ai
+
+    def record_iter(self, ai):
+        for idx, (field_name, field_shape, _) in enumerate(ai.layout):
+            if isinstance(field_shape, Layout):
+                val = ai.fields
+            else:
+                val = ai
+            if hasattr(val, field_name): # check for attribute
+                val = getattr(val, field_name)
+            else:
+                val = val[field_name] # dictionary-style specification
+            #print ("recidx", idx, field_name, field_shape, val)
+            yield from self.iterate(val)
+
+    def array_iter(self, ai):
+        for p in ai.ports():
+            yield from self.iterate(p)
 
 
 def eq(o, i):
@@ -384,51 +522,32 @@ def eq(o, i):
         passed a list (or tuple) of objects, or signals, or Records, and calls
         the objects' eq function.
     """
-    return Eq()(o, i)
-
-
-def flatten(i):
-    """ flattens a compound structure recursively using Cat
-    """
-    if not isinstance(i, Sequence):
-        i = [i]
     res = []
-    for ai in i:
-        print ("flatten", ai)
-        if isinstance(ai, Record):
-            print ("record", list(ai.layout))
-            rres = []
-            for idx, (field_name, field_shape, _) in enumerate(ai.layout):
-                if isinstance(field_shape, Layout):
-                    val = ai.fields
-                else:
-                    val = ai
-                if hasattr(val, field_name): # check for attribute
-                    val = getattr(val, field_name)
-                else:
-                    val = val[field_name] # dictionary-style specification
-                print ("recidx", idx, field_name, field_shape, val)
-                val = flatten(val)
-                print ("recidx flat", idx, val)
-                if isinstance(val, Sequence):
-                    rres += val
-                else:
-                    rres.append(val)
-
-        elif isinstance(ai, ArrayProxy) and not isinstance(ai, Value):
-            rres = []
-            for p in ai.ports():
-                op = getattr(ai, p.name)
-                #print (op, p, p.name)
-                rres.append(flatten(p))
-        else:
-            rres = ai
+    for (ao, ai) in Visitor2().iterator2(o, i):
+        rres = ao.eq(ai)
         if not isinstance(rres, Sequence):
             rres = [rres]
         res += rres
-        print ("flatten res", res)
-    return Cat(*res)
+    return res
+
 
+def shape(i):
+    #print ("shape", i)
+    r = 0
+    for part in list(i):
+        #print ("shape?", part)
+        s, _ = part.shape()
+        r += s
+    return r, False
+
+
+def cat(i):
+    """ flattens a compound structure recursively using Cat
+    """
+    from nmigen.tools import flatten
+    #res = list(flatten(i)) # works (as of nmigen commit f22106e5) HOWEVER...
+    res = list(Visitor().iterate(i)) # needed because input may be a sequence
+    return Cat(*res)
 
 
 class StageCls(metaclass=ABCMeta):
@@ -507,20 +626,22 @@ class StageChain(StageCls):
         self.specallocate = specallocate
 
     def ispec(self):
-        return self.chain[0].ispec()
+        return _spec(self.chain[0].ispec, "chainin")
 
     def ospec(self):
-        return self.chain[-1].ospec()
+        return _spec(self.chain[-1].ospec, "chainout")
 
     def _specallocate_setup(self, m, i):
         for (idx, c) in enumerate(self.chain):
             if hasattr(c, "setup"):
                 c.setup(m, i)               # stage may have some module stuff
-            o = self.chain[idx].ospec()     # last assignment survives
+            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"
             if idx == len(self.chain)-1:
                 break
-            i = self.chain[idx+1].ispec()   # new input on next loop
+            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
         return o                            # last loop is the output
 
@@ -541,7 +662,7 @@ class StageChain(StageCls):
         return self.o # conform to Stage API: return last-loop output
 
 
-class ControlBase:
+class ControlBase(Elaboratable):
     """ Common functions for Pipeline API
     """
     def __init__(self, stage=None, in_multi=None, stage_ctl=False):
@@ -562,8 +683,8 @@ class ControlBase:
 
         # set up the input and output data
         if stage is not None:
-            self.p.i_data = stage.ispec() # input type
-            self.n.o_data = stage.ospec()
+            self.p.i_data = _spec(stage.ispec, "i_data") # input type
+            self.n.o_data = _spec(stage.ospec, "o_data") # output type
 
     def connect_to_next(self, nxt):
         """ helper function to connect to the next stage data/valid/ready.
@@ -622,39 +743,40 @@ class ControlBase:
 
         # connect front of chain to ourselves
         front = pipechain[0]
-        self.p.i_data = front.stage.ispec()
+        self.p.i_data = _spec(front.stage.ispec, "chainin")
         eqs += front._connect_in(self)
 
         # connect end of chain to ourselves
         end = pipechain[-1]
-        self.n.o_data = end.stage.ospec()
+        self.n.o_data = _spec(end.stage.ospec, "chainout")
         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
         """
         return eq(self.p.i_data, i)
 
+    def __iter__(self):
+        yield from self.p
+        yield from self.n
+
     def ports(self):
-        res = [self.p.i_valid, self.n.i_ready,
-                self.n.o_valid, self.p.o_ready,
-               ]
-        if hasattr(self.p.i_data, "ports"):
-            res += self.p.i_data.ports()
-        else:
-            res += self.p.i_data
-        if hasattr(self.n.o_data, "ports"):
-            res += self.n.o_data.ports()
-        else:
-            res += self.n.o_data
-        return res
+        return list(self)
 
-    def _elaborate(self, platform):
+    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.i_data)
@@ -663,7 +785,7 @@ class ControlBase:
             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)
+        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.i_ready)
@@ -680,7 +802,7 @@ class BufferedHandshake(ControlBase):
         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.ready_o <<out  stage   n.i_ready <<in    stage+1
         stage-1   p.i_data  >>in   stage   n.o_data  out>>   stage+1
                               |             |
                             process --->----^
@@ -703,10 +825,10 @@ class BufferedHandshake(ControlBase):
     """
 
     def elaborate(self, platform):
-        self.m = ControlBase._elaborate(self, platform)
+        self.m = ControlBase.elaborate(self, platform)
 
-        result = self.stage.ospec()
-        r_data = self.stage.ospec()
+        result = _spec(self.stage.ospec, "r_tmp")
+        r_data = _spec(self.stage.ospec, "r_data")
 
         # establish some combinatorial temporaries
         o_n_validn = Signal(reset_less=True)
@@ -721,34 +843,36 @@ class BufferedHandshake(ControlBase):
         self.m.d.comb += [p_i_valid.eq(self.p.i_valid_test),
                      o_n_validn.eq(~self.n.o_valid),
                      n_i_ready.eq(self.n.i_ready_test),
-                     nir_por.eq(n_i_ready & self.p._o_ready),
-                     nir_por_n.eq(n_i_ready & ~self.p._o_ready),
+                     nir_por.eq(n_i_ready & self.p._ready_o),
+                     nir_por_n.eq(n_i_ready & ~self.p._ready_o),
                      nir_novn.eq(n_i_ready | o_n_validn),
                      nirn_novn.eq(~n_i_ready & o_n_validn),
                      npnn.eq(nir_por | nirn_novn),
-                     por_pivn.eq(self.p._o_ready & ~p_i_valid)
+                     por_pivn.eq(self.p._ready_o & ~p_i_valid)
         ]
 
         # 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
+        with self.m.If(self.p.ready_o): # not stalled
             self.m.d.sync += eq(r_data, result) # update buffer
 
         # data pass-through conditions
         with self.m.If(npnn):
+            o_data = self._postprocess(result)
             self.m.d.sync += [self.n.o_valid.eq(p_i_valid), # valid if p_valid
-                              eq(self.n.o_data, result),    # update output
+                              eq(self.n.o_data, o_data),    # update output
                              ]
         # buffer flush conditions (NOTE: can override data passthru conditions)
         with self.m.If(nir_por_n): # not stalled
             # Flush the [already processed] buffer to the output port.
+            o_data = self._postprocess(r_data)
             self.m.d.sync += [self.n.o_valid.eq(1),  # reg empty
-                              eq(self.n.o_data, r_data), # flush buffer
+                              eq(self.n.o_data, o_data), # flush buffer
                              ]
         # output ready conditions
-        self.m.d.sync += self.p._o_ready.eq(nir_novn | por_pivn)
+        self.m.d.sync += self.p._ready_o.eq(nir_novn | por_pivn)
 
         return self.m
 
@@ -760,74 +884,76 @@ class SimpleHandshake(ControlBase):
         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.ready_o <<out  stage   n.i_ready <<in    stage+1
         stage-1   p.i_data  >>in   stage   n.o_data  out>>   stage+1
                               |             |
                               +--process->--^
         Truth Table
 
-        Inputs   Temporary  Output
-        -------  ---------- -----
-        P P N N  PiV& ~NiV&  N P
+        Inputs   Temporary  Output Data
+        -------  ---------- -----  ----
+        P P N N  PiV& ~NiR&  N P
         i o i o  PoR  NoV    o o
         V R R V              V R
 
         -------   -    -     - -
-        0 0 0 0   0    0    >0 0
-        0 0 0 1   0    1    >1 0
-        0 0 1 0   0    0     0 1
-        0 0 1 1   0    0     0 1
+        0 0 0 0   0    0    >0 0    reg
+        0 0 0 1   0    1    >1 0    reg
+        0 0 1 0   0    0     0 1    process(i_data)
+        0 0 1 1   0    0     0 1    process(i_data)
         -------   -    -     - -
-        0 1 0 0   0    0    >0 0
-        0 1 0 1   0    1    >1 0
-        0 1 1 0   0    0     0 1
-        0 1 1 1   0    0     0 1
+        0 1 0 0   0    0    >0 0    reg
+        0 1 0 1   0    1    >1 0    reg
+        0 1 1 0   0    0     0 1    process(i_data)
+        0 1 1 1   0    0     0 1    process(i_data)
         -------   -    -     - -
-        1 0 0 0   0    0    >0 0
-        1 0 0 1   0    1    >1 0
-        1 0 1 0   0    0     0 1
-        1 0 1 1   0    0     0 1
+        1 0 0 0   0    0    >0 0    reg
+        1 0 0 1   0    1    >1 0    reg
+        1 0 1 0   0    0     0 1    process(i_data)
+        1 0 1 1   0    0     0 1    process(i_data)
         -------   -    -     - -
-        1 1 0 0   1    0     1 0
-        1 1 0 1   1    1     1 0
-        1 1 1 0   1    0     1 1
-        1 1 1 1   1    0     1 1
+        1 1 0 0   1    0     1 0    process(i_data)
+        1 1 0 1   1    1     1 0    process(i_data)
+        1 1 1 0   1    0     1 1    process(i_data)
+        1 1 1 1   1    0     1 1    process(i_data)
         -------   -    -     - -
     """
 
     def elaborate(self, platform):
-        self.m = m = ControlBase._elaborate(self, platform)
+        self.m = m = ControlBase.elaborate(self, platform)
 
         r_busy = Signal()
-        result = self.stage.ospec()
+        result = _spec(self.stage.ospec, "r_tmp")
 
         # establish some combinatorial temporaries
         n_i_ready = Signal(reset_less=True, name="n_i_rdy_data")
-        p_i_valid_p_o_ready = Signal(reset_less=True)
+        p_i_valid_p_ready_o = Signal(reset_less=True)
         p_i_valid = Signal(reset_less=True)
         m.d.comb += [p_i_valid.eq(self.p.i_valid_test),
                      n_i_ready.eq(self.n.i_ready_test),
-                     p_i_valid_p_o_ready.eq(p_i_valid & self.p.o_ready),
+                     p_i_valid_p_ready_o.eq(p_i_valid & self.p.ready_o),
         ]
 
         # store result of processing in combinatorial temporary
         m.d.comb += eq(result, self.stage.process(self.p.i_data))
 
         # previous valid and ready
-        with m.If(p_i_valid_p_o_ready):
+        with m.If(p_i_valid_p_ready_o):
+            o_data = self._postprocess(result)
             m.d.sync += [r_busy.eq(1),      # output valid
-                         eq(self.n.o_data, result), # update output
+                         eq(self.n.o_data, o_data), # update output
                         ]
         # previous invalid or not ready, however next is accepting
         with m.Elif(n_i_ready):
-            m.d.sync += [eq(self.n.o_data, result)]
+            o_data = self._postprocess(result)
+            m.d.sync += [eq(self.n.o_data, o_data)]
             # TODO: could still send data here (if there was any)
             #m.d.sync += self.n.o_valid.eq(0) # ...so set output invalid
             m.d.sync += r_busy.eq(0) # ...so set output invalid
 
         m.d.comb += self.n.o_valid.eq(r_busy)
         # if next is ready, so is previous
-        m.d.comb += self.p._o_ready.eq(n_i_ready)
+        m.d.comb += self.p._ready_o.eq(n_i_ready)
 
         return self.m
 
@@ -847,7 +973,7 @@ class UnbufferedPipeline(ControlBase):
         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.ready_o <<out  stage   n.i_ready <<in    stage+1
         stage-1   p.i_data  >>in   stage   n.o_data  out>>   stage+1
                               |             |
                             r_data        result
@@ -870,60 +996,62 @@ class UnbufferedPipeline(ControlBase):
 
         Truth Table
 
-        Inputs  Temp  Output
-        -------   -   -----
+        Inputs  Temp  Output  Data
+        -------   -   -----   ----
         P P N N ~NiR&  N P
         i o i o  NoV   o o
         V R R V        V R
 
         -------   -    - -
-        0 0 0 0   0    0 1
-        0 0 0 1   1    1 0
-        0 0 1 0   0    0 1
-        0 0 1 1   0    0 1
+        0 0 0 0   0    0 1    reg
+        0 0 0 1   1    1 0    reg
+        0 0 1 0   0    0 1    reg
+        0 0 1 1   0    0 1    reg
         -------   -    - -
-        0 1 0 0   0    0 1
-        0 1 0 1   1    1 0
-        0 1 1 0   0    0 1
-        0 1 1 1   0    0 1
+        0 1 0 0   0    0 1    reg
+        0 1 0 1   1    1 0    reg
+        0 1 1 0   0    0 1    reg
+        0 1 1 1   0    0 1    reg
         -------   -    - -
-        1 0 0 0   0    1 1
-        1 0 0 1   1    1 0
-        1 0 1 0   0    1 1
-        1 0 1 1   0    1 1
+        1 0 0 0   0    1 1    reg
+        1 0 0 1   1    1 0    reg
+        1 0 1 0   0    1 1    reg
+        1 0 1 1   0    1 1    reg
         -------   -    - -
-        1 1 0 0   0    1 1
-        1 1 0 1   1    1 0
-        1 1 1 0   0    1 1
-        1 1 1 1   0    1 1
+        1 1 0 0   0    1 1    process(i_data)
+        1 1 0 1   1    1 0    process(i_data)
+        1 1 1 0   0    1 1    process(i_data)
+        1 1 1 1   0    1 1    process(i_data)
         -------   -    - -
 
         Note: PoR is *NOT* involved in the above decision-making.
     """
 
     def elaborate(self, platform):
-        self.m = m = ControlBase._elaborate(self, platform)
+        self.m = m = ControlBase.elaborate(self, platform)
 
         data_valid = Signal() # is data valid or not
-        r_data = self.stage.ospec() # output type
+        r_data = _spec(self.stage.ospec, "r_tmp") # output type
 
         # some temporaries
         p_i_valid = Signal(reset_less=True)
         pv = Signal(reset_less=True)
+        buf_full = Signal(reset_less=True)
         m.d.comb += p_i_valid.eq(self.p.i_valid_test)
-        m.d.comb += pv.eq(self.p.i_valid & self.p.o_ready)
+        m.d.comb += pv.eq(self.p.i_valid & self.p.ready_o)
+        m.d.comb += buf_full.eq(~self.n.i_ready_test & data_valid)
 
         m.d.comb += self.n.o_valid.eq(data_valid)
-        m.d.comb += self.p._o_ready.eq(~data_valid | self.n.i_ready_test)
-        m.d.sync += data_valid.eq(p_i_valid | \
-                                        (~self.n.i_ready_test & data_valid))
+        m.d.comb += self.p._ready_o.eq(~data_valid | self.n.i_ready_test)
+        m.d.sync += data_valid.eq(p_i_valid | buf_full)
+
         with m.If(pv):
             m.d.sync += eq(r_data, self.stage.process(self.p.i_data))
-        m.d.comb += eq(self.n.o_data, r_data)
+        o_data = self._postprocess(r_data)
+        m.d.comb += eq(self.n.o_data, o_data)
 
         return self.m
 
-
 class UnbufferedPipeline2(ControlBase):
     """ A simple pipeline stage with single-clock synchronisation
         and two-way valid/ready synchronised signalling.
@@ -939,7 +1067,7 @@ class UnbufferedPipeline2(ControlBase):
         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.ready_o <<out  stage   n.i_ready <<in    stage+1
         stage-1   p.i_data  >>in   stage   n.o_data  out>>   stage+1
                               |             |    |
                               +- process-> buf <-+
@@ -953,24 +1081,55 @@ class UnbufferedPipeline2(ControlBase):
             A temporary (buffered) copy of a valid output
             This is HELD if the output is not ready.  It is updated
             SYNCHRONOUSLY.
+
+        Inputs  Temp  Output Data
+        -------   -   -----
+        P P N N ~NiR&  N P   (buf_full)
+        i o i o  NoV   o o
+        V R R V        V R
+
+        -------   -    - -
+        0 0 0 0   0    0 1   process(i_data)
+        0 0 0 1   1    1 0   reg (odata, unchanged)
+        0 0 1 0   0    0 1   process(i_data)
+        0 0 1 1   0    0 1   process(i_data)
+        -------   -    - -
+        0 1 0 0   0    0 1   process(i_data)
+        0 1 0 1   1    1 0   reg (odata, unchanged)
+        0 1 1 0   0    0 1   process(i_data)
+        0 1 1 1   0    0 1   process(i_data)
+        -------   -    - -
+        1 0 0 0   0    1 1   process(i_data)
+        1 0 0 1   1    1 0   reg (odata, unchanged)
+        1 0 1 0   0    1 1   process(i_data)
+        1 0 1 1   0    1 1   process(i_data)
+        -------   -    - -
+        1 1 0 0   0    1 1   process(i_data)
+        1 1 0 1   1    1 0   reg (odata, unchanged)
+        1 1 1 0   0    1 1   process(i_data)
+        1 1 1 1   0    1 1   process(i_data)
+        -------   -    - -
+
+        Note: PoR is *NOT* involved in the above decision-making.
     """
 
     def elaborate(self, platform):
-        self.m = m = ControlBase._elaborate(self, platform)
+        self.m = m = ControlBase.elaborate(self, platform)
 
         buf_full = Signal() # is data valid or not
-        buf = self.stage.ospec() # output type
+        buf = _spec(self.stage.ospec, "r_tmp") # output type
 
         # some temporaries
         p_i_valid = Signal(reset_less=True)
         m.d.comb += p_i_valid.eq(self.p.i_valid_test)
 
         m.d.comb += self.n.o_valid.eq(buf_full | p_i_valid)
-        m.d.comb += self.p._o_ready.eq(~buf_full)
+        m.d.comb += self.p._ready_o.eq(~buf_full)
         m.d.sync += buf_full.eq(~self.n.i_ready_test & self.n.o_valid)
 
-        odata = Mux(buf_full, buf, self.stage.process(self.p.i_data))
-        m.d.comb += eq(self.n.o_data, odata)
+        o_data = Mux(buf_full, buf, self.stage.process(self.p.i_data))
+        o_data = self._postprocess(o_data)
+        m.d.comb += eq(self.n.o_data, o_data)
         m.d.sync += eq(buf, self.n.o_data)
 
         return self.m
@@ -989,22 +1148,55 @@ class PassThroughStage(StageCls):
 
 class PassThroughHandshake(ControlBase):
     """ A control block that delays by one clock cycle.
+
+        Inputs   Temporary          Output Data
+        -------  ------------------  ----- ----
+        P P N N  PiV& PiV| NiR| pvr   N P  (pvr)
+        i o i o  PoR  ~PoR ~NoV       o o
+        V R R V                       V R
+
+        -------   -    -    -   -     - -
+        0 0 0 0   0    1    1   0     1 1   odata (unchanged)
+        0 0 0 1   0    1    0   0     1 0   odata (unchanged)
+        0 0 1 0   0    1    1   0     1 1   odata (unchanged)
+        0 0 1 1   0    1    1   0     1 1   odata (unchanged)
+        -------   -    -    -   -     - -
+        0 1 0 0   0    0    1   0     0 1   odata (unchanged)
+        0 1 0 1   0    0    0   0     0 0   odata (unchanged)
+        0 1 1 0   0    0    1   0     0 1   odata (unchanged)
+        0 1 1 1   0    0    1   0     0 1   odata (unchanged)
+        -------   -    -    -   -     - -
+        1 0 0 0   0    1    1   1     1 1   process(in)
+        1 0 0 1   0    1    0   0     1 0   odata (unchanged)
+        1 0 1 0   0    1    1   1     1 1   process(in)
+        1 0 1 1   0    1    1   1     1 1   process(in)
+        -------   -    -    -   -     - -
+        1 1 0 0   1    1    1   1     1 1   process(in)
+        1 1 0 1   1    1    0   0     1 0   odata (unchanged)
+        1 1 1 0   1    1    1   1     1 1   process(in)
+        1 1 1 1   1    1    1   1     1 1   process(in)
+        -------   -    -    -   -     - -
+
     """
 
     def elaborate(self, platform):
-        self.m = m = ControlBase._elaborate(self, platform)
+        self.m = m = ControlBase.elaborate(self, platform)
+
+        r_data = _spec(self.stage.ospec, "r_tmp") # output type
 
         # temporaries
         p_i_valid = Signal(reset_less=True)
         pvr = Signal(reset_less=True)
         m.d.comb += p_i_valid.eq(self.p.i_valid_test)
-        m.d.comb += pvr.eq(p_i_valid & self.p.o_ready)
+        m.d.comb += pvr.eq(p_i_valid & self.p.ready_o)
 
-        m.d.comb += self.p.o_ready.eq(~self.n.o_valid |  self.n.i_ready_test)
-        m.d.sync += self.n.o_valid.eq(p_i_valid       | ~self.p.o_ready)
+        m.d.comb += self.p.ready_o.eq(~self.n.o_valid |  self.n.i_ready_test)
+        m.d.sync += self.n.o_valid.eq(p_i_valid       | ~self.p.ready_o)
 
-        odata = Mux(pvr, self.stage.process(self.p.i_data), self.n.o_data)
-        m.d.sync += eq(self.n.o_data, odata)
+        odata = Mux(pvr, self.stage.process(self.p.i_data), r_data)
+        m.d.sync += eq(r_data, odata)
+        r_data = self._postprocess(r_data)
+        m.d.comb += eq(self.n.o_data, r_data)
 
         return m
 
@@ -1018,44 +1210,108 @@ class RegisterPipeline(UnbufferedPipeline):
         UnbufferedPipeline.__init__(self, PassThroughStage(iospecfn))
 
 
-class FIFOtest(ControlBase):
-    """ A test of using a SyncFIFO to see if it will work.
-        Note: the only things it will accept is a Signal of width "width".
+class FIFOControl(ControlBase):
+    """ FIFO Control.  Uses SyncFIFO to store data, coincidentally
+        happens to have same valid/ready signalling as Stage API.
+
+        i_data -> fifo.din -> FIFO -> fifo.dout -> o_data
     """
 
-    def __init__(self, iospecfn, width, depth):
+    def __init__(self, depth, stage, in_multi=None, stage_ctl=False,
+                                     fwft=True, buffered=False, pipe=False):
+        """ FIFO Control
 
-        self.iospecfn = iospecfn
-        self.fwidth = width # XXX temporary
-        self.fdepth = depth
-        #stage = PassThroughStage(iospecfn)
-        ControlBase.__init__(self, stage=self)
+            * depth: number of entries in the FIFO
+            * stage: data processing block
+            * fwft : first word fall-thru mode (non-fwft introduces delay)
+            * buffered: use buffered FIFO (introduces extra cycle delay)
 
-    def ispec(self): return self.iospecfn()
-    def ospec(self): return Signal(self.fwidth, name="dout")
-    def process(self, i): return i
+            NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO
+                    (fwft=True, buffered=False)
+
+            NOTE 2: i_data *must* have a shape function.  it can therefore
+                    be a Signal, or a Record, or a RecordObject.
+
+            data is processed (and located) as follows:
+
+            self.p  self.stage temp    fn temp  fn  temp  fp   self.n
+            i_data->process()->result->cat->din.FIFO.dout->cat(o_data)
+
+            yes, really: cat produces a Cat() which can be assigned to.
+            this is how the FIFO gets de-catted without needing a de-cat
+            function
+        """
+
+        assert not (fwft and buffered), "buffered cannot do fwft"
+        if buffered:
+            depth += 1
+        self.fwft = fwft
+        self.buffered = buffered
+        self.pipe = pipe
+        self.fdepth = depth
+        ControlBase.__init__(self, stage, in_multi, stage_ctl)
 
     def elaborate(self, platform):
-        self.m = m = ControlBase._elaborate(self, platform)
+        self.m = m = ControlBase.elaborate(self, platform)
 
-        (fwidth, _) = self.p.i_data.shape()
-        fifo = SyncFIFO(fwidth, self.fdepth)
+        # make a FIFO with a signal of equal width to the o_data.
+        (fwidth, _) = shape(self.n.o_data)
+        if self.buffered:
+            fifo = SyncFIFOBuffered(fwidth, self.fdepth)
+        else:
+            fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
         m.submodules.fifo = fifo
 
-        # connect the rdy/valid/data
+        # store result of processing in combinatorial temporary
+        result = _spec(self.stage.ospec, "r_temp")
+        m.d.comb += eq(result, self.stage.process(self.p.i_data))
+
+        # connect previous rdy/valid/data - do cat on i_data
+        # NOTE: cannot do the PrevControl-looking trick because
+        # of need to process the data.  shaaaame....
         m.d.comb += [fifo.we.eq(self.p.i_valid_test),
-                     self.p.o_ready.eq(fifo.writable),
-                     eq(fifo.din, flatten(self.p.i_data)),
+                     self.p.ready_o.eq(fifo.writable),
+                     eq(fifo.din, cat(result)),
                    ]
 
-        # next: make the FIFO "look" like a NextControl...
-        fn = NextControl()
-        fn.o_valid = fifo.readable
-        fn.i_ready = fifo.re
-        fn.o_data = fifo.dout
-        # ... so we can do this!
-        m.d.comb += fn._connect_out(self.n)
+        # connect next rdy/valid/data - do cat on o_data
+        connections = [self.n.o_valid.eq(fifo.readable),
+                     fifo.re.eq(self.n.i_ready_test),
+                   ]
+        if self.fwft or self.buffered:
+            m.d.comb += connections
+        else:
+            m.d.sync += connections # unbuffered fwft mode needs sync
+        o_data = cat(self.n.o_data).eq(fifo.dout)
+        o_data = self._postprocess(o_data)
+        m.d.comb += o_data
 
-        # err... that should be all!
         return m
 
+
+# aka "RegStage".
+class UnbufferedPipeline(FIFOControl):
+    def __init__(self, stage, in_multi=None, stage_ctl=False):
+        FIFOControl.__init__(self, 1, stage, in_multi, stage_ctl,
+                                   fwft=True, pipe=False)
+
+# aka "BreakReadyStage" XXX had to set fwft=True to get it to work
+class PassThroughHandshake(FIFOControl):
+    def __init__(self, stage, in_multi=None, stage_ctl=False):
+        FIFOControl.__init__(self, 1, stage, in_multi, stage_ctl,
+                                   fwft=True, pipe=True)
+
+# this is *probably* BufferedHandshake, although test #997 now succeeds.
+class BufferedHandshake(FIFOControl):
+    def __init__(self, stage, in_multi=None, stage_ctl=False):
+        FIFOControl.__init__(self, 2, stage, in_multi, stage_ctl,
+                                   fwft=True, pipe=False)
+
+
+"""
+# this is *probably* SimpleHandshake (note: memory cell size=0)
+class SimpleHandshake(FIFOControl):
+    def __init__(self, stage, in_multi=None, stage_ctl=False):
+        FIFOControl.__init__(self, 0, stage, in_multi, stage_ctl,
+                                   fwft=True, pipe=False)
+"""